2049 lines
71 KiB
TypeScript
2049 lines
71 KiB
TypeScript
|
|
import {A, D, DOWN_ARROW, END, ENTER, HOME, SPACE, UP_ARROW} from '@angular/cdk/keycodes';
|
||
|
|
import {
|
||
|
|
createKeyboardEvent,
|
||
|
|
dispatchEvent,
|
||
|
|
dispatchFakeEvent,
|
||
|
|
dispatchKeyboardEvent,
|
||
|
|
dispatchMouseEvent,
|
||
|
|
} from '@angular/cdk/testing/private';
|
||
|
|
import {
|
||
|
|
ChangeDetectionStrategy,
|
||
|
|
Component,
|
||
|
|
DebugElement,
|
||
|
|
QueryList,
|
||
|
|
ViewChildren,
|
||
|
|
} from '@angular/core';
|
||
|
|
import {
|
||
|
|
ComponentFixture,
|
||
|
|
TestBed,
|
||
|
|
fakeAsync,
|
||
|
|
flush,
|
||
|
|
tick,
|
||
|
|
waitForAsync,
|
||
|
|
} from '@angular/core/testing';
|
||
|
|
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
|
||
|
|
import {ThemePalette} from '@angular/material/core';
|
||
|
|
import {By} from '@angular/platform-browser';
|
||
|
|
import {
|
||
|
|
MAT_LIST_CONFIG,
|
||
|
|
MatListConfig,
|
||
|
|
MatListModule,
|
||
|
|
MatListOption,
|
||
|
|
MatListOptionTogglePosition,
|
||
|
|
MatSelectionList,
|
||
|
|
MatSelectionListChange,
|
||
|
|
} from './index';
|
||
|
|
|
||
|
|
describe('MatSelectionList without forms', () => {
|
||
|
|
const typeaheadInterval = 200;
|
||
|
|
|
||
|
|
describe('with list option', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithListOptions>;
|
||
|
|
let listOptions: DebugElement[];
|
||
|
|
let selectionList: DebugElement;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
MatListModule,
|
||
|
|
SelectionListWithListOptions,
|
||
|
|
SelectionListWithCheckboxPositionAfter,
|
||
|
|
SelectionListWithListDisabled,
|
||
|
|
SelectionListWithOnlyOneOption,
|
||
|
|
SelectionListWithIndirectChildOptions,
|
||
|
|
SelectionListWithSelectedOptionAndValue,
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
it('should be able to set a value on a list option', () => {
|
||
|
|
const optionValues = ['inbox', 'starred', 'sent-mail', 'archive', 'drafts'];
|
||
|
|
|
||
|
|
optionValues.forEach((optionValue, index) => {
|
||
|
|
expect(listOptions[index].componentInstance.value).toBe(optionValue);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not emit a selectionChange event if an option changed programmatically', () => {
|
||
|
|
spyOn(fixture.componentInstance, 'onSelectionChange');
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.onSelectionChange).toHaveBeenCalledTimes(0);
|
||
|
|
|
||
|
|
listOptions[2].componentInstance.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.onSelectionChange).toHaveBeenCalledTimes(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should emit a selectionChange event if an option got clicked', () => {
|
||
|
|
spyOn(fixture.componentInstance, 'onSelectionChange');
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.onSelectionChange).toHaveBeenCalledTimes(0);
|
||
|
|
|
||
|
|
dispatchMouseEvent(listOptions[2].nativeElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.onSelectionChange).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to dispatch one selected item', () => {
|
||
|
|
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
let selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('false');
|
||
|
|
|
||
|
|
testListItem.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('true');
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
|
||
|
|
expect(selectList.selected.length).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to dispatch multiple selected items', () => {
|
||
|
|
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
let testListItem2 = listOptions[1].injector.get<MatListOption>(MatListOption);
|
||
|
|
let selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('false');
|
||
|
|
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('false');
|
||
|
|
|
||
|
|
testListItem.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
testListItem2.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(2);
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('true');
|
||
|
|
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('true');
|
||
|
|
expect(listOptions[1].nativeElement.getAttribute('aria-disabled')).toBe('false');
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to specify a color for list options', () => {
|
||
|
|
const optionNativeElements = listOptions.map(option => option.nativeElement);
|
||
|
|
|
||
|
|
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary'))).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
expect(optionNativeElements.every(option => !option.classList.contains('mat-warn'))).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
|
||
|
|
// All options will be set to the "warn" color.
|
||
|
|
fixture.componentInstance.selectionListColor = 'warn';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary'))).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
expect(optionNativeElements.every(option => option.classList.contains('mat-warn'))).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
|
||
|
|
// Color will be set explicitly for an option and should take precedence.
|
||
|
|
fixture.componentInstance.firstOptionColor = 'primary';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(optionNativeElements[0].classList).not.toContain('mat-accent');
|
||
|
|
expect(optionNativeElements[0].classList).not.toContain('mat-warn');
|
||
|
|
expect(
|
||
|
|
optionNativeElements.slice(1).every(option => option.classList.contains('mat-warn')),
|
||
|
|
).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should explicitly set the `accent` color', () => {
|
||
|
|
const classList = listOptions[0].nativeElement.classList;
|
||
|
|
|
||
|
|
fixture.componentInstance.firstOptionColor = 'primary';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(classList).not.toContain('mat-accent');
|
||
|
|
expect(classList).not.toContain('mat-warn');
|
||
|
|
|
||
|
|
fixture.componentInstance.firstOptionColor = 'accent';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(classList).not.toContain('mat-primary');
|
||
|
|
expect(classList).toContain('mat-accent');
|
||
|
|
expect(classList).not.toContain('mat-warn');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to deselect an option', () => {
|
||
|
|
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
let selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
testListItem.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(1);
|
||
|
|
|
||
|
|
testListItem.toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not add the mdc-list-item--selected class (in multiple mode)', () => {
|
||
|
|
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[2].nativeElement.classList.contains('mdc-list-item--selected')).toBe(
|
||
|
|
false,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not allow selection of disabled items', () => {
|
||
|
|
let testListItem = listOptions[0].injector.get<MatListOption>(MatListOption);
|
||
|
|
let selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).withContext('before click').toBe(0);
|
||
|
|
expect(listOptions[0].nativeElement.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).withContext('after click').toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to un-disable disabled items', () => {
|
||
|
|
let testListItem = listOptions[0].injector.get<MatListOption>(MatListOption);
|
||
|
|
|
||
|
|
expect(listOptions[0].nativeElement.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
|
||
|
|
testListItem.disabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[0].nativeElement.getAttribute('aria-disabled')).toBe('false');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to use keyboard select with SPACE', () => {
|
||
|
|
const testListItem = listOptions[1].nativeElement as HTMLElement;
|
||
|
|
const selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
testListItem.focus();
|
||
|
|
expect(getFocusIndex()).toBe(1);
|
||
|
|
|
||
|
|
const event = dispatchKeyboardEvent(testListItem, 'keydown', SPACE);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(1);
|
||
|
|
expect(event.defaultPrevented).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to select an item using ENTER', () => {
|
||
|
|
const testListItem = listOptions[1].nativeElement as HTMLElement;
|
||
|
|
const selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
testListItem.focus();
|
||
|
|
expect(getFocusIndex()).toBe(1);
|
||
|
|
|
||
|
|
const event = dispatchKeyboardEvent(testListItem, 'keydown', ENTER);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(1);
|
||
|
|
expect(event.defaultPrevented).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not be able to toggle a disabled option using SPACE', () => {
|
||
|
|
const testListItem = listOptions[1].nativeElement as HTMLElement;
|
||
|
|
const selectionModel = selectionList.componentInstance.selectedOptions;
|
||
|
|
|
||
|
|
expect(selectionModel.selected.length).toBe(0);
|
||
|
|
|
||
|
|
listOptions[1].componentInstance.disabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
testListItem.focus();
|
||
|
|
expect(getFocusIndex()).toBe(1);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(testListItem, 'keydown', SPACE);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectionModel.selected.length).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should focus the first option when the list takes focus for the first time', () => {
|
||
|
|
expect(listOptions[0].nativeElement.tabIndex).toBe(0);
|
||
|
|
expect(listOptions.slice(1).every(o => o.nativeElement.tabIndex === -1)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should focus the first selected option when list receives focus', fakeAsync(() => {
|
||
|
|
dispatchMouseEvent(listOptions[2].nativeElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.map(o => o.nativeElement.tabIndex)).toEqual([-1, -1, 0, -1, -1]);
|
||
|
|
|
||
|
|
dispatchMouseEvent(listOptions[1].nativeElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.map(o => o.nativeElement.tabIndex)).toEqual([-1, 0, -1, -1, -1]);
|
||
|
|
|
||
|
|
// De-select both options to ensure that the first item in the list-item
|
||
|
|
// becomes the designated option for focus.
|
||
|
|
dispatchMouseEvent(listOptions[1].nativeElement, 'click');
|
||
|
|
dispatchMouseEvent(listOptions[2].nativeElement, 'click');
|
||
|
|
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();
|
||
|
|
expect(getFocusIndex()).toEqual(2);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', UP_ARROW);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toEqual(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should focus next item when press DOWN ARROW', () => {
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toEqual(2);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', DOWN_ARROW);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toEqual(3);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to focus the first item when pressing HOME', () => {
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(2);
|
||
|
|
|
||
|
|
const event = dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', HOME);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(0);
|
||
|
|
expect(event.defaultPrevented).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should focus the last item when pressing END', () => {
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(2);
|
||
|
|
|
||
|
|
const event = dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', END);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(4);
|
||
|
|
expect(event.defaultPrevented).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should select all items using ctrl + a', () => {
|
||
|
|
listOptions.forEach(option => (option.componentInstance.disabled = false));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.some(option => option.componentInstance.selected)).toBe(false);
|
||
|
|
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', A, 'A', {control: true});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => option.componentInstance.selected)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not select disabled items when pressing ctrl + a', () => {
|
||
|
|
listOptions.slice(0, 2).forEach(option => (option.componentInstance.disabled = true));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.map(option => option.componentInstance.selected)).toEqual([
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
listOptions[3].nativeElement.focus();
|
||
|
|
dispatchKeyboardEvent(listOptions[3].nativeElement, 'keydown', A, 'A', {control: true});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.map(option => option.componentInstance.selected)).toEqual([
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
true,
|
||
|
|
true,
|
||
|
|
true,
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should select all items using ctrl + a if some items are selected', () => {
|
||
|
|
listOptions.slice(0, 2).forEach(option => (option.componentInstance.selected = true));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.some(option => option.componentInstance.selected)).toBe(true);
|
||
|
|
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', A, 'A', {control: true});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => option.componentInstance.selected)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should deselect all with ctrl + a if all options are selected', () => {
|
||
|
|
listOptions.forEach(option => (option.componentInstance.selected = true));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => option.componentInstance.selected)).toBe(true);
|
||
|
|
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', A, 'A', {control: true});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => option.componentInstance.selected)).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should dispatch the selectionChange event when selecting via ctrl + a', () => {
|
||
|
|
const spy = spyOn(fixture.componentInstance, 'onSelectionChange');
|
||
|
|
listOptions.forEach(option => (option.componentInstance.disabled = false));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
listOptions[2].nativeElement.focus();
|
||
|
|
dispatchKeyboardEvent(listOptions[2].nativeElement, 'keydown', A, 'A', {control: true});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
||
|
|
expect(spy).toHaveBeenCalledWith(
|
||
|
|
jasmine.objectContaining({
|
||
|
|
options: listOptions.map(option => option.componentInstance),
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to jump focus down to an item by typing', fakeAsync(() => {
|
||
|
|
const firstOption = listOptions[0].nativeElement;
|
||
|
|
|
||
|
|
firstOption.focus();
|
||
|
|
expect(getFocusIndex()).toBe(0);
|
||
|
|
|
||
|
|
dispatchEvent(firstOption, createKeyboardEvent('keydown', 83, 's'));
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick(typeaheadInterval);
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(1);
|
||
|
|
|
||
|
|
dispatchEvent(firstOption, createKeyboardEvent('keydown', 68, 'd'));
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick(typeaheadInterval);
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(4);
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should be able to skip to an item by typing', fakeAsync(() => {
|
||
|
|
listOptions[0].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(0);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', D, 'd');
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick(typeaheadInterval);
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
listOptions[0].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(0);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', A, 'a');
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick(typeaheadInterval);
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(3);
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should not select items while using the typeahead', fakeAsync(() => {
|
||
|
|
const testListItem = listOptions[1].nativeElement as HTMLElement;
|
||
|
|
const model = selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
testListItem.focus();
|
||
|
|
dispatchFakeEvent(testListItem, 'focus');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(1);
|
||
|
|
expect(model.isEmpty()).toBe(true);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(testListItem, 'keydown', D, 'd');
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick(typeaheadInterval / 2); // Tick 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
|
||
|
|
// the buffer timeout.
|
||
|
|
tick(typeaheadInterval);
|
||
|
|
|
||
|
|
expect(getFocusIndex()).toBe(4);
|
||
|
|
expect(model.isEmpty()).toBe(true);
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should be able to select all options', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(false);
|
||
|
|
|
||
|
|
const result = list.selectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(true);
|
||
|
|
expect(result).toEqual(list.options.toArray());
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to select all options, even if they are disabled', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
list.options.forEach(option => (option.disabled = true));
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(false);
|
||
|
|
|
||
|
|
list.selectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to deselect all options', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
list.options.forEach(option => option.toggle());
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(true);
|
||
|
|
|
||
|
|
const result = list.deselectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(false);
|
||
|
|
expect(result).toEqual(list.options.toArray());
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to deselect all options, even if they are disabled', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
list.options.forEach(option => option.toggle());
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(true);
|
||
|
|
|
||
|
|
list.options.forEach(option => (option.disabled = true));
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
list.deselectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should update the list value when an item is selected programmatically', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
expect(list.selectedOptions.isEmpty()).toBe(true);
|
||
|
|
|
||
|
|
listOptions[0].componentInstance.selected = true;
|
||
|
|
listOptions[2].componentInstance.selected = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(list.selectedOptions.isEmpty()).toBe(false);
|
||
|
|
expect(list.selectedOptions.isSelected(listOptions[0].componentInstance)).toBe(true);
|
||
|
|
expect(list.selectedOptions.isSelected(listOptions[2].componentInstance)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should update the item selected state when it is selected via the model', () => {
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
const item: MatListOption = listOptions[0].componentInstance;
|
||
|
|
|
||
|
|
expect(item.selected).toBe(false);
|
||
|
|
|
||
|
|
list.selectedOptions.select(item);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(item.selected).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set aria-multiselectable to true on the selection list element', () => {
|
||
|
|
expect(selectionList.nativeElement.getAttribute('aria-multiselectable')).toBe('true');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to reach list options that are indirect descendants', () => {
|
||
|
|
const descendatsFixture = TestBed.createComponent(SelectionListWithIndirectChildOptions);
|
||
|
|
descendatsFixture.detectChanges();
|
||
|
|
listOptions = descendatsFixture.debugElement.queryAll(By.directive(MatListOption));
|
||
|
|
selectionList = descendatsFixture.debugElement.query(By.directive(MatSelectionList))!;
|
||
|
|
const list: MatSelectionList = selectionList.componentInstance;
|
||
|
|
|
||
|
|
expect(list.options.toArray().every(option => option.selected)).toBe(false);
|
||
|
|
|
||
|
|
list.selectAll();
|
||
|
|
descendatsFixture.detectChanges();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
const rippleTarget = fixture.nativeElement.querySelector(
|
||
|
|
'.mat-mdc-list-option:not(.mdc-list-item--disabled)',
|
||
|
|
);
|
||
|
|
dispatchMouseEvent(rippleTarget, 'mousedown');
|
||
|
|
dispatchMouseEvent(rippleTarget, 'mouseup');
|
||
|
|
|
||
|
|
// Flush the ripple enter animation.
|
||
|
|
dispatchFakeEvent(rippleTarget.querySelector('.mat-ripple-element')!, 'transitionend');
|
||
|
|
|
||
|
|
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
|
||
|
|
.withContext('Expected ripples to be enabled by default.')
|
||
|
|
.toBe(1);
|
||
|
|
|
||
|
|
// Flush the ripple exit animation.
|
||
|
|
dispatchFakeEvent(rippleTarget.querySelector('.mat-ripple-element')!, 'transitionend');
|
||
|
|
|
||
|
|
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
|
||
|
|
.withContext('Expected ripples to go away.')
|
||
|
|
.toBe(0);
|
||
|
|
|
||
|
|
fixture.componentInstance.listRippleDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
dispatchMouseEvent(rippleTarget, 'mousedown');
|
||
|
|
dispatchMouseEvent(rippleTarget, 'mouseup');
|
||
|
|
|
||
|
|
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);
|
||
|
|
componentFixture.detectChanges();
|
||
|
|
const listItemEl = componentFixture.debugElement.query(By.directive(MatListOption))!;
|
||
|
|
expect(listItemEl.componentInstance.selected).toBe(true);
|
||
|
|
expect(listItemEl.componentInstance.value).toBe(componentFixture.componentInstance.itemValue);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should have a focus indicator', () => {
|
||
|
|
const optionNativeElements = listOptions.map(option => option.nativeElement as HTMLElement);
|
||
|
|
|
||
|
|
expect(
|
||
|
|
optionNativeElements.every(
|
||
|
|
element => element.querySelector('.mat-focus-indicator') !== null,
|
||
|
|
),
|
||
|
|
).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should hide the internal SVG', () => {
|
||
|
|
listOptions.forEach(option => {
|
||
|
|
const svg = option.nativeElement.querySelector('.mdc-checkbox svg');
|
||
|
|
expect(svg.getAttribute('aria-hidden')).toBe('true');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('multiple-selection with list option selected', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithSelectedOption>;
|
||
|
|
let listOptionElements: DebugElement[];
|
||
|
|
let selectionList: DebugElement;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SelectionListWithSelectedOption],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
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 =>
|
||
|
|
optionEl.injector.get<MatListOption>(MatListOption),
|
||
|
|
);
|
||
|
|
let selectedOptions = selectionList.componentInstance.selectedOptions;
|
||
|
|
expect(selectedOptions.isSelected(options[0])).toBeFalse();
|
||
|
|
expect(selectedOptions.isSelected(options[1])).toBeTrue();
|
||
|
|
expect(selectedOptions.isSelected(options[2])).toBeTrue();
|
||
|
|
expect(selectedOptions.isSelected(options[3])).toBeFalse();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should focus the first selected option on first focus if an item is pre-selected', fakeAsync(() => {
|
||
|
|
// 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<SingleSelectionListWithSelectedOption>;
|
||
|
|
let listOptionElements: DebugElement[];
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SingleSelectionListWithSelectedOption],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption);
|
||
|
|
listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!;
|
||
|
|
fixture.detectChanges();
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('displays radio indicators by default', () => {
|
||
|
|
expect(
|
||
|
|
listOptionElements[0].nativeElement.querySelector('input[type="radio"]'),
|
||
|
|
).not.toBeNull();
|
||
|
|
expect(
|
||
|
|
listOptionElements[1].nativeElement.querySelector('input[type="radio"]'),
|
||
|
|
).not.toBeNull();
|
||
|
|
|
||
|
|
expect(listOptionElements[0].nativeElement.classList).not.toContain(
|
||
|
|
'mdc-list-item--selected',
|
||
|
|
);
|
||
|
|
expect(listOptionElements[1].nativeElement.classList).not.toContain(
|
||
|
|
'mdc-list-item--selected',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with token to hide radio indicators', () => {
|
||
|
|
let fixture: ComponentFixture<SingleSelectionListWithSelectedOption>;
|
||
|
|
let listOptionElements: DebugElement[];
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
const matListConfig: MatListConfig = {hideSingleSelectionIndicator: true};
|
||
|
|
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SingleSelectionListWithSelectedOption],
|
||
|
|
providers: [{provide: MAT_LIST_CONFIG, useValue: matListConfig}],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
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();
|
||
|
|
expect(listOptionElements[1].nativeElement.querySelector('input[type="radio"]')).toBeNull();
|
||
|
|
|
||
|
|
expect(listOptionElements[0].nativeElement.classList).not.toContain(
|
||
|
|
'mdc-list-item--selected',
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(listOptionElements[1].nativeElement.getAttribute('aria-selected'))
|
||
|
|
.withContext('Expected second option to be selected')
|
||
|
|
.toBe('true');
|
||
|
|
expect(listOptionElements[1].nativeElement.classList).toContain('mdc-list-item--selected');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with option disabled', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithDisabledOption>;
|
||
|
|
let listOptionEl: HTMLElement;
|
||
|
|
let listOption: MatListOption;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SelectionListWithDisabledOption],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithDisabledOption);
|
||
|
|
|
||
|
|
const listOptionDebug = fixture.debugElement.query(By.directive(MatListOption))!;
|
||
|
|
|
||
|
|
listOption = listOptionDebug.componentInstance;
|
||
|
|
listOptionEl = listOptionDebug.nativeElement;
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should disable ripples for disabled option', () => {
|
||
|
|
expect(listOption.rippleDisabled)
|
||
|
|
.withContext('Expected ripples to be enabled by default')
|
||
|
|
.toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.disableItem = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOption.rippleDisabled)
|
||
|
|
.withContext('Expected ripples to be disabled if option is disabled')
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should apply the "mat-list-item-disabled" class properly', () => {
|
||
|
|
expect(listOptionEl.classList).not.toContain('mdc-list-item--disabled');
|
||
|
|
|
||
|
|
fixture.componentInstance.disableItem = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptionEl.classList).toContain('mdc-list-item--disabled');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with list disabled', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithListDisabled>;
|
||
|
|
let listOption: DebugElement[];
|
||
|
|
let selectionList: DebugElement;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
MatListModule,
|
||
|
|
SelectionListWithListOptions,
|
||
|
|
SelectionListWithCheckboxPositionAfter,
|
||
|
|
SelectionListWithListDisabled,
|
||
|
|
SelectionListWithOnlyOneOption,
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
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>(MatListOption);
|
||
|
|
let selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should update state of options if list state has changed', () => {
|
||
|
|
const testOption = listOption[2].componentInstance as MatListOption;
|
||
|
|
|
||
|
|
expect(testOption.rippleDisabled)
|
||
|
|
.withContext('Expected ripples of list option to be disabled')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.disabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(testOption.rippleDisabled)
|
||
|
|
.withContext('Expected ripples of list option to be enabled')
|
||
|
|
.toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
// when the entire list is disabled, its listitems should always have tabindex="-1"
|
||
|
|
it('should remove all listitems from the tab order when disabled state is enabled', () => {
|
||
|
|
fixture.componentInstance.disabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
let testListItem = listOption[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
testListItem.focus();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(
|
||
|
|
listOption.filter(option => option.nativeElement.getAttribute('tabindex') === '0').length,
|
||
|
|
)
|
||
|
|
.withContext('Expected at least one list option to be in the tab order')
|
||
|
|
.toBeGreaterThanOrEqual(1);
|
||
|
|
|
||
|
|
fixture.componentInstance.disabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(
|
||
|
|
listOption.filter(option => option.nativeElement.getAttribute('tabindex') !== '-1').length,
|
||
|
|
)
|
||
|
|
.withContext('Expected all list options to be excluded from the tab order')
|
||
|
|
.toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not allow focusin event to change the tabindex', () => {
|
||
|
|
fixture.componentInstance.disabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(
|
||
|
|
listOption.filter(option => option.nativeElement.getAttribute('tabindex') !== '-1').length,
|
||
|
|
)
|
||
|
|
.withContext('Expected all list options to be excluded from the tab order')
|
||
|
|
.toBe(0);
|
||
|
|
|
||
|
|
listOption[1].triggerEventHandler('focusin', {target: listOption[1].nativeElement});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(
|
||
|
|
listOption.filter(option => option.nativeElement.getAttribute('tabindex') !== '-1').length,
|
||
|
|
)
|
||
|
|
.withContext('Expected all list options to be excluded from the tab order')
|
||
|
|
.toBe(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with checkbox position after', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithCheckboxPositionAfter>;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
MatListModule,
|
||
|
|
SelectionListWithListOptions,
|
||
|
|
SelectionListWithCheckboxPositionAfter,
|
||
|
|
SelectionListWithListDisabled,
|
||
|
|
SelectionListWithOnlyOneOption,
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithCheckboxPositionAfter);
|
||
|
|
fixture.detectChanges();
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should be able to customize checkbox position', () => {
|
||
|
|
expect(fixture.nativeElement.querySelector('.mdc-list-item__end .mdc-checkbox'))
|
||
|
|
.withContext('Expected checkbox to show up after content.')
|
||
|
|
.toBeTruthy();
|
||
|
|
expect(fixture.nativeElement.querySelector('.mdc-list-item__start .mdc-checkbox'))
|
||
|
|
.withContext('Expected no checkbox to show up before content.')
|
||
|
|
.toBeFalsy();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with list item elements', () => {
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SelectionListWithAvatar, SelectionListWithIcon],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
function expectCheckboxAtPosition(
|
||
|
|
listItemElement: HTMLElement,
|
||
|
|
position: MatListOptionTogglePosition,
|
||
|
|
) {
|
||
|
|
const containerSelector =
|
||
|
|
position === 'before' ? '.mdc-list-item__start' : 'mdc-list-item__end';
|
||
|
|
const checkboxPositionClass =
|
||
|
|
position === 'before'
|
||
|
|
? 'mdc-list-item--with-leading-checkbox'
|
||
|
|
: 'mdc-list-item--with-trailing-checkbox';
|
||
|
|
expect(listItemElement.querySelector(`${containerSelector} .mdc-checkbox`))
|
||
|
|
.withContext(`Expected checkbox to be aligned ${position}`)
|
||
|
|
.toBeDefined();
|
||
|
|
expect(listItemElement.classList).toContain(checkboxPositionClass);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Expects an icon to be shown at the given position. Also
|
||
|
|
* ensures no avatar is shown at the specified position.
|
||
|
|
*/
|
||
|
|
function expectIconAt(item: HTMLElement, position: 'before' | 'after') {
|
||
|
|
const icon = item.querySelector('.mat-mdc-list-item-icon')!;
|
||
|
|
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-leading-avatar');
|
||
|
|
expect(item.classList).not.toContain('mat-mdc-list-option-with-trailing-avatar');
|
||
|
|
|
||
|
|
if (position === 'before') {
|
||
|
|
expect(icon.classList).toContain('mdc-list-item__start');
|
||
|
|
expect(item.classList).toContain('mdc-list-item--with-leading-icon');
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-trailing-icon');
|
||
|
|
} else {
|
||
|
|
expect(icon.classList).toContain('mdc-list-item__end');
|
||
|
|
expect(item.classList).toContain('mdc-list-item--with-trailing-icon');
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-leading-icon');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Expects an avatar to be shown at the given position. Also
|
||
|
|
* ensures that no icon is shown at the specified position.
|
||
|
|
*/
|
||
|
|
function expectAvatarAt(item: HTMLElement, position: 'before' | 'after') {
|
||
|
|
const avatar = item.querySelector('.mat-mdc-list-item-avatar')!;
|
||
|
|
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-leading-icon');
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-trailing-icon');
|
||
|
|
|
||
|
|
if (position === 'before') {
|
||
|
|
expect(avatar.classList).toContain('mdc-list-item__start');
|
||
|
|
expect(item.classList).toContain('mdc-list-item--with-leading-avatar');
|
||
|
|
expect(item.classList).not.toContain('mat-mdc-list-option-with-trailing-avatar');
|
||
|
|
} else {
|
||
|
|
expect(avatar.classList).toContain('mdc-list-item__end');
|
||
|
|
expect(item.classList).toContain('mat-mdc-list-option-with-trailing-avatar');
|
||
|
|
expect(item.classList).not.toContain('mdc-list-item--with-leading-avatar');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
it('should add a class to reflect that it has an avatar', () => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithAvatar);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
const listOption = fixture.nativeElement.querySelector('.mat-mdc-list-option');
|
||
|
|
expect(listOption.classList).toContain('mdc-list-item--with-leading-avatar');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should add a class to reflect that it has an icon', () => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithIcon);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
const listOption = fixture.nativeElement.querySelector('.mat-mdc-list-option');
|
||
|
|
expect(listOption.classList).toContain('mdc-list-item--with-leading-icon');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should align icons properly together with checkbox', () => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithIcon);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const listOption = fixture.nativeElement.querySelector(
|
||
|
|
'.mat-mdc-list-option',
|
||
|
|
)! as HTMLElement;
|
||
|
|
|
||
|
|
expectCheckboxAtPosition(listOption, 'after');
|
||
|
|
expectIconAt(listOption, 'before');
|
||
|
|
|
||
|
|
fixture.componentInstance.togglePosition = 'before';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expectCheckboxAtPosition(listOption, 'before');
|
||
|
|
expectIconAt(listOption, 'after');
|
||
|
|
|
||
|
|
fixture.componentInstance.togglePosition = 'after';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expectCheckboxAtPosition(listOption, 'after');
|
||
|
|
expectIconAt(listOption, 'before');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should align avatars properly together with checkbox', () => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithAvatar);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const listOption = fixture.nativeElement.querySelector(
|
||
|
|
'.mat-mdc-list-option',
|
||
|
|
)! as HTMLElement;
|
||
|
|
|
||
|
|
expectCheckboxAtPosition(listOption, 'after');
|
||
|
|
expectAvatarAt(listOption, 'before');
|
||
|
|
|
||
|
|
fixture.componentInstance.togglePosition = 'before';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expectCheckboxAtPosition(listOption, 'before');
|
||
|
|
expectAvatarAt(listOption, 'after');
|
||
|
|
|
||
|
|
fixture.componentInstance.togglePosition = 'after';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expectCheckboxAtPosition(listOption, 'after');
|
||
|
|
expectAvatarAt(listOption, 'before');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with single selection', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithListOptions>;
|
||
|
|
let listOptions: DebugElement[];
|
||
|
|
let selectionList: DebugElement;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, SelectionListWithListOptions],
|
||
|
|
});
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
it('should select one option at a time', () => {
|
||
|
|
const testListItem1 = listOptions[1].injector.get<MatListOption>(MatListOption);
|
||
|
|
const testListItem2 = listOptions[2].injector.get<MatListOption>(MatListOption);
|
||
|
|
const selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem1._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected).toEqual([testListItem1]);
|
||
|
|
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('true');
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem2._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected).toEqual([testListItem2]);
|
||
|
|
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('false');
|
||
|
|
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('true');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not show check boxes', () => {
|
||
|
|
expect(fixture.nativeElement.querySelector('mat-pseudo-checkbox')).toBeFalsy();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not deselect the target option on click', () => {
|
||
|
|
const testListItem1 = listOptions[1].injector.get<MatListOption>(MatListOption);
|
||
|
|
const selectList =
|
||
|
|
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
|
||
|
|
|
||
|
|
expect(selectList.selected.length).toBe(0);
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem1._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected).toEqual([testListItem1]);
|
||
|
|
|
||
|
|
dispatchMouseEvent(testListItem1._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectList.selected).toEqual([testListItem1]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws an exception when toggling single/multiple mode after bootstrap', () => {
|
||
|
|
fixture.componentInstance.multiple = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
expect(() => fixture.detectChanges()).toThrow(
|
||
|
|
new Error('Cannot change `multiple` mode of mat-selection-list after initialization.'),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should do nothing when pressing ctrl + a', () => {
|
||
|
|
const event = createKeyboardEvent('keydown', A, undefined, {control: true});
|
||
|
|
|
||
|
|
expect(listOptions.every(option => !option.componentInstance.selected)).toBe(true);
|
||
|
|
|
||
|
|
dispatchEvent(selectionList.nativeElement, event);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => !option.componentInstance.selected)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it(
|
||
|
|
'should focus, but not toggle, the next item when pressing SHIFT + UP_ARROW in single ' +
|
||
|
|
'selection mode',
|
||
|
|
() => {
|
||
|
|
listOptions[3].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(3);
|
||
|
|
|
||
|
|
expect(listOptions[1].componentInstance.selected).toBe(false);
|
||
|
|
expect(listOptions[2].componentInstance.selected).toBe(false);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[3].nativeElement, 'keydown', UP_ARROW, undefined, {
|
||
|
|
shift: true,
|
||
|
|
});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[1].componentInstance.selected).toBe(false);
|
||
|
|
expect(listOptions[2].componentInstance.selected).toBe(false);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
it(
|
||
|
|
'should focus, but not toggle, the next item when pressing SHIFT + DOWN_ARROW ' +
|
||
|
|
'in single selection mode',
|
||
|
|
() => {
|
||
|
|
listOptions[3].nativeElement.focus();
|
||
|
|
expect(getFocusIndex()).toBe(3);
|
||
|
|
|
||
|
|
expect(listOptions[1].componentInstance.selected).toBe(false);
|
||
|
|
expect(listOptions[2].componentInstance.selected).toBe(false);
|
||
|
|
|
||
|
|
dispatchKeyboardEvent(listOptions[3].nativeElement, 'keydown', DOWN_ARROW, undefined, {
|
||
|
|
shift: true,
|
||
|
|
});
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[1].componentInstance.selected).toBe(false);
|
||
|
|
expect(listOptions[2].componentInstance.selected).toBe(false);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('with single selection', () => {
|
||
|
|
let fixture: ComponentFixture<ListOptionWithTwoWayBinding>;
|
||
|
|
let optionElement: HTMLElement;
|
||
|
|
let option: MatListOption;
|
||
|
|
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatListModule, ListOptionWithTwoWayBinding],
|
||
|
|
});
|
||
|
|
|
||
|
|
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);
|
||
|
|
|
||
|
|
fixture.componentInstance.selected = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(option.selected).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should sync the value from the option to the view', () => {
|
||
|
|
expect(fixture.componentInstance.selected).toBe(false);
|
||
|
|
|
||
|
|
optionElement.click();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.selected).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('MatSelectionList with forms', () => {
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
MatListModule,
|
||
|
|
FormsModule,
|
||
|
|
ReactiveFormsModule,
|
||
|
|
SelectionListWithModel,
|
||
|
|
SelectionListWithFormControl,
|
||
|
|
SelectionListWithPreselectedOption,
|
||
|
|
SelectionListWithPreselectedOptionAndModel,
|
||
|
|
SelectionListWithPreselectedFormControlOnPush,
|
||
|
|
SelectionListWithCustomComparator,
|
||
|
|
],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
describe('and ngModel', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithModel>;
|
||
|
|
let selectionListDebug: DebugElement;
|
||
|
|
let listOptions: MatListOption[];
|
||
|
|
let ngModel: NgModel;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithModel);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
selectionListDebug = fixture.debugElement.query(By.directive(MatSelectionList))!;
|
||
|
|
ngModel = selectionListDebug.injector.get<NgModel>(NgModel);
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should update the model if an option got selected programmatically', fakeAsync(() => {
|
||
|
|
expect(fixture.componentInstance.selectedOptions.length)
|
||
|
|
.withContext('Expected no options to be selected by default')
|
||
|
|
.toBe(0);
|
||
|
|
|
||
|
|
listOptions[0].toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
expect(fixture.componentInstance.selectedOptions.length)
|
||
|
|
.withContext('Expected no options to be selected by default')
|
||
|
|
.toBe(0);
|
||
|
|
|
||
|
|
dispatchMouseEvent(listOptions[0]._hostElement, 'click');
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
expect(fixture.componentInstance.selectedOptions.length)
|
||
|
|
.withContext('Expected no options to be selected by default')
|
||
|
|
.toBe(0);
|
||
|
|
|
||
|
|
fixture.componentInstance.selectedOptions = ['opt3'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
fixture.componentInstance.options = ['first option', 'second option'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
tick();
|
||
|
|
|
||
|
|
const listElements = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.nativeElement);
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
expect(ngModel.touched)
|
||
|
|
.withContext('Expected the selection-list to be untouched by default.')
|
||
|
|
.toBe(false);
|
||
|
|
|
||
|
|
dispatchFakeEvent(selectionListDebug.nativeElement, 'blur');
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
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();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithModel);
|
||
|
|
fixture.componentInstance.selectedOptions = ['opt2'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
ngModel = fixture.debugElement
|
||
|
|
.query(By.directive(MatSelectionList))!
|
||
|
|
.injector.get<NgModel>(NgModel);
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
// Flush the initial tick to ensure that every action from the ControlValueAccessor
|
||
|
|
// happened before the actual test starts.
|
||
|
|
tick();
|
||
|
|
|
||
|
|
expect(ngModel.pristine)
|
||
|
|
.withContext('Expected the selection-list to be pristine by default.')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
listOptions[1].toggle();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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;
|
||
|
|
listOptions[2].selected = true;
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.selectedOptions).toEqual(['opt2', 'opt3']);
|
||
|
|
|
||
|
|
fixture.componentInstance.options.pop();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']);
|
||
|
|
}));
|
||
|
|
|
||
|
|
it('should update the model if an option got selected via the model', fakeAsync(() => {
|
||
|
|
expect(fixture.componentInstance.selectedOptions).toEqual([]);
|
||
|
|
|
||
|
|
selectionListDebug.componentInstance.selectedOptions.select(listOptions[0]);
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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();
|
||
|
|
|
||
|
|
selectionListDebug.componentInstance.selectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1);
|
||
|
|
|
||
|
|
selectionListDebug.componentInstance.selectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not dispatch the model change event if nothing changed using selectAll', () => {
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled();
|
||
|
|
|
||
|
|
selectionListDebug.componentInstance.deselectAll();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to programmatically set an array with duplicate values', fakeAsync(() => {
|
||
|
|
fixture.componentInstance.options = ['one', 'two', 'two', 'two', 'three'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
fixture.componentInstance.selectedOptions = ['one', 'two', 'two'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
fixture.destroy();
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithModel);
|
||
|
|
fixture.componentInstance.multiple = false;
|
||
|
|
fixture.componentInstance.selectedOptions = ['opt3'];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
const options = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.nativeElement);
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled();
|
||
|
|
|
||
|
|
options[0].click();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1);
|
||
|
|
expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']);
|
||
|
|
|
||
|
|
options[1].click();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2);
|
||
|
|
expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']);
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('and formControl', () => {
|
||
|
|
let fixture: ComponentFixture<SelectionListWithFormControl>;
|
||
|
|
let listOptions: MatListOption[];
|
||
|
|
let selectionList: MatSelectionList;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithFormControl);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!.componentInstance;
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to disable options from the control', () => {
|
||
|
|
selectionList.focus();
|
||
|
|
expect(selectionList.disabled)
|
||
|
|
.withContext('Expected the selection list to be enabled.')
|
||
|
|
.toBe(false);
|
||
|
|
expect(listOptions.every(option => !option.disabled))
|
||
|
|
.withContext('Expected every list option to be enabled.')
|
||
|
|
.toBe(true);
|
||
|
|
expect(
|
||
|
|
listOptions.some(
|
||
|
|
option => option._elementRef.nativeElement.getAttribute('tabindex') === '0',
|
||
|
|
),
|
||
|
|
)
|
||
|
|
.withContext('Expected one list item to be in the tab order')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.disable();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(selectionList.disabled)
|
||
|
|
.withContext('Expected the selection list to be disabled.')
|
||
|
|
.toBe(true);
|
||
|
|
expect(listOptions.every(option => option.disabled))
|
||
|
|
.withContext('Expected every list option to be disabled.')
|
||
|
|
.toBe(true);
|
||
|
|
expect(
|
||
|
|
listOptions.every(
|
||
|
|
option => option._elementRef.nativeElement.getAttribute('tabindex') === '-1',
|
||
|
|
),
|
||
|
|
)
|
||
|
|
.withContext('Expected every list option to be removed from the tab order')
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to update the disabled property after form control disabling', () => {
|
||
|
|
expect(listOptions.every(option => !option.disabled))
|
||
|
|
.withContext('Expected every list option to be enabled.')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.disable();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => option.disabled))
|
||
|
|
.withContext('Expected every list option to be disabled.')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
// Previously the selection list has been disabled through FormControl#disable. Now we
|
||
|
|
// want to verify that we can still change the disabled state through updating the disabled
|
||
|
|
// property. Calling FormControl#disable should not lock the disabled property.
|
||
|
|
// See: https://github.com/angular/material2/issues/12107
|
||
|
|
selectionList.disabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => !option.disabled))
|
||
|
|
.withContext('Expected every list option to be enabled.')
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to set the value through the form control', () => {
|
||
|
|
expect(listOptions.every(option => !option.selected))
|
||
|
|
.withContext('Expected every list option to be unselected.')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.setValue(['opt2', 'opt3']);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions[1].selected)
|
||
|
|
.withContext('Expected second option to be selected.')
|
||
|
|
.toBe(true);
|
||
|
|
expect(listOptions[2].selected)
|
||
|
|
.withContext('Expected third option to be selected.')
|
||
|
|
.toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.setValue(null);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(listOptions.every(option => !option.selected))
|
||
|
|
.withContext('Expected every list option to be unselected.')
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should deselect option whose value no longer matches', () => {
|
||
|
|
const option = listOptions[1];
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.setValue(['opt2']);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(option.selected).withContext('Expected option to be selected.').toBe(true);
|
||
|
|
|
||
|
|
option.value = 'something-different';
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(option.selected).withContext('Expected option not to be selected.').toBe(false);
|
||
|
|
expect(fixture.componentInstance.formControl.value).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should mark options as selected when the value is set before they are initialized', () => {
|
||
|
|
fixture.destroy();
|
||
|
|
fixture = TestBed.createComponent(SelectionListWithFormControl);
|
||
|
|
|
||
|
|
fixture.componentInstance.formControl.setValue(['opt2', 'opt3']);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
expect(listOptions[1].selected)
|
||
|
|
.withContext('Expected second option to be selected.')
|
||
|
|
.toBe(true);
|
||
|
|
expect(listOptions[2].selected)
|
||
|
|
.withContext('Expected third option to be selected.')
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not clear the form control when the list is destroyed', fakeAsync(() => {
|
||
|
|
const option = listOptions[1];
|
||
|
|
|
||
|
|
option.selected = true;
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fixture.componentInstance.formControl.value).toEqual(['opt2']);
|
||
|
|
|
||
|
|
fixture.componentInstance.renderList = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
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']);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
fixture.componentInstance.renderExtraOption = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
expect(listOptions.length).toBe(4);
|
||
|
|
expect(listOptions[3].selected).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('preselected values', () => {
|
||
|
|
it('should add preselected options to the model value', fakeAsync(() => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithPreselectedOption);
|
||
|
|
const listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithPreselectedOptionAndModel);
|
||
|
|
const listOptions = fixture.debugElement
|
||
|
|
.queryAll(By.directive(MatListOption))
|
||
|
|
.map(optionDebugEl => optionDebugEl.componentInstance);
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
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(() => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithPreselectedFormControlOnPush);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
const option = fixture.debugElement.queryAll(By.directive(MatListOption))[1];
|
||
|
|
const checkbox = option.nativeElement.querySelector(
|
||
|
|
'.mdc-checkbox__native-control',
|
||
|
|
) as HTMLInputElement;
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
flush();
|
||
|
|
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(() => {
|
||
|
|
const fixture = TestBed.createComponent(SelectionListWithCustomComparator);
|
||
|
|
const testComponent = fixture.componentInstance;
|
||
|
|
|
||
|
|
testComponent.compareWith = jasmine
|
||
|
|
.createSpy('comparator', (o1: any, o2: any) => {
|
||
|
|
return o1 && o2 && o1.id === o2.id;
|
||
|
|
})
|
||
|
|
.and.callThrough();
|
||
|
|
|
||
|
|
testComponent.selectedOptions = [{id: 2, label: 'Two'}];
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
tick();
|
||
|
|
|
||
|
|
expect(testComponent.compareWith).toHaveBeenCalled();
|
||
|
|
expect(testComponent.optionInstances.toArray()[1].selected).toBe(true);
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list
|
||
|
|
id="selection-list-1"
|
||
|
|
(selectionChange)="onSelectionChange($event)"
|
||
|
|
[disableRipple]="listRippleDisabled"
|
||
|
|
[color]="selectionListColor"
|
||
|
|
[multiple]="multiple">
|
||
|
|
<mat-list-option togglePosition="before" disabled="true" value="inbox"
|
||
|
|
[color]="firstOptionColor">
|
||
|
|
Inbox (disabled selection-option)
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option id="testSelect" togglePosition="before" class="test-native-focus"
|
||
|
|
value="starred">
|
||
|
|
Starred
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="before" value="sent-mail">
|
||
|
|
Sent Mail
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="before" value="archive">
|
||
|
|
Archive
|
||
|
|
</mat-list-option>
|
||
|
|
@if (showLastOption) {
|
||
|
|
<mat-list-option togglePosition="before" value="drafts">
|
||
|
|
Drafts
|
||
|
|
</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithListOptions {
|
||
|
|
showLastOption = true;
|
||
|
|
listRippleDisabled = false;
|
||
|
|
multiple = true;
|
||
|
|
selectionListColor: ThemePalette;
|
||
|
|
firstOptionColor: ThemePalette;
|
||
|
|
|
||
|
|
onSelectionChange(_change: MatSelectionListChange) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list id="selection-list-2">
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Inbox (disabled selection-option)
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option id="testSelect" togglePosition="after">
|
||
|
|
Starred
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Sent Mail
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Drafts
|
||
|
|
</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithCheckboxPositionAfter {}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list id="selection-list-3" [disabled]="disabled">
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Inbox (disabled selection-option)
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option id="testSelect" togglePosition="after">
|
||
|
|
Starred
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Sent Mail
|
||
|
|
</mat-list-option>
|
||
|
|
<mat-list-option togglePosition="after">
|
||
|
|
Drafts
|
||
|
|
</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithListDisabled {
|
||
|
|
disabled: boolean = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option [disabled]="disableItem">Item</mat-list-option>
|
||
|
|
</mat-selection-list>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithDisabledOption {
|
||
|
|
disableItem: boolean = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option>Not selected - Item #1</mat-list-option>
|
||
|
|
<mat-list-option [selected]="true">Pre-selected - Item #2</mat-list-option>
|
||
|
|
<mat-list-option [selected]="true">Pre-selected - Item #3</mat-list-option>
|
||
|
|
<mat-list-option>Not selected - Item #4</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithSelectedOption {}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list [multiple]="false">
|
||
|
|
<mat-list-option>Not selected - Item #1</mat-list-option>
|
||
|
|
<mat-list-option [selected]="true">Pre-selected - Item #2</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SingleSelectionListWithSelectedOption {}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option [selected]="true" [value]="itemValue">Item</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithSelectedOptionAndValue {
|
||
|
|
itemValue = 'item1';
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list id="selection-list-4">
|
||
|
|
<mat-list-option togglePosition="after" class="test-focus" id="123">
|
||
|
|
Inbox
|
||
|
|
</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithOnlyOneOption {}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list
|
||
|
|
[(ngModel)]="selectedOptions"
|
||
|
|
(ngModelChange)="modelChangeSpy()"
|
||
|
|
[multiple]="multiple">
|
||
|
|
@for (option of options; track option) {
|
||
|
|
<mat-list-option [value]="option">{{option}}</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithModel {
|
||
|
|
modelChangeSpy = jasmine.createSpy('model change spy');
|
||
|
|
selectedOptions: string[] = [];
|
||
|
|
multiple = true;
|
||
|
|
options = ['opt1', 'opt2', 'opt3'];
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
@if (renderList) {
|
||
|
|
<mat-selection-list [formControl]="formControl">
|
||
|
|
<mat-list-option value="opt1">Option 1</mat-list-option>
|
||
|
|
<mat-list-option value="opt2">Option 2</mat-list-option>
|
||
|
|
<mat-list-option value="opt3">Option 3</mat-list-option>
|
||
|
|
@if (renderExtraOption) {
|
||
|
|
<mat-list-option value="opt4">Option 4</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>
|
||
|
|
}
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithFormControl {
|
||
|
|
formControl = new FormControl([] as string[]);
|
||
|
|
renderList = true;
|
||
|
|
renderExtraOption = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list [(ngModel)]="selectedOptions">
|
||
|
|
<mat-list-option value="opt1">Option 1</mat-list-option>
|
||
|
|
<mat-list-option value="opt2" selected>Option 2</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithPreselectedOption {
|
||
|
|
selectedOptions: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list [(ngModel)]="selectedOptions">
|
||
|
|
<mat-list-option value="opt1">Option 1</mat-list-option>
|
||
|
|
<mat-list-option value="opt2" selected>Option 2</mat-list-option>
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithPreselectedOptionAndModel {
|
||
|
|
selectedOptions = ['opt1'];
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||
|
|
template: `
|
||
|
|
<mat-selection-list [formControl]="formControl">
|
||
|
|
@for (opt of opts; track opt) {
|
||
|
|
<mat-list-option [value]="opt">{{opt}}</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithPreselectedFormControlOnPush {
|
||
|
|
opts = ['opt1', 'opt2', 'opt3'];
|
||
|
|
formControl = new FormControl(['opt2']);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list [(ngModel)]="selectedOptions" [compareWith]="compareWith">
|
||
|
|
@for (option of options; track option) {
|
||
|
|
<mat-list-option [value]="option">{{option.label}}</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule, FormsModule, ReactiveFormsModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithCustomComparator {
|
||
|
|
@ViewChildren(MatListOption) optionInstances: QueryList<MatListOption>;
|
||
|
|
selectedOptions: {id: number; label: string}[] = [];
|
||
|
|
compareWith?: (o1: any, o2: any) => boolean;
|
||
|
|
options = [
|
||
|
|
{id: 1, label: 'One'},
|
||
|
|
{id: 2, label: 'Two'},
|
||
|
|
{id: 3, label: 'Three'},
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option [togglePosition]="togglePosition">
|
||
|
|
<div matListItemAvatar>I</div>
|
||
|
|
Inbox
|
||
|
|
</mat-list-option>
|
||
|
|
</mat-selection-list>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithAvatar {
|
||
|
|
togglePosition: MatListOptionTogglePosition | undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option [togglePosition]="togglePosition">
|
||
|
|
<div matListItemIcon>I</div>
|
||
|
|
Inbox
|
||
|
|
</mat-list-option>
|
||
|
|
</mat-selection-list>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithIcon {
|
||
|
|
togglePosition: MatListOptionTogglePosition | undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
// Note the blank `@if` which we need in order to hit the bug that we're testing.
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
@if (true) {
|
||
|
|
<mat-list-option [value]="1">One</mat-list-option>
|
||
|
|
<mat-list-option [value]="2">Two</mat-list-option>
|
||
|
|
}
|
||
|
|
</mat-selection-list>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class SelectionListWithIndirectChildOptions {
|
||
|
|
@ViewChildren(MatListOption) optionInstances: QueryList<MatListOption>;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-selection-list>
|
||
|
|
<mat-list-option [(selected)]="selected">Item</mat-list-option>
|
||
|
|
</mat-selection-list>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatListModule],
|
||
|
|
})
|
||
|
|
class ListOptionWithTwoWayBinding {
|
||
|
|
selected = false;
|
||
|
|
}
|