Skip to content

Commit efa8263

Browse files
crisbetojelbourn
authored andcommitted
build: rework flaky focus trap tests (#18304)
Reworks the `EventListenerFocusTrapInertStrategy` to avoid some of the flakes that we've been seeing in master recently. This is something that we've dealt with in the past where the timing of when focus events are dispatched is inconsistent between browsers and is asynchronous in some cases.
1 parent 0760454 commit efa8263

File tree

1 file changed

+22
-6
lines changed

1 file changed

+22
-6
lines changed

src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {AfterViewInit, Component, ElementRef, Type, ViewChild} from '@angular/core';
22
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
3+
import {patchElementFocus} from '@angular/cdk/testing/private';
34
import {
45
A11yModule,
56
ConfigurableFocusTrapFactory,
@@ -22,7 +23,7 @@ describe('EventListenerFocusTrapInertStrategy', () => {
2223
componentInstance.outsideFocusableElement.nativeElement.focus();
2324
flush();
2425

25-
expect(document.activeElement).toBe(
26+
expect(componentInstance.activeElement).toBe(
2627
componentInstance.firstFocusableElement.nativeElement,
2728
'Expected first focusable element to be focused');
2829
}));
@@ -36,7 +37,7 @@ describe('EventListenerFocusTrapInertStrategy', () => {
3637
componentInstance.secondFocusableElement.nativeElement.focus();
3738
flush();
3839

39-
expect(document.activeElement).toBe(
40+
expect(componentInstance.activeElement).toBe(
4041
componentInstance.secondFocusableElement.nativeElement,
4142
'Expected second focusable element to be focused');
4243
}));
@@ -63,17 +64,32 @@ function createComponent<T>(componentType: Type<T>, providers: Array<Object> = [
6364
`
6465
})
6566
class SimpleFocusTrap implements AfterViewInit {
66-
@ViewChild('focusTrapElement') focusTrapElement!: ElementRef;
67-
@ViewChild('outsideFocusable') outsideFocusableElement!: ElementRef;
68-
@ViewChild('firstFocusable') firstFocusableElement!: ElementRef;
69-
@ViewChild('secondFocusable') secondFocusableElement!: ElementRef;
67+
@ViewChild('focusTrapElement') focusTrapElement!: ElementRef<HTMLElement>;
68+
@ViewChild('outsideFocusable') outsideFocusableElement!: ElementRef<HTMLElement>;
69+
@ViewChild('firstFocusable') firstFocusableElement!: ElementRef<HTMLElement>;
70+
@ViewChild('secondFocusable') secondFocusableElement!: ElementRef<HTMLElement>;
7071

7172
focusTrap: ConfigurableFocusTrap;
7273

74+
// Since our custom stubbing in `patchElementFocus` won't update
75+
// the `document.activeElement`, we need to keep track of it here.
76+
activeElement: EventTarget | null;
77+
7378
constructor(private _focusTrapFactory: ConfigurableFocusTrapFactory) {
7479
}
7580

7681
ngAfterViewInit() {
82+
// Ensure consistent focus timing across browsers.
83+
[
84+
this.focusTrapElement,
85+
this.outsideFocusableElement,
86+
this.firstFocusableElement,
87+
this.secondFocusableElement
88+
].forEach(({nativeElement}) => {
89+
patchElementFocus(nativeElement);
90+
nativeElement.addEventListener('focus', event => this.activeElement = event.target);
91+
});
92+
7793
this.focusTrap = this._focusTrapFactory.create(this.focusTrapElement.nativeElement);
7894
this.focusTrap.focusFirstTabbableElementWhenReady();
7995
}

0 commit comments

Comments
 (0)