fix: add event object for slide-toggle and checkbox. (#554)

* update(): add event object for slide-toggle and checkbox.

Closes #552.

* Fix Linters... again :P

* update(): Address comments from @jelborun
This commit is contained in:
Paul Gschwendtner
2016-05-31 22:12:29 +02:00
committed by Jeremy Elbourn
parent e1ff20220f
commit 55cc197cd4
5 changed files with 56 additions and 35 deletions
+8 -11
View File
@@ -11,7 +11,7 @@ import {FORM_DIRECTIVES, NgModel, NgControl} from '@angular/common';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdCheckbox} from './checkbox';
import {MdCheckbox, MdCheckboxChange} from './checkbox';
import {PromiseCompleter} from '@angular2-material/core/async/promise-completer';
@@ -273,26 +273,24 @@ describe('MdCheckbox', () => {
testComponent = fixture.debugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
labelElement = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
spyOn(testComponent, 'handleChange');
});
}));
it('should call the change event on first change after initialization', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.handleChange).not.toHaveBeenCalled();
expect(testComponent.lastEvent).toBeUndefined();
checkboxInstance.checked = true;
fixture.detectChanges();
tick();
expect(testComponent.handleChange).toHaveBeenCalledTimes(1);
expect(testComponent.handleChange).toHaveBeenCalledWith(true);
expect(testComponent.lastEvent.checked).toBe(true);
}));
it('should not emit a DOM event to the change output', async(() => {
expect(testComponent.handleChange).not.toHaveBeenCalled();
fixture.detectChanges();
expect(testComponent.lastEvent).toBeUndefined();
// Trigger the click on the inputElement, because the input will probably
// emit a DOM event to the change output.
@@ -303,8 +301,7 @@ describe('MdCheckbox', () => {
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
// emitted value can be a DOM Event, which is not valid.
// See angular/angular#4059
expect(testComponent.handleChange).toHaveBeenCalledTimes(1);
expect(testComponent.handleChange).toHaveBeenCalledWith(true);
expect(testComponent.lastEvent.checked).toBe(true);
});
}));
@@ -541,8 +538,8 @@ class CheckboxWithNameAttribute {}
/** Simple test component with change event */
@Component({
directives: [MdCheckbox],
template: `<md-checkbox (change)="handleChange($event)"></md-checkbox>`
template: `<md-checkbox (change)="lastEvent = $event"></md-checkbox>`
})
class CheckboxWithChangeEvent {
handleChange(value: boolean): void {}
lastEvent: MdCheckboxChange;
}
+17 -3
View File
@@ -42,6 +42,12 @@ enum TransitionCheckState {
Indeterminate
}
// A simple change event emitted by the MdCheckbox component.
export class MdCheckboxChange {
source: MdCheckbox;
checked: boolean;
}
/**
* A material design checkbox component. Supports all of the functionality of an HTML5 checkbox,
* and exposes a similar API. An MdCheckbox can be either checked, unchecked, indeterminate, or
@@ -105,7 +111,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
@Input() name: string = null;
/** Event emitted when the checkbox's `checked` value changes. */
@Output() change: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() change: EventEmitter<MdCheckboxChange> = new EventEmitter<MdCheckboxChange>();
/** Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. */
onTouched: () => any = () => {};
@@ -144,7 +150,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
// Only fire a change event if this isn't the first time the checked property is ever set.
if (this._isInitialized) {
this.change.emit(this._checked);
this._emitChangeEvent();
}
}
}
@@ -225,6 +231,14 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
}
}
private _emitChangeEvent() {
let event = new MdCheckboxChange();
event.source = this;
event.checked = this.checked;
this.change.emit(event);
}
/**
* Informs the component when the input has focus so that we can style accordingly
* @internal
@@ -258,7 +272,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
onInteractionEvent(event: Event) {
// We always have to stop propagation on the change event.
// Otherwise the change event, from the input element, will bubble up and
// emit its event object to the `change` output.
// emit its event object to the `change` output.
event.stopPropagation();
if (!this.disabled) {
+4 -5
View File
@@ -324,20 +324,19 @@ describe('MdRadio', () => {
radioInstances = radioDebugElements.map(debugEl => debugEl.componentInstance);
fixture.detectChanges();
spyOn(testComponent, 'onChange');
});
}));
it('should update the model before firing change event', fakeAsync(() => {
expect(testComponent.modelValue).toBeUndefined();
expect(testComponent.lastEvent).toBeUndefined();
groupInstance.value = 'chocolate';
fixture.detectChanges();
tick();
expect(testComponent.modelValue).toBe('chocolate');
expect(testComponent.onChange).toHaveBeenCalledWith('chocolate');
expect(testComponent.lastEvent.value).toBe('chocolate');
}));
});
@@ -432,7 +431,7 @@ class StandaloneRadioButtons { }
@Component({
directives: [MD_RADIO_DIRECTIVES, FORM_DIRECTIVES],
template: `
<md-radio-group [(ngModel)]="modelValue" (change)="onChange(modelValue)">
<md-radio-group [(ngModel)]="modelValue" (change)="lastEvent = $event">
<md-radio-button *ngFor="let option of options" [value]="option.value">
{{option.label}}
</md-radio-button>
@@ -446,7 +445,7 @@ class RadioGroupWithNgModel {
{label: 'Chocolate', value: 'chocolate'},
{label: 'Strawberry', value: 'strawberry'},
];
onChange(value: MdRadioChange) {}
lastEvent: MdRadioChange;
}
// TODO(jelbourn): remove eveything below when Angular supports faking events.
@@ -4,13 +4,12 @@ import {
expect,
beforeEach,
inject,
async,
fakeAsync,
async
} from '@angular/core/testing';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {By} from '@angular/platform-browser';
import {Component} from '@angular/core';
import {MdSlideToggle} from './slide-toggle';
import {MdSlideToggle, MdSlideToggleChange} from './slide-toggle';
import {NgControl} from '@angular/common';
describe('MdSlideToggle', () => {
@@ -162,21 +161,21 @@ describe('MdSlideToggle', () => {
expect(slideToggleElement.classList).not.toContain('ng-dirty');
});
it('should emit the new values properly', fakeAsync(() => {
spyOn(testComponent, 'onValueChange');
it('should emit the new values properly', async(() => {
labelElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(testComponent.onValueChange).toHaveBeenCalledTimes(1);
expect(testComponent.onValueChange).toHaveBeenCalledWith(true);
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
// emitted value can be a DOM Event, which is not valid.
// See angular/angular#4059
expect(testComponent.lastEvent.checked).toBe(true);
});
}));
it('should support subscription on the change observable', () => {
slideToggle.change.subscribe(value => {
expect(value).toBe(true);
slideToggle.change.subscribe((event: MdSlideToggleChange) => {
expect(event.checked).toBe(true);
});
slideToggle.toggle();
@@ -269,7 +268,7 @@ function dispatchFocusChangeEvent(eventName: string, element: HTMLElement): void
<md-slide-toggle [(ngModel)]="slideModel" [disabled]="isDisabled" [color]="slideColor"
[id]="slideId" [checked]="slideChecked" [name]="slideName"
[aria-label]="slideLabel" [ariaLabel]="slideLabel"
[ariaLabelledby]="slideLabelledBy" (change)="onValueChange($event)">
[ariaLabelledby]="slideLabelledBy" (change)="lastEvent = $event">
<span>Test Slide Toggle</span>
</md-slide-toggle>
`,
@@ -284,6 +283,5 @@ class SlideToggleTestApp {
slideName: string;
slideLabel: string;
slideLabelledBy: string;
onValueChange(value: boolean): void {};
lastEvent: MdSlideToggleChange;
}
+16 -3
View File
@@ -21,6 +21,12 @@ export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
multi: true
};
// A simple change event emitted by the MdSlideToggle component.
export class MdSlideToggleChange {
source: MdSlideToggle;
checked: boolean;
}
// Increasing integer for generating unique ids for slide-toggle components.
let nextId = 0;
@@ -59,8 +65,8 @@ export class MdSlideToggle implements ControlValueAccessor {
@Input() ariaLabel: string = null;
@Input() ariaLabelledby: string = null;
@Output('change') private _change: EventEmitter<boolean> = new EventEmitter<boolean>();
change: Observable<boolean> = this._change.asObservable();
private _change: EventEmitter<MdSlideToggleChange> = new EventEmitter<MdSlideToggleChange>();
@Output() change: Observable<MdSlideToggleChange> = this._change.asObservable();
// Returns the unique id for the visual hidden input.
getInputId = () => `${this.id || this._uniqueId}-input`;
@@ -144,7 +150,7 @@ export class MdSlideToggle implements ControlValueAccessor {
if (this.checked !== !!value) {
this._checked = value;
this.onChange(this._checked);
this._change.emit(this._checked);
this._emitChangeEvent();
}
}
@@ -173,6 +179,13 @@ export class MdSlideToggle implements ControlValueAccessor {
}
}
private _emitChangeEvent() {
let event = new MdSlideToggleChange();
event.source = this;
event.checked = this.checked;
this._change.emit(event);
}
}
export const MD_SLIDE_TOGGLE_DIRECTIVES = [MdSlideToggle];