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

Adding _registerHook / _runHooks functionality allow adding work start/end of test helpers #878

Merged
merged 17 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions addon-test-support/@ember/test-helpers/-internal/helper-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
type Hook = (...args: any[]) => void | Promise<void>;
type HookUnregister = {
unregister: () => void;
};

const registeredHooks = new Map<string, Set<Hook>>();

/**
* @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<Hook>();
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<void>} A promise representing the serial invocation of the hooks.
*/
export function runHooks(helperName: string, label: string, ...args: any[]): Promise<void> {
let hooks = registeredHooks.get(getHelperKey(helperName, label)) || new Set<Hook>();
let promises = [...hooks].map(hook => hook(...args));

return Promise.all(promises).then(() => {});
}
26 changes: 16 additions & 10 deletions addon-test-support/@ember/test-helpers/dom/blur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -56,16 +61,17 @@ export function __blur__(element: HTMLElement | Element | Document | SVGElement)
blur('input');
*/
export default function blur(target: Target = document.activeElement!): Promise<void> {
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));
}
38 changes: 22 additions & 16 deletions addon-test-support/@ember/test-helpers/dom/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<void> {
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));
}
38 changes: 22 additions & 16 deletions addon-test-support/@ember/test-helpers/dom/double-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<void> {
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));
}
72 changes: 39 additions & 33 deletions addon-test-support/@ember/test-helpers/dom/fill-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<void> {
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));
}
38 changes: 22 additions & 16 deletions addon-test-support/@ember/test-helpers/dom/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -59,20 +64,21 @@ export function __focus__(element: HTMLElement | Element | Document | SVGElement
focus('input');
*/
export default function focus(target: Target): Promise<void> {
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));
}
Loading