Skip to content

Commit

Permalink
feat: add start and end hooks to the fireEvent helper (#1185)
Browse files Browse the repository at this point in the history
Co-authored-by: Shane Martin <dev.sh@nemartin>
  • Loading branch information
shamrt and Shane Martin authored Apr 4, 2022
1 parent b5c2387 commit a395635
Show file tree
Hide file tree
Showing 21 changed files with 472 additions and 314 deletions.
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))
.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

0 comments on commit a395635

Please sign in to comment.