Skip to content

Commit 5ee6261

Browse files
committed
fix(material/tabs): avoid timeouts in background tabs
The tabs have a couple of `requestAnimationFrame` calls when checking for the size/position of elements which can cause tests to hang when they're in a background tab. The problem is that browsers block `requestAnimationFrame` when the browser is out of focus and test harnesses will wait for the call to resolve. These changes switch to `NgZone.onStable` in an attempt to resolve the issue. Fixes #23964.
1 parent 7bbe68d commit 5ee6261

File tree

5 files changed

+24
-52
lines changed

5 files changed

+24
-52
lines changed

src/material-experimental/mdc-tabs/ink-bar.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export class MatInkBarFoundation {
8080
}
8181
},
8282
setContentStyleProperty: (propName, value) => {
83-
this._inkBarContentElement.style.setProperty(propName, value);
83+
if (!this._destroyed) {
84+
this._inkBarContentElement.style.setProperty(propName, value);
85+
}
8486
},
8587
computeContentClientRect: () => {
8688
// `getBoundingClientRect` isn't available on the server.

src/material-experimental/mdc-tabs/tab-header.spec.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,18 @@ import {MatRippleModule} from '@angular/material-experimental/mdc-core';
2323
import {By} from '@angular/platform-browser';
2424
import {MatTabHeader} from './tab-header';
2525
import {MatTabLabelWrapper} from './tab-label-wrapper';
26-
import {Subject} from 'rxjs';
2726
import {ObserversModule, MutationObserverFactory} from '@angular/cdk/observers';
2827

2928
describe('MDC-based MatTabHeader', () => {
30-
let dir: Direction = 'ltr';
31-
let change = new Subject();
3229
let fixture: ComponentFixture<SimpleTabHeaderApp>;
3330
let appComponent: SimpleTabHeaderApp;
3431

3532
beforeEach(
3633
waitForAsync(() => {
37-
dir = 'ltr';
3834
TestBed.configureTestingModule({
3935
imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule, ObserversModule],
4036
declarations: [MatTabHeader, MatTabLabelWrapper, SimpleTabHeaderApp],
41-
providers: [
42-
ViewportRuler,
43-
{provide: Directionality, useFactory: () => ({value: dir, change: change})},
44-
],
37+
providers: [ViewportRuler],
4538
});
4639

4740
TestBed.compileComponents();
@@ -238,11 +231,10 @@ describe('MDC-based MatTabHeader', () => {
238231
describe('pagination', () => {
239232
describe('ltr', () => {
240233
beforeEach(() => {
241-
dir = 'ltr';
242234
fixture = TestBed.createComponent(SimpleTabHeaderApp);
243-
fixture.detectChanges();
244-
245235
appComponent = fixture.componentInstance;
236+
appComponent.dir = 'ltr';
237+
fixture.detectChanges();
246238
});
247239

248240
it('should show width when tab list width exceeds container', () => {
@@ -322,11 +314,9 @@ describe('MDC-based MatTabHeader', () => {
322314

323315
describe('rtl', () => {
324316
beforeEach(() => {
325-
dir = 'rtl';
326317
fixture = TestBed.createComponent(SimpleTabHeaderApp);
327318
appComponent = fixture.componentInstance;
328319
appComponent.dir = 'rtl';
329-
330320
fixture.detectChanges();
331321
});
332322

@@ -607,9 +597,9 @@ describe('MDC-based MatTabHeader', () => {
607597

608598
fixture.detectChanges();
609599

610-
change.next();
600+
fixture.componentInstance.dir = 'rtl';
611601
fixture.detectChanges();
612-
tick(20); // Angular turns rAF calls into 16.6ms timeouts in tests.
602+
tick();
613603

614604
expect(inkBar.alignToElement).toHaveBeenCalled();
615605
}));

src/material/tabs/ink-bar.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {Directive, ElementRef, Inject, InjectionToken, NgZone, Optional} from '@angular/core';
1010
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
11+
import {take} from 'rxjs/operators';
1112

1213
/**
1314
* Interface for a a MatInkBar positioner method, defining the positioning and width of the ink
@@ -65,14 +66,12 @@ export class MatInkBar {
6566
*/
6667
alignToElement(element: HTMLElement) {
6768
this.show();
68-
69-
if (typeof requestAnimationFrame !== 'undefined') {
70-
this._ngZone.runOutsideAngular(() => {
71-
requestAnimationFrame(() => this._setStyles(element));
72-
});
73-
} else {
74-
this._setStyles(element);
75-
}
69+
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
70+
const positions = this._inkBarPositioner(element);
71+
const inkBar: HTMLElement = this._elementRef.nativeElement;
72+
inkBar.style.left = positions.left;
73+
inkBar.style.width = positions.width;
74+
});
7675
}
7776

7877
/** Shows the ink bar. */
@@ -84,16 +83,4 @@ export class MatInkBar {
8483
hide(): void {
8584
this._elementRef.nativeElement.style.visibility = 'hidden';
8685
}
87-
88-
/**
89-
* Sets the proper styles to the ink bar element.
90-
* @param element
91-
*/
92-
private _setStyles(element: HTMLElement) {
93-
const positions = this._inkBarPositioner(element);
94-
const inkBar: HTMLElement = this._elementRef.nativeElement;
95-
96-
inkBar.style.left = positions.left;
97-
inkBar.style.width = positions.width;
98-
}
9986
}

src/material/tabs/paginated-tab-header.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {ViewportRuler} from '@angular/cdk/scrolling';
2727
import {FocusKeyManager, FocusableOption} from '@angular/cdk/a11y';
2828
import {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
2929
import {merge, of as observableOf, Subject, timer, fromEvent} from 'rxjs';
30-
import {takeUntil} from 'rxjs/operators';
30+
import {take, takeUntil} from 'rxjs/operators';
3131
import {Platform, normalizePassiveListenerOptions} from '@angular/cdk/platform';
3232
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
3333

@@ -201,7 +201,9 @@ export abstract class MatPaginatedTabHeader
201201

202202
// Defer the first call in order to allow for slower browsers to lay out the elements.
203203
// This helps in cases where the user lands directly on a page with paginated tabs.
204-
typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(realign) : realign();
204+
// Note that we use `onStable` instead of `requestAnimationFrame`, because the latter
205+
// can hold up tests that are in a background tab.
206+
this._ngZone.onStable.pipe(take(1)).subscribe(realign);
205207

206208
// On dir change or window resize, realign the ink bar and update the orientation of
207209
// the key manager if the direction has changed.

src/material/tabs/tab-header.spec.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,15 @@ import {Subject} from 'rxjs';
2828
import {ObserversModule, MutationObserverFactory} from '@angular/cdk/observers';
2929

3030
describe('MatTabHeader', () => {
31-
let dir: Direction = 'ltr';
32-
let change = new Subject();
3331
let fixture: ComponentFixture<SimpleTabHeaderApp>;
3432
let appComponent: SimpleTabHeaderApp;
3533

3634
beforeEach(
3735
waitForAsync(() => {
38-
dir = 'ltr';
3936
TestBed.configureTestingModule({
4037
imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule, ObserversModule],
4138
declarations: [MatTabHeader, MatInkBar, MatTabLabelWrapper, SimpleTabHeaderApp],
42-
providers: [
43-
ViewportRuler,
44-
{provide: Directionality, useFactory: () => ({value: dir, change: change})},
45-
],
39+
providers: [ViewportRuler],
4640
});
4741

4842
TestBed.compileComponents();
@@ -239,11 +233,10 @@ describe('MatTabHeader', () => {
239233
describe('pagination', () => {
240234
describe('ltr', () => {
241235
beforeEach(() => {
242-
dir = 'ltr';
243236
fixture = TestBed.createComponent(SimpleTabHeaderApp);
244-
fixture.detectChanges();
245-
246237
appComponent = fixture.componentInstance;
238+
appComponent.dir = 'ltr';
239+
fixture.detectChanges();
247240
});
248241

249242
it('should show width when tab list width exceeds container', () => {
@@ -319,11 +312,9 @@ describe('MatTabHeader', () => {
319312

320313
describe('rtl', () => {
321314
beforeEach(() => {
322-
dir = 'rtl';
323315
fixture = TestBed.createComponent(SimpleTabHeaderApp);
324316
appComponent = fixture.componentInstance;
325317
appComponent.dir = 'rtl';
326-
327318
fixture.detectChanges();
328319
});
329320

@@ -603,9 +594,9 @@ describe('MatTabHeader', () => {
603594

604595
fixture.detectChanges();
605596

606-
change.next();
597+
fixture.componentInstance.dir = 'rtl';
607598
fixture.detectChanges();
608-
tick(20); // Angular turns rAF calls into 16.6ms timeouts in tests.
599+
tick();
609600

610601
expect(inkBar.alignToElement).toHaveBeenCalled();
611602
}));

0 commit comments

Comments
 (0)