From 8228c5f1fd02b8361ad4e94e38228597b8a90509 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 6 Mar 2018 00:08:54 +0100 Subject: [PATCH] refactor(tabs): switch to fakeAsync tests (#10263) * Reworks the tabs unit tests to run in the `fakeAsync` zone, rather than `async`. This has the advantage of being quicker, cleaner and avoiding timeouts in IE and Edge when the browser isn't focused. * Fixes a few unit tests that weren't asserting correctly, because their assertions were inside `whenStable` promises that weren't resolving. --- src/lib/tabs/tab-body.ts | 6 +- src/lib/tabs/tab-group.spec.ts | 143 +++++++++++++++++---------------- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index aec8614bfec..e214199fe71 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -77,10 +77,8 @@ export class MatTabBodyPortal extends CdkPortalOutlet implements OnInit, OnDestr this.attach(this._host._content); } this._centeringSub = this._host._beforeCentering.subscribe((isCentering: boolean) => { - if (isCentering) { - if (!this.hasAttached()) { - this.attach(this._host._content); - } + if (isCentering && !this.hasAttached()) { + this.attach(this._host._content); } }); diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index 04cb79a366d..1b35ebcf062 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -8,7 +8,7 @@ import {MatTab, MatTabGroup, MatTabHeaderPosition, MatTabsModule} from './index' describe('MatTabGroup', () => { - beforeEach(async(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ imports: [MatTabsModule, NoopAnimationsModule], declarations: [ @@ -59,7 +59,7 @@ describe('MatTabGroup', () => { checkSelectedIndex(2, fixture); }); - it('should support two-way binding for selectedIndex', async(() => { + it('should support two-way binding for selectedIndex', fakeAsync(() => { let component = fixture.componentInstance; component.selectedIndex = 0; @@ -67,13 +67,13 @@ describe('MatTabGroup', () => { let tabLabel = fixture.debugElement.queryAll(By.css('.mat-tab-label'))[1]; tabLabel.nativeElement.click(); - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(component.selectedIndex).toBe(1); - }); + tick(); + + expect(component.selectedIndex).toBe(1); })); + // Note: needs to be `async` in order to fail when we expect it to. it('should set to correct tab on fast change', async(() => { let component = fixture.componentInstance; component.selectedIndex = 0; @@ -255,74 +255,76 @@ describe('MatTabGroup', () => { describe('dynamic binding tabs', () => { let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(SimpleDynamicTabsTestApp); fixture.detectChanges(); + tick(); + fixture.detectChanges(); })); - it('should be able to add a new tab, select it, and have correct origin position', () => { - fixture.detectChanges(); - const component: MatTabGroup = - fixture.debugElement.query(By.css('mat-tab-group')).componentInstance; + it('should be able to add a new tab, select it, and have correct origin position', + fakeAsync(() => { + const component: MatTabGroup = + fixture.debugElement.query(By.css('mat-tab-group')).componentInstance; - let tabs: MatTab[] = component._tabs.toArray(); - expect(tabs[0].origin).toBe(null); - expect(tabs[1].origin).toBe(0); - expect(tabs[2].origin).toBe(null); + let tabs: MatTab[] = component._tabs.toArray(); + expect(tabs[0].origin).toBe(null); + expect(tabs[1].origin).toBe(0); + expect(tabs[2].origin).toBe(null); - // Add a new tab on the right and select it, expect an origin >= than 0 (animate right) - fixture.componentInstance.tabs.push({label: 'New tab', content: 'to right of index'}); - fixture.componentInstance.selectedIndex = 4; - fixture.detectChanges(); + // Add a new tab on the right and select it, expect an origin >= than 0 (animate right) + fixture.componentInstance.tabs.push({label: 'New tab', content: 'to right of index'}); + fixture.componentInstance.selectedIndex = 4; + fixture.detectChanges(); + tick(); - tabs = component._tabs.toArray(); - expect(tabs[3].origin).toBeGreaterThanOrEqual(0); + tabs = component._tabs.toArray(); + expect(tabs[3].origin).toBeGreaterThanOrEqual(0); - // Add a new tab in the beginning and select it, expect an origin < than 0 (animate left) - fixture.componentInstance.tabs.push({label: 'New tab', content: 'to left of index'}); - fixture.componentInstance.selectedIndex = 0; - fixture.detectChanges(); + // Add a new tab in the beginning and select it, expect an origin < than 0 (animate left) + fixture.componentInstance.tabs.push({label: 'New tab', content: 'to left of index'}); + fixture.componentInstance.selectedIndex = 0; + fixture.detectChanges(); + tick(); - tabs = component._tabs.toArray(); - expect(tabs[0].origin).toBeLessThan(0); - }); + tabs = component._tabs.toArray(); + expect(tabs[0].origin).toBeLessThan(0); + })); - it('should update selected index if the last tab removed while selected', () => { - fixture.detectChanges(); + it('should update selected index if the last tab removed while selected', fakeAsync(() => { const component: MatTabGroup = fixture.debugElement.query(By.css('mat-tab-group')).componentInstance; const numberOfTabs = component._tabs.length; fixture.componentInstance.selectedIndex = numberOfTabs - 1; fixture.detectChanges(); + tick(); // Remove last tab while last tab is selected, expect next tab over to be selected fixture.componentInstance.tabs.pop(); fixture.detectChanges(); + tick(); expect(component.selectedIndex).toBe(numberOfTabs - 2); - }); + })); }); describe('async tabs', () => { let fixture: ComponentFixture; - it('should show tabs when they are available', async(() => { + it('should show tabs when they are available', fakeAsync(() => { fixture = TestBed.createComponent(AsyncTabsTestApp); - let labels = fixture.debugElement.queryAll(By.css('.mat-tab-label')); - - expect(labels.length).toBe(0); + expect(fixture.debugElement.queryAll(By.css('.mat-tab-label')).length).toBe(0); fixture.detectChanges(); + tick(); + fixture.detectChanges(); + tick(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - labels = fixture.debugElement.queryAll(By.css('.mat-tab-label')); - expect(labels.length).toBe(2); - }); + expect(fixture.debugElement.queryAll(By.css('.mat-tab-label')).length).toBe(2); })); }); @@ -330,50 +332,48 @@ describe('MatTabGroup', () => { let fixture: ComponentFixture; let tabGroup: MatTabGroup; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(TabGroupWithSimpleApi); fixture.detectChanges(); + tick(); tabGroup = fixture.debugElement.query(By.directive(MatTabGroup)).componentInstance as MatTabGroup; - }); + })); - it('should support a tab-group with the simple api', async(() => { + it('should support a tab-group with the simple api', fakeAsync(() => { expect(getSelectedLabel(fixture).textContent).toMatch('Junk food'); expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries'); tabGroup.selectedIndex = 2; fixture.detectChanges(); - // Use whenStable to wait for async observables and change detection to run in content. - fixture.whenStable().then(() => { + tick(); - expect(getSelectedLabel(fixture).textContent).toMatch('Fruit'); - expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes'); + expect(getSelectedLabel(fixture).textContent).toMatch('Fruit'); + expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes'); - fixture.componentInstance.otherLabel = 'Chips'; - fixture.componentInstance.otherContent = 'Salt, vinegar'; - fixture.detectChanges(); + fixture.componentInstance.otherLabel = 'Chips'; + fixture.componentInstance.otherContent = 'Salt, vinegar'; + fixture.detectChanges(); - expect(getSelectedLabel(fixture).textContent).toMatch('Chips'); - expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar'); - }); + expect(getSelectedLabel(fixture).textContent).toMatch('Chips'); + expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar'); })); it('should support @ViewChild in the tab content', () => { expect(fixture.componentInstance.legumes).toBeTruthy(); }); - it('should only have the active tab in the DOM', async(() => { + it('should only have the active tab in the DOM', fakeAsync(() => { expect(fixture.nativeElement.textContent).toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).not.toContain('Peanuts'); tabGroup.selectedIndex = 3; fixture.detectChanges(); - // Use whenStable to wait for async observables and change detection to run in content. - fixture.whenStable().then(() => { - expect(fixture.nativeElement.textContent).not.toContain('Pizza, fries'); - expect(fixture.nativeElement.textContent).toContain('Peanuts'); - }); + tick(); + + expect(fixture.nativeElement.textContent).not.toContain('Pizza, fries'); + expect(fixture.nativeElement.textContent).toContain('Peanuts'); })); it('should support setting the header position', () => { @@ -389,20 +389,20 @@ describe('MatTabGroup', () => { }); describe('lazy loaded tabs', () => { - it('should lazy load the second tab', async () => { - let fixture = TestBed.createComponent(TemplateTabs); + it('should lazy load the second tab', fakeAsync(() => { + const fixture = TestBed.createComponent(TemplateTabs); fixture.detectChanges(); + tick(); - let secondLabel = fixture.debugElement.queryAll(By.css('.mat-tab-label'))[1]; + const secondLabel = fixture.debugElement.queryAll(By.css('.mat-tab-label'))[1]; secondLabel.nativeElement.click(); fixture.detectChanges(); + tick(); + fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - let child = fixture.debugElement.query(By.css('.child')); - expect(child.nativeElement).toBeDefined(); - }); - }); + const child = fixture.debugElement.query(By.css('.child')); + expect(child.nativeElement).toBeDefined(); + })); }); /** @@ -436,7 +436,7 @@ describe('MatTabGroup', () => { describe('nested MatTabGroup with enabled animations', () => { - beforeEach(async(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ imports: [MatTabsModule, BrowserAnimationsModule], declarations: [NestedTabs] @@ -445,10 +445,11 @@ describe('nested MatTabGroup with enabled animations', () => { TestBed.compileComponents(); })); - it('should not throw when creating a component with nested tab groups', async(() => { + it('should not throw when creating a component with nested tab groups', fakeAsync(() => { expect(() => { let fixture = TestBed.createComponent(NestedTabs); fixture.detectChanges(); + tick(); }).not.toThrow(); })); }); @@ -594,7 +595,7 @@ class AsyncTabsTestApp { ngOnInit() { // Use ngOnInit because there is some issue with scheduling the async task in the constructor. this.tabs = Observable.create((observer: any) => { - requestAnimationFrame(() => observer.next(this._tabs)); + setTimeout(() => observer.next(this._tabs)); }); } }