Skip to content

Commit

Permalink
feat(selection-list): support for ngModel
Browse files Browse the repository at this point in the history
* Adds support for NgModel to the selection-list.

Fixes angular#6896
  • Loading branch information
devversion committed Oct 1, 2017
1 parent c1712ac commit e918191
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 35 deletions.
9 changes: 7 additions & 2 deletions src/demo-app/list/list-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ <h2>Nav lists</h2>
<div>
<h2>Selection list</h2>

<mat-selection-list #groceries>
<mat-selection-list #groceries [ngModel]="selectedOptions"
(ngModelChange)="onSelectedOptionsChange($event)"
(change)="changeEventCount = changeEventCount + 1">
<h3 mat-subheader>Groceries</h3>

<mat-list-option value="bananas">Bananas</mat-list-option>
Expand All @@ -114,7 +116,10 @@ <h3 mat-subheader>Groceries</h3>
<mat-list-option value="strawberries">Strawberries</mat-list-option>
</mat-selection-list>

<p>Selected: {{groceries.selectedOptions.selected.length}}</p>
<p>Selected: {{selectedOptions | json}}</p>
<p>Change Event Count {{changeEventCount}}</p>
<p>Model Change Event Count {{modelChangeEventCount}}</p>

<p>
<button mat-raised-button (click)="groceries.selectAll()">Select all</button>
<button mat-raised-button (click)="groceries.deselectAll()">Deselect all</button>
Expand Down
9 changes: 9 additions & 0 deletions src/demo-app/list/list-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,13 @@ export class ListDemo {

thirdLine: boolean = false;
infoClicked: boolean = false;

selectedOptions: string[] = ['apples'];
changeEventCount: number = 0;
modelChangeEventCount: number = 0;

onSelectedOptionsChange(values: string[]) {
this.selectedOptions = values;
this.modelChangeEventCount++;
}
}
142 changes: 140 additions & 2 deletions src/lib/list/selection-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import {DOWN_ARROW, SPACE, UP_ARROW} from '@angular/cdk/keycodes';
import {Platform} from '@angular/cdk/platform';
import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
import {Component, DebugElement} from '@angular/core';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {MatListModule, MatListOption, MatSelectionList} from './index';
import {FormsModule, NgModel} from '@angular/forms';


describe('MatSelectionList', () => {
describe('MatSelectionList without forms', () => {
describe('with list option', () => {
let fixture: ComponentFixture<SelectionListWithListOptions>;
let listOptions: DebugElement[];
Expand Down Expand Up @@ -442,6 +443,131 @@ describe('MatSelectionList', () => {
});
});

describe('MatSelectionList with forms', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatListModule, FormsModule],
declarations: [
SelectionListWithModel
]
});

TestBed.compileComponents();
}));

describe('and ngModel', () => {
let fixture: ComponentFixture<SelectionListWithModel>;
let selectionListDebug: DebugElement;
let selectionList: MatSelectionList;
let listOptions: MatListOption[];
let ngModel: NgModel;

beforeEach(() => {
fixture = TestBed.createComponent(SelectionListWithModel);
fixture.detectChanges();

selectionListDebug = fixture.debugElement.query(By.directive(MatSelectionList));
selectionList = selectionListDebug.componentInstance;
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', fakeAsync(() => {
expect(fixture.componentInstance.selectedOptions.length)
.toBe(0, 'Expected no options to be selected by default');

listOptions[0].toggle();
fixture.detectChanges();

tick();

expect(fixture.componentInstance.selectedOptions.length)
.toBe(1, 'Expected first list option to be selected');
}));

it('should update the model if an option got clicked', fakeAsync(() => {
expect(fixture.componentInstance.selectedOptions.length)
.toBe(0, 'Expected no options to be selected by default');

dispatchFakeEvent(listOptions[0]._getHostElement(), 'click');
fixture.detectChanges();

tick();

expect(fixture.componentInstance.selectedOptions.length)
.toBe(1, 'Expected first list option to be selected');
}));

it('should update the options if a model value is set', fakeAsync(() => {
expect(fixture.componentInstance.selectedOptions.length)
.toBe(0, 'Expected no options to be selected by default');

fixture.componentInstance.selectedOptions = ['opt3'];
fixture.detectChanges();

tick();

expect(fixture.componentInstance.selectedOptions.length)
.toBe(1, 'Expected first list option to be selected');
}));

it('should set the selection-list to touched on blur', fakeAsync(() => {
expect(ngModel.touched)
.toBe(false, 'Expected the selection-list to be untouched by default.');

dispatchFakeEvent(selectionListDebug.nativeElement, 'blur');
fixture.detectChanges();

tick();

expect(ngModel.touched).toBe(true, 'Expected the selection-list to be touched after blur');
}));

it('should be able to disable the selection-list using the form control', fakeAsync(() => {
expect(listOptions.every(option => !option.disabled))
.toBe(true, 'Expected every list-option to be enabled');

ngModel.control.disable();
fixture.detectChanges();

tick();

expect(listOptions.every(option => option.disabled))
.toBe(true, 'Expected every list-option to be disabled');
}));


it('should be pristine by default', fakeAsync(() => {
fixture = TestBed.createComponent(SelectionListWithModel);
fixture.componentInstance.selectedOptions = ['opt2'];
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)
.toBe(true, 'Expected the selection-list to be pristine by default.');

listOptions[1].toggle();
fixture.detectChanges();

tick();

expect(ngModel.pristine)
.toBe(false, 'Expected the selection-list to be dirty after state change.');
}));
});

});


@Component({template: `
<mat-selection-list id="selection-list-1">
Expand Down Expand Up @@ -523,3 +649,15 @@ class SelectionListWithSelectedOption {
</mat-selection-list>`})
class SelectionListWithOnlyOneOption {
}

@Component({
template: `
<mat-selection-list [(ngModel)]="selectedOptions">
<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>
</mat-selection-list>`
})
class SelectionListWithModel {
selectedOptions: string[] = [];
}
Loading

0 comments on commit e918191

Please sign in to comment.