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

Ensure blur event fires on prior focused element even if click target is not focusable #1033

Merged
merged 1 commit into from
May 30, 2021
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
6 changes: 3 additions & 3 deletions addon-test-support/@ember/test-helpers/dom/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { getWindowOrElement } from './-get-window-or-element';
import fireEvent from './fire-event';
import { __focus__ } from './focus';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { Promise } from '../-utils';
import isFormControl from './-is-form-control';
import Target from './-target';
import Target, { isWindow } from './-target';
import { log } from '@ember/test-helpers/dom/-logging';
import { runHooks, registerHook } from '../-internal/helper-hooks';
import { __blur__ } from './blur';

const PRIMARY_BUTTON = 1;
const MAIN_BUTTON_PRESSED = 0;
Expand All @@ -34,7 +34,7 @@ export const DEFAULT_CLICK_OPTIONS = {
export function __click__(element: Element | Document | Window, options: MouseEventInit): void {
fireEvent(element, 'mousedown', options);

if (isFocusable(element)) {
if (!isWindow(element)) {
__focus__(element);
}

Expand Down
5 changes: 2 additions & 3 deletions addon-test-support/@ember/test-helpers/dom/double-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { getWindowOrElement } from './-get-window-or-element';
import fireEvent from './fire-event';
import { __focus__ } from './focus';
import settled from '../settled';
import isFocusable from './-is-focusable';
import { Promise } from '../-utils';
import { DEFAULT_CLICK_OPTIONS } from './click';
import Target from './-target';
import Target, { isWindow } from './-target';
import { log } from '@ember/test-helpers/dom/-logging';
import isFormControl from './-is-form-control';
import { runHooks, registerHook } from '../-internal/helper-hooks';
Expand All @@ -26,7 +25,7 @@ export function __doubleClick__(
): void {
fireEvent(element, 'mousedown', options);

if (isFocusable(element)) {
if (!isWindow(element)) {
__focus__(element);
}

Expand Down
28 changes: 20 additions & 8 deletions addon-test-support/@ember/test-helpers/dom/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,29 @@ registerHook('focus', 'start', (target: Target) => {
@param {Element} element the element to trigger events on
*/
export function __focus__(element: HTMLElement | Element | Document | SVGElement): void {
const previousFocusedElement =
document.activeElement &&
document.activeElement !== element &&
isFocusable(document.activeElement)
? document.activeElement
: null;

// fire __blur__ manually with the null relatedTarget when the target is not focusable
// and there was a previously focused element
if (!isFocusable(element)) {
throw new Error(`${element} is not focusable`);
if (previousFocusedElement) {
__blur__(previousFocusedElement, null);
}

return;
}

let browserIsNotFocused = document.hasFocus && !document.hasFocus();

// fire __blur__ manually with the correct relatedTarget when the browser is not
// already in focus and there was a previously focused element
if (
document.activeElement &&
document.activeElement !== element &&
isFocusable(document.activeElement) &&
browserIsNotFocused
) {
__blur__(document.activeElement, element);
if (previousFocusedElement && browserIsNotFocused) {
__blur__(previousFocusedElement, element);
}

// makes `document.activeElement` be `element`. If the browser is focused, it also fires a focus event
Expand Down Expand Up @@ -88,6 +96,10 @@ export default function focus(target: Target): Promise<void> {
throw new Error(`Element not found when calling \`focus('${target}')\`.`);
}

if (!isFocusable(element)) {
throw new Error(`${element} is not focusable`);
}

__focus__(element);

return settled();
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/dom/click-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,47 @@ module('DOM Helper: click', function (hooks) {
assert.verifySteps(['mousedown', 'mouseup', 'click']);
});
});

module('focusable and non-focusable elements interaction', function () {
test('clicking on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await click(focusableElement);
await click(element);

assert.verifySteps(['mousedown', 'focus', 'focusin', 'mouseup', 'click', 'blur', 'focusout']);
});

test('clicking on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await click(focusableElement);
await click(element);

assert.verifySteps(['mousedown', 'focus', 'focusin', 'mouseup', 'click', 'blur', 'focusout']);
});

test('clicking on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await click(nonFocusableElement);
await click(element);

assert.verifySteps(['mousedown', 'mouseup', 'click']);
});
});
});

module('DOM Helper: click with window', function () {
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/dom/double-click-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,79 @@ module('DOM Helper: doubleClick', function (hooks) {
]);
});
});

module('focusable and non-focusable elements interaction', function () {
test('cdouble-licking on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await doubleClick(focusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
'blur',
'focusout',
]);
});

test('double-clicking on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await doubleClick(focusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
'blur',
'focusout',
]);
});

test('double-clicking on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await doubleClick(nonFocusableElement);
await doubleClick(element);

assert.verifySteps([
'mousedown',
'mouseup',
'click',
'mousedown',
'mouseup',
'click',
'dblclick',
]);
});
});
});

module('DOM Helper: doubleClick with window', function () {
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/dom/tap-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,65 @@ module('DOM Helper: tap', function (hooks) {
assert.rejects(tap(element), new Error('Can not `tap` disabled [object HTMLInputElement]'));
});
});

module('focusable and non-focusable elements interaction', function () {
test('tapping on non-focusable element triggers blur on active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await tap(focusableElement);
await tap(element);

assert.verifySteps([
'touchstart',
'touchend',
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'blur',
'focusout',
]);
});

test('tapping on focusable element triggers blur on active element', async function (assert) {
element = document.createElement('input');

insertElement(element);

const focusableElement = buildInstrumentedElement('input');

await tap(focusableElement);
await tap(element);

assert.verifySteps([
'touchstart',
'touchend',
'mousedown',
'focus',
'focusin',
'mouseup',
'click',
'blur',
'focusout',
]);
});

test('tapping on non-focusable element does not trigger blur on non-focusable active element', async function (assert) {
element = document.createElement('div');

insertElement(element);

const nonFocusableElement = buildInstrumentedElement('div');

await tap(nonFocusableElement);
await tap(element);

assert.verifySteps(['touchstart', 'touchend', 'mousedown', 'mouseup', 'click']);
});
});
});