Skip to content

Commit 5fefbd8

Browse files
committed
fix(cdk/testing): simulate focusin/focusout events
Fixes that our fake fallback focus events weren't dispatching `focusin` and `focusout` events as well. Fixes #23757.
1 parent 92863cc commit 5fefbd8

File tree

2 files changed

+21
-4
lines changed

2 files changed

+21
-4
lines changed

src/cdk/testing/testbed/fake-events/element-focus.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,31 @@
99
import {dispatchFakeEvent} from './dispatch-events';
1010

1111
function triggerFocusChange(element: HTMLElement, event: 'focus' | 'blur') {
12+
const hasFocus = document.activeElement === element;
13+
14+
if ((event === 'focus' && hasFocus) || (event === 'blur' && !hasFocus)) {
15+
return;
16+
}
17+
1218
let eventFired = false;
1319
const handler = () => (eventFired = true);
1420
element.addEventListener(event, handler);
1521
element[event]();
1622
element.removeEventListener(event, handler);
23+
24+
// Some browsers won't move focus if the browser window is blurred while other will move it
25+
// asynchronously. If that is the case, we fake the event sequence as a fallback.
1726
if (!eventFired) {
18-
dispatchFakeEvent(element, event);
27+
simulateFocusSequence(element, event);
1928
}
2029
}
2130

31+
/** Simulates the full event sequence for a focus event. */
32+
function simulateFocusSequence(element: HTMLElement, event: 'focus' | 'blur') {
33+
dispatchFakeEvent(element, event);
34+
dispatchFakeEvent(element, event === 'focus' ? 'focusin' : 'focusout');
35+
}
36+
2237
/**
2338
* Patches an elements focus and blur methods to emit events consistently and predictably.
2439
* This is necessary, because some browsers can call the focus handlers asynchronously,
@@ -28,8 +43,8 @@ function triggerFocusChange(element: HTMLElement, event: 'focus' | 'blur') {
2843
// TODO: Check if this element focus patching is still needed for local testing,
2944
// where browser is not necessarily focused.
3045
export function patchElementFocus(element: HTMLElement) {
31-
element.focus = () => dispatchFakeEvent(element, 'focus');
32-
element.blur = () => dispatchFakeEvent(element, 'blur');
46+
element.focus = () => simulateFocusSequence(element, 'focus');
47+
element.blur = () => simulateFocusSequence(element, 'blur');
3348
}
3449

3550
/** @docs-private */

src/components-examples/material/form-field/form-field-harness/form-field-harness-example.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ describe('FormFieldHarnessExample', () => {
3535

3636
it('should be able to get error messages and hints of form-field', async () => {
3737
const formField = await loader.getHarness(MatFormFieldHarness);
38+
const control = (await formField.getControl()) as MatInputHarness;
3839
expect(await formField.getTextErrors()).toEqual([]);
3940
expect(await formField.getTextHints()).toEqual(['Hint']);
4041

4142
fixture.componentInstance.requiredControl.setValue('');
42-
await ((await formField.getControl()) as MatInputHarness)?.blur();
43+
await control.focus();
44+
await control.blur();
4345
expect(await formField.getTextErrors()).toEqual(['Error']);
4446
expect(await formField.getTextHints()).toEqual([]);
4547
});

0 commit comments

Comments
 (0)