Skip to content

Commit b020c05

Browse files
authored
Merge pull request #18 from pixelhandler/two-find-helpers
find and findAll helpers, two types of queries
2 parents bdb5ebb + 7f6481b commit b020c05

File tree

15 files changed

+457
-308
lines changed

15 files changed

+457
-308
lines changed

README.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,30 @@
22

33
# ember-native-dom-helpers
44

5-
Test helpers for integration tests that mimic the behaviour of the acceptance test
6-
helpers provided by Ember.
5+
Test helpers for integration tests that mimic the behaviour of the acceptance
6+
test helpers provided by Ember.
7+
8+
Use this addon as a way to start the gradual migration towards the future
9+
"testing unification" [RFC][emberjs/rfcs/pull/119] which proposes only native DOM.
10+
11+
See the [Grand Testing Unification RFC][emberjs/rfcs/pull/119]
12+
13+
- [shared-test-helpers]
14+
- [example-migration-of-component-integration-test]
15+
16+
[emberjs/rfcs/pull/119]: https://github.com/emberjs/rfcs/pull/119
17+
[shared-test-helpers]: https://github.com/rwjblue/rfcs/blob/42/text/0000-grand-testing-unification.md#shared-test-helpers
18+
[example-migration-of-component-integration-test]: https://github.com/rwjblue/rfcs/blob/42/text/0000-grand-testing-unification.md#example-migration-of-component-integration-test
19+
20+
**Status**: (Pre) 1.0, although we have a good idea what the needs are for
21+
test helpers, we are working though a few points on what changes are needed
22+
when using only standard DOM APIs (i.e. without jQuery).
723

8-
Use this addon as a way to start the gradual migration towards the future "testing unification" RFC setup. That RFC proposes only native DOM.
924

1025
## Usage
1126

1227
```js
13-
import { click, fillIn, find, keyEvent, triggerEvent } from 'ember-native-dom-helpers/test-support/helpers';
28+
import { click, fillIn, find, findAll, keyEvent, triggerEvent } from 'ember-native-dom-helpers/test-support/helpers';
1429

1530
moduleForComponent('my-component', 'Integration | Component | my-component', {
1631
integration: true
@@ -27,6 +42,7 @@ test('I can interact with my component', function(assert) {
2742
keyEvent('.other-input', 'keyup', 40); // down arrow
2843
triggerEvent('.some-drop-area', 'mouseenter');
2944
assert.ok(find('.result-of-event-happened'));
45+
assert.equal(findAll('.result-list-item').length, 3);
3046
})
3147
```
3248

@@ -64,8 +80,9 @@ The main advantages are:
6480

6581
## Standard DOM elements returned using a `find` helper
6682

67-
- The `find` helper uses `document.querySelectorAll` and will return a single `HTMLElement`
68-
or a `NodeList` of elements. The `find` helper will query the DOM within `#ember-testing`
83+
- The `find` helper uses `document.querySelector` and will return a single `HTMLElement` or `null`.
84+
- The `findAll` helper uses `document.querySelectorAll` and returns `NodeList` with zero or more elements.
85+
- Both `find` and `findAll` helpers query the DOM within `#ember-testing`.
6986
- To use a different value from your `config/environment.js` settings, add to `tests/test-helper.js`
7087

7188
```js
@@ -80,6 +97,8 @@ settings.rootElement = rootElement || settings.rootElement;
8097

8198
- `click(selector, eventOptions)`
8299
- `fillIn(selector, text)`
83-
- `find(selector, contextHTMLElement)` (query within test DOM, `#ember-testing`)
100+
- `find(selector, contextHTMLElement)` (query for an element in test DOM, `#ember-testing`)
101+
- `findAll(selector, contextHTMLElement)` (query for elements in test DOM, `#ember-testing`)
102+
- `findWithAssert(selector, contextHTMLElement)` (same as `find`, but raises Error if no result)
84103
- `keyEvent(selector, type, keyCode)` (type being `keydown`, `keyup` or `keypress`)
85104
- `triggerEvent(selector, type, options)`

addon-test-support/click.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Ember from 'ember';
2+
import { findWithAssert } from 'ember-native-dom-helpers/test-support/find-with-assert';
3+
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
4+
import { focus } from 'ember-native-dom-helpers/test-support/focus';
5+
import wait from 'ember-test-helpers/wait';
6+
7+
const { run } = Ember;
8+
9+
10+
/*
11+
@method click
12+
@param {String} selector
13+
@param {Object} options
14+
@return {RSVP.Promise}
15+
@public
16+
*/
17+
export function click(selector, options = {}) {
18+
let el = findWithAssert(selector);
19+
run(() => fireEvent(el, 'mousedown', options));
20+
focus(el);
21+
run(() => fireEvent(el, 'mouseup', options));
22+
run(() => fireEvent(el, 'click', options));
23+
return wait();
24+
}

addon-test-support/fill-in.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Ember from 'ember';
2+
import { findWithAssert } from 'ember-native-dom-helpers/test-support/find-with-assert';
3+
import { focus } from 'ember-native-dom-helpers/test-support/focus';
4+
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
5+
import wait from 'ember-test-helpers/wait';
6+
7+
const { run } = Ember;
8+
9+
/*
10+
@method fillIn
11+
@param {String} selector
12+
@param {String} text
13+
@return {RSVP.Promise}
14+
@public
15+
*/
16+
export function fillIn(selector, text) {
17+
let el = findWithAssert(selector);
18+
run(() => focus(el));
19+
run(() => el.value = text);
20+
run(() => fireEvent(el, 'input'));
21+
run(() => fireEvent(el, 'change'));
22+
return wait();
23+
}

addon-test-support/find-all.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import settings from 'ember-native-dom-helpers/test-support/settings';
2+
3+
/*
4+
The findAll test helper uses `querySelectorAll` to search inside the test
5+
DOM (based on app configuration for the rootElement).
6+
7+
Alternalively, a second argument may be passed which is an element as the
8+
DOM context to search within.
9+
10+
@method findAll
11+
@param {String} CSS selector to find elements in the test DOM
12+
@param {HTMLElement} contextEl to query within, query from its contained DOM
13+
@return {NodeList} A (non-live) list of zero or more HTMLElement objects
14+
@public
15+
*/
16+
export function findAll(selector, contextEl) {
17+
let result;
18+
if (contextEl instanceof HTMLElement) {
19+
result = contextEl.querySelectorAll(selector);
20+
} else {
21+
result = document.querySelectorAll(`${settings.rootElement} ${selector}`);
22+
}
23+
return result;
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { find } from './find';
2+
3+
/*
4+
@method findWithAssert
5+
@param {String} CSS selector to find elements in the test DOM
6+
@param {HTMLElement} contextEl to query within, query from its contained DOM
7+
@return {Error|HTMLElement} element if found, or raises an error
8+
@public
9+
*/
10+
export function findWithAssert(selector, contextEl) {
11+
let el = find(selector, contextEl);
12+
if (el === null) {
13+
throw new Error(`Element ${selector} not found.`);
14+
} else {
15+
return el;
16+
}
17+
}

addon-test-support/find.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import settings from 'ember-native-dom-helpers/test-support/settings';
2+
3+
/*
4+
The find test helper uses `querySelector` to search inside the test
5+
DOM (based on app configuration for the rootElement).
6+
7+
Alternalively, a second argument may be passed which is an element as the
8+
DOM context to search within.
9+
10+
@method find
11+
@param {String} CSS selector to find one or more elements in the test DOM
12+
@param {HTMLElement} contextEl to query within, query from its contained DOM
13+
@return {null|HTMLElement} null or an element
14+
@public
15+
*/
16+
export function find(selector, contextEl) {
17+
let result;
18+
if (contextEl instanceof HTMLElement) {
19+
result = contextEl.querySelector(selector);
20+
} else {
21+
result = document.querySelector(`${settings.rootElement} ${selector}`);
22+
}
23+
return result;
24+
}

addon-test-support/fire-event.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import Ember from 'ember';
2+
3+
const { merge } = Ember;
4+
const DEFAULT_EVENT_OPTIONS = { canBubble: true, cancelable: true };
5+
const KEYBOARD_EVENT_TYPES = ['keydown', 'keypress', 'keyup'];
6+
const MOUSE_EVENT_TYPES = ['click', 'mousedown', 'mouseup', 'dblclick', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover'];
7+
8+
9+
/*
10+
@method fireEvent
11+
@param {HTMLElement} element
12+
@param {String} type
13+
@param {Object} (optional) options
14+
@private
15+
*/
16+
export function fireEvent(element, type, options = {}) {
17+
if (!element) {
18+
return;
19+
}
20+
let event;
21+
if (KEYBOARD_EVENT_TYPES.indexOf(type) > -1) {
22+
event = buildKeyboardEvent(type, options);
23+
} else if (MOUSE_EVENT_TYPES.indexOf(type) > -1) {
24+
let rect = element.getBoundingClientRect();
25+
let x = rect.left + 1;
26+
let y = rect.top + 1;
27+
let simulatedCoordinates = {
28+
screenX: x + 5,
29+
screenY: y + 95,
30+
clientX: x,
31+
clientY: y
32+
};
33+
event = buildMouseEvent(type, merge(simulatedCoordinates, options));
34+
} else {
35+
event = buildBasicEvent(type, options);
36+
}
37+
element.dispatchEvent(event);
38+
}
39+
40+
/*
41+
@method buildBasicEvent
42+
@param {String} type
43+
@param {Object} (optional) options
44+
@param {Boolean} (optional) bubbles
45+
@param {Boolean} (optional) cancelable
46+
@return {Event}
47+
@private
48+
*/
49+
function buildBasicEvent(type, options = {}, bubbles = true, cancelable = true) {
50+
let event = document.createEvent('Events');
51+
event.initEvent(type, bubbles, cancelable);
52+
merge(event, options);
53+
return event;
54+
}
55+
56+
/*
57+
@method buildMouseEvent
58+
@param {String} type
59+
@param {Object} (optional) options
60+
@return {Event}
61+
@private
62+
*/
63+
function buildMouseEvent(type, options = {}) {
64+
let event;
65+
try {
66+
event = document.createEvent('MouseEvents');
67+
let eventOpts = merge(merge({}, DEFAULT_EVENT_OPTIONS), options);
68+
event.initMouseEvent(
69+
type,
70+
eventOpts.canBubble,
71+
eventOpts.cancelable,
72+
window,
73+
eventOpts.detail,
74+
eventOpts.screenX,
75+
eventOpts.screenY,
76+
eventOpts.clientX,
77+
eventOpts.clientY,
78+
eventOpts.ctrlKey,
79+
eventOpts.altKey,
80+
eventOpts.shiftKey,
81+
eventOpts.metaKey,
82+
eventOpts.button,
83+
eventOpts.relatedTarget);
84+
} catch (e) {
85+
event = buildBasicEvent(type, options);
86+
}
87+
return event;
88+
}
89+
90+
/*
91+
@method buildKeyboardEvent
92+
@param {String} type
93+
@param {Object} (optional) options
94+
@return {Event}
95+
@private
96+
*/
97+
function buildKeyboardEvent(type, options = {}) {
98+
let event;
99+
try {
100+
event = document.createEvent('KeyEvents');
101+
let eventOpts = merge(merge({}, DEFAULT_EVENT_OPTIONS), options);
102+
event.initKeyEvent(
103+
type,
104+
eventOpts.canBubble,
105+
eventOpts.cancelable,
106+
window,
107+
eventOpts.ctrlKey,
108+
eventOpts.altKey,
109+
eventOpts.shiftKey,
110+
eventOpts.metaKey,
111+
eventOpts.keyCode,
112+
eventOpts.charCode
113+
);
114+
} catch (e) {
115+
event = buildBasicEvent(type, options);
116+
}
117+
return event;
118+
}

addon-test-support/focus.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Ember from 'ember';
2+
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
3+
import wait from 'ember-test-helpers/wait';
4+
5+
const { run } = Ember;
6+
7+
/*
8+
@method focus
9+
@param {HTMLElement} el
10+
@private
11+
*/
12+
export function focus(el) {
13+
if (!el) { return; }
14+
15+
if (el.tagName === 'INPUT' || el.contentEditable || el.tagName === 'A') {
16+
let type = el.type;
17+
if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
18+
run(null, function() {
19+
// Firefox does not trigger the `focusin` event if the window
20+
// does not have focus. If the document does not have focus then
21+
// fire `focusin` event as well.
22+
if (document.hasFocus && !document.hasFocus()) {
23+
fireEvent(el, 'focusin');
24+
fireEvent(el, 'focus', null, false); // focus does not bubble
25+
} else {
26+
el.focus();
27+
}
28+
});
29+
}
30+
}
31+
return wait();
32+
}

0 commit comments

Comments
 (0)