Skip to content
Merged
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
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];