Skip to content

Commit

Permalink
fix: add event object for slide-toggle and checkbox. (#554)
Browse files Browse the repository at this point in the history
* update(): add event object for slide-toggle and checkbox.

Closes #552.

* Fix Linters... again :P

* update(): Address comments from @jelborun
  • Loading branch information
devversion authored and jelbourn committed May 31, 2016
1 parent e1ff202 commit 55cc197
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 35 deletions.
19 changes: 8 additions & 11 deletions src/components/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';


Expand Down Expand Up @@ -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.
Expand All @@ -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);
});

}));
Expand Down Expand Up @@ -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;
}
20 changes: 17 additions & 3 deletions src/components/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = () => {};
Expand Down Expand Up @@ -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();
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 4 additions & 5 deletions src/components/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}));
});

Expand Down Expand Up @@ -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>
Expand All @@ -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.
Expand Down
24 changes: 11 additions & 13 deletions src/components/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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>
`,
Expand All @@ -284,6 +283,5 @@ class SlideToggleTestApp {
slideName: string;
slideLabel: string;
slideLabelledBy: string;

onValueChange(value: boolean): void {};
lastEvent: MdSlideToggleChange;
}
19 changes: 16 additions & 3 deletions src/components/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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`;
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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];

0 comments on commit 55cc197

Please sign in to comment.