Skip to content

Commit

Permalink
Merge pull request #18 from pixelhandler/two-find-helpers
Browse files Browse the repository at this point in the history
find and findAll helpers, two types of queries
  • Loading branch information
cibernox authored Feb 27, 2017
2 parents bdb5ebb + 7f6481b commit b020c05
Show file tree
Hide file tree
Showing 15 changed files with 457 additions and 308 deletions.
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@

# ember-native-dom-helpers

Test helpers for integration tests that mimic the behaviour of the acceptance test
helpers provided by Ember.
Test helpers for integration tests that mimic the behaviour of the acceptance
test helpers provided by Ember.

Use this addon as a way to start the gradual migration towards the future
"testing unification" [RFC][emberjs/rfcs/pull/119] which proposes only native DOM.

See the [Grand Testing Unification RFC][emberjs/rfcs/pull/119]

- [shared-test-helpers]
- [example-migration-of-component-integration-test]

[emberjs/rfcs/pull/119]: https://github.com/emberjs/rfcs/pull/119
[shared-test-helpers]: https://github.com/rwjblue/rfcs/blob/42/text/0000-grand-testing-unification.md#shared-test-helpers
[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

**Status**: (Pre) 1.0, although we have a good idea what the needs are for
test helpers, we are working though a few points on what changes are needed
when using only standard DOM APIs (i.e. without jQuery).

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

## Usage

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

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

Expand Down Expand Up @@ -64,8 +80,9 @@ The main advantages are:

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

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

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

- `click(selector, eventOptions)`
- `fillIn(selector, text)`
- `find(selector, contextHTMLElement)` (query within test DOM, `#ember-testing`)
- `find(selector, contextHTMLElement)` (query for an element in test DOM, `#ember-testing`)
- `findAll(selector, contextHTMLElement)` (query for elements in test DOM, `#ember-testing`)
- `findWithAssert(selector, contextHTMLElement)` (same as `find`, but raises Error if no result)
- `keyEvent(selector, type, keyCode)` (type being `keydown`, `keyup` or `keypress`)
- `triggerEvent(selector, type, options)`
24 changes: 24 additions & 0 deletions addon-test-support/click.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Ember from 'ember';
import { findWithAssert } from 'ember-native-dom-helpers/test-support/find-with-assert';
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
import { focus } from 'ember-native-dom-helpers/test-support/focus';
import wait from 'ember-test-helpers/wait';

const { run } = Ember;


/*
@method click
@param {String} selector
@param {Object} options
@return {RSVP.Promise}
@public
*/
export function click(selector, options = {}) {
let el = findWithAssert(selector);
run(() => fireEvent(el, 'mousedown', options));
focus(el);
run(() => fireEvent(el, 'mouseup', options));
run(() => fireEvent(el, 'click', options));
return wait();
}
23 changes: 23 additions & 0 deletions addon-test-support/fill-in.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Ember from 'ember';
import { findWithAssert } from 'ember-native-dom-helpers/test-support/find-with-assert';
import { focus } from 'ember-native-dom-helpers/test-support/focus';
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
import wait from 'ember-test-helpers/wait';

const { run } = Ember;

/*
@method fillIn
@param {String} selector
@param {String} text
@return {RSVP.Promise}
@public
*/
export function fillIn(selector, text) {
let el = findWithAssert(selector);
run(() => focus(el));
run(() => el.value = text);
run(() => fireEvent(el, 'input'));
run(() => fireEvent(el, 'change'));
return wait();
}
24 changes: 24 additions & 0 deletions addon-test-support/find-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import settings from 'ember-native-dom-helpers/test-support/settings';

/*
The findAll test helper uses `querySelectorAll` to search inside the test
DOM (based on app configuration for the rootElement).
Alternalively, a second argument may be passed which is an element as the
DOM context to search within.
@method findAll
@param {String} CSS selector to find elements in the test DOM
@param {HTMLElement} contextEl to query within, query from its contained DOM
@return {NodeList} A (non-live) list of zero or more HTMLElement objects
@public
*/
export function findAll(selector, contextEl) {
let result;
if (contextEl instanceof HTMLElement) {
result = contextEl.querySelectorAll(selector);
} else {
result = document.querySelectorAll(`${settings.rootElement} ${selector}`);
}
return result;
}
17 changes: 17 additions & 0 deletions addon-test-support/find-with-assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { find } from './find';

/*
@method findWithAssert
@param {String} CSS selector to find elements in the test DOM
@param {HTMLElement} contextEl to query within, query from its contained DOM
@return {Error|HTMLElement} element if found, or raises an error
@public
*/
export function findWithAssert(selector, contextEl) {
let el = find(selector, contextEl);
if (el === null) {
throw new Error(`Element ${selector} not found.`);
} else {
return el;
}
}
24 changes: 24 additions & 0 deletions addon-test-support/find.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import settings from 'ember-native-dom-helpers/test-support/settings';

/*
The find test helper uses `querySelector` to search inside the test
DOM (based on app configuration for the rootElement).
Alternalively, a second argument may be passed which is an element as the
DOM context to search within.
@method find
@param {String} CSS selector to find one or more elements in the test DOM
@param {HTMLElement} contextEl to query within, query from its contained DOM
@return {null|HTMLElement} null or an element
@public
*/
export function find(selector, contextEl) {
let result;
if (contextEl instanceof HTMLElement) {
result = contextEl.querySelector(selector);
} else {
result = document.querySelector(`${settings.rootElement} ${selector}`);
}
return result;
}
118 changes: 118 additions & 0 deletions addon-test-support/fire-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Ember from 'ember';

const { merge } = Ember;
const DEFAULT_EVENT_OPTIONS = { canBubble: true, cancelable: true };
const KEYBOARD_EVENT_TYPES = ['keydown', 'keypress', 'keyup'];
const MOUSE_EVENT_TYPES = ['click', 'mousedown', 'mouseup', 'dblclick', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover'];


/*
@method fireEvent
@param {HTMLElement} element
@param {String} type
@param {Object} (optional) options
@private
*/
export function fireEvent(element, type, options = {}) {
if (!element) {
return;
}
let event;
if (KEYBOARD_EVENT_TYPES.indexOf(type) > -1) {
event = buildKeyboardEvent(type, options);
} else if (MOUSE_EVENT_TYPES.indexOf(type) > -1) {
let rect = element.getBoundingClientRect();
let x = rect.left + 1;
let y = rect.top + 1;
let simulatedCoordinates = {
screenX: x + 5,
screenY: y + 95,
clientX: x,
clientY: y
};
event = buildMouseEvent(type, merge(simulatedCoordinates, options));
} else {
event = buildBasicEvent(type, options);
}
element.dispatchEvent(event);
}

/*
@method buildBasicEvent
@param {String} type
@param {Object} (optional) options
@param {Boolean} (optional) bubbles
@param {Boolean} (optional) cancelable
@return {Event}
@private
*/
function buildBasicEvent(type, options = {}, bubbles = true, cancelable = true) {
let event = document.createEvent('Events');
event.initEvent(type, bubbles, cancelable);
merge(event, options);
return event;
}

/*
@method buildMouseEvent
@param {String} type
@param {Object} (optional) options
@return {Event}
@private
*/
function buildMouseEvent(type, options = {}) {
let event;
try {
event = document.createEvent('MouseEvents');
let eventOpts = merge(merge({}, DEFAULT_EVENT_OPTIONS), options);
event.initMouseEvent(
type,
eventOpts.canBubble,
eventOpts.cancelable,
window,
eventOpts.detail,
eventOpts.screenX,
eventOpts.screenY,
eventOpts.clientX,
eventOpts.clientY,
eventOpts.ctrlKey,
eventOpts.altKey,
eventOpts.shiftKey,
eventOpts.metaKey,
eventOpts.button,
eventOpts.relatedTarget);
} catch (e) {
event = buildBasicEvent(type, options);
}
return event;
}

/*
@method buildKeyboardEvent
@param {String} type
@param {Object} (optional) options
@return {Event}
@private
*/
function buildKeyboardEvent(type, options = {}) {
let event;
try {
event = document.createEvent('KeyEvents');
let eventOpts = merge(merge({}, DEFAULT_EVENT_OPTIONS), options);
event.initKeyEvent(
type,
eventOpts.canBubble,
eventOpts.cancelable,
window,
eventOpts.ctrlKey,
eventOpts.altKey,
eventOpts.shiftKey,
eventOpts.metaKey,
eventOpts.keyCode,
eventOpts.charCode
);
} catch (e) {
event = buildBasicEvent(type, options);
}
return event;
}
32 changes: 32 additions & 0 deletions addon-test-support/focus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Ember from 'ember';
import { fireEvent } from 'ember-native-dom-helpers/test-support/fire-event';
import wait from 'ember-test-helpers/wait';

const { run } = Ember;

/*
@method focus
@param {HTMLElement} el
@private
*/
export function focus(el) {
if (!el) { return; }

if (el.tagName === 'INPUT' || el.contentEditable || el.tagName === 'A') {
let type = el.type;
if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
run(null, function() {
// Firefox does not trigger the `focusin` event if the window
// does not have focus. If the document does not have focus then
// fire `focusin` event as well.
if (document.hasFocus && !document.hasFocus()) {
fireEvent(el, 'focusin');
fireEvent(el, 'focus', null, false); // focus does not bubble
} else {
el.focus();
}
});
}
}
return wait();
}
Loading

0 comments on commit b020c05

Please sign in to comment.