diff --git a/src/material/list/list.spec.ts b/src/material/list/list.spec.ts index 656ceeb1084c..691f04e437d4 100644 --- a/src/material/list/list.spec.ts +++ b/src/material/list/list.spec.ts @@ -6,7 +6,7 @@ import { ViewChildren, ChangeDetectionStrategy, } from '@angular/core'; -import {TestBed, fakeAsync} from '@angular/core/testing'; +import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MatListItem, MatListModule} from './index'; @@ -133,7 +133,7 @@ describe('MatList', () => { expect(activeItem.getAttribute('aria-current')).toBe('page'); }); - it('should not show ripples for non-nav lists', fakeAsync(() => { + it('should not show ripples for non-nav lists', () => { const fixture = TestBed.createComponent(ListWithOneAnchorItem); fixture.detectChanges(); @@ -144,7 +144,7 @@ describe('MatList', () => { dispatchMouseEvent(item._hostElement, 'mousedown'); expect(fixture.nativeElement.querySelector('.mat-ripple-element')).toBe(null); }); - })); + }); it('should allow disabling ripples for specific nav-list items', () => { const fixture = TestBed.createComponent(NavListWithOneAnchorItem); @@ -252,7 +252,7 @@ describe('MatList', () => { expect(items.every(item => item.rippleDisabled)).toBe(true); }); - it('should disable item ripples when list ripples are disabled via the input in nav list', fakeAsync(() => { + it('should disable item ripples when list ripples are disabled via the input in nav list', () => { const fixture = TestBed.createComponent(NavListWithOneAnchorItem); fixture.detectChanges(); @@ -285,9 +285,9 @@ describe('MatList', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - })); + }); - it('should disable item ripples when list ripples are disabled via the input in an action list', fakeAsync(() => { + it('should disable item ripples when list ripples are disabled via the input in an action list', () => { const fixture = TestBed.createComponent(ActionListWithoutType); fixture.detectChanges(); @@ -320,7 +320,7 @@ describe('MatList', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - })); + }); it('should be able to disable a single list item', () => { const fixture = TestBed.createComponent(ListWithDisabledItems); diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts index 24fb0f2a6c73..0f6768818571 100644 --- a/src/material/list/selection-list.spec.ts +++ b/src/material/list/selection-list.spec.ts @@ -13,14 +13,7 @@ import { QueryList, ViewChildren, } from '@angular/core'; -import { - ComponentFixture, - TestBed, - fakeAsync, - flush, - tick, - waitForAsync, -} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {ThemePalette} from '../core'; import {By} from '@angular/platform-browser'; @@ -37,18 +30,22 @@ import { describe('MatSelectionList without forms', () => { const typeaheadInterval = 200; + function wait(milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + describe('with list option', () => { let fixture: ComponentFixture; let listOptions: DebugElement[]; let selectionList: DebugElement; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithListOptions); fixture.detectChanges(); listOptions = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; - })); + }); function getFocusIndex() { return listOptions.findIndex(o => document.activeElement === o.nativeElement); @@ -287,7 +284,7 @@ describe('MatSelectionList without forms', () => { expect(listOptions.slice(1).every(o => o.nativeElement.tabIndex === -1)).toBe(true); }); - it('should focus the first selected option when list receives focus', fakeAsync(() => { + it('should focus the first selected option when list receives focus', () => { dispatchMouseEvent(listOptions[2].nativeElement, 'click'); fixture.detectChanges(); @@ -305,7 +302,7 @@ describe('MatSelectionList without forms', () => { fixture.detectChanges(); expect(listOptions.map(o => o.nativeElement.tabIndex)).toEqual([0, -1, -1, -1, -1]); - })); + }); it('should focus previous item when press UP ARROW', () => { listOptions[2].nativeElement.focus(); @@ -458,7 +455,7 @@ describe('MatSelectionList without forms', () => { ); }); - it('should be able to jump focus down to an item by typing', fakeAsync(() => { + it('should be able to jump focus down to an item by typing', async () => { const firstOption = listOptions[0].nativeElement; firstOption.focus(); @@ -466,42 +463,42 @@ describe('MatSelectionList without forms', () => { dispatchEvent(firstOption, createKeyboardEvent('keydown', 83, 's')); fixture.detectChanges(); - tick(typeaheadInterval); + await wait(typeaheadInterval + 50); expect(getFocusIndex()).toBe(1); dispatchEvent(firstOption, createKeyboardEvent('keydown', 68, 'd')); fixture.detectChanges(); - tick(typeaheadInterval); + await wait(typeaheadInterval + 50); expect(getFocusIndex()).toBe(4); - })); + }); - it('should be able to skip to an item by typing', fakeAsync(() => { + it('should be able to skip to an item by typing', async () => { listOptions[0].nativeElement.focus(); expect(getFocusIndex()).toBe(0); dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', D, 'd'); fixture.detectChanges(); - tick(typeaheadInterval); + await wait(typeaheadInterval + 50); expect(getFocusIndex()).toBe(4); - })); + }); // Test for "A" specifically, because it's a special case that can be used // to select all values. - it('should be able to skip to an item by typing the letter "A"', fakeAsync(() => { + it('should be able to skip to an item by typing the letter "A"', async () => { listOptions[0].nativeElement.focus(); expect(getFocusIndex()).toBe(0); dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', A, 'a'); fixture.detectChanges(); - tick(typeaheadInterval); + await wait(typeaheadInterval + 50); expect(getFocusIndex()).toBe(3); - })); + }); - it('should not select items while using the typeahead', fakeAsync(() => { + it('should not select items while using the typeahead', async () => { const testListItem = listOptions[1].nativeElement as HTMLElement; const model = selectionList.injector.get(MatSelectionList).selectedOptions; @@ -514,17 +511,17 @@ describe('MatSelectionList without forms', () => { dispatchKeyboardEvent(testListItem, 'keydown', D, 'd'); fixture.detectChanges(); - tick(typeaheadInterval / 2); // Tick only half the typeahead timeout. + await wait(typeaheadInterval / 2); // Wait only half the typeahead timeout. dispatchKeyboardEvent(testListItem, 'keydown', SPACE); fixture.detectChanges(); - // Tick the buffer timeout again as a new key has been pressed that resets + // Wait the buffer timeout again as a new key has been pressed that resets // the buffer timeout. - tick(typeaheadInterval); + await wait(typeaheadInterval + 50); expect(getFocusIndex()).toBe(4); expect(model.isEmpty()).toBe(true); - })); + }); it('should be able to select all options', () => { const list: MatSelectionList = selectionList.componentInstance; @@ -627,7 +624,7 @@ describe('MatSelectionList without forms', () => { expect(list.options.toArray().every(option => option.selected)).toBe(true); }); - it('should disable list item ripples when the ripples on the list have been disabled', fakeAsync(() => { + it('should disable list item ripples when the ripples on the list have been disabled', () => { const rippleTarget = fixture.nativeElement.querySelector( '.mat-mdc-list-option:not(.mdc-list-item--disabled)', ); @@ -658,7 +655,7 @@ describe('MatSelectionList without forms', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - })); + }); it('can bind both selected and value at the same time', () => { const componentFixture = TestBed.createComponent(SelectionListWithSelectedOptionAndValue); @@ -691,12 +688,12 @@ describe('MatSelectionList without forms', () => { let listOptionElements: DebugElement[]; let selectionList: DebugElement; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - })); + }); it('should set its initial selected state in the selectedOptions', () => { let options = listOptionElements.map(optionEl => @@ -709,23 +706,23 @@ describe('MatSelectionList without forms', () => { expect(selectedOptions.isSelected(options[3])).toBeFalse(); }); - it('should focus the first selected option on first focus if an item is pre-selected', fakeAsync(() => { + it('should focus the first selected option on first focus if an item is pre-selected', () => { // MDC manages the focus through setting a `tabindex` on the designated list item. We // assert that the proper tabindex is set on the pre-selected option at index 1, and // ensure that other options are not reachable through tab. expect(listOptionElements.map(el => el.nativeElement.tabIndex)).toEqual([-1, 0, -1, -1]); - })); + }); }); describe('single-selection with list option selected', () => { let fixture: ComponentFixture; let listOptionElements: DebugElement[]; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; fixture.detectChanges(); - })); + }); it('displays radio indicators by default', () => { expect( @@ -748,19 +745,19 @@ describe('MatSelectionList without forms', () => { let fixture: ComponentFixture; let listOptionElements: DebugElement[]; - beforeEach(waitForAsync(() => { + beforeEach(() => { const matListConfig: MatListConfig = {hideSingleSelectionIndicator: true}; TestBed.configureTestingModule({ providers: [{provide: MAT_LIST_CONFIG, useValue: matListConfig}], }); - })); + }); - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; fixture.detectChanges(); - })); + }); it('does not display radio indicators', () => { expect(listOptionElements[0].nativeElement.querySelector('input[type="radio"]')).toBeNull(); @@ -782,7 +779,7 @@ describe('MatSelectionList without forms', () => { let listOptionEl: HTMLElement; let listOption: MatListOption; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithDisabledOption); const listOptionDebug = fixture.debugElement.query(By.directive(MatListOption))!; @@ -791,7 +788,7 @@ describe('MatSelectionList without forms', () => { listOptionEl = listOptionDebug.nativeElement; fixture.detectChanges(); - })); + }); it('should disable ripples for disabled option', () => { expect(listOption.rippleDisabled) @@ -823,12 +820,12 @@ describe('MatSelectionList without forms', () => { let listOption: DebugElement[]; let selectionList: DebugElement; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithListDisabled); listOption = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - })); + }); it('should not allow selection on disabled selection-list', () => { let testListItem = listOption[2].injector.get(MatListOption); @@ -910,10 +907,10 @@ describe('MatSelectionList without forms', () => { describe('with checkbox position after', () => { let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithCheckboxPositionAfter); fixture.detectChanges(); - })); + }); it('should be able to customize checkbox position', () => { expect(fixture.nativeElement.querySelector('.mdc-list-item__end .mdc-checkbox')) @@ -1052,14 +1049,14 @@ describe('MatSelectionList without forms', () => { let listOptions: DebugElement[]; let selectionList: DebugElement; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(SelectionListWithListOptions); fixture.componentInstance.multiple = false; fixture.changeDetectorRef.markForCheck(); listOptions = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - })); + }); function getFocusIndex() { return listOptions.findIndex(o => document.activeElement === o.nativeElement); @@ -1174,13 +1171,13 @@ describe('MatSelectionList without forms', () => { let optionElement: HTMLElement; let option: MatListOption; - beforeEach(waitForAsync(() => { + beforeEach(() => { fixture = TestBed.createComponent(ListOptionWithTwoWayBinding); fixture.detectChanges(); const optionDebug = fixture.debugElement.query(By.directive(MatListOption)); option = optionDebug.componentInstance; optionElement = optionDebug.nativeElement; - })); + }); it('should sync the value from the view to the option', () => { expect(option.selected).toBe(false); @@ -1221,7 +1218,7 @@ describe('MatSelectionList with forms', () => { .map(optionDebugEl => optionDebugEl.componentInstance); }); - it('should update the model if an option got selected programmatically', fakeAsync(() => { + it('should update the model if an option got selected programmatically', async () => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1229,14 +1226,14 @@ describe('MatSelectionList with forms', () => { listOptions[0].toggle(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - })); + }); - it('should update the model if an option got clicked', fakeAsync(() => { + it('should update the model if an option got clicked', async () => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1244,14 +1241,14 @@ describe('MatSelectionList with forms', () => { dispatchMouseEvent(listOptions[0]._hostElement, 'click'); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - })); + }); - it('should update the options if a model value is set', fakeAsync(() => { + it('should update the options if a model value is set', async () => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1260,19 +1257,19 @@ describe('MatSelectionList with forms', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - })); + }); - it('should focus the first option when the list items are changed', fakeAsync(() => { + it('should focus the first option when the list items are changed', async () => { fixture.componentInstance.options = ['first option', 'second option']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); const listElements = fixture.debugElement .queryAll(By.directive(MatListOption)) @@ -1281,37 +1278,37 @@ describe('MatSelectionList with forms', () => { expect(listElements.length).toBe(2); expect(listElements[0].tabIndex).toBe(0); expect(listElements[1].tabIndex).toBe(-1); - })); + }); - it('should not mark the model as touched when the list is blurred', fakeAsync(() => { + it('should not mark the model as touched when the list is blurred', async () => { expect(ngModel.touched) .withContext('Expected the selection-list to be untouched by default.') .toBe(false); dispatchFakeEvent(selectionListDebug.nativeElement, 'blur'); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(ngModel.touched) .withContext('Expected the selection-list to remain untouched.') .toBe(false); - })); + }); - it('should mark the model as touched when a list item is blurred', fakeAsync(() => { + it('should mark the model as touched when a list item is blurred', async () => { expect(ngModel.touched) .withContext('Expected the selection-list to be untouched by default.') .toBe(false); dispatchFakeEvent(fixture.nativeElement.querySelector('.mat-mdc-list-option'), 'blur'); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(ngModel.touched) .withContext('Expected the selection-list to be touched after an item is blurred.') .toBe(true); - })); + }); - it('should be pristine by default', fakeAsync(() => { + it('should be pristine by default', async () => { fixture = TestBed.createComponent(SelectionListWithModel); fixture.componentInstance.selectedOptions = ['opt2']; fixture.changeDetectorRef.markForCheck(); @@ -1326,7 +1323,7 @@ describe('MatSelectionList with forms', () => { // Flush the initial tick to ensure that every action from the ControlValueAccessor // happened before the actual test starts. - tick(); + await fixture.whenStable(); expect(ngModel.pristine) .withContext('Expected the selection-list to be pristine by default.') @@ -1335,12 +1332,12 @@ describe('MatSelectionList with forms', () => { listOptions[1].toggle(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(ngModel.pristine) .withContext('Expected the selection-list to be dirty after state change.') .toBe(false); - })); + }); it('should remove a selected option from the value on destroy', fakeAsync(() => { listOptions[1].selected = true; @@ -1357,15 +1354,15 @@ describe('MatSelectionList with forms', () => { expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); })); - it('should update the model if an option got selected via the model', fakeAsync(() => { + it('should update the model if an option got selected via the model', async () => { expect(fixture.componentInstance.selectedOptions).toEqual([]); selectionListDebug.componentInstance.selectedOptions.select(listOptions[0]); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); - })); + }); it('should not dispatch the model change event if nothing changed using selectAll', () => { expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); @@ -1390,11 +1387,11 @@ describe('MatSelectionList with forms', () => { expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); }); - it('should be able to programmatically set an array with duplicate values', fakeAsync(() => { + it('should be able to programmatically set an array with duplicate values', async () => { fixture.componentInstance.options = ['one', 'two', 'two', 'two', 'three']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) @@ -1403,12 +1400,12 @@ describe('MatSelectionList with forms', () => { fixture.componentInstance.selectedOptions = ['one', 'two', 'two']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(listOptions.map(option => option.selected)).toEqual([true, true, true, false, false]); - })); + }); - it('should dispatch one change event per change when updating a single-selection list', fakeAsync(() => { + it('should dispatch one change event per change when updating a single-selection list', async () => { fixture.destroy(); fixture = TestBed.createComponent(SelectionListWithModel); fixture.componentInstance.multiple = false; @@ -1423,18 +1420,18 @@ describe('MatSelectionList with forms', () => { options[0].click(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); options[1].click(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2); expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); - })); + }); }); describe('and formControl', () => { @@ -1571,7 +1568,7 @@ describe('MatSelectionList with forms', () => { .toBe(true); }); - it('should not clear the form control when the list is destroyed', fakeAsync(() => { + it('should not clear the form control when the list is destroyed', async () => { const option = listOptions[1]; option.selected = true; @@ -1582,11 +1579,11 @@ describe('MatSelectionList with forms', () => { fixture.componentInstance.renderList = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); fixture.detectChanges(); expect(fixture.componentInstance.formControl.value).toEqual(['opt2']); - })); + }); it('should mark options added at a later point as selected', () => { fixture.componentInstance.formControl.setValue(['opt4']); @@ -1606,34 +1603,34 @@ describe('MatSelectionList with forms', () => { }); describe('preselected values', () => { - it('should add preselected options to the model value', fakeAsync(() => { + it('should add preselected options to the model value', async () => { const fixture = TestBed.createComponent(SelectionListWithPreselectedOption); const listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) .map(optionDebugEl => optionDebugEl.componentInstance); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(listOptions[1].selected).toBe(true); expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); - })); + }); - it('should handle preselected option both through the model and the view', fakeAsync(() => { + it('should handle preselected option both through the model and the view', async () => { const fixture = TestBed.createComponent(SelectionListWithPreselectedOptionAndModel); const listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) .map(optionDebugEl => optionDebugEl.componentInstance); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(listOptions[0].selected).toBe(true); expect(listOptions[1].selected).toBe(true); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1', 'opt2']); - })); + }); - it('should show the item as selected when preselected inside OnPush parent', fakeAsync(() => { + it('should show the item as selected when preselected inside OnPush parent', async () => { const fixture = TestBed.createComponent(SelectionListWithPreselectedFormControlOnPush); fixture.detectChanges(); @@ -1643,16 +1640,16 @@ describe('MatSelectionList with forms', () => { ) as HTMLInputElement; fixture.detectChanges(); - flush(); + await fixture.whenStable(); fixture.detectChanges(); expect(option.componentInstance.selected).toBe(true); expect(checkbox.checked).toBe(true); - })); + }); }); describe('with custom compare function', () => { - it('should use a custom comparator to determine which options are selected', fakeAsync(() => { + it('should use a custom comparator to determine which options are selected', async () => { const fixture = TestBed.createComponent(SelectionListWithCustomComparator); const testComponent = fixture.componentInstance; @@ -1665,11 +1662,11 @@ describe('MatSelectionList with forms', () => { testComponent.selectedOptions = [{id: 2, label: 'Two'}]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - tick(); + await fixture.whenStable(); expect(testComponent.compareWith).toHaveBeenCalled(); expect(testComponent.optionInstances.toArray()[1].selected).toBe(true); - })); + }); }); });