Skip to content

refactor(button-toggle): switch to SelectionModel #2773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/lib/button-toggle/button-toggle-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {MdError} from '../core';

/**
* Exception thrown when trying to assign a non-array value
* to a button toggle group in multiple selection mode.
* @docs-private
*/
export class MdButtonToggleGroupNonArrayValueError extends MdError {
constructor() {
super('Cannot assign non-array value to button toggle group in `multiple` mode.');
}
}
4 changes: 2 additions & 2 deletions src/lib/button-toggle/button-toggle.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<label [attr.for]="inputId" class="mat-button-toggle-label">
<label [attr.for]="_inputId" class="mat-button-toggle-label">
<input #input class="mat-button-toggle-input cdk-visually-hidden"
[type]="_type"
[id]="inputId"
[id]="_inputId"
[checked]="checked"
[disabled]="disabled"
[name]="name"
Expand Down
114 changes: 50 additions & 64 deletions src/lib/button-toggle/button-toggle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import {
async,
fakeAsync,
tick,
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NgControl, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {
MdButtonToggleGroup,
MdButtonToggle,
MdButtonToggleGroupMultiple,
MdButtonToggleChange, MdButtonToggleModule,
} from './button-toggle';
MdButtonToggleChange,
MdButtonToggleModule,
MdButtonToggleGroupNonArrayValueError,
} from './index';


describe('MdButtonToggle', () => {
Expand Down Expand Up @@ -136,7 +131,7 @@ describe('MdButtonToggle', () => {
expect(groupNativeElement.classList).toContain('mat-button-toggle-vertical');
});

it('should emit a change event from button toggles', fakeAsync(() => {
it('should emit a change event from button toggles', () => {
expect(buttonToggleInstances[0].checked).toBe(false);

let changeSpy = jasmine.createSpy('button-toggle change listener');
Expand All @@ -145,34 +140,43 @@ describe('MdButtonToggle', () => {

buttonToggleLabelElements[0].click();
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();

buttonToggleLabelElements[0].click();
fixture.detectChanges();
tick();

// The default browser behavior is to not emit a change event, when the value was set
// to false. That's because the current input type is set to `radio`
expect(changeSpy).toHaveBeenCalledTimes(1);
}));
});

it('should emit a change event from the button toggle group', fakeAsync(() => {
it('should emit a change event from the button toggle group', () => {
expect(groupInstance.value).toBeFalsy();

let changeSpy = jasmine.createSpy('button-toggle-group change listener');
groupInstance.change.subscribe(changeSpy);

groupInstance.value = 'test1';
buttonToggleLabelElements[0].click();
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();

groupInstance.value = 'test2';
buttonToggleLabelElements[1].click();
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalledTimes(2);
}));
});

it('should not emit a change event if the action was not generated by the user', () => {
let changeSpy = jasmine.createSpy('button-toggle-group change listener');
groupInstance.change.subscribe(changeSpy);

groupInstance.value = 'test1';
fixture.detectChanges();
expect(changeSpy).not.toHaveBeenCalled();

buttonToggleLabelElements[1].click();
fixture.detectChanges();
expect(changeSpy).toHaveBeenCalled();
});

it('should update the group and button toggles when updating the group value', () => {
expect(groupInstance.value).toBeFalsy();
Expand Down Expand Up @@ -255,17 +259,16 @@ describe('MdButtonToggle', () => {
for (let buttonToggle of buttonToggleInstances) {
expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value);
}
expect(groupInstance.selected.value).toBe(groupInstance.value);
expect((groupInstance.selected as MdButtonToggle).value).toBe(groupInstance.value);
});

it('should have the correct ngControl state initially and after interaction', fakeAsync(() => {
it('should have the correct ngControl state initially and after interaction', () => {
expect(groupNgControl.valid).toBe(true);
expect(groupNgControl.pristine).toBe(true);
expect(groupNgControl.touched).toBe(false);

buttonToggleInstances[1].checked = true;
fixture.detectChanges();
tick();

expect(groupNgControl.valid).toBe(true);
expect(groupNgControl.pristine).toBe(false);
Expand All @@ -274,63 +277,43 @@ describe('MdButtonToggle', () => {
let nativeRadioLabel = buttonToggleDebugElements[2].query(By.css('label')).nativeElement;
nativeRadioLabel.click();
fixture.detectChanges();
tick();

expect(groupNgControl.valid).toBe(true);
expect(groupNgControl.pristine).toBe(false);
expect(groupNgControl.touched).toBe(true);
}));
});

it('should update the ngModel value when selecting a button toggle', fakeAsync(() => {
it('should update the ngModel value when selecting a button toggle', () => {
buttonToggleInstances[1].checked = true;
fixture.detectChanges();

tick();

expect(testComponent.modelValue).toBe('green');
}));
});
});

describe('button toggle group with ngModel and change event', () => {
let fixture: ComponentFixture<ButtonToggleGroupWithNgModel>;
let groupDebugElement: DebugElement;
let groupNativeElement: HTMLElement;
let buttonToggleDebugElements: DebugElement[];
let buttonToggleNativeElements: HTMLElement[];
let groupInstance: MdButtonToggleGroup;
let buttonToggleInstances: MdButtonToggle[];
let labels: DebugElement[];
let testComponent: ButtonToggleGroupWithNgModel;
let groupNgControl: NgControl;

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

testComponent = fixture.debugElement.componentInstance;

groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup));
groupNativeElement = groupDebugElement.nativeElement;
groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup);
groupNgControl = groupDebugElement.injector.get(NgControl);

buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle));
buttonToggleNativeElements =
buttonToggleDebugElements.map(debugEl => debugEl.nativeElement);
buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance);

fixture.detectChanges();
labels = fixture.debugElement.queryAll(By.css('label'));
}));

it('should update the model before firing change event', fakeAsync(() => {
it('should update the model before firing change event', () => {
expect(testComponent.modelValue).toBeUndefined();
expect(testComponent.lastEvent).toBeUndefined();

groupInstance.value = 'red';
labels[0].nativeElement.click();
fixture.detectChanges();

tick();
expect(testComponent.modelValue).toBe('red');
expect(testComponent.lastEvent.value).toBe('red');
}));
});

});

Expand All @@ -341,13 +324,14 @@ describe('MdButtonToggle', () => {
let testComponent = fixture.debugElement.componentInstance;
let groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup));
let groupInstance: MdButtonToggleGroup = groupDebugElement.injector.get(MdButtonToggleGroup);
let labels = fixture.debugElement.queryAll(By.css('label'));

fixture.detectChanges();

expect(groupInstance.value).toBe('red');
expect(testComponent.lastEvent).toBeFalsy();

groupInstance.value = 'green';
labels[1].nativeElement.click();
fixture.detectChanges();

expect(groupInstance.value).toBe('green');
Expand All @@ -363,7 +347,7 @@ describe('MdButtonToggle', () => {
let buttonToggleDebugElements: DebugElement[];
let buttonToggleNativeElements: HTMLElement[];
let buttonToggleLabelElements: HTMLLabelElement[];
let groupInstance: MdButtonToggleGroupMultiple;
let groupInstance: MdButtonToggleGroup;
let buttonToggleInstances: MdButtonToggle[];
let testComponent: ButtonTogglesInsideButtonToggleGroupMultiple;

Expand All @@ -373,9 +357,9 @@ describe('MdButtonToggle', () => {

testComponent = fixture.debugElement.componentInstance;

groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroupMultiple));
groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup));
groupNativeElement = groupDebugElement.nativeElement;
groupInstance = groupDebugElement.injector.get(MdButtonToggleGroupMultiple);
groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup);

buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle));
buttonToggleNativeElements = buttonToggleDebugElements
Expand All @@ -393,6 +377,12 @@ describe('MdButtonToggle', () => {
expect(buttonToggleInstances[0].checked).toBe(false);
});

it('should throw an exception when trying to assign a non-array value', () => {
expect(() => {
groupInstance.value = 'green';
}).toThrowError(MdButtonToggleGroupNonArrayValueError);
});

it('should check a button toggle when clicked', () => {
expect(buttonToggleInstances.every(buttonToggle => !buttonToggle.checked)).toBe(true);

Expand Down Expand Up @@ -441,7 +431,7 @@ describe('MdButtonToggle', () => {
expect(buttonToggleInstances[0].checked).toBe(false);
});

it('should emit a change event for state changes', fakeAsync(() => {
it('should emit a change event for state changes', () => {

expect(buttonToggleInstances[0].checked).toBe(false);

Expand All @@ -450,18 +440,16 @@ describe('MdButtonToggle', () => {

buttonToggleLabelElements[0].click();
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();

buttonToggleLabelElements[0].click();
fixture.detectChanges();
tick();

// The default browser behavior is to emit an event, when the value was set
// to false. That's because the current input type is set to `checkbox` when
// using the multiple mode.
expect(changeSpy).toHaveBeenCalledTimes(2);
}));
});

});

Expand Down Expand Up @@ -544,7 +532,7 @@ describe('MdButtonToggle', () => {
expect(buttonToggleInstance.checked).toBe(false);
});

it('should emit a change event for state changes', fakeAsync(() => {
it('should emit a change event for state changes', () => {

expect(buttonToggleInstance.checked).toBe(false);

Expand All @@ -553,17 +541,15 @@ describe('MdButtonToggle', () => {

buttonToggleLabelElement.click();
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();

buttonToggleLabelElement.click();
fixture.detectChanges();
tick();

// The default browser behavior is to emit an event, when the value was set
// to false. That's because the current input type is set to `checkbox`.
expect(changeSpy).toHaveBeenCalledTimes(2);
}));
});

it('should focus on underlying input element when focus() is called', () => {
let nativeRadioInput = buttonToggleDebugElement.query(By.css('input')).nativeElement;
Expand Down
Loading