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

Bring over DOM helper implementation. #258

Merged
merged 61 commits into from Dec 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f5396fa
Add `waitUntil` helper.
rwjblue Dec 5, 2017
657e8d8
Add initial implementations for `click` and `focus`.
rwjblue Dec 6, 2017
31e66a8
Bring over `blur` implementation.
rwjblue Dec 6, 2017
4b91a8c
Add triggerEvent and triggerKeyEvent.
rwjblue Dec 6, 2017
534e330
Refactor tests to use public API helpers.
rwjblue Dec 6, 2017
56c6026
Guard for strings in -get-element.
rwjblue Dec 6, 2017
1220ed4
Fix documentation blocks for helpers.
rwjblue Dec 6, 2017
59596f8
Defaults `blur`'s element to document.activeElement.
rwjblue Dec 6, 2017
e5f84cd
Ensure `focus` throws if invoked with unfocusable selector.
rwjblue Dec 6, 2017
51a5da8
Refactor inline docs to be consistent.
rwjblue Dec 6, 2017
457333e
Refactor triggerKeyEvent to be more readable.
rwjblue Dec 6, 2017
2e0cc6c
Move wait-until-test to correct location.
rwjblue Dec 6, 2017
622ce97
Reorder events in `focus`.
rwjblue Dec 6, 2017
da7d8e5
Add unit tests for `click` helper.
rwjblue Dec 6, 2017
fa9f7c1
Add helpful (eager) error conditions for `click` and `getElement`.
rwjblue Dec 6, 2017
05d1f00
Ensure `click` is _always_ async.
rwjblue Dec 6, 2017
dbbb7a3
Refactor `focus` to be always async.
rwjblue Dec 6, 2017
8f55c94
Add unit tests for `focus`.
rwjblue Dec 6, 2017
88beb6b
Create private `nextTick` utility.
rwjblue Dec 6, 2017
043c4bc
Use `nextTick` in `waitUntil`.
rwjblue Dec 6, 2017
cf98536
Add monkey patch for QUnit until qunit > 2.4.1 is released.
rwjblue Dec 7, 2017
c919c77
Add blur unit tests.
rwjblue Dec 7, 2017
58441b0
Refactor `blur`
rwjblue Dec 7, 2017
1f10141
Fix stupid mistake in reexports.
rwjblue Dec 7, 2017
88c88ed
Update tests to deal with async dom helpers.
rwjblue Dec 7, 2017
e523951
Add and utilize `nextTickPromise` helper method.
rwjblue Dec 7, 2017
b751706
Ensure events fired receive native event.
rwjblue Dec 7, 2017
d21bf56
Add unit tests for `triggerEvent`.
rwjblue Dec 7, 2017
fbd0353
Flesh out `triggerEvent` input validation.
rwjblue Dec 7, 2017
6d17cc9
Add unit tests for triggerKeyEvent.
rwjblue Dec 7, 2017
48ab310
Add input validation to triggerKeyEvent.
rwjblue Dec 7, 2017
2221b42
Incorporate changes from cibernox/ember-native-dom-helpers#104.
rwjblue Dec 7, 2017
83444ea
Initial implementation of `fillIn`.
rwjblue Dec 7, 2017
5988674
Mark internal helper functions as private.
rwjblue Dec 10, 2017
5b7da22
Fix more API doc comment blocks.
rwjblue Dec 10, 2017
ca88ee2
Tweak more documentation.
rwjblue Dec 10, 2017
1ffe33c
Add explicit `assert.expect`.
rwjblue Dec 10, 2017
d91821c
Use `nextTick` to avoid time shifting issues.
rwjblue Dec 10, 2017
2f34b26
Add missing test case from DOM helpers.
rwjblue Dec 10, 2017
61222a5
Refactor DOM helper tests.
rwjblue Dec 10, 2017
b6073e4
Add tests for `fillIn`.
rwjblue Dec 10, 2017
ead022a
Leverage element.isContentEditable instead of local util.
rwjblue Dec 10, 2017
62e22c7
Remove LINK from `FOCUSABLE_TAGS`.
rwjblue Dec 10, 2017
612c815
Replace HTMLElement || SVGElement with Element.
rwjblue Dec 10, 2017
f2b3c35
Split "click even sequence" from `click`.
rwjblue Dec 10, 2017
2cc394e
Add `tap` implementation.
rwjblue Dec 10, 2017
f03eead
Add unit tests for tap.
rwjblue Dec 10, 2017
5c23057
Add basic `waitFor` implementation.
rwjblue Dec 10, 2017
2a8b97d
More API documentation tweaks.
rwjblue Dec 12, 2017
caf3e13
Ensure that `waitUntil` properly handles callbacks with errors.
rwjblue Dec 14, 2017
3b3d7c1
Return an array of elements for `waitFor` with `count`.
rwjblue Dec 14, 2017
326b968
Add unit tests for `waitFor`.
rwjblue Dec 14, 2017
fdefbc9
Remove tests requiring `click` only wait for subset of settledness.
rwjblue Dec 14, 2017
95b34ee
Add basic `assert.rejects` infrastructure.
rwjblue Dec 14, 2017
adb006b
Make argument assertions into rejections.
rwjblue Dec 14, 2017
7ee206a
Refactor manual rejection assertions to assert.rejects.
rwjblue Dec 14, 2017
7e41f7f
Update `moduleForAcceptance` interop test to use `click`.
rwjblue Dec 15, 2017
e6f8668
Add comment RE: eager error creation without throwing.
rwjblue Dec 15, 2017
0412bbf
Add helpful errors to helpers for `selector|element` arg.
rwjblue Dec 15, 2017
d07492b
Ensure `triggerKeyEvent` rejects (and doesn't throw eagerly).
rwjblue Dec 15, 2017
1848f83
Validate `target` in `waitFor`.
rwjblue Dec 15, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions addon-test-support/@ember/test-helpers/-utils.js
@@ -0,0 +1,9 @@
import { Promise } from 'rsvp';

export const nextTick = setTimeout;

export function nextTickPromise() {
return new Promise(resolve => {
nextTick(resolve);
});
}
21 changes: 21 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/-get-element.js
@@ -0,0 +1,21 @@
import { getContext } from '../setup-context';

export default function getElement(selectorOrElement) {
if (
selectorOrElement instanceof Window ||
selectorOrElement instanceof Document ||
selectorOrElement instanceof Element
) {
return selectorOrElement;
} else if (typeof selectorOrElement === 'string') {
let context = getContext();
let rootElement = context && context.element;
if (!rootElement) {
throw new Error(`Must setup rendering context before attempting to interact with elements.`);
}

return rootElement.querySelector(selectorOrElement);
} else {
throw new Error('Must use an element or a selector string');
}
}
14 changes: 14 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/-is-focusable.js
@@ -0,0 +1,14 @@
import isFormControl from './-is-form-control';

const FOCUSABLE_TAGS = ['A'];
export default function isFocusable(element) {
if (
isFormControl(element) ||
element.isContentEditable ||
FOCUSABLE_TAGS.indexOf(element.tagName) > -1
) {
return true;
}

return element.hasAttribute('tabindex');
}
11 changes: 11 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/-is-form-control.js
@@ -0,0 +1,11 @@
const FORM_CONTROL_TAGS = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'];

export default function isFormControl(el) {
let { tagName, type } = el;

if (type === 'hidden') {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could improve the logic here. I don't think that disabled buttons or inputs should be focusable.
I found this nice table we can copy from, even if we're not soooo exhaustive: https://allyjs.io/data-tables/focusable.html


return FORM_CONTROL_TAGS.indexOf(tagName) > -1;
}
49 changes: 49 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/blur.js
@@ -0,0 +1,49 @@
import getElement from './-get-element';
import fireEvent from './fire-event';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { nextTickPromise } from '../-utils';

/**
@private
@method __blur__
@param {Element} element
*/
export function __blur__(element) {
let browserIsNotFocused = document.hasFocus && !document.hasFocus();

// makes `document.activeElement` be `body`.
// If the browser is focused, it also fires a blur event
element.blur();

// Chrome/Firefox does not trigger the `blur` event if the window
// does not have focus. If the document does not have focus then
// fire `blur` event via native event.
if (browserIsNotFocused) {
fireEvent(element, 'blur', { bubbles: false });
fireEvent(element, 'focusout');
}
}

/**
@method blur
@param {String|Element} [target=document.activeElement] the element to blur
@return {Promise<void>}
@public
*/
export default function blur(target = document.activeElement) {
return nextTickPromise().then(() => {
let element = getElement(target);
if (!element) {
throw new Error(`Element not found when calling \`blur('${target}')\`.`);
}

if (!isFocusable(element)) {
throw new Error(`${target} is not focusable`);
}

__blur__(element);

return settled();
});
}
44 changes: 44 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/click.js
@@ -0,0 +1,44 @@
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
@method __click__
@param {Element} element
*/
export function __click__(element) {
fireEvent(element, 'mousedown');

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

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

/**
@method click
@param {String|Element} target
@return {Promise<void>}
@public
*/
export default function click(target) {
return nextTickPromise().then(() => {
if (!target) {
throw new Error('Must pass an element or selector to `click`.');
}

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

__click__(element);
return settled();
});
}
47 changes: 47 additions & 0 deletions addon-test-support/@ember/test-helpers/dom/fill-in.js
@@ -0,0 +1,47 @@
import getElement from './-get-element';
import isFormControl from './-is-form-control';
import { __focus__ } from './focus';
import settled from '../settled';
import fireEvent from './fire-event';
import { nextTickPromise } from '../-utils';

/*
@method fillIn
@param {String|Element} target
@param {String} text
@return {Promise<void>}
@public
*/
export default function fillIn(target, text) {
return nextTickPromise().then(() => {
if (!target) {
throw new Error('Must pass an element or selector to `fillIn`.');
}

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

if (!isFormControl(element) && !element.isContentEditable) {
throw new Error('`fillIn` is only usable on form controls or contenteditable elements.');
}

if (!text) {
throw new Error('Must provide `text` when calling `fillIn`.');
}

__focus__(element);

if (element.isContentEditable) {
element.innerHTML = text;
} else {
element.value = text;
}

fireEvent(element, 'input');
fireEvent(element, 'change');

return settled();
});
}