diff --git a/src/material/tabs/tab-group.spec.ts b/src/material/tabs/tab-group.spec.ts index ebe327d2696e..71705913bbfa 100644 --- a/src/material/tabs/tab-group.spec.ts +++ b/src/material/tabs/tab-group.spec.ts @@ -10,14 +10,7 @@ import { ViewChildren, ChangeDetectionStrategy, } from '@angular/core'; -import { - ComponentFixture, - TestBed, - fakeAsync, - flush, - tick, - waitForAsync, -} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Observable} from 'rxjs'; import { @@ -31,11 +24,11 @@ import { import {MATERIAL_ANIMATIONS} from '../core'; describe('MatTabGroup', () => { - beforeEach(fakeAsync(() => { + beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}], }); - })); + }); describe('basic behavior', () => { let fixture: ComponentFixture; @@ -72,7 +65,7 @@ describe('MatTabGroup', () => { checkSelectedIndex(2, fixture); }); - it('should support two-way binding for selectedIndex', fakeAsync(() => { + it('should support two-way binding for selectedIndex', async () => { let component = fixture.componentInstance; component.selectedIndex = 0; @@ -81,13 +74,12 @@ describe('MatTabGroup', () => { let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; tabLabel.nativeElement.click(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); 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', waitForAsync(() => { + it('should set to correct tab on fast change', () => { let component = fixture.componentInstance; component.selectedIndex = 0; fixture.changeDetectorRef.markForCheck(); @@ -101,9 +93,9 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(component.selectedIndex).toBe(0); - })); + }); - it('should change tabs based on selectedIndex', fakeAsync(() => { + it('should change tabs based on selectedIndex', async () => { let component = fixture.componentInstance; let tabComponent = fixture.debugElement.query(By.css('mat-tab-group')).componentInstance; @@ -115,11 +107,11 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); checkSelectedIndex(2, fixture); - tick(); + await fixture.whenStable(); expect(component.handleSelection).toHaveBeenCalledTimes(1); expect(component.selectEvent.index).toBe(2); - })); + }); it('should update tab positions when selected index is changed', () => { fixture.detectChanges(); @@ -214,9 +206,9 @@ describe('MatTabGroup', () => { .toBe(0); }); - it('should set the isActive flag on each of the tabs', fakeAsync(() => { + it('should set the isActive flag on each of the tabs', async () => { fixture.detectChanges(); - tick(); + await fixture.whenStable(); const tabs = fixture.componentInstance.tabs.toArray(); @@ -227,24 +219,24 @@ describe('MatTabGroup', () => { fixture.componentInstance.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(tabs[0].isActive).toBe(false); expect(tabs[1].isActive).toBe(false); expect(tabs[2].isActive).toBe(true); - })); + }); - it('should fire animation done event', fakeAsync(() => { + it('should fire animation done event', async () => { fixture.detectChanges(); spyOn(fixture.componentInstance, 'animationDone'); let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; tabLabel.nativeElement.click(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.animationDone).toHaveBeenCalledTimes(1); - })); + }); it('should add the proper `aria-setsize` and `aria-posinset`', () => { fixture.detectChanges(); @@ -320,7 +312,7 @@ describe('MatTabGroup', () => { ); }); - it('should emit focusChange when a tab receives focus', fakeAsync(() => { + it('should emit focusChange when a tab receives focus', async () => { spyOn(fixture.componentInstance, 'handleFocus'); fixture.detectChanges(); @@ -332,14 +324,14 @@ describe('MatTabGroup', () => { // index, we focus the second tab before testing the keyboard navigation. dispatchFakeEvent(tabLabels[2].nativeElement, 'focus'); fixture.detectChanges(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.handleFocus).toHaveBeenCalledWith( jasmine.objectContaining({index: 2}), ); - })); + }); it('should be able to programmatically focus a particular tab', () => { fixture.detectChanges(); @@ -392,9 +384,9 @@ describe('MatTabGroup', () => { expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '-1', '0']); }); - it('should be able to set the aria-label of the tablist', fakeAsync(() => { + it('should be able to set the aria-label of the tablist', async () => { fixture.detectChanges(); - tick(); + await fixture.whenStable(); const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement; expect(tabList.hasAttribute('aria-label')).toBe(false); @@ -408,11 +400,11 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(tabList.hasAttribute('aria-label')).toBe(false); - })); + }); - it('should be able to set the aria-labelledby of the tablist', fakeAsync(() => { + it('should be able to set the aria-labelledby of the tablist', async () => { fixture.detectChanges(); - tick(); + await fixture.whenStable(); const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement; expect(tabList.hasAttribute('aria-labelledby')).toBe(false); @@ -426,7 +418,7 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(tabList.hasAttribute('aria-labelledby')).toBe(false); - })); + }); it('should set IDs on individual tabs and use them to label the tab bodies', () => { fixture.detectChanges(); @@ -492,12 +484,12 @@ describe('MatTabGroup', () => { let fixture: ComponentFixture; let tab: HTMLElement; - beforeEach(fakeAsync(() => { + beforeEach(async () => { fixture = TestBed.createComponent(TabGroupWithAriaInputs); fixture.detectChanges(); - tick(); + await fixture.whenStable(); tab = fixture.nativeElement.querySelector('.mat-mdc-tab'); - })); + }); it('should not set aria-label or aria-labelledby attributes if they are not passed in', () => { expect(tab.hasAttribute('aria-label')).toBe(false); @@ -540,12 +532,12 @@ describe('MatTabGroup', () => { let fixture: ComponentFixture; let tabPanels: HTMLElement[]; - beforeEach(fakeAsync(() => { + beforeEach(async () => { fixture = TestBed.createComponent(BindedTabsTestApp); fixture.detectChanges(); - tick(); + await fixture.whenStable(); tabPanels = Array.from(fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body')); - })); + }); it('should set `aria-hidden="true"` on inactive tab panels', () => { fixture.detectChanges(); @@ -594,14 +586,14 @@ describe('MatTabGroup', () => { describe('dynamic binding tabs', () => { let fixture: ComponentFixture; - beforeEach(fakeAsync(() => { + beforeEach(async () => { fixture = TestBed.createComponent(SimpleDynamicTabsTestApp); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); - })); + }); - it('should update selected index if the last tab removed while selected', fakeAsync(() => { + it('should update selected index if the last tab removed while selected', async () => { const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), ).componentInstance; @@ -610,16 +602,16 @@ describe('MatTabGroup', () => { fixture.componentInstance.selectedIndex = numberOfTabs - 1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); // Remove last tab while last tab is selected, expect next tab over to be selected fixture.componentInstance.tabs.pop(); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(component.selectedIndex).toBe(numberOfTabs - 2); - })); + }); it('should maintain the selected tab if a new tab is added', () => { fixture.detectChanges(); @@ -661,7 +653,7 @@ describe('MatTabGroup', () => { expect(component._tabs.toArray()[0].isActive).toBe(true); }); - it('should be able to select a new tab after creation', fakeAsync(() => { + it('should be able to select a new tab after creation', async () => { fixture.detectChanges(); const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), @@ -672,13 +664,13 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(component.selectedIndex).toBe(3); expect(component._tabs.toArray()[3].isActive).toBe(true); - })); + }); - it('should not fire `selectedTabChange` when the amount of tabs changes', fakeAsync(() => { + it('should not fire `selectedTabChange` when the amount of tabs changes', async () => { fixture.detectChanges(); fixture.componentInstance.selectedIndex = 1; fixture.changeDetectorRef.markForCheck(); @@ -689,13 +681,13 @@ describe('MatTabGroup', () => { fixture.componentInstance.tabs.unshift({label: 'New tab', content: 'at the start'}); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(fixture.componentInstance.handleSelection).not.toHaveBeenCalled(); - })); + }); - it('should update the newly-selected tab if the previously-selected tab is replaced', fakeAsync(() => { + it('should update the newly-selected tab if the previously-selected tab is replaced', async () => { const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), )!.componentInstance; @@ -708,19 +700,19 @@ describe('MatTabGroup', () => { }; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(component._tabs.get(1)?.isActive).toBe(true); expect(fixture.componentInstance.handleSelection).toHaveBeenCalledWith( jasmine.objectContaining({index: 1}), ); - })); + }); - it('should be able to disable the pagination', fakeAsync(() => { + it('should be able to disable the pagination', async () => { fixture.componentInstance.disablePagination = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); for (let i = 0; i < 50; i++) { fixture.componentInstance.tabs.push({label: `Extra ${i}`, content: ''}); @@ -728,52 +720,52 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect( fixture.nativeElement.querySelector('.mat-mdc-tab-header-pagination-controls-enabled'), ).toBeFalsy(); - })); + }); }); describe('async tabs', () => { let fixture: ComponentFixture; - it('should show tabs when they are available', fakeAsync(() => { + it('should show tabs when they are available', async () => { fixture = TestBed.createComponent(AsyncTabsTestApp); expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(0); fixture.detectChanges(); - tick(); + await new Promise(resolve => setTimeout(resolve)); fixture.detectChanges(); - tick(); + await new Promise(resolve => setTimeout(resolve)); expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(2); - })); + }); }); describe('with simple api', () => { let fixture: ComponentFixture; let tabGroup: MatTabGroup; - beforeEach(fakeAsync(() => { + beforeEach(async () => { fixture = TestBed.createComponent(TabGroupWithSimpleApi); fixture.detectChanges(); - tick(); + await fixture.whenStable(); tabGroup = fixture.debugElement.query(By.directive(MatTabGroup)) .componentInstance as MatTabGroup; - })); + }); - it('should support a tab-group with the simple api', fakeAsync(() => { + it('should support a tab-group with the simple api', async () => { expect(getSelectedLabel(fixture).textContent).toMatch('Junk food'); expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries'); tabGroup.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(getSelectedLabel(fixture).textContent).toMatch('Fruit'); expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes'); @@ -785,24 +777,24 @@ describe('MatTabGroup', () => { 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', fakeAsync(() => { + it('should only have the active tab in the DOM', async () => { expect(fixture.nativeElement.textContent).toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).not.toContain('Peanuts'); tabGroup.selectedIndex = 3; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.nativeElement.textContent).not.toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).toContain('Peanuts'); - })); + }); it('should support setting the header position', () => { let tabGroupNode = fixture.debugElement.query(By.css('mat-tab-group')).nativeElement; @@ -816,7 +808,7 @@ describe('MatTabGroup', () => { expect(tabGroupNode.classList).toContain('mat-mdc-tab-group-inverted-header'); }); - it('should be able to opt into keeping the inactive tab content in the DOM', fakeAsync(() => { + it('should be able to opt into keeping the inactive tab content in the DOM', async () => { fixture.componentInstance.preserveContent = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -827,13 +819,13 @@ describe('MatTabGroup', () => { tabGroup.selectedIndex = 3; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.nativeElement.textContent).toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).toContain('Peanuts'); - })); + }); - it('should visibly hide the content of inactive tabs', fakeAsync(() => { + it('should visibly hide the content of inactive tabs', async () => { const contentElements: HTMLElement[] = Array.from( fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body-content'), ); @@ -845,51 +837,53 @@ describe('MatTabGroup', () => { tabGroup.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(getVisibilities()).toEqual(['hidden', 'hidden', 'visible', 'hidden']); tabGroup.selectedIndex = 1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(getVisibilities()).toEqual(['hidden', 'visible', 'hidden', 'hidden']); - })); + }); }); describe('lazy loaded tabs', () => { - it('should lazy load the second tab', fakeAsync(() => { + it('should lazy load the second tab', async () => { const fixture = TestBed.createComponent(TemplateTabs); fixture.detectChanges(); - tick(); + await fixture.whenStable(); const secondLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; secondLabel.nativeElement.click(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); const child = fixture.debugElement.query(By.css('.child')); expect(child.nativeElement).toBeDefined(); - })); + }); }); describe('special cases', () => { - it('should not throw an error when binding isActive to the view', fakeAsync(() => { + it('should not throw an error when binding isActive to the view', async () => { const fixture = TestBed.createComponent(TabGroupWithIsActiveBinding); - expect(() => { - fixture.detectChanges(); - tick(); - fixture.detectChanges(); - }).not.toThrow(); + await expectAsync( + (async () => { + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + })(), + ).not.toBeRejected(); expect(fixture.nativeElement.textContent).toContain('pizza is active'); - })); + }); - it('should not pick up mat-tab-label from a child tab', fakeAsync(() => { + it('should not pick up mat-tab-label from a child tab', async () => { const fixture = TestBed.createComponent(NestedTabGroupWithLabel); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); const labels = fixture.nativeElement.querySelectorAll('.mdc-tab__text-label'); @@ -903,14 +897,14 @@ describe('MatTabGroup', () => { 'Child 2', 'Child 3', ]); - })); + }); }); describe('nested tabs', () => { - it('should not pick up the tabs from descendant tab groups', fakeAsync(() => { + it('should not pick up the tabs from descendant tab groups', async () => { const fixture = TestBed.createComponent(NestedTabs); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); const groups = fixture.componentInstance.groups.toArray(); @@ -921,17 +915,17 @@ describe('MatTabGroup', () => { 'Inner tab one', 'Inner tab two', ]); - })); + }); - it('should pick up indirect descendant tabs', fakeAsync(() => { + it('should pick up indirect descendant tabs', async () => { const fixture = TestBed.createComponent(TabGroupWithIndirectDescendantTabs); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); const tabs = fixture.componentInstance.tabGroup._tabs; expect(tabs.map((tab: MatTab) => tab.textLabel)).toEqual(['One', 'Two']); - })); + }); }); describe('tall tabs', () => { @@ -939,10 +933,10 @@ describe('MatTabGroup', () => { window.scrollTo({top: 0}); }); - it('should not scroll when changing tabs by clicking', fakeAsync(() => { + it('should not scroll when changing tabs by clicking', async () => { const fixture = TestBed.createComponent(TabGroupWithSpaceAbove); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); window.scrollBy(0, 250); @@ -954,13 +948,13 @@ describe('MatTabGroup', () => { checkSelectedIndex(1, fixture); expect(window.scrollY).toBe(250); - tick(); - })); + await fixture.whenStable(); + }); - it('should not scroll when changing tabs programatically', fakeAsync(() => { + it('should not scroll when changing tabs programatically', async () => { const fixture = TestBed.createComponent(TabGroupWithSpaceAbove); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); window.scrollBy(0, 250); @@ -971,8 +965,8 @@ describe('MatTabGroup', () => { fixture.detectChanges(); expect(window.scrollY).toBe(250); - tick(); - })); + await fixture.whenStable(); + }); }); describe('tabs with custom css classes', () => { @@ -1068,31 +1062,35 @@ describe('MatTabGroup', () => { }); describe('nested MatTabGroup with enabled animations', () => { - it('should not throw when creating a component with nested tab groups', fakeAsync(() => { - expect(() => { - let fixture = TestBed.createComponent(NestedTabs); - fixture.detectChanges(); - tick(); - }).not.toThrow(); - })); + it('should not throw when creating a component with nested tab groups', async () => { + await expectAsync( + (async () => { + let fixture = TestBed.createComponent(NestedTabs); + fixture.detectChanges(); + await fixture.whenStable(); + })(), + ).not.toBeRejected(); + }); - it('should not throw when setting an animationDuration without units', fakeAsync(() => { - expect(() => { - let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); - fixture.detectChanges(); - tick(); - }).not.toThrow(); - })); + it('should not throw when setting an animationDuration without units', async () => { + await expectAsync( + (async () => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + await fixture.whenStable(); + })(), + ).not.toBeRejected(); + }); - it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { + it('should set appropiate css variable given a specified animationDuration', async () => { let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); fixture.detectChanges(); - tick(); + await fixture.whenStable(); const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms'); expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); - })); + }); }); describe('MatTabGroup with ink bar fit to content', () => { @@ -1134,7 +1132,7 @@ describe('MatTabGroup with ink bar fit to content', () => { describe('MatTabGroup with a default config', () => { let fixture: ComponentFixture; - beforeEach(fakeAsync(() => { + beforeEach(() => { TestBed.configureTestingModule({ providers: [ { @@ -1143,7 +1141,7 @@ describe('MatTabGroup with a default config', () => { }, ], }); - })); + }); beforeEach(() => { fixture = TestBed.createComponent(SimpleTabsTestApp); diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index db80c8b1ec08..71483fcc0b8b 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -16,16 +16,7 @@ import { inject, ChangeDetectionStrategy, } from '@angular/core'; -import { - ComponentFixture, - TestBed, - discardPeriodicTasks, - fakeAsync, - flush, - flushMicrotasks, - tick, - waitForAsync, -} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MatTabHeader} from './tab-header'; @@ -36,10 +27,14 @@ describe('MatTabHeader', () => { let appComponent: SimpleTabHeaderApp; let resizeEvents: Subject; - beforeEach(waitForAsync(() => { + function wait(milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + + beforeEach(() => { resizeEvents = new Subject(); spyOn(TestBed.inject(SharedResizeObserver), 'observe').and.returnValue(resizeEvents); - })); + }); describe('focusing', () => { let tabListContainer: HTMLElement; @@ -326,7 +321,7 @@ describe('MatTabHeader', () => { .toBe(0); }); - it('should update the scroll distance if a tab is removed and no tabs are selected', fakeAsync(() => { + it('should update the scroll distance if a tab is removed and no tabs are selected', async () => { appComponent.selectedIndex = 0; fixture.changeDetectorRef.markForCheck(); appComponent.addTabsForScrolling(); @@ -346,12 +341,12 @@ describe('MatTabHeader', () => { appComponent.tabs = appComponent.tabs.slice(2); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(appComponent.tabHeader.scrollDistance).toBe( appComponent.tabHeader._getMaxScrollDistance(), ); - })); + }); }); describe('rtl', () => { @@ -402,103 +397,70 @@ describe('MatTabHeader', () => { headerElement = fixture.nativeElement.querySelector('.mat-mdc-tab-header'); }); - it('should scroll towards the end while holding down the next button using a mouse', fakeAsync(() => { - assertNextButtonScrolling('mousedown', 'click'); - })); + it('should scroll towards the end while holding down the next button using a mouse', async () => { + await assertNextButtonScrolling('mousedown', 'click'); + }); - it('should scroll towards the start while holding down the prev button using a mouse', fakeAsync(() => { - assertPrevButtonScrolling('mousedown', 'click'); - })); + it('should scroll towards the start while holding down the prev button using a mouse', async () => { + await assertPrevButtonScrolling('mousedown', 'click'); + }); - it('should scroll towards the end while holding down the next button using touch', fakeAsync(() => { - assertNextButtonScrolling('touchstart', 'touchend'); - })); + it('should scroll towards the end while holding down the next button using touch', async () => { + await assertNextButtonScrolling('touchstart', 'touchend'); + }); - it('should scroll towards the start while holding down the prev button using touch', fakeAsync(() => { - assertPrevButtonScrolling('touchstart', 'touchend'); - })); + it('should scroll towards the start while holding down the prev button using touch', async () => { + await assertPrevButtonScrolling('touchstart', 'touchend'); + }); - it('should not scroll if the sequence is interrupted quickly', fakeAsync(() => { + it('should not scroll if the sequence is interrupted quickly', async () => { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - tick(100); + await wait(100); dispatchFakeEvent(headerElement, 'mouseleave'); fixture.detectChanges(); - tick(3000); + await wait(1000); expect(header.scrollDistance) .withContext('Expected not to have scrolled after a while.') .toBe(0); - })); + }); - it('should clear the timeouts on destroy', fakeAsync(() => { + it('should clear the timeouts on destroy', async () => { dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); fixture.destroy(); - // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. - })); - - it('should clear the timeouts on click', fakeAsync(() => { - dispatchFakeEvent(nextButton, 'mousedown'); - fixture.detectChanges(); - - dispatchFakeEvent(nextButton, 'click'); - fixture.detectChanges(); - - // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. - })); - - it('should clear the timeouts on touchend', fakeAsync(() => { - dispatchFakeEvent(nextButton, 'touchstart'); - fixture.detectChanges(); - - dispatchFakeEvent(nextButton, 'touchend'); - fixture.detectChanges(); - - // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. - })); + // Wait to see if anything throws or leaks + await wait(100); + }); - it('should clear the timeouts when reaching the end', fakeAsync(() => { + it('should clear the timeouts when reaching the end', async () => { dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - // Simulate a very long timeout. - tick(60000); - - // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. - })); - - it('should clear the timeouts when reaching the start', fakeAsync(() => { - header.scrollDistance = Infinity; - fixture.detectChanges(); - - dispatchFakeEvent(prevButton, 'mousedown'); - fixture.detectChanges(); - - // Simulate a very long timeout. - tick(60000); - - // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. - })); + // Wait long enough to reach the end + await wait(1500); + }); - it('should stop scrolling if the pointer leaves the header', fakeAsync(() => { + it('should stop scrolling if the pointer leaves the header', async () => { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - tick(300); + + await wait(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(0); - tick(1000); + await wait(500); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') @@ -508,12 +470,13 @@ describe('MatTabHeader', () => { dispatchFakeEvent(headerElement, 'mouseleave'); fixture.detectChanges(); - tick(100); + + await wait(200); expect(header.scrollDistance).toBe(previousDistance); - })); + }); - it('should not scroll when pressing the right mouse button', fakeAsync(() => { + it('should not scroll when pressing the right mouse button', async () => { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchEvent( @@ -521,30 +484,31 @@ describe('MatTabHeader', () => { createMouseEvent('mousedown', undefined, undefined, undefined, undefined, 2), ); fixture.detectChanges(); - tick(3000); + + await wait(1000); expect(header.scrollDistance) .withContext('Expected not to have scrolled after a while.') .toBe(0); - })); + }); /** * Asserts that auto scrolling using the next button works. * @param startEventName Name of the event that is supposed to start the scrolling. * @param endEventName Name of the event that is supposed to end the scrolling. */ - function assertNextButtonScrolling(startEventName: string, endEventName: string) { + async function assertNextButtonScrolling(startEventName: string, endEventName: string) { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchFakeEvent(nextButton, startEventName); fixture.detectChanges(); - tick(300); + await wait(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(0); - tick(1000); + await wait(600); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') @@ -552,14 +516,15 @@ describe('MatTabHeader', () => { let previousDistance = header.scrollDistance; - tick(100); + // Wait for interval (100ms) + await wait(200); expect(header.scrollDistance) .withContext('Expected to scroll again after some more time.') .toBeGreaterThan(previousDistance); dispatchFakeEvent(nextButton, endEventName); - flush(); + await wait(100); } /** @@ -567,7 +532,7 @@ describe('MatTabHeader', () => { * @param startEventName Name of the event that is supposed to start the scrolling. * @param endEventName Name of the event that is supposed to end the scrolling. */ - function assertPrevButtonScrolling(startEventName: string, endEventName: string) { + async function assertPrevButtonScrolling(startEventName: string, endEventName: string) { header.scrollDistance = Infinity; fixture.detectChanges(); @@ -577,28 +542,29 @@ describe('MatTabHeader', () => { dispatchFakeEvent(prevButton, startEventName); fixture.detectChanges(); - tick(300); + await wait(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(currentScroll); - tick(1000); + // Wait to exceed delay (300 + 500 = 800ms > 650ms) + await wait(600); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') .toBeLessThan(currentScroll); currentScroll = header.scrollDistance; - - tick(100); + await wait(200); expect(header.scrollDistance) .withContext('Expected to scroll again after some more time.') .toBeLessThan(currentScroll); dispatchFakeEvent(nextButton, endEventName); - flush(); + // Wait for any cleanup or final events + await wait(100); } }); @@ -636,7 +602,7 @@ describe('MatTabHeader', () => { }); }); - it('should re-align the ink bar when the direction changes', fakeAsync(() => { + it('should re-align the ink bar when the direction changes', async () => { fixture = TestBed.createComponent(SimpleTabHeaderApp); fixture.detectChanges(); @@ -648,12 +614,12 @@ describe('MatTabHeader', () => { fixture.componentInstance.dir = 'rtl'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(inkBar.alignToElement).toHaveBeenCalled(); - })); + }); - it('should re-align the ink bar when the header is resized', fakeAsync(() => { + it('should re-align the ink bar when the header is resized', async () => { fixture = TestBed.createComponent(SimpleTabHeaderApp); fixture.detectChanges(); @@ -663,13 +629,12 @@ describe('MatTabHeader', () => { resizeEvents.next([]); fixture.detectChanges(); - tick(32); + await wait(100); expect(inkBar.alignToElement).toHaveBeenCalled(); - discardPeriodicTasks(); - })); + }); - it('should update arrows when the header is resized', fakeAsync(() => { + it('should update arrows when the header is resized', async () => { fixture = TestBed.createComponent(SimpleTabHeaderApp); const header = fixture.componentInstance.tabHeader; @@ -678,11 +643,10 @@ describe('MatTabHeader', () => { resizeEvents.next([]); fixture.detectChanges(); - flushMicrotasks(); + await fixture.whenStable(); expect(header._checkPaginationEnabled).toHaveBeenCalled(); - discardPeriodicTasks(); - })); + }); it('should update the pagination state if the content of the labels changes', () => { const mutationCallbacks: Function[] = []; diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts index 896c71baa164..62252ff08fa7 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts @@ -16,7 +16,7 @@ import { WritableSignal, ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '../../core'; @@ -29,7 +29,11 @@ describe('MatTabNavBar', () => { let globalRippleOptions: RippleGlobalOptions; let resizeEvents: Subject; - beforeEach(waitForAsync(() => { + function wait(milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + + beforeEach(() => { globalRippleOptions = {}; dir = signal('ltr'); @@ -42,7 +46,7 @@ describe('MatTabNavBar', () => { resizeEvents = new Subject(); spyOn(TestBed.inject(SharedResizeObserver), 'observe').and.returnValue(resizeEvents); - })); + }); describe('basic behavior', () => { let fixture: ComponentFixture; @@ -127,19 +131,19 @@ describe('MatTabNavBar', () => { expect(enterEvent.defaultPrevented).toBe(true); }); - it('should re-align the ink bar when the direction changes', fakeAsync(() => { + it('should re-align the ink bar when the direction changes', async () => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); dir.set('rtl'); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(inkBar.alignToElement).toHaveBeenCalled(); - })); + }); - it('should re-align the ink bar when the tabs list change', fakeAsync(() => { + it('should re-align the ink bar when the tabs list change', async () => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); @@ -147,10 +151,10 @@ describe('MatTabNavBar', () => { fixture.componentInstance.tabs = [1, 2, 3, 4]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(inkBar.alignToElement).toHaveBeenCalled(); - })); + }); it('should re-align the ink bar when the tab labels change the width', done => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; @@ -167,17 +171,17 @@ describe('MatTabNavBar', () => { expect(spy.calls.any()).toBe(false); }); - it('should re-align the ink bar when the nav bar is resized', fakeAsync(() => { + it('should re-align the ink bar when the nav bar is resized', async () => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); resizeEvents.next([]); fixture.detectChanges(); - tick(32); + await wait(50); expect(inkBar.alignToElement).toHaveBeenCalled(); - })); + }); it('should hide the ink bar when all the links are inactive', () => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; @@ -200,14 +204,14 @@ describe('MatTabNavBar', () => { }); }); - it('should hide the ink bar if no tabs are active on init', fakeAsync(() => { + it('should hide the ink bar if no tabs are active on init', async () => { const fixture = TestBed.createComponent(TabBarWithInactiveTabsOnInit); fixture.detectChanges(); - tick(20); // Angular turns rAF calls into 16.6ms timeouts in tests. + await wait(50); fixture.detectChanges(); expect(fixture.nativeElement.querySelectorAll('.mdc-tab-indicator--active').length).toBe(0); - })); + }); it('should clean up the ripple event handlers on destroy', () => { let fixture: ComponentFixture = TestBed.createComponent(TabLinkWithNgIf); @@ -350,27 +354,27 @@ describe('MatTabNavBar', () => { expect(tabLinks[1].classList.contains('mdc-tab--active')).toBe(true); }); - it('should re-show the ink bar if the same tab is cleared and re-activated', fakeAsync(() => { + it('should re-show the ink bar if the same tab is cleared and re-activated', async () => { const getInkBars = () => fixture.nativeElement.querySelectorAll('.mdc-tab-indicator--active').length; const fixture = TestBed.createComponent(SimpleTabNavBarTestApp); fixture.componentInstance.activeIndex = 0; fixture.detectChanges(); - tick(20); + await wait(50); expect(getInkBars()).toBe(1); fixture.componentInstance.activeIndex = -1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(20); + await wait(50); expect(getInkBars()).toBe(0); fixture.componentInstance.activeIndex = 0; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(20); + await wait(50); expect(getInkBars()).toBe(1); - })); + }); describe('ripples', () => { let fixture: ComponentFixture; @@ -503,11 +507,11 @@ describe('MatTabNavBar', () => { describe('MatTabNavBar with a default config', () => { let fixture: ComponentFixture; - beforeEach(fakeAsync(() => { + beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: MAT_TABS_CONFIG, useValue: {fitInkBarToContent: true}}], }); - })); + }); beforeEach(() => { fixture = TestBed.createComponent(TabLinkWithNgIf); @@ -524,22 +528,24 @@ describe('MatTabNavBar with a default config', () => { }); describe('MatTabNavBar with enabled animations', () => { - it('should not throw when setting an animationDuration without units', fakeAsync(() => { - expect(() => { - let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); - fixture.detectChanges(); - tick(); - }).not.toThrow(); - })); + it('should not throw when setting an animationDuration without units', async () => { + await expectAsync( + (async () => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + await fixture.whenStable(); + })(), + ).not.toBeRejected(); + }); - it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { + it('should set appropiate css variable given a specified animationDuration', async () => { let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); fixture.detectChanges(); - tick(); + await fixture.whenStable(); const tabNavBar = fixture.nativeElement.querySelector('.mat-mdc-tab-nav-bar'); expect(tabNavBar.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); - })); + }); }); @Component({