diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts index 8ff20613a8c..ee71934b54c 100644 --- a/src/lib/list/selection-list.spec.ts +++ b/src/lib/list/selection-list.spec.ts @@ -4,7 +4,7 @@ import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing'; import {Component, DebugElement} from '@angular/core'; import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {MatListModule, MatListOption, MatSelectionList} from './index'; +import {MatListModule, MatListOption, MatSelectionList, MatListOptionChange} from './index'; describe('MatSelectionList', () => { @@ -483,9 +483,88 @@ describe('MatSelectionList', () => { expect(listItemContent.nativeElement.classList).toContain('mat-list-item-content-reverse'); }); }); -}); + describe('with multiple values', () => { + let fixture: ComponentFixture; + let listOption: DebugElement[]; + let listItemEl: DebugElement; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatListModule], + declarations: [ + SelectionListWithMultipleValues + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithMultipleValues); + listOption = fixture.debugElement.queryAll(By.directive(MatListOption)); + listItemEl = fixture.debugElement.query(By.css('.mat-list-item')); + selectionList = fixture.debugElement.query(By.directive(MatSelectionList)); + fixture.detectChanges(); + })); + + it('should have a value for each item', () => { + expect(listOption[0].componentInstance.value).toBe(1); + expect(listOption[1].componentInstance.value).toBe('a'); + expect(listOption[2].componentInstance.value).toBe(true); + }); + + }); + + describe('with option selected events', () => { + let fixture: ComponentFixture; + let testComponent: SelectionListWithOptionEvents; + let listOption: DebugElement[]; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatListModule], + declarations: [ + SelectionListWithOptionEvents + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(SelectionListWithOptionEvents); + testComponent = fixture.debugElement.componentInstance; + listOption = fixture.debugElement.queryAll(By.directive(MatListOption)); + selectionList = fixture.debugElement.query(By.directive(MatSelectionList)); + fixture.detectChanges(); + })); + + it('should trigger the selected and deselected events when clicked in succession.', () => { + + let selected: boolean = false; + + spyOn(testComponent, 'onOptionSelectionChange') + .and.callFake((event: MatListOptionChange) => { + selected = event.selected; + }); + + listOption[0].nativeElement.click(); + expect(testComponent.onOptionSelectionChange).toHaveBeenCalledTimes(1); + expect(selected).toBe(true); + + listOption[0].nativeElement.click(); + expect(testComponent.onOptionSelectionChange).toHaveBeenCalledTimes(2); + expect(selected).toBe(false); + }); + + }); + +}); + @Component({template: ` @@ -507,17 +586,17 @@ class SelectionListWithListOptions { } @Component({template: ` - - + + Inbox (disabled selection-option) - + Starred - + Sent Mail - + Drafts `}) @@ -525,17 +604,17 @@ class SelectionListWithCheckboxPositionAfter { } @Component({template: ` - - + + Inbox (disabled selection-option) - + Starred - + Sent Mail - + Drafts `}) @@ -559,8 +638,8 @@ class SelectionListWithSelectedOption { } @Component({template: ` - - + + Inbox `}) @@ -579,3 +658,28 @@ class SelectionListWithTabindexBinding { tabIndex: number; disabled: boolean; } + +@Component({template: ` + + + 1 + + + a + + + true + +`}) +class SelectionListWithMultipleValues { +} + +@Component({template: ` + + + Inbox + +`}) +class SelectionListWithOptionEvents { + onOptionSelectionChange: (event?: MatListOptionChange) => void = () => {}; +} diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 4e68a34d5c0..26b8d014673 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -51,15 +51,18 @@ export const _MatSelectionListMixinBase = export class MatListOptionBase {} export const _MatListOptionMixinBase = mixinDisableRipple(MatListOptionBase); -/** Event emitted by a selection-list whenever the state of an option is changed. */ -export interface MatSelectionListOptionEvent { - option: MatListOption; +/** Change event object emitted by MatListOption */ +export class MatListOptionChange { + /** The source MatListOption of the event. */ + source: MatListOption; + /** The new `selected` value of the option. */ + selected: boolean; } /** * Component for list-options of selection-list. Each list-option can automatically * generate a checkbox and can put current item into the selectionModel of selection-list - * if the current item is checked. + * if the current item is selected. */ @Component({ moduleId: module.id, @@ -86,7 +89,6 @@ export interface MatSelectionListOptionEvent { export class MatListOption extends _MatListOptionMixinBase implements AfterContentInit, OnInit, OnDestroy, FocusableOption, CanDisableRipple { private _lineSetter: MatLineSetter; - private _selected: boolean = false; private _disabled: boolean = false; /** Whether the option has focus. */ @@ -97,34 +99,31 @@ export class MatListOption extends _MatListOptionMixinBase /** Whether the label should appear before or after the checkbox. Defaults to 'after' */ @Input() checkboxPosition: 'before' | 'after' = 'after'; + /** Whether the option is disabled. */ + @Input() + get disabled(): boolean { + return (this.selectionList && this.selectionList.disabled) || this._disabled; + } + set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); } + /** Value of the option */ @Input() value: any; - /** Whether the option is disabled. */ - @Input() - get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; } - set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } - /** Whether the option is selected. */ @Input() - get selected() { return this._selected; } + get selected(): boolean { return this.selectionList.selectedOptions.isSelected(this); } set selected(value: boolean) { const isSelected = coerceBooleanProperty(value); - if (isSelected !== this._selected) { - const selectionModel = this.selectionList.selectedOptions; - - this._selected = isSelected; - isSelected ? selectionModel.select(this) : selectionModel.deselect(this); + if (isSelected !== this.selected) { + this.selectionList.selectedOptions.toggle(this); this._changeDetector.markForCheck(); + this.selectionChange.emit(this._createChangeEvent()); } } - /** Emitted when the option is selected. */ - @Output() selectChange = new EventEmitter(); - - /** Emitted when the option is deselected. */ - @Output() deselected = new EventEmitter(); + /** Emitted when the option is selected or deselected. */ + @Output() selectionChange = new EventEmitter(); constructor(private _renderer: Renderer2, private _element: ElementRef, @@ -174,6 +173,16 @@ export class MatListOption extends _MatListOptionMixinBase this.selectionList._setFocusedOption(this); } + /** Creates a selection event object from the specified option. */ + private _createChangeEvent(option: MatListOption = this): MatListOptionChange { + const event = new MatListOptionChange(); + + event.source = option; + event.selected = option.selected; + + return event; + } + /** Retrieves the DOM element of the component host. */ _getHostElement(): HTMLElement { return this._element.nativeElement;