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

feat: add start and end hooks to the fireEvent helper #1185

Merged
merged 39 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
eded8e7
feat: add `start` and `end` hooks to `fireEvent` helper
Jan 13, 2022
5b2c2ae
refactor: migrate blur, click, focus, tap to fire async events
Jan 13, 2022
1b89095
refactor: migrate fillIn, typeIn to fire async events
Jan 13, 2022
599ae74
docs: add details to JSDoc
Jan 14, 2022
d488116
test: move all register hook tests into helper modules
Jan 14, 2022
31b62f8
refactor: promisify `triggerEvent` helper
Jan 14, 2022
2690988
chore: remove lingering import
Jan 14, 2022
37a9414
refactor: promisify `doubleClick` helper
Jan 14, 2022
ab797ef
test: add more/improve register hooks helpers
Jan 14, 2022
96ea125
refactor: always return Promise for focus step
Jan 14, 2022
c6e3ff2
test: add assertions for fire event hook steps
Jan 14, 2022
f292678
test: use improved hook test helpers
Jan 14, 2022
2e8de27
test: use improved hook test helpers
Jan 14, 2022
4318212
docs: clarify param description
Jan 14, 2022
f410f57
refactor: promisify `tab` fire event func
Jan 14, 2022
0a6e687
refactor: promisify `trigger-key-event` helper
Jan 14, 2022
786c245
refactor: promisify `select` helper
Jan 14, 2022
f633cf1
test: add event hooks assertions to execution test
Jan 14, 2022
c7c851f
test: rename helper `eventTypes` params to `expectedEvents`
Jan 14, 2022
2d6170c
test: use hook test helpers
Jan 14, 2022
93bc5df
chore: remove finished TODOs
Jan 14, 2022
9ded7a2
refactor: encapsulate specific start/end hooks with generic hooks
Jan 14, 2022
b70b17a
refactor: fully promisify `fillIn` helper
Jan 14, 2022
ef8f92b
fix: re-add `settled` func after events fired
Jan 14, 2022
b2f08fe
chore: remove unused imports
Jan 14, 2022
03db0d7
refactor: promisify `scrollTo` helper
Jan 14, 2022
7506700
test: add event hooks assertions to execution test
Jan 14, 2022
add69f6
refactor: restore prior `settled` behaviour
Jan 14, 2022
5b7bf9c
refactor: let -> const
Jan 14, 2022
03f93db
chore: remove unused import
Jan 14, 2022
056a8b3
chore: restore import statements order
Jan 14, 2022
51af5a4
refactor: flatten Promise chain
Jan 14, 2022
53815e0
test: improve helper readability
Jan 14, 2022
27cde93
refactor: add fallback Promise resolution
Jan 15, 2022
f1ff128
Revert "refactor: let -> const"
Jan 15, 2022
126d4b0
Revert "test: use hook test helpers"
Jan 15, 2022
1bab92a
revert: undo unrelated assertion replacements
Jan 15, 2022
1fb879a
revert: restore chained `settled` Promise
Jan 15, 2022
61cfbb3
revert: restore `_registerHook` import
Jan 15, 2022
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
18 changes: 9 additions & 9 deletions addon-test-support/@ember/test-helpers/dom/blur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ registerHook('blur', 'start', (target: Target) => {
@private
@param {Element} element the element to trigger events on
@param {Element} relatedTarget the element that is focused after blur
@return {Promise<Event | void>} resolves when settled
*/
export function __blur__(
element: HTMLElement | Element | Document | SVGElement,
relatedTarget: HTMLElement | Element | Document | SVGElement | null = null
): void {
): Promise<Event | void> {
if (!isFocusable(element)) {
throw new Error(`${element} is not focusable`);
}
Expand All @@ -36,11 +37,12 @@ export function __blur__(
// Chrome/Firefox does not trigger the `blur` event if the window
// does not have focus. If the document does not have focus then
// fire `blur` event via native event.
if (browserIsNotFocused || needsCustomEventOptions) {
let options = { relatedTarget };
fireEvent(element, 'blur', { bubbles: false, ...options });
fireEvent(element, 'focusout', options);
}
let options = { relatedTarget };
return browserIsNotFocused || needsCustomEventOptions
? Promise.resolve()
.then(() => fireEvent(element, 'blur', { bubbles: false, ...options }))
.then(() => fireEvent(element, 'focusout', options))
: Promise.resolve();
}

/**
Expand Down Expand Up @@ -81,9 +83,7 @@ export default function blur(
);
}

__blur__(element);

return settled();
return __blur__(element).then(() => settled());
})
.then(() => runHooks('blur', 'end', target));
}
24 changes: 12 additions & 12 deletions addon-test-support/@ember/test-helpers/dom/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,21 @@ export const DEFAULT_CLICK_OPTIONS = {
@private
@param {Element} element the element to click on
@param {MouseEventInit} options the options to be merged into the mouse events
@return {Promise<Event | void>} resolves when settled
*/
export function __click__(
element: Element | Document | Window,
options: MouseEventInit
): void {
let mouseDownEvent = fireEvent(element, 'mousedown', options);

if (!isWindow(element) && !mouseDownEvent?.defaultPrevented) {
__focus__(element);
}

fireEvent(element, 'mouseup', options);
fireEvent(element, 'click', options);
): Promise<Event | void> {
return Promise.resolve()
.then(() => fireEvent(element, 'mousedown', options))
.then((mouseDownEvent) =>
!isWindow(element) && !mouseDownEvent?.defaultPrevented
? __focus__(element)
: Promise.resolve()
)
.then(() => fireEvent(element, 'mouseup', options))
.then(() => fireEvent(element, 'click', options));
}

/**
Expand Down Expand Up @@ -112,9 +114,7 @@ export default function click(
throw new Error(`Can not \`click\` disabled ${element}`);
}

__click__(element, options);

return settled();
return __click__(element, options).then(settled);
})
.then(() => runHooks('click', 'end', target, _options));
}
32 changes: 16 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 @@ -17,23 +17,25 @@ registerHook('doubleClick', 'start', (target: Target) => {
@private
@param {Element} element the element to double-click on
@param {MouseEventInit} options the options to be merged into the mouse events
@returns {Promise<Event | void>} resolves when settled
*/
export function __doubleClick__(
element: Element | Document | Window,
options: MouseEventInit
): void {
let mouseDownEvent = fireEvent(element, 'mousedown', options);

if (!isWindow(element) && !mouseDownEvent?.defaultPrevented) {
__focus__(element);
}

fireEvent(element, 'mouseup', options);
fireEvent(element, 'click', options);
fireEvent(element, 'mousedown', options);
fireEvent(element, 'mouseup', options);
fireEvent(element, 'click', options);
fireEvent(element, 'dblclick', options);
): Promise<Event | void> {
return Promise.resolve()
.then(() => fireEvent(element, 'mousedown', options))
.then((mouseDownEvent) => {
return !isWindow(element) && !mouseDownEvent?.defaultPrevented
? __focus__(element)
: Promise.resolve();
})
.then(() => fireEvent(element, 'mouseup', options))
.then(() => fireEvent(element, 'click', options))
.then(() => fireEvent(element, 'mousedown', options))
.then(() => fireEvent(element, 'mouseup', options))
.then(() => fireEvent(element, 'click', options))
.then(() => fireEvent(element, 'dblclick', options));
}

/**
Expand Down Expand Up @@ -113,9 +115,7 @@ export default function doubleClick(
throw new Error(`Can not \`doubleClick\` disabled ${element}`);
}

__doubleClick__(element, options);

return settled();
return __doubleClick__(element, options).then(settled);
})
.then(() => runHooks('doubleClick', 'end', target, _options));
}
32 changes: 19 additions & 13 deletions addon-test-support/@ember/test-helpers/dom/fill-in.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import getElement from './-get-element';
import isFormControl from './-is-form-control';
import isFormControl, { FormControl } from './-is-form-control';
import guardForMaxlength from './-guard-for-maxlength';
import { __focus__ } from './focus';
import settled from '../settled';
Expand All @@ -21,7 +21,7 @@ registerHook('fillIn', 'start', (target: Target, text: string) => {
@public
@param {string|Element} target the element or selector to enter text into
@param {string} text the text to fill into the target element
@return {Promise<void>} resolves when the application is settled
@return {Promise<Element | void>} resolves when the application is settled
@example
<caption>
Expand All @@ -30,7 +30,10 @@ registerHook('fillIn', 'start', (target: Target, text: string) => {
fillIn('input', 'hello world');
*/
export default function fillIn(target: Target, text: string): Promise<void> {
export default function fillIn(
target: Target,
text: string
): Promise<Element | void> {
return Promise.resolve()
.then(() => runHooks('fillIn', 'start', target, text))
.then(() => {
Expand Down Expand Up @@ -60,22 +63,25 @@ export default function fillIn(target: Target, text: string): Promise<void> {

guardForMaxlength(element, text, 'fillIn');

__focus__(element);

element.value = text;
return __focus__(element).then(() => {
(element as FormControl).value = text;
return element;
});
} else if (isContentEditable(element)) {
__focus__(element);

element.innerHTML = text;
return __focus__(element).then(() => {
element.innerHTML = text;
return element;
});
} else {
throw new Error(
'`fillIn` is only usable on form controls or contenteditable elements.'
);
}
fireEvent(element, 'input');
fireEvent(element, 'change');

return settled();
})
.then((element) =>
fireEvent(element, 'input')
.then(() => fireEvent(element, 'change'))
.then(settled)
)
.then(() => runHooks('fillIn', 'end', target, text));
}
106 changes: 61 additions & 45 deletions addon-test-support/@ember/test-helpers/dom/fire-event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { isDocument, isElement } from './-target';
import tuple from '../-tuple';
import Target from './-target';
import { log } from '@ember/test-helpers/dom/-logging';
import { runHooks, registerHook } from '../-internal/helper-hooks';

registerHook('fireEvent', 'start', (target: Target) => {
log('fireEvent', target);
});

// eslint-disable-next-line require-jsdoc
const MOUSE_EVENT_CONSTRUCTOR = (() => {
Expand Down Expand Up @@ -61,19 +68,19 @@ function fireEvent(
element: Element | Document | Window,
eventType: KeyboardEventType,
options?: any
): Event;
): Promise<Event>;

function fireEvent(
element: Element | Document | Window,
eventType: MouseEventType,
options?: any
): Event | void;
): Promise<Event | void>;

function fireEvent(
element: Element | Document | Window,
eventType: string,
options?: any
): Event;
): Promise<Event>;

/**
Internal helper used to build and dispatch events throughout the other DOM helpers.
Expand All @@ -88,48 +95,57 @@ function fireEvent(
element: Element | Document | Window,
eventType: string,
options = {}
): Event | void {
if (!element) {
throw new Error('Must pass an element to `fireEvent`');
}

let event;
if (isKeyboardEventType(eventType)) {
event = _buildKeyboardEvent(eventType, options);
} else if (isMouseEventType(eventType)) {
let rect;
if (element instanceof Window && element.document.documentElement) {
rect = element.document.documentElement.getBoundingClientRect();
} else if (isDocument(element)) {
rect = element.documentElement!.getBoundingClientRect();
} else if (isElement(element)) {
rect = element.getBoundingClientRect();
} else {
return;
}

let x = rect.left + 1;
let y = rect.top + 1;
let simulatedCoordinates = {
screenX: x + 5, // Those numbers don't really mean anything.
screenY: y + 95, // They're just to make the screenX/Y be different of clientX/Y..
clientX: x,
clientY: y,
...options,
};

event = buildMouseEvent(eventType, simulatedCoordinates);
} else if (
isFileSelectionEventType(eventType) &&
isFileSelectionInput(element)
) {
event = buildFileEvent(eventType, element, options);
} else {
event = buildBasicEvent(eventType, options);
}

element.dispatchEvent(event);
return event;
): Promise<Event | void> {
return Promise.resolve()
.then(() => runHooks('fireEvent', 'start', element))
.then(() => runHooks(`fireEvent:${eventType}`, 'start', element))
Comment on lines +100 to +101
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the flexibility this provides. It's a nice composition of general to specific!

.then(() => {
if (!element) {
throw new Error('Must pass an element to `fireEvent`');
}

let event;
if (isKeyboardEventType(eventType)) {
event = _buildKeyboardEvent(eventType, options);
} else if (isMouseEventType(eventType)) {
let rect;
if (element instanceof Window && element.document.documentElement) {
rect = element.document.documentElement.getBoundingClientRect();
} else if (isDocument(element)) {
rect = element.documentElement!.getBoundingClientRect();
} else if (isElement(element)) {
rect = element.getBoundingClientRect();
} else {
return;
}

let x = rect.left + 1;
let y = rect.top + 1;
let simulatedCoordinates = {
screenX: x + 5, // Those numbers don't really mean anything.
screenY: y + 95, // They're just to make the screenX/Y be different of clientX/Y..
clientX: x,
clientY: y,
...options,
};

event = buildMouseEvent(eventType, simulatedCoordinates);
} else if (
isFileSelectionEventType(eventType) &&
isFileSelectionInput(element)
) {
event = buildFileEvent(eventType, element, options);
} else {
event = buildBasicEvent(eventType, options);
}

element.dispatchEvent(event);
return event;
})
.then((event) =>
runHooks(`fireEvent:${eventType}`, 'end', element).then(() => event)
)
.then((event) => runHooks('fireEvent', 'end', element).then(() => event));
}

export default fireEvent;
Expand Down
Loading