Skip to content

Commit

Permalink
Merge pull request #382 from emberjs/double-click
Browse files Browse the repository at this point in the history
Add doubleClick helper
  • Loading branch information
rwjblue committed Jun 13, 2018
2 parents 46cb903 + 0dbc341 commit 3e91a29
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 0 deletions.
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',
]);
});
});
});

0 comments on commit 3e91a29

Please sign in to comment.