diff --git a/addon-test-support/@ember/test-helpers/-internal/helper-hooks.ts b/addon-test-support/@ember/test-helpers/-internal/helper-hooks.ts new file mode 100644 index 000000000..2be12378d --- /dev/null +++ b/addon-test-support/@ember/test-helpers/-internal/helper-hooks.ts @@ -0,0 +1,62 @@ +type Hook = (...args: any[]) => void | Promise; +type HookUnregister = { + unregister: () => void; +}; + +const registeredHooks = new Map>(); + +/** + * @private + * @param {string} helperName The name of the test helper in which to run the hook. + * @param {string} label A label to help identify the hook. + * @returns {string} The compound key for the helper. + */ +function getHelperKey(helperName: string, label: string) { + return `${helperName}:${label}`; +} + +/** + * Registers a hook function to be run during the invocation of a test helper. + * + * @private + * @param {string} helperName The name of the test helper in which to run the hook. + * @param {string} label A label to help identify the hook. Built-in labels are `start` and `end`, + * designating the start of the helper invocation and the end. + * @param {Function} hook The hook function to run when the test helper is invoked. + * @returns {HookUnregister} An object containing an unregister function that will unregister + * the specific hook registered to the helper. + */ +export function registerHook(helperName: string, label: string, hook: Hook): HookUnregister { + let helperKey = getHelperKey(helperName, label); + let hooksForHelper = registeredHooks.get(helperKey); + + if (hooksForHelper === undefined) { + hooksForHelper = new Set(); + registeredHooks.set(helperKey, hooksForHelper); + } + + hooksForHelper.add(hook); + + return { + unregister() { + hooksForHelper!.delete(hook); + }, + }; +} + +/** + * Runs all hooks registered for a specific test helper. + * + * @private + * @param {string} helperName The name of the test helper. + * @param {string} label A label to help identify the hook. Built-in labels are `start` and `end`, + * designating the start of the helper invocation and the end. + * @param {any[]} args Any arguments originally passed to the test helper. + * @returns {Promise} A promise representing the serial invocation of the hooks. + */ +export function runHooks(helperName: string, label: string, ...args: any[]): Promise { + let hooks = registeredHooks.get(getHelperKey(helperName, label)) || new Set(); + let promises = [...hooks].map(hook => hook(...args)); + + return Promise.all(promises).then(() => {}); +} diff --git a/addon-test-support/@ember/test-helpers/dom/blur.ts b/addon-test-support/@ember/test-helpers/dom/blur.ts index 807dacd5e..ac4de5eae 100644 --- a/addon-test-support/@ember/test-helpers/dom/blur.ts +++ b/addon-test-support/@ember/test-helpers/dom/blur.ts @@ -5,6 +5,11 @@ import { nextTickPromise } from '../-utils'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; import isFocusable from './-is-focusable'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('blur', 'start', (target: Target) => { + log('blur', target); +}); /** @private @@ -56,16 +61,17 @@ export function __blur__(element: HTMLElement | Element | Document | SVGElement) blur('input'); */ export default function blur(target: Target = document.activeElement!): Promise { - log('blur', target); - - return nextTickPromise().then(() => { - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`blur('${target}')\`.`); - } + return nextTickPromise() + .then(() => runHooks('blur', 'start', target)) + .then(() => { + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`blur('${target}')\`.`); + } - __blur__(element); + __blur__(element); - return settled(); - }); + return settled(); + }) + .then(() => runHooks('blur', 'end', target)); } diff --git a/addon-test-support/@ember/test-helpers/dom/click.ts b/addon-test-support/@ember/test-helpers/dom/click.ts index c70517c2e..6b4150102 100644 --- a/addon-test-support/@ember/test-helpers/dom/click.ts +++ b/addon-test-support/@ember/test-helpers/dom/click.ts @@ -8,10 +8,15 @@ import { nextTickPromise } from '../-utils'; import isFormControl from './-is-form-control'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; const PRIMARY_BUTTON = 1; const MAIN_BUTTON_PRESSED = 0; +registerHook('click', 'start', (target: Target) => { + log('click', target); +}); + /** * Represent a particular mouse button being clicked. * See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons for available options. @@ -83,26 +88,27 @@ export function __click__(element: Element | Document, options: MouseEventInit): click('button', { shiftKey: true }); */ export default function click(target: Target, _options: MouseEventInit = {}): Promise { - log('click', target); - let options = assign({}, DEFAULT_CLICK_OPTIONS, _options); - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `click`.'); - } + return nextTickPromise() + .then(() => runHooks('click', 'start', target, _options)) + .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}')\`.`); - } + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`click('${target}')\`.`); + } - if (isFormControl(element) && element.disabled) { - throw new Error(`Can not \`click\` disabled ${element}`); - } + if (isFormControl(element) && element.disabled) { + throw new Error(`Can not \`click\` disabled ${element}`); + } - __click__(element, options); + __click__(element, options); - return settled(); - }); + return settled(); + }) + .then(() => runHooks('click', 'end', target, _options)); } diff --git a/addon-test-support/@ember/test-helpers/dom/double-click.ts b/addon-test-support/@ember/test-helpers/dom/double-click.ts index 8163c1bc9..c4d4bb6f2 100644 --- a/addon-test-support/@ember/test-helpers/dom/double-click.ts +++ b/addon-test-support/@ember/test-helpers/dom/double-click.ts @@ -9,6 +9,11 @@ import { DEFAULT_CLICK_OPTIONS } from './click'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; import isFormControl from './-is-form-control'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('doubleClick', 'start', (target: Target) => { + log('doubleClick', target); +}); /** @private @@ -84,26 +89,27 @@ export function __doubleClick__(element: Element | Document, options: MouseEvent doubleClick('button', { shiftKey: true }); */ export default function doubleClick(target: Target, _options: MouseEventInit = {}): Promise { - log('doubleClick', target); - let options = assign({}, DEFAULT_CLICK_OPTIONS, _options); - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `doubleClick`.'); - } + return nextTickPromise() + .then(() => runHooks('doubleClick', 'start', target, _options)) + .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}')\`.`); - } + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`doubleClick('${target}')\`.`); + } - if (isFormControl(element) && element.disabled) { - throw new Error(`Can not \`doubleClick\` disabled ${element}`); - } + if (isFormControl(element) && element.disabled) { + throw new Error(`Can not \`doubleClick\` disabled ${element}`); + } - __doubleClick__(element, options); + __doubleClick__(element, options); - return settled(); - }); + return settled(); + }) + .then(() => runHooks('doubleClick', 'end', target, _options)); } diff --git a/addon-test-support/@ember/test-helpers/dom/fill-in.ts b/addon-test-support/@ember/test-helpers/dom/fill-in.ts index c7f031d0a..61e82c8d2 100644 --- a/addon-test-support/@ember/test-helpers/dom/fill-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/fill-in.ts @@ -7,6 +7,11 @@ import fireEvent from './fire-event'; import { nextTickPromise } from '../-utils'; import Target, { isContentEditable } from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('fillIn', 'start', (target: Target, text: string) => { + log('fillIn', target, text); +}); /** Fill the provided text into the `value` property (or set `.innerHTML` when @@ -26,46 +31,47 @@ import { log } from '@ember/test-helpers/dom/-logging'; fillIn('input', 'hello world'); */ export default function fillIn(target: Target, text: string): Promise { - log('fillIn', target, text); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `fillIn`.'); - } - - let element = getElement(target) as Element | HTMLElement; - if (!element) { - throw new Error(`Element not found when calling \`fillIn('${target}')\`.`); - } - - if (typeof text === 'undefined' || text === null) { - throw new Error('Must provide `text` when calling `fillIn`.'); - } + return nextTickPromise() + .then(() => runHooks('fillIn', 'start', target, text)) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `fillIn`.'); + } - if (isFormControl(element)) { - if (element.disabled) { - throw new Error(`Can not \`fillIn\` disabled '${target}'.`); + let element = getElement(target) as Element | HTMLElement; + if (!element) { + throw new Error(`Element not found when calling \`fillIn('${target}')\`.`); } - if ('readOnly' in element && element.readOnly) { - throw new Error(`Can not \`fillIn\` readonly '${target}'.`); + if (typeof text === 'undefined' || text === null) { + throw new Error('Must provide `text` when calling `fillIn`.'); } - guardForMaxlength(element, text, 'fillIn'); + if (isFormControl(element)) { + if (element.disabled) { + throw new Error(`Can not \`fillIn\` disabled '${target}'.`); + } + + if ('readOnly' in element && element.readOnly) { + throw new Error(`Can not \`fillIn\` readonly '${target}'.`); + } - __focus__(element); + guardForMaxlength(element, text, 'fillIn'); - element.value = text; - } else if (isContentEditable(element)) { - __focus__(element); + __focus__(element); - element.innerHTML = text; - } else { - throw new Error('`fillIn` is only usable on form controls or contenteditable elements.'); - } - fireEvent(element, 'input'); - fireEvent(element, 'change'); + element.value = text; + } else if (isContentEditable(element)) { + __focus__(element); + + element.innerHTML = text; + } else { + throw new Error('`fillIn` is only usable on form controls or contenteditable elements.'); + } + fireEvent(element, 'input'); + fireEvent(element, 'change'); - return settled(); - }); + return settled(); + }) + .then(() => runHooks('fillIn', 'end', target, text)); } diff --git a/addon-test-support/@ember/test-helpers/dom/focus.ts b/addon-test-support/@ember/test-helpers/dom/focus.ts index 76e45a4a2..1d5fe1a50 100644 --- a/addon-test-support/@ember/test-helpers/dom/focus.ts +++ b/addon-test-support/@ember/test-helpers/dom/focus.ts @@ -5,6 +5,11 @@ import isFocusable from './-is-focusable'; import { nextTickPromise } from '../-utils'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('focus', 'start', (target: Target) => { + log('focus', target); +}); /** @private @@ -59,20 +64,21 @@ export function __focus__(element: HTMLElement | Element | Document | SVGElement focus('input'); */ export default function focus(target: Target): Promise { - log('focus', target); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `focus`.'); - } - - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`focus('${target}')\`.`); - } - - __focus__(element); - - return settled(); - }); + return nextTickPromise() + .then(() => runHooks('focus', 'start', target)) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `focus`.'); + } + + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`focus('${target}')\`.`); + } + + __focus__(element); + + return settled(); + }) + .then(() => runHooks('focus', 'end', target)); } diff --git a/addon-test-support/@ember/test-helpers/dom/scroll-to.ts b/addon-test-support/@ember/test-helpers/dom/scroll-to.ts index d44e29f61..729192775 100644 --- a/addon-test-support/@ember/test-helpers/dom/scroll-to.ts +++ b/addon-test-support/@ember/test-helpers/dom/scroll-to.ts @@ -3,6 +3,7 @@ import fireEvent from './fire-event'; import settled from '../settled'; import { nextTickPromise } from '../-utils'; import { isElement } from './-target'; +import { runHooks } from '../-internal/helper-hooks'; /** Scrolls DOM element or selector to the given coordinates. @@ -25,31 +26,34 @@ export default function scrollTo( x: number, y: number ): Promise { - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `scrollTo`.'); - } - - if (x === undefined || y === undefined) { - throw new Error('Must pass both x and y coordinates to `scrollTo`.'); - } - - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`scrollTo('${target}')\`.`); - } - - if (!isElement(element)) { - throw new Error( - `"target" must be an element, but was a ${element.nodeType} when calling \`scrollTo('${target}')\`.` - ); - } - - element.scrollTop = y; - element.scrollLeft = x; - - fireEvent(element, 'scroll'); - - return settled(); - }); + return nextTickPromise() + .then(() => runHooks('scrollTo', 'start', target)) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `scrollTo`.'); + } + + if (x === undefined || y === undefined) { + throw new Error('Must pass both x and y coordinates to `scrollTo`.'); + } + + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`scrollTo('${target}')\`.`); + } + + if (!isElement(element)) { + throw new Error( + `"target" must be an element, but was a ${element.nodeType} when calling \`scrollTo('${target}')\`.` + ); + } + + element.scrollTop = y; + element.scrollLeft = x; + + fireEvent(element, 'scroll'); + + return settled(); + }) + .then(() => runHooks('scrollTo', 'end', target)); } diff --git a/addon-test-support/@ember/test-helpers/dom/select.ts b/addon-test-support/@ember/test-helpers/dom/select.ts index 975adf852..51248d864 100644 --- a/addon-test-support/@ember/test-helpers/dom/select.ts +++ b/addon-test-support/@ember/test-helpers/dom/select.ts @@ -5,6 +5,7 @@ import settled from '../settled'; import fireEvent from './fire-event'; import { nextTickPromise } from '../-utils'; import Target from './-target'; +import { runHooks } from '../-internal/helper-hooks'; /** Set the `selected` property true for the provided option the target is a @@ -34,52 +35,55 @@ export default function select( options: string | string[], keepPreviouslySelected = false ): Promise { - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `select`.'); - } - - if (typeof options === 'undefined' || options === null) { - throw new Error('Must provide an `option` or `options` to select when calling `select`.'); - } - - const element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`select('${target}')\`.`); - } - - if (!isSelectElement(element)) { - throw new Error(`Element is not a HTMLSelectElement when calling \`select('${target}')\`.`); - } - - if (element.disabled) { - throw new Error(`Element is disabled when calling \`select('${target}')\`.`); - } - - options = Array.isArray(options) ? options : [options]; - - if (!element.multiple && options.length > 1) { - throw new Error( - `HTMLSelectElement \`multiple\` attribute is set to \`false\` but multiple options were passed when calling \`select('${target}')\`.` - ); - } - - __focus__(element); - - for (let i = 0; i < element.options.length; i++) { - let elementOption = element.options.item(i); - if (elementOption) { - if (options.indexOf(elementOption.value) > -1) { - elementOption.selected = true; - } else if (!keepPreviouslySelected) { - elementOption.selected = false; + return nextTickPromise() + .then(() => runHooks('select', 'start', target, options, keepPreviouslySelected)) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `select`.'); + } + + if (typeof options === 'undefined' || options === null) { + throw new Error('Must provide an `option` or `options` to select when calling `select`.'); + } + + const element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`select('${target}')\`.`); + } + + if (!isSelectElement(element)) { + throw new Error(`Element is not a HTMLSelectElement when calling \`select('${target}')\`.`); + } + + if (element.disabled) { + throw new Error(`Element is disabled when calling \`select('${target}')\`.`); + } + + options = Array.isArray(options) ? options : [options]; + + if (!element.multiple && options.length > 1) { + throw new Error( + `HTMLSelectElement \`multiple\` attribute is set to \`false\` but multiple options were passed when calling \`select('${target}')\`.` + ); + } + + __focus__(element); + + for (let i = 0; i < element.options.length; i++) { + let elementOption = element.options.item(i); + if (elementOption) { + if (options.indexOf(elementOption.value) > -1) { + elementOption.selected = true; + } else if (!keepPreviouslySelected) { + elementOption.selected = false; + } } } - } - fireEvent(element, 'input'); - fireEvent(element, 'change'); + fireEvent(element, 'input'); + fireEvent(element, 'change'); - return settled(); - }); + return settled(); + }) + .then(() => runHooks('select', 'end', target, options, keepPreviouslySelected)); } diff --git a/addon-test-support/@ember/test-helpers/dom/tap.ts b/addon-test-support/@ember/test-helpers/dom/tap.ts index b0f0b6c1b..032edbd37 100644 --- a/addon-test-support/@ember/test-helpers/dom/tap.ts +++ b/addon-test-support/@ember/test-helpers/dom/tap.ts @@ -6,6 +6,11 @@ import { nextTickPromise } from '../-utils'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; import isFormControl from './-is-form-control'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('tap', 'start', (target: Target) => { + log('tap', target); +}); /** Taps on the specified target. @@ -50,29 +55,34 @@ import isFormControl from './-is-form-control'; tap('button'); */ export default function tap(target: Target, options: object = {}): Promise { - log('tap', target); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `tap`.'); - } - - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`tap('${target}')\`.`); - } - - if (isFormControl(element) && element.disabled) { - throw new Error(`Can not \`tap\` disabled ${element}`); - } - - let touchstartEv = fireEvent(element, 'touchstart', options); - let touchendEv = fireEvent(element, 'touchend', options); - - if (!touchstartEv.defaultPrevented && !touchendEv.defaultPrevented) { - __click__(element, options); - } - - return settled(); - }); + return nextTickPromise() + .then(() => { + return runHooks('tap', 'start', target, options); + }) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `tap`.'); + } + + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`tap('${target}')\`.`); + } + + if (isFormControl(element) && element.disabled) { + throw new Error(`Can not \`tap\` disabled ${element}`); + } + + let touchstartEv = fireEvent(element, 'touchstart', options); + let touchendEv = fireEvent(element, 'touchend', options); + + if (!touchstartEv.defaultPrevented && !touchendEv.defaultPrevented) { + __click__(element, options); + } + + return settled(); + }) + .then(() => { + return runHooks('tap', 'end', target, options); + }); } diff --git a/addon-test-support/@ember/test-helpers/dom/trigger-event.ts b/addon-test-support/@ember/test-helpers/dom/trigger-event.ts index 17c7e3741..62621568c 100644 --- a/addon-test-support/@ember/test-helpers/dom/trigger-event.ts +++ b/addon-test-support/@ember/test-helpers/dom/trigger-event.ts @@ -5,6 +5,11 @@ import { nextTickPromise } from '../-utils'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; import isFormControl from './-is-form-control'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook('triggerEvent', 'start', (target: Target, eventType: string) => { + log('triggerEvent', target, eventType); +}); /** * Triggers an event on the specified target. @@ -54,28 +59,33 @@ export default function triggerEvent( eventType: string, options?: object ): Promise { - log('triggerEvent', target, eventType); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `triggerEvent`.'); - } + return nextTickPromise() + .then(() => { + return runHooks('triggerEvent', 'start', target, eventType, options); + }) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `triggerEvent`.'); + } - if (!eventType) { - throw new Error(`Must provide an \`eventType\` to \`triggerEvent\``); - } + if (!eventType) { + throw new Error(`Must provide an \`eventType\` to \`triggerEvent\``); + } - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`triggerEvent('${target}', ...)\`.`); - } + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`triggerEvent('${target}', ...)\`.`); + } - if (isFormControl(element) && element.disabled) { - throw new Error(`Can not \`triggerEvent\` on disabled ${element}`); - } + if (isFormControl(element) && element.disabled) { + throw new Error(`Can not \`triggerEvent\` on disabled ${element}`); + } - fireEvent(element, eventType, options); + fireEvent(element, eventType, options); - return settled(); - }); + return settled(); + }) + .then(() => { + return runHooks('triggerEvent', 'end', target, eventType, options); + }); } diff --git a/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts b/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts index 5a56d6ea3..15306ac88 100644 --- a/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts +++ b/addon-test-support/@ember/test-helpers/dom/trigger-key-event.ts @@ -7,6 +7,15 @@ import { nextTickPromise, isNumeric } from '../-utils'; import Target from './-target'; import { log } from '@ember/test-helpers/dom/-logging'; import isFormControl from './-is-form-control'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; + +registerHook( + 'triggerKeyEvent', + 'start', + (target: Target, eventType: KeyboardEventType, key: number | string) => { + log('triggerKeyEvent', target, eventType, key); + } +); export interface KeyModifiers { ctrlKey?: boolean; @@ -185,35 +194,40 @@ export default function triggerKeyEvent( key: number | string, modifiers: KeyModifiers = DEFAULT_MODIFIERS ): Promise { - log('triggerKeyEvent', target, eventType, key); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `triggerKeyEvent`.'); - } - - let element = getElement(target); - if (!element) { - throw new Error(`Element not found when calling \`triggerKeyEvent('${target}', ...)\`.`); - } - - if (!eventType) { - throw new Error(`Must provide an \`eventType\` to \`triggerKeyEvent\``); - } - - if (!isKeyboardEventType(eventType)) { - let validEventTypes = KEYBOARD_EVENT_TYPES.join(', '); - throw new Error( - `Must provide an \`eventType\` of ${validEventTypes} to \`triggerKeyEvent\` but you passed \`${eventType}\`.` - ); - } - - if (isFormControl(element) && element.disabled) { - throw new Error(`Can not \`triggerKeyEvent\` on disabled ${element}`); - } - - __triggerKeyEvent__(element, eventType, key, modifiers); - - return settled(); - }); + return nextTickPromise() + .then(() => { + return runHooks('triggerKeyEvent', 'start', target, eventType, key); + }) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `triggerKeyEvent`.'); + } + + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`triggerKeyEvent('${target}', ...)\`.`); + } + + if (!eventType) { + throw new Error(`Must provide an \`eventType\` to \`triggerKeyEvent\``); + } + + if (!isKeyboardEventType(eventType)) { + let validEventTypes = KEYBOARD_EVENT_TYPES.join(', '); + throw new Error( + `Must provide an \`eventType\` of ${validEventTypes} to \`triggerKeyEvent\` but you passed \`${eventType}\`.` + ); + } + + if (isFormControl(element) && element.disabled) { + throw new Error(`Can not \`triggerKeyEvent\` on disabled ${element}`); + } + + __triggerKeyEvent__(element, eventType, key, modifiers); + + return settled(); + }) + .then(() => { + return runHooks('triggerKeyEvent', 'end', target, eventType, key); + }); } diff --git a/addon-test-support/@ember/test-helpers/dom/type-in.ts b/addon-test-support/@ember/test-helpers/dom/type-in.ts index a73029ee6..6c46a875b 100644 --- a/addon-test-support/@ember/test-helpers/dom/type-in.ts +++ b/addon-test-support/@ember/test-helpers/dom/type-in.ts @@ -9,11 +9,16 @@ import guardForMaxlength from './-guard-for-maxlength'; import Target, { isContentEditable, isDocument, HTMLElementContentEditable } from './-target'; import { __triggerKeyEvent__ } from './trigger-key-event'; import { log } from '@ember/test-helpers/dom/-logging'; +import { runHooks, registerHook } from '../-internal/helper-hooks'; export interface Options { delay?: number; } +registerHook('typeIn', 'start', (target: Target, text: string) => { + log('typeIn', target, text); +}); + /** * Mimics character by character entry into the target `input` or `textarea` element. * @@ -39,45 +44,50 @@ export interface Options { * typeIn('input', 'hello world'); */ export default function typeIn(target: Target, text: string, options: Options = {}): Promise { - log('typeIn', target, text); - - return nextTickPromise().then(() => { - if (!target) { - throw new Error('Must pass an element or selector to `typeIn`.'); - } - - const element = getElement(target); + return nextTickPromise() + .then(() => { + return runHooks('typeIn', 'start', target, text, options); + }) + .then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `typeIn`.'); + } - if (!element) { - throw new Error(`Element not found when calling \`typeIn('${target}')\``); - } + const element = getElement(target); - if (isDocument(element) || (!isFormControl(element) && !isContentEditable(element))) { - throw new Error('`typeIn` is only usable on form controls or contenteditable elements.'); - } + if (!element) { + throw new Error(`Element not found when calling \`typeIn('${target}')\``); + } - if (typeof text === 'undefined' || text === null) { - throw new Error('Must provide `text` when calling `typeIn`.'); - } + if (isDocument(element) || (!isFormControl(element) && !isContentEditable(element))) { + throw new Error('`typeIn` is only usable on form controls or contenteditable elements.'); + } - if (isFormControl(element)) { - if (element.disabled) { - throw new Error(`Can not \`typeIn\` disabled '${target}'.`); + if (typeof text === 'undefined' || text === null) { + throw new Error('Must provide `text` when calling `typeIn`.'); } - if ('readOnly' in element && element.readOnly) { - throw new Error(`Can not \`typeIn\` readonly '${target}'.`); + if (isFormControl(element)) { + if (element.disabled) { + throw new Error(`Can not \`typeIn\` disabled '${target}'.`); + } + + if ('readOnly' in element && element.readOnly) { + throw new Error(`Can not \`typeIn\` readonly '${target}'.`); + } } - } - __focus__(element); + __focus__(element); - let { delay = 50 } = options; + let { delay = 50 } = options; - return fillOut(element, text, delay) - .then(() => fireEvent(element, 'change')) - .then(settled); - }); + return fillOut(element, text, delay) + .then(() => fireEvent(element, 'change')) + .then(settled) + .then(() => { + return runHooks('typeIn', 'end', target, text, options); + }); + }); } // eslint-disable-next-line require-jsdoc diff --git a/addon-test-support/@ember/test-helpers/index.ts b/addon-test-support/@ember/test-helpers/index.ts index 6518e38b3..a8d03c921 100644 --- a/addon-test-support/@ember/test-helpers/index.ts +++ b/addon-test-support/@ember/test-helpers/index.ts @@ -25,6 +25,7 @@ export { default as setupOnerror, resetOnerror } from './setup-onerror'; export { getDebugInfo } from './-internal/debug-info'; export { default as registerDebugInfoHelper } from './-internal/debug-info-helpers'; export { default as getTestMetadata } from './test-metadata'; +export { registerHook as _registerHook, runHooks as _runHooks } from './-internal/helper-hooks'; // DOM Helpers export { default as click } from './dom/click'; diff --git a/addon-test-support/@ember/test-helpers/setup-application-context.ts b/addon-test-support/@ember/test-helpers/setup-application-context.ts index 4bc0b87cd..fc4cc7081 100644 --- a/addon-test-support/@ember/test-helpers/setup-application-context.ts +++ b/addon-test-support/@ember/test-helpers/setup-application-context.ts @@ -5,6 +5,7 @@ import global from './global'; import hasEmberVersion from './has-ember-version'; import settled from './settled'; import getTestMetadata, { ITestMetadata } from './test-metadata'; +import { runHooks } from './-internal/helper-hooks'; export interface ApplicationTestContext extends TestContext { element?: Element | null; @@ -119,6 +120,9 @@ export function visit(url: string, options?: { [key: string]: any }): Promise { + return runHooks('visit', 'start', url, options); + }) .then(() => { let visitResult = owner.visit(url, options); @@ -133,7 +137,10 @@ export function visit(url: string, options?: { [key: string]: any }): Promise { + return runHooks('visit', 'end', url, options); + }); } /** @@ -149,6 +156,7 @@ export function currentRouteName(): string { } let router = context.owner.lookup('router:main'); + return get(router, 'currentRouteName'); } diff --git a/addon-test-support/@ember/test-helpers/setup-rendering-context.ts b/addon-test-support/@ember/test-helpers/setup-rendering-context.ts index e9e51077b..61e0feb06 100644 --- a/addon-test-support/@ember/test-helpers/setup-rendering-context.ts +++ b/addon-test-support/@ember/test-helpers/setup-rendering-context.ts @@ -11,6 +11,7 @@ import getRootElement from './dom/get-root-element'; import { Owner } from './build-owner'; import getTestMetadata, { ITestMetadata } from './test-metadata'; import { deprecate } from '@ember/application/deprecations'; +import { runHooks } from './-internal/helper-hooks'; export const RENDERING_CLEANUP = Object.create(null); const OUTLET_TEMPLATE = hbs`{{outlet}}`; @@ -76,58 +77,63 @@ export function render(template: TemplateFactory): Promise { throw new Error('you must pass a template to `render()`'); } - return nextTickPromise().then(() => { - if (!context || !isRenderingTestContext(context)) { - throw new Error('Cannot call `render` without having first called `setupRenderingContext`.'); - } - - let { owner } = context; - let testMetadata = getTestMetadata(context); - testMetadata.usedHelpers.push('render'); - - let toplevelView = owner.lookup('-top-level-view:main'); - let OutletTemplate = lookupOutletTemplate(owner); - templateId += 1; - let templateFullName = `template:-undertest-${templateId}`; - owner.register(templateFullName, template); - - let outletState = { - render: { - owner, - into: undefined, - outlet: 'main', - name: 'application', - controller: undefined, - ViewClass: undefined, - template: OutletTemplate, - }, - - outlets: { - main: { - render: { - owner, - into: undefined, - outlet: 'main', - name: 'index', - controller: context, - ViewClass: undefined, - template: lookupTemplate(owner, templateFullName), + return nextTickPromise() + .then(() => runHooks('render', 'start')) + .then(() => { + if (!context || !isRenderingTestContext(context)) { + throw new Error( + 'Cannot call `render` without having first called `setupRenderingContext`.' + ); + } + + let { owner } = context; + let testMetadata = getTestMetadata(context); + testMetadata.usedHelpers.push('render'); + + let toplevelView = owner.lookup('-top-level-view:main'); + let OutletTemplate = lookupOutletTemplate(owner); + templateId += 1; + let templateFullName = `template:-undertest-${templateId}`; + owner.register(templateFullName, template); + + let outletState = { + render: { + owner, + into: undefined, + outlet: 'main', + name: 'application', + controller: undefined, + ViewClass: undefined, + template: OutletTemplate, + }, + + outlets: { + main: { + render: { + owner, + into: undefined, + outlet: 'main', + name: 'index', + controller: context, + ViewClass: undefined, + template: lookupTemplate(owner, templateFullName), + outlets: {}, + }, outlets: {}, }, - outlets: {}, }, - }, - }; - toplevelView.setOutletState(outletState); - - // returning settled here because the actual rendering does not happen until - // the renderer detects it is dirty (which happens on backburner's end - // hook), see the following implementation details: - // - // * [view:outlet](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/views/outlet.js#L129-L145) manually dirties its own tag upon `setOutletState` - // * [backburner's custom end hook](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/renderer.js#L145-L159) detects that the current revision of the root is no longer the latest, and triggers a new rendering transaction - return settled(); - }); + }; + toplevelView.setOutletState(outletState); + + // returning settled here because the actual rendering does not happen until + // the renderer detects it is dirty (which happens on backburner's end + // hook), see the following implementation details: + // + // * [view:outlet](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/views/outlet.js#L129-L145) manually dirties its own tag upon `setOutletState` + // * [backburner's custom end hook](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/renderer.js#L145-L159) detects that the current revision of the root is no longer the latest, and triggers a new rendering transaction + return settled(); + }) + .then(() => runHooks('render', 'end')); } /** diff --git a/tests/integration/dom/scroll-to-test.js b/tests/integration/dom/scroll-to-test.js index bbc2b6058..dbaf1ac40 100644 --- a/tests/integration/dom/scroll-to-test.js +++ b/tests/integration/dom/scroll-to-test.js @@ -6,6 +6,7 @@ import { setupContext, setupRenderingContext, teardownContext, + _registerHook, } from '@ember/test-helpers'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -23,6 +24,39 @@ module('DOM Helper: scroll-to', function (hooks) { await teardownContext(this); }); + test('it executes registered scrollTo hooks', async function (assert) { + assert.expect(3); + + let startHook = _registerHook('scrollTo', 'start', () => { + assert.step('scrollTo:start'); + }); + let endHook = _registerHook('scrollTo', 'end', () => { + assert.step('scrollTo:end'); + }); + + await render(hbs` +
+
    +
  • A
  • +
  • B
  • +
  • C
  • +
  • D
  • +
  • E
  • +
+
+ `); + + await scrollTo('.container', 0, 50); + + assert.verifySteps(['scrollTo:start', 'scrollTo:end']); + + startHook.unregister(); + endHook.unregister(); + }); + test('Scroll in vertical direction', async function (assert) { assert.expect(1); diff --git a/tests/unit/dom/blur-test.js b/tests/unit/dom/blur-test.js index 95f52e70f..02b497836 100644 --- a/tests/unit/dom/blur-test.js +++ b/tests/unit/dom/blur-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { focus, blur, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { focus, blur, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11, isEdge } from '../../helpers/browser-detect'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -44,6 +44,31 @@ module('DOM Helper: blur', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered blur hooks', async function (assert) { + assert.expect(13); + + let element = document.createElement('input'); + insertElement(element); + + await focus(element); + + let startHook = _registerHook('blur', 'start', () => { + assert.step('blur:start'); + }); + let endHook = _registerHook('blur', 'end', () => { + assert.step('blur:end'); + }); + + try { + await blur(elementWithFocus); + + assert.verifySteps(['blur', 'focusout', 'blur:start', 'blur:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('does not run sync', async function (assert) { let promise = blur(elementWithFocus); diff --git a/tests/unit/dom/click-test.js b/tests/unit/dom/click-test.js index 7440d673d..e0ab2c383 100644 --- a/tests/unit/dom/click-test.js +++ b/tests/unit/dom/click-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { click, setupContext, teardownContext } from '@ember/test-helpers'; +import { click, setupContext, teardownContext, _registerHook } 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'; @@ -28,6 +28,29 @@ module('DOM Helper: click', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered click hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('div'); + insertElement(element); + + let startHook = _registerHook('click', 'start', () => { + assert.step('click:start'); + }); + let endHook = _registerHook('click', 'end', () => { + assert.step('click:end'); + }); + + try { + await click(element); + + assert.verifySteps(['click:start', 'click:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + module('non-focusable element types', function () { test('clicking a div via selector with context set', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/double-click-test.js b/tests/unit/dom/double-click-test.js index 8a33d2630..e82bbdee4 100644 --- a/tests/unit/dom/double-click-test.js +++ b/tests/unit/dom/double-click-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { doubleClick, setupContext, teardownContext } from '@ember/test-helpers'; +import { doubleClick, setupContext, teardownContext, _registerHook } 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'; @@ -29,6 +29,29 @@ module('DOM Helper: doubleClick', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered doubleClick hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('div'); + insertElement(element); + + let startHook = _registerHook('doubleClick', 'start', () => { + assert.step('doubleClick:start'); + }); + let endHook = _registerHook('doubleClick', 'end', () => { + assert.step('doubleClick:end'); + }); + + try { + await doubleClick(element); + + assert.verifySteps(['doubleClick:start', 'doubleClick:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + module('non-focusable element types', function () { test('double-clicking a div via selector with context set', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/fill-in-test.js b/tests/unit/dom/fill-in-test.js index cd9aabd29..7e0d71a30 100644 --- a/tests/unit/dom/fill-in-test.js +++ b/tests/unit/dom/fill-in-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { fillIn, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { fillIn, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11 } from '../../helpers/browser-detect'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -35,6 +35,29 @@ module('DOM Helper: fillIn', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered fillIn hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('input'); + insertElement(element); + + let startHook = _registerHook('fillIn', 'start', () => { + assert.step('fillIn:start'); + }); + let endHook = _registerHook('fillIn', 'end', () => { + assert.step('fillIn:end'); + }); + + try { + await fillIn(element, 'foo'); + + assert.verifySteps(['fillIn:start', 'fillIn:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('filling in a non-fillable element', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/focus-test.js b/tests/unit/dom/focus-test.js index ed5226b32..c753eb093 100644 --- a/tests/unit/dom/focus-test.js +++ b/tests/unit/dom/focus-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { focus, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { focus, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11 } from '../../helpers/browser-detect'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -35,6 +35,29 @@ module('DOM Helper: focus', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered focus hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('input'); + insertElement(element); + + let startHook = _registerHook('focus', 'start', () => { + assert.step('focus:start'); + }); + let endHook = _registerHook('focus', 'end', () => { + assert.step('focus:end'); + }); + + try { + await focus(element); + + assert.verifySteps(['focus:start', 'focus:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('focusing a div via selector with context set', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/helper-hooks-test.js b/tests/unit/dom/helper-hooks-test.js new file mode 100644 index 000000000..358cda8cf --- /dev/null +++ b/tests/unit/dom/helper-hooks-test.js @@ -0,0 +1,86 @@ +import { module, test } from 'qunit'; +import { Promise } from 'rsvp'; +import { _registerHook, _runHooks } from '@ember/test-helpers'; + +module('helper hooks', function () { + test('it can register a hook for a helper', async function (assert) { + let func = () => assert.step('click:start hook'); + let hook = _registerHook('click', 'start', func); + + try { + await _runHooks('click', 'start'); + assert.verifySteps(['click:start hook']); + + await _runHooks('click', 'start'); + assert.verifySteps(['click:start hook']); + } finally { + hook.unregister(); + } + }); + + test('it can register an unregister a hook for a helper', async function (assert) { + let func = () => assert.step('click:start hook'); + let hook = _registerHook('click', 'start', func); + + try { + await _runHooks('click', 'start'); + assert.verifySteps(['click:start hook']); + + await _runHooks('click', 'start'); + assert.verifySteps(['click:start hook']); + + hook.unregister(); + await _runHooks('click', 'start'); + assert.verifySteps([]); + } finally { + hook.unregister(); + } + }); + + test('it can register a hook that returns a promise / has a delay', async function (assert) { + let func = () => { + assert.step('starting hook'); + + return new Promise(resolve => { + setTimeout(() => { + assert.step('resolving hook promise'); + resolve(); + }, 100); + }); + }; + let hook = _registerHook('click', 'start', func); + + try { + assert.step('running hooks for click:start'); + await _runHooks('click', 'start'); + assert.step('hooks finished for click:start'); + + assert.verifySteps([ + 'running hooks for click:start', + 'starting hook', + 'resolving hook promise', + 'hooks finished for click:start', + ]); + } finally { + hook.unregister(); + } + }); + + test('it can run hooks for a helper by label', async function (assert) { + let fooHook1 = _registerHook('click', 'foo', () => { + assert.step('click:foo1'); + }); + let fooHook2 = _registerHook('click', 'foo', () => { + assert.step('click:foo2'); + }); + + try { + await _runHooks('click', 'foo'); + + assert.verifySteps(['click:foo1', 'click:foo2']); + } finally { + fooHook1.unregister(); + fooHook2.unregister(); + } + }); +}); diff --git a/tests/unit/dom/select-test.js b/tests/unit/dom/select-test.js index cca6ac154..e416aae91 100644 --- a/tests/unit/dom/select-test.js +++ b/tests/unit/dom/select-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { select, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { select, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11 } from '../../helpers/browser-detect'; let selectSteps = ['focus', 'focusin', 'input', 'change']; @@ -29,6 +29,29 @@ module('DOM Helper: select', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered select hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('select'); + insertElement(element); + + let startHook = _registerHook('select', 'start', () => { + assert.step('select:start'); + }); + let endHook = _registerHook('select', 'end', () => { + assert.step('select:end'); + }); + + try { + await select(element, 'apple'); + + assert.verifySteps(['select:start', 'select:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('select without target', async function (assert) { await setupContext(context); assert.rejects(select(), /Must pass an element or selector to `select`./); diff --git a/tests/unit/dom/tap-test.js b/tests/unit/dom/tap-test.js index ba9d54802..a38bc60ed 100644 --- a/tests/unit/dom/tap-test.js +++ b/tests/unit/dom/tap-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { tap, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { tap, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11 } from '../../helpers/browser-detect'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -33,6 +33,29 @@ module('DOM Helper: tap', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered tap hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('div'); + insertElement(element); + + let startHook = _registerHook('tap', 'start', () => { + assert.step('tap:start'); + }); + let endHook = _registerHook('tap', 'end', () => { + assert.step('tap:end'); + }); + + try { + await tap(element); + + assert.verifySteps(['tap:start', 'tap:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + module('non-focusable element types', function () { test('taping a div via selector with context set', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/trigger-event-test.js b/tests/unit/dom/trigger-event-test.js index 56e4bac22..e1426b7af 100644 --- a/tests/unit/dom/trigger-event-test.js +++ b/tests/unit/dom/trigger-event-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { triggerEvent, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { triggerEvent, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; module('DOM Helper: triggerEvent', function (hooks) { @@ -33,6 +33,29 @@ module('DOM Helper: triggerEvent', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered triggerEvent hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('div'); + insertElement(element); + + let startHook = _registerHook('triggerEvent', 'start', () => { + assert.step('triggerEvent:start'); + }); + let endHook = _registerHook('triggerEvent', 'end', () => { + assert.step('triggerEvent:end'); + }); + + try { + await triggerEvent(element, 'mouseenter'); + + assert.verifySteps(['triggerEvent:start', 'triggerEvent:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('can trigger arbitrary event types', async function (assert) { element = buildInstrumentedElement('div'); element.addEventListener('fliberty', e => { diff --git a/tests/unit/dom/trigger-key-event-test.js b/tests/unit/dom/trigger-key-event-test.js index 58c17afb1..8736c546f 100644 --- a/tests/unit/dom/trigger-key-event-test.js +++ b/tests/unit/dom/trigger-key-event-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { triggerKeyEvent, setupContext, teardownContext } from '@ember/test-helpers'; +import { triggerKeyEvent, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; @@ -33,6 +33,29 @@ module('DOM Helper: triggerKeyEvent', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered triggerKeyEvent hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('div'); + insertElement(element); + + let startHook = _registerHook('triggerKeyEvent', 'start', () => { + assert.step('triggerKeyEvent:start'); + }); + let endHook = _registerHook('triggerKeyEvent', 'end', () => { + assert.step('triggerKeyEvent:end'); + }); + + try { + await triggerKeyEvent(element, 'keypress', 13); + + assert.verifySteps(['triggerKeyEvent:start', 'triggerKeyEvent:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('rejects if event type is missing', async function (assert) { element = buildInstrumentedElement('div'); diff --git a/tests/unit/dom/type-in-test.js b/tests/unit/dom/type-in-test.js index 8019e354e..e3f412961 100644 --- a/tests/unit/dom/type-in-test.js +++ b/tests/unit/dom/type-in-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; -import { typeIn, setupContext, teardownContext } from '@ember/test-helpers'; -import { buildInstrumentedElement } from '../../helpers/events'; +import { typeIn, setupContext, teardownContext, _registerHook } from '@ember/test-helpers'; +import { buildInstrumentedElement, insertElement } from '../../helpers/events'; import { isIE11 } from '../../helpers/browser-detect'; import { debounce } from '@ember/runloop'; import { Promise } from 'rsvp'; @@ -71,6 +71,29 @@ module('DOM Helper: typeIn', function (hooks) { document.getElementById('ember-testing').innerHTML = ''; }); + test('it executes registered typeIn hooks', async function (assert) { + assert.expect(3); + + element = document.createElement('input'); + insertElement(element); + + let startHook = _registerHook('typeIn', 'start', () => { + assert.step('typeIn:start'); + }); + let endHook = _registerHook('typeIn', 'end', () => { + assert.step('typeIn:end'); + }); + + try { + await typeIn(element, 'foo'); + + assert.verifySteps(['typeIn:start', 'typeIn:end']); + } finally { + startHook.unregister(); + endHook.unregister(); + } + }); + test('typing in an input', async function (assert) { element = buildInstrumentedElement('input'); await typeIn(element, 'foo'); diff --git a/tests/unit/setup-application-context-test.js b/tests/unit/setup-application-context-test.js index 9a99daeb8..c30f7233b 100644 --- a/tests/unit/setup-application-context-test.js +++ b/tests/unit/setup-application-context-test.js @@ -13,6 +13,7 @@ import { visit, currentRouteName, currentURL, + _registerHook, } from '@ember/test-helpers'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import { setResolverRegistry } from '../helpers/resolver'; @@ -83,6 +84,28 @@ module('setupApplicationContext', function (hooks) { assert.deepEqual(testMetadata.setupTypes, ['setupContext', 'setupApplicationContext']); }); + test('it executes registered visit hooks for start and end at the right time', async function (assert) { + assert.expect(5); + + let hookStart = _registerHook('visit', 'start', () => { + assert.equal(currentURL(), null); + assert.step('visit:start'); + }); + let hookEnd = _registerHook('visit', 'end', () => { + assert.equal(currentURL(), '/'); + assert.step('visit:end'); + }); + + try { + await visit('/'); + + assert.verifySteps(['visit:start', 'visit:end']); + } finally { + hookStart.unregister(); + hookEnd.unregister(); + } + }); + test('it returns true for isApplication in an application test', function (assert) { let testMetadata = getTestMetadata(this); diff --git a/tests/unit/setup-rendering-context-test.js b/tests/unit/setup-rendering-context-test.js index 12e3b1f47..034b08683 100644 --- a/tests/unit/setup-rendering-context-test.js +++ b/tests/unit/setup-rendering-context-test.js @@ -9,6 +9,7 @@ import { setupRenderingContext, teardownContext, teardownRenderingContext, + _registerHook, getTestMetadata, render, clearRender, @@ -91,12 +92,32 @@ module('setupRenderingContext', function (hooks) { assert.deepEqual(testMetadata.setupTypes, ['setupContext', 'setupRenderingContext']); }); - test('it retutns true for isRendering in an rendering test', function (assert) { + test('it returns true for isRendering in an rendering test', function (assert) { let testMetadata = getTestMetadata(this); assert.ok(testMetadata.isRendering); }); + test('it executes registered render hooks for start and end at the right time', async function (assert) { + assert.expect(3); + + let hookStart = _registerHook('render', 'start', () => { + assert.step('render:start'); + }); + let hookEnd = _registerHook('render', 'end', () => { + assert.step('render:end'); + }); + + try { + await render(hbs`

Hello!

`); + + assert.verifySteps(['render:start', 'render:end']); + } finally { + hookStart.unregister(); + hookEnd.unregister(); + } + }); + test('render can be used multiple times', async function (assert) { await this.render(hbs`

Hello!

`); assert.equal(this.element.textContent, 'Hello!');