From 0e2ce90c38658d554986c7c1d6b0416415ca857e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 20 Apr 2026 08:58:09 +0200 Subject: [PATCH] refactor(material/autocomplete): switch tests away from fakeAsync Reworks all the autocomplete tests not to depend on `fakeAsync`. --- .../autocomplete/autocomplete.spec.ts | 841 ++++++++---------- 1 file changed, 362 insertions(+), 479 deletions(-) diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index b15d45573450..7b158a9090cc 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -27,14 +27,7 @@ import { ViewEncapsulation, signal, } from '@angular/core'; -import { - ComponentFixture, - TestBed, - fakeAsync, - flush, - tick, - waitForAsync, -} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {AsyncPipe} from '@angular/common'; import {By} from '@angular/platform-browser'; @@ -83,6 +76,10 @@ describe('MatAutocomplete', () => { ); } + function wait(milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + describe('panel toggling', () => { let fixture: ComponentFixture; let input: HTMLInputElement; @@ -112,7 +109,7 @@ describe('MatAutocomplete', () => { .toContain('California'); }); - it('should not open the panel on focus if the input is readonly', fakeAsync(() => { + it('should not open the panel on focus if the input is readonly', async () => { const trigger = fixture.componentInstance.trigger; input.readOnly = true; fixture.detectChanges(); @@ -121,13 +118,13 @@ describe('MatAutocomplete', () => { .withContext('Expected panel state to start out closed.') .toBe(false); dispatchFakeEvent(input, 'focusin'); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - })); + }); - it('should not open using the arrow keys when the input is readonly', fakeAsync(() => { + it('should not open using the arrow keys when the input is readonly', async () => { const trigger = fixture.componentInstance.trigger; input.readOnly = true; fixture.detectChanges(); @@ -136,11 +133,11 @@ describe('MatAutocomplete', () => { .withContext('Expected panel state to start out closed.') .toBe(false); dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - })); + }); it('should open the panel programmatically', () => { expect(fixture.componentInstance.trigger.panelOpen) @@ -161,12 +158,12 @@ describe('MatAutocomplete', () => { .toContain('California'); }); - it('should close the panel when the user clicks away', waitForAsync(async () => { + it('should close the panel when the user clicks away', async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); dispatchFakeEvent(document, 'click'); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) @@ -174,14 +171,14 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) .toBeFalsy(); - })); + }); - it('should close the panel when the user clicks away via auxiliary button', waitForAsync(async () => { + it('should close the panel when the user clicks away via auxiliary button', async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); dispatchFakeEvent(document, 'auxclick'); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) @@ -189,12 +186,12 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) .toBeFalsy(); - })); + }); - it('should close the panel when the user taps away on a touch device', fakeAsync(() => { + it('should close the panel when the user taps away on a touch device', async () => { dispatchFakeEvent(input, 'focus'); fixture.detectChanges(); - flush(); + await fixture.whenStable(); dispatchFakeEvent(document, 'touchend'); expect(fixture.componentInstance.trigger.panelOpen) @@ -203,17 +200,15 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected tapping outside the panel to close the panel.`) .toBeFalsy(); - })); + }); - it('should close the panel when an option is clicked', waitForAsync(async () => { + it('should close the panel when an option is clicked', () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking an option to set the panel state to closed.`) @@ -221,17 +216,15 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking an option to close the panel.`) .toBeFalsy(); - })); + }); - it('should close the panel when a newly created option is clicked', waitForAsync(async () => { + it('should close the panel when a newly created option is clicked', () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); // Filter down the option list to a subset of original options ('Alabama', 'California') typeInElement(input, 'al'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -244,12 +237,10 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'al'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[1].click(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking a new option to set the panel state to closed.`) @@ -257,15 +248,15 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking a new option to close the panel.`) .toBeFalsy(); - })); + }); - it('should close the panel programmatically', fakeAsync(() => { + it('should close the panel programmatically', async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); fixture.componentInstance.trigger.closePanel(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected closing programmatically to set the panel state to closed.`) @@ -273,7 +264,7 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected closing programmatically to close the panel.`) .toBeFalsy(); - })); + }); it('should not throw when attempting to close the panel of a destroyed autocomplete', () => { const trigger = fixture.componentInstance.trigger; @@ -285,7 +276,7 @@ describe('MatAutocomplete', () => { expect(() => trigger.closePanel()).not.toThrow(); }); - it('should hide the panel when the options list is empty', fakeAsync(() => { + it('should hide the panel when the options list is empty', async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); @@ -300,21 +291,20 @@ describe('MatAutocomplete', () => { // Filter down the option list such that no options match the value typeInElement(input, 'af'); fixture.detectChanges(); - tick(); + await wait(100); fixture.detectChanges(); expect(panel.classList) .withContext(`Expected panel to hide itself when empty.`) .toContain('mat-mdc-autocomplete-hidden'); - })); + }); - it('should keep the label floating until the panel closes', waitForAsync(async () => { + it('should keep the label floating until the panel closes', () => { fixture.componentInstance.trigger.openPanel(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to float as soon as panel opens.') .toEqual('always'); - await new Promise(r => setTimeout(r)); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -326,7 +316,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to return to auto state after panel closes.') .toEqual('auto'); - })); + }); it('should not open the panel when the `input` event is invoked on a non-focused input', () => { expect(fixture.componentInstance.trigger.panelOpen) @@ -342,7 +332,7 @@ describe('MatAutocomplete', () => { .toBe(false); }); - it('should not mess with label placement if set to never', fakeAsync(() => { + it('should not mess with label placement if set to never', async () => { // TODO(crisbeto): this test likely doesn't make sense anymore. fixture.componentInstance.floatLabel = 'never' as any; fixture.changeDetectorRef.markForCheck(); @@ -352,7 +342,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay static.') .toEqual('never'); - flush(); + await fixture.whenStable(); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -360,14 +350,14 @@ describe('MatAutocomplete', () => { ) as NodeListOf; options[1].click(); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay in static state after close.') .toEqual('never'); - })); + }); - it('should not mess with label placement if set to always', fakeAsync(() => { + it('should not mess with label placement if set to always', async () => { fixture.componentInstance.floatLabel = 'always'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -376,7 +366,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay elevated on open.') .toEqual('always'); - flush(); + await fixture.whenStable(); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -384,16 +374,16 @@ describe('MatAutocomplete', () => { ) as NodeListOf; options[1].click(); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay elevated after close.') .toEqual('always'); - })); + }); - it('should toggle the visibility when typing and closing the panel', fakeAsync(() => { + it('should toggle the visibility when typing and closing the panel', async () => { fixture.componentInstance.trigger.openPanel(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) @@ -402,7 +392,7 @@ describe('MatAutocomplete', () => { typeInElement(input, 'x'); fixture.detectChanges(); - tick(); + await wait(0); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) @@ -418,13 +408,13 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'al'); fixture.detectChanges(); - tick(); + await wait(0); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) .withContext('Expected panel to be visible.') .toContain('mat-mdc-autocomplete-visible'); - })); + }); it('should animate the label when the input is focused', () => { const inputContainer = fixture.componentInstance.formField; @@ -436,19 +426,19 @@ describe('MatAutocomplete', () => { expect(inputContainer._animateAndLockLabel).toHaveBeenCalled(); }); - it('should provide the open state of the panel', fakeAsync(() => { + it('should provide the open state of the panel', async () => { expect(fixture.componentInstance.panel.isOpen).toBeFalsy( `Expected the panel to be unopened initially.`, ); dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(fixture.componentInstance.panel.isOpen).toBeTruthy( `Expected the panel to be opened on focus.`, ); - })); + }); it('should emit an event when the panel is opened', () => { fixture.componentInstance.trigger.openPanel(); @@ -468,7 +458,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.openedSpy).not.toHaveBeenCalled(); }); - it('should emit the `opened` event if the options come in after the panel is shown', fakeAsync(() => { + it('should emit the `opened` event if the options come in after the panel is shown', async () => { fixture.componentInstance.filteredStates = fixture.componentInstance.states = []; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -483,13 +473,13 @@ describe('MatAutocomplete', () => { ]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await wait(0); fixture.detectChanges(); expect(fixture.componentInstance.openedSpy).toHaveBeenCalled(); - })); + }); - it('should not emit the opened event multiple times while typing', fakeAsync(() => { + it('should not emit the opened event multiple times while typing', async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); @@ -497,11 +487,11 @@ describe('MatAutocomplete', () => { typeInElement(input, 'Alabam'); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(fixture.componentInstance.openedSpy).toHaveBeenCalledTimes(1); - })); + }); it('should emit an event when the panel is closed', () => { fixture.componentInstance.trigger.openPanel(); @@ -565,11 +555,11 @@ describe('MatAutocomplete', () => { expect(input.hasAttribute('aria-haspopup')).toBe(false); }); - it('should reopen the panel when clicking on the input', fakeAsync(() => { + it('should reopen the panel when clicking on the input', async () => { const trigger = fixture.componentInstance.trigger; input.focus(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -584,21 +574,20 @@ describe('MatAutocomplete', () => { expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); input.click(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to reopen on click.').toBe(true); - })); + }); }); - it('should not close the panel when clicking on the input', waitForAsync(async () => { + it('should not close the panel when clicking on the input', () => { const fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); const input = fixture.debugElement.query(By.css('input'))!.nativeElement; dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to be opened on focus.') @@ -610,9 +599,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to remain opened after clicking on the input.') .toBe(true); - })); + }); - it('should not close the panel when clicking on the input inside shadow DOM', waitForAsync(async () => { + it('should not close the panel when clicking on the input inside shadow DOM', () => { // This test is only relevant for Shadow DOM-capable browsers. if (!_supportsShadowDom()) { return; @@ -624,7 +613,6 @@ describe('MatAutocomplete', () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to be opened on focus.') @@ -636,7 +624,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to remain opened after clicking on the input.') .toBe(true); - })); + }); it('should have the correct text direction in RTL', () => { const rtlFixture = createComponent(SimpleAutocomplete, [provideFakeDirectionality('rtl')]); @@ -687,23 +675,19 @@ describe('MatAutocomplete', () => { }).not.toThrow(); }); - it('should clear the selected option if it no longer matches the input text while typing', waitForAsync(async () => { + it('should clear the selected option if it no longer matches the input text while typing', () => { const fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); @@ -711,30 +695,25 @@ describe('MatAutocomplete', () => { input.value = ''; typeInElement(input, 'Ala'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(false); - })); + }); - it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', waitForAsync(async () => { + it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', () => { const fixture = createComponent(SimpleAutocomplete); fixture.componentInstance.requireSelection = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); @@ -742,10 +721,9 @@ describe('MatAutocomplete', () => { input.value = ''; typeInElement(input, 'Ala'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); - })); + }); describe('forms integration', () => { let fixture: ComponentFixture; @@ -758,10 +736,9 @@ describe('MatAutocomplete', () => { input = fixture.debugElement.query(By.css('input'))!.nativeElement; }); - it('should update control value as user types with input value', waitForAsync(async () => { + it('should update control value as user types with input value', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); typeInElement(input, 'a'); fixture.detectChanges(); @@ -776,7 +753,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to be updated as user types.') .toEqual('al'); - })); + }); it('should update control value when autofilling', () => { // Simulate the browser autofilling the input by setting a value and @@ -791,10 +768,9 @@ describe('MatAutocomplete', () => { .toBe('Alabama'); }); - it('should update control value when option is selected with option value', waitForAsync(async () => { + it('should update control value when option is selected with option value', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -805,12 +781,11 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to equal the selected option value.') .toEqual({code: 'CA', name: 'California'}); - })); + }); - it('should update the control back to a string if user types after an option is selected', waitForAsync(async () => { + it('should update the control back to a string if user types after an option is selected', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -821,17 +796,15 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'Californi'); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to revert back to string.') .toEqual('Californi'); - })); + }); - it('should fill the text field with display value when an option is selected', waitForAsync(async () => { + it('should fill the text field with display value when an option is selected', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -842,12 +815,11 @@ describe('MatAutocomplete', () => { expect(input.value) .withContext(`Expected text field to fill with selected value.`) .toContain('California'); - })); + }); - it('should fill the text field with value if displayWith is not set', waitForAsync(async () => { + it('should fill the text field with value if displayWith is not set', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.componentInstance.panel.displayWith = null; fixture.componentInstance.options.toArray()[1].value = 'test value'; @@ -863,37 +835,36 @@ describe('MatAutocomplete', () => { expect(input.value) .withContext(`Expected input to fall back to selected option's value.`) .toContain('test value'); - })); + }); - it('should fill the text field correctly if value is set to obj programmatically', fakeAsync(() => { + it('should fill the text field correctly if value is set to obj programmatically', async () => { fixture.componentInstance.stateCtrl.setValue({code: 'AL', name: 'Alabama'}); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(input.value) .withContext(`Expected input to fill with matching option's viewValue.`) .toContain('Alabama'); - })); + }); - it('should clear the text field if value is reset programmatically', fakeAsync(() => { + it('should clear the text field if value is reset programmatically', async () => { typeInElement(input, 'Alabama'); fixture.detectChanges(); - tick(); fixture.componentInstance.stateCtrl.reset(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(input.value).withContext(`Expected input value to be empty after reset.`).toEqual(''); - })); + }); - it('should clear the previous selection when reactive form field is reset programmatically', waitForAsync(async () => { + it('should clear the previous selection when reactive form field is reset programmatically', async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -908,14 +879,14 @@ describe('MatAutocomplete', () => { expect(option.selected).toBe(true); fixture.componentInstance.stateCtrl.reset(); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); + await fixture.whenStable(); expect(fixture.componentInstance.stateCtrl.value).toEqual(null); expect(option.selected).toBe(false); - })); + }); it('should disable input in view when disabled programmatically', () => { const formFieldElement = fixture.debugElement.query( @@ -953,14 +924,13 @@ describe('MatAutocomplete', () => { .toBe(true); }); - it('should mark the autocomplete control as dirty when an option is selected', waitForAsync(async () => { + it('should mark the autocomplete control as dirty when an option is selected', () => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to start out pristine.`) .toBe(false); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -971,7 +941,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to become dirty when an option was selected.`) .toBe(true); - })); + }); it('should not mark the control dirty when the value is set programmatically', () => { expect(fixture.componentInstance.stateCtrl.dirty) @@ -1048,7 +1018,7 @@ describe('MatAutocomplete', () => { let UP_ARROW_EVENT: KeyboardEvent; let ENTER_EVENT: KeyboardEvent; - beforeEach(waitForAsync(async () => { + beforeEach(async () => { fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); @@ -1059,8 +1029,7 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); - })); + }); it('should not focus the option when DOWN key is pressed', () => { spyOn(fixture.componentInstance.options.first, 'focus'); @@ -1140,14 +1109,6 @@ describe('MatAutocomplete', () => { expect(optionEls[0].classList).toContain('mat-mdc-option-active'); }); - it('should set the active item properly after filtering in fakeAsync', fakeAsync(() => { - const componentInstance = fixture.componentInstance; - - componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - tick(); - fixture.detectChanges(); - })); - it('should set the active item properly after filtering', () => { const componentInstance = fixture.componentInstance; @@ -1168,30 +1129,30 @@ describe('MatAutocomplete', () => { expect(optionEls[1].classList).not.toContain('mat-mdc-option-active'); }); - it('should fill the text field when an option is selected with ENTER', fakeAsync(() => { + it('should fill the text field when an option is selected with ENTER', async () => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - flush(); + await fixture.whenStable(); fixture.detectChanges(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(input.value) .withContext(`Expected text field to fill with selected value on ENTER.`) .toContain('Alabama'); - })); + }); - it('should prevent the default enter key action', fakeAsync(() => { + it('should prevent the default enter key action', async () => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - flush(); + await fixture.whenStable(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); - flush(); + await fixture.whenStable(); expect(ENTER_EVENT.defaultPrevented) .withContext('Expected the default action to have been prevented.') .toBe(true); - })); + }); it('should not prevent the default enter action for a closed panel after a user action', () => { fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); @@ -1206,14 +1167,14 @@ describe('MatAutocomplete', () => { .toBe(false); }); - it('should not interfere with the ENTER key when pressing a modifier', fakeAsync(() => { + it('should not interfere with the ENTER key when pressing a modifier', async () => { const trigger = fixture.componentInstance.trigger; expect(input.value).withContext('Expected input to start off blank.').toBeFalsy(); expect(trigger.panelOpen).withContext('Expected panel to start off open.').toBe(true); fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - flush(); + await fixture.whenStable(); fixture.detectChanges(); Object.defineProperty(ENTER_EVENT, 'altKey', {get: () => true}); @@ -1225,7 +1186,7 @@ describe('MatAutocomplete', () => { expect(ENTER_EVENT.defaultPrevented) .withContext('Expected the default ENTER action not to have been prevented.') .toBe(false); - })); + }); it('should fill the text field, not select an option, when SPACE is entered', () => { typeInElement(input, 'New'); @@ -1243,28 +1204,28 @@ describe('MatAutocomplete', () => { .toContain('New York'); }); - it('should mark the control dirty when selecting an option from the keyboard', fakeAsync(() => { + it('should mark the control dirty when selecting an option from the keyboard', async () => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to start out pristine.`) .toBe(false); fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - flush(); + await fixture.whenStable(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to become dirty when option was selected by ENTER.`) .toBe(true); - })); + }); - it('should open the panel again when typing after making a selection', fakeAsync(() => { + it('should open the panel again when typing after making a selection', async () => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - flush(); + await fixture.whenStable(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read closed after ENTER key.`) @@ -1277,7 +1238,6 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'Alabama'); fixture.detectChanges(); - tick(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when typing in input.`) @@ -1285,15 +1245,14 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when typing in input.`) .toContain('Alabama'); - })); + }); - it('should not open the panel if the `input` event was dispatched with changing the value', fakeAsync(() => { + it('should not open the panel if the `input` event was dispatched with changing the value', () => { const trigger = fixture.componentInstance.trigger; dispatchFakeEvent(input, 'focusin'); typeInElement(input, 'A'); fixture.detectChanges(); - tick(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); @@ -1306,10 +1265,9 @@ describe('MatAutocomplete', () => { // to simulate what happen in some cases on IE. dispatchFakeEvent(input, 'input'); fixture.detectChanges(); - tick(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - })); + }); it('should scroll to active options below the fold', () => { const trigger = fixture.componentInstance.trigger; @@ -1428,11 +1386,11 @@ describe('MatAutocomplete', () => { .toEqual(56); }); - it('should close the panel when pressing escape', fakeAsync(() => { + it('should close the panel when pressing escape', async () => { const trigger = fixture.componentInstance.trigger; input.focus(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1440,13 +1398,13 @@ describe('MatAutocomplete', () => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') .toBe(input); expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); - })); + }); it('should prevent the default action when pressing escape', () => { const escapeEvent = dispatchKeyboardEvent(input, 'keydown', ESCAPE); @@ -1455,11 +1413,11 @@ describe('MatAutocomplete', () => { expect(escapeEvent.defaultPrevented).toBe(true); }); - it('should not close the panel when pressing escape with a modifier', fakeAsync(() => { + it('should not close the panel when pressing escape with a modifier', async () => { const trigger = fixture.componentInstance.trigger; input.focus(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1467,6 +1425,7 @@ describe('MatAutocomplete', () => { const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE, undefined, {alt: true}); fixture.detectChanges(); + await fixture.whenStable(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') @@ -1475,15 +1434,15 @@ describe('MatAutocomplete', () => { expect(event.defaultPrevented) .withContext('Expected default action not to be prevented.') .toBe(false); - })); + }); - it('should close the panel when pressing ALT + UP_ARROW', fakeAsync(() => { + it('should close the panel when pressing ALT + UP_ARROW', async () => { const trigger = fixture.componentInstance.trigger; const upArrowEvent = createKeyboardEvent('keydown', UP_ARROW, undefined, {alt: true}); spyOn(upArrowEvent, 'stopPropagation').and.callThrough(); input.focus(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1491,22 +1450,22 @@ describe('MatAutocomplete', () => { dispatchEvent(document.body, upArrowEvent); fixture.detectChanges(); - flush(); + await fixture.whenStable(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') .toBe(input); expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); expect(upArrowEvent.stopPropagation).toHaveBeenCalled(); - })); + }); - it('should close the panel when tabbing away from a trigger without results', fakeAsync(() => { + it('should close the panel when tabbing away from a trigger without results', async () => { fixture.componentInstance.states = []; fixture.componentInstance.filteredStates = []; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); input.focus(); - flush(); + await fixture.whenStable(); expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be rendered.') @@ -1514,18 +1473,18 @@ describe('MatAutocomplete', () => { dispatchKeyboardEvent(input, 'keydown', TAB); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be removed.') .toBeFalsy(); - })); + }); - it('should not close when a click event occurs on the outside while the panel has focus', fakeAsync(() => { + it('should not close when a click event occurs on the outside while the panel has focus', async () => { const trigger = fixture.componentInstance.trigger; input.focus(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(document.activeElement).toBe(input, 'Expected input to be focused.'); @@ -1533,68 +1492,65 @@ describe('MatAutocomplete', () => { dispatchMouseEvent(document.body, 'click'); fixture.detectChanges(); + await fixture.whenStable(); expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.'); expect(trigger.panelOpen).toBe(true, 'Expected panel to stay open.'); - })); + }); - it('should reset the active option when closing with the escape key', fakeAsync(() => { + it('should reset the active option when closing with the escape key', async () => { const trigger = fixture.componentInstance.trigger; trigger.openPanel(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); expect(!!trigger.activeOption).withContext('Expected no active option.').toBe(false); // Press the down arrow a few times. - [1, 2, 3].forEach(() => { + for (const _ of [1, 2, 3]) { trigger._handleKeydown(DOWN_ARROW_EVENT); - tick(); + await fixture.whenStable(); fixture.detectChanges(); - }); + } - // Note that this casts to a boolean, in order to prevent Jasmine - // from crashing when trying to stringify the option if the test fails. // Note that this casts to a boolean, in order to prevent Jasmine // from crashing when trying to stringify the option if the test fails. expect(!!trigger.activeOption).withContext('Expected to find an active option.').toBe(true); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - tick(); + await fixture.whenStable(); expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); - })); + }); - it('should reset the active option when closing by selecting with enter', fakeAsync(() => { + it('should reset the active option when closing by selecting with enter', async () => { const trigger = fixture.componentInstance.trigger; trigger.openPanel(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); expect(!!trigger.activeOption).withContext('Expected no active option.').toBe(false); // Press the down arrow a few times. - [1, 2, 3].forEach(() => { + for (const _ of [1, 2, 3]) { trigger._handleKeydown(DOWN_ARROW_EVENT); - tick(); + await fixture.whenStable(); fixture.detectChanges(); - }); + } - // Note that this casts to a boolean, in order to prevent Jasmine - // from crashing when trying to stringify the option if the test fails. // Note that this casts to a boolean, in order to prevent Jasmine // from crashing when trying to stringify the option if the test fails. expect(!!trigger.activeOption).withContext('Expected to find an active option.').toBe(true); trigger._handleKeydown(ENTER_EVENT); - tick(); + await fixture.whenStable(); expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); - })); + }); it('should not prevent the default action when a modifier key is pressed', () => { ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'].forEach(name => { @@ -1620,25 +1576,22 @@ describe('MatAutocomplete', () => { UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW); }); - it('should scroll to active options below the fold', waitForAsync(async () => { + it('should scroll to active options below the fold', () => { const fixture = createComponent(AutocompleteWithGroups); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await new Promise(r => setTimeout(r)); fixture.detectChanges(); const container = document.querySelector('.mat-mdc-autocomplete-panel') as HTMLElement; fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await new Promise(r => setTimeout(r)); fixture.detectChanges(); expect(container.scrollTop).withContext('Expected the panel not to scroll.').toBe(0); // Press the down arrow five times. for (const _unused of [1, 2, 3, 4, 5]) { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await new Promise(r => setTimeout(r)); } //