Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add doubleClick helper #382

Merged
merged 1 commit into from
Jun 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [DOM Interaction Helpers](#dom-interaction-helpers)
- [click](#click)
- [doubleClick](#doubleclick)
- [tap](#tap)
- [focus](#focus)
- [blur](#blur)
Expand Down Expand Up @@ -82,6 +83,45 @@ to continue to emulate how actual browsers handle clicking a given element.

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void>** resolves when settled

### doubleClick

Double-clicks on the specified target.

Sends a number of events intending to simulate a "real" user double-clicking on an
element.

For non-focusable elements the following events are triggered (in order):

- `mousedown`
- `mouseup`
- `click`
- `mousedown`
- `mouseup`
- `click`
- `dblclick`

For focusable (e.g. form control) elements the following events are triggered
(in order):

- `mousedown`
- `focus`
- `focusin`
- `mouseup`
- `click`
- `mousedown`
- `mouseup`
- `click`
- `dblclick`

The exact listing of events that are triggered may change over time as needed
to continue to emulate how actual browsers handle double-clicking a given element.

**Parameters**

- `target` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Element](https://developer.mozilla.org/docs/Web/API/Element))** the element or selector to double-click on

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void>** resolves when settled

### tap

Taps on the specified target.
Expand Down
77 changes: 77 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/double-click.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import getElement from './-get-element';
import fireEvent from './fire-event';
import { __focus__ } from './focus';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { nextTickPromise } from '../-utils';

/**
@private
@param {Element} element the element to double-click on
*/
export function __doubleClick__(element) {
fireEvent(element, 'mousedown');

if (isFocusable(element)) {
__focus__(element);
}

fireEvent(element, 'mouseup');
fireEvent(element, 'click');
fireEvent(element, 'mousedown');
fireEvent(element, 'mouseup');
fireEvent(element, 'click');
fireEvent(element, 'dblclick');
}

/**
Double-clicks on the specified target.

Sends a number of events intending to simulate a "real" user clicking on an
element.

For non-focusable elements the following events are triggered (in order):

- `mousedown`
- `mouseup`
- `click`
- `mousedown`
- `mouseup`
- `click`
- `dblclick`

For focusable (e.g. form control) elements the following events are triggered
(in order):

- `mousedown`
- `focus`
- `focusin`
- `mouseup`
- `click`
- `mousedown`
- `mouseup`
- `click`
- `dblclick`

The exact listing of events that are triggered may change over time as needed
to continue to emulate how actual browsers handle clicking a given element.

@public
@param {string|Element} target the element or selector to double-click on
@return {Promise<void>} resolves when settled
*/
export default function doubleClick(target) {
return nextTickPromise().then(() => {
if (!target) {
throw new Error('Must pass an element or selector to `doubleClick`.');
}

let element = getElement(target);
if (!element) {
throw new Error(`Element not found when calling \`doubleClick('${target}')\`.`);
}

__doubleClick__(element);
return settled();
});
}
1 change: 1 addition & 0 deletions addon-test-support/@ember/test-helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export { default as validateErrorHandler } from './validate-error-handler';

// DOM Helpers
export { default as click } from './dom/click';
export { default as doubleClick } from './dom/double-click';
export { default as tap } from './dom/tap';
export { default as focus } from './dom/focus';
export { default as blur } from './dom/blur';
Expand Down
1 change: 1 addition & 0 deletions documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ toc:
- name: DOM Interaction Helpers
children:
- click
- doubleClick
- tap
- focus
- blur
Expand Down
213 changes: 213 additions & 0 deletions tests/unit/dom/double-click-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { module, test } from 'qunit';
import { doubleClick, setupContext, teardownContext } from '@ember/test-helpers';
import { buildInstrumentedElement, instrumentElement, insertElement } from '../../helpers/events';
import { isIE11 } from '../../helpers/browser-detect';
import hasEmberVersion from 'ember-test-helpers/has-ember-version';

module('DOM Helper: doubleClick', function(hooks) {
if (!hasEmberVersion(2, 4)) {
return;
}

let context, element;

hooks.beforeEach(function() {
context = {};
});

hooks.afterEach(async function() {
element.setAttribute('data-skip-steps', true);

if (element) {
element.parentNode.removeChild(element);
}
if (context.owner) {
await teardownContext(context);
}

document.getElementById('ember-testing').innerHTML = '';
});

module('non-focusable element types', function() {
test('double-clicking a div via selector with context set', async function(assert) {
element = buildInstrumentedElement('div');

await setupContext(context);
await doubleClick(`#${element.id}`);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});

test('double-clicking a div via element with context set', async function(assert) {
element = buildInstrumentedElement('div');

await setupContext(context);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});

test('double-clicking a div via element without context set', async function(assert) {
element = buildInstrumentedElement('div');

await doubleClick(element);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});

test('does not run sync', async function(assert) {
element = buildInstrumentedElement('div');

let promise = doubleClick(element);

assert.verifySteps([]);

await promise;

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});

test('rejects if selector is not found', async function(assert) {
element = buildInstrumentedElement('div');

await setupContext(context);

assert.rejects(
doubleClick(`#foo-bar-baz-not-here-ever-bye-bye`),
/Element not found when calling `doubleClick\('#foo-bar-baz-not-here-ever-bye-bye'\)`/
);
});

test('double-clicking a div via selector without context set', function(assert) {
element = buildInstrumentedElement('div');

assert.rejects(
doubleClick(`#${element.id}`),
/Must setup rendering context before attempting to interact with elements/
);
});
});

module('focusable element types', function() {
let clickSteps = [
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
];

if (isIE11) {
clickSteps = [
'mousedown',
'focusin',
'mouseup',
'click',
'focus',
'mousedown',
'mouseup',
'click',
'dblclick',
];
}

test('double-clicking a input via selector with context set', async function(assert) {
element = buildInstrumentedElement('input');

await setupContext(context);
await doubleClick(`#${element.id}`);

assert.verifySteps(clickSteps);
assert.strictEqual(document.activeElement, element, 'activeElement updated');
});

test('double-clicking a input via element with context set', async function(assert) {
element = buildInstrumentedElement('input');

await setupContext(context);
await doubleClick(element);

assert.verifySteps(clickSteps);
assert.strictEqual(document.activeElement, element, 'activeElement updated');
});

test('double-clicking a input via element without context set', async function(assert) {
element = buildInstrumentedElement('input');

await doubleClick(element);

assert.verifySteps(clickSteps);
assert.strictEqual(document.activeElement, element, 'activeElement updated');
});

test('double-clicking a input via selector without context set', function(assert) {
element = buildInstrumentedElement('input');

assert.rejects(
doubleClick(`#${element.id}`),
/Must setup rendering context before attempting to interact with elements/
);
});
});

module('elements in different realms', function() {
test('double-clicking an element in a different realm', async function(assert) {
element = document.createElement('iframe');

insertElement(element);

let iframeDocument = element.contentDocument;
let iframeElement = iframeDocument.createElement('div');

instrumentElement(iframeElement);

await doubleClick(iframeElement);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});
});
});