Skip to content

fix(checkbox, input, radio, slide-toggle): implement setDisabledState from ControlValueAccessor #1750

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

Merged
merged 1 commit into from
Nov 29, 2016
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
57 changes: 49 additions & 8 deletions src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
import {
NgControl,
FormsModule,
ReactiveFormsModule,
FormControl,
} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
Expand All @@ -21,7 +23,7 @@ describe('MdCheckbox', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdCheckboxModule.forRoot(), FormsModule],
imports: [MdCheckboxModule.forRoot(), FormsModule, ReactiveFormsModule],
declarations: [
SingleCheckbox,
CheckboxWithFormDirectives,
Expand All @@ -31,6 +33,7 @@ describe('MdCheckbox', () => {
CheckboxWithAriaLabelledby,
CheckboxWithNameAttribute,
CheckboxWithChangeEvent,
CheckboxWithFormControl,
],
});

Expand Down Expand Up @@ -561,18 +564,48 @@ describe('MdCheckbox', () => {
expect(inputElement.getAttribute('name')).toBe('test-name');
});
});


describe('with form control', () => {
let checkboxDebugElement: DebugElement;
let checkboxInstance: MdCheckbox;
let testComponent: CheckboxWithFormControl;

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

checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
});

it('should toggle the disabled state', () => {
expect(checkboxInstance.disabled).toBe(false);

testComponent.formControl.disable();
fixture.detectChanges();

expect(checkboxInstance.disabled).toBe(true);

testComponent.formControl.enable();
fixture.detectChanges();

expect(checkboxInstance.disabled).toBe(false);
});
});
});

/** Simple component for testing a single checkbox. */
@Component({
template: `
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
<md-checkbox
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
<md-checkbox
id="simple-check"
[required]="isRequired"
[align]="alignment"
[checked]="isChecked"
[indeterminate]="isIndeterminate"
[checked]="isChecked"
[indeterminate]="isIndeterminate"
[disabled]="isDisabled"
[color]="checkboxColor"
(change)="changeCount = changeCount + 1"
Expand Down Expand Up @@ -623,9 +656,9 @@ class MultipleCheckboxes { }
/** Simple test component with tabIndex */
@Component({
template: `
<md-checkbox
[tabindex]="customTabIndex"
[disabled]="isDisabled"
<md-checkbox
[tabindex]="customTabIndex"
[disabled]="isDisabled"
[disableRipple]="disableRipple">
</md-checkbox>`,
})
Expand Down Expand Up @@ -660,3 +693,11 @@ class CheckboxWithNameAttribute {}
class CheckboxWithChangeEvent {
lastEvent: MdCheckboxChange;
}

/** Test component with reactive forms */
@Component({
template: `<md-checkbox [formControl]="formControl"></md-checkbox>`
})
class CheckboxWithFormControl {
formControl = new FormControl();
}
9 changes: 8 additions & 1 deletion src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class MdCheckbox implements ControlValueAccessor {
/** Whether or not the checkbox should come before or after the label. */
@Input() align: 'start' | 'end' = 'start';

private _disabled: boolean;
private _disabled: boolean = false;

/**
* Whether the checkbox is disabled. When the checkbox is disabled it cannot be interacted with.
Expand Down Expand Up @@ -245,6 +245,13 @@ export class MdCheckbox implements ControlValueAccessor {
this.onTouched = fn;
}

/**
* Implemented as a part of ControlValueAccessor.
*/
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}

private _transitionCheckState(newState: TransitionCheckState) {
let oldState = this._currentCheckState;
let renderer = this._renderer;
Expand Down
31 changes: 29 additions & 2 deletions src/lib/input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
TestBed,
} from '@angular/core/testing';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {MdInput, MdInputModule} from './input';

Expand All @@ -14,7 +14,7 @@ function isInternetExplorer11() {
describe('MdInput', function () {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdInputModule.forRoot(), FormsModule],
imports: [MdInputModule.forRoot(), FormsModule, ReactiveFormsModule],
declarations: [
MdInputNumberTypeConservedTestComponent,
MdInputPlaceholderRequiredTestComponent,
Expand Down Expand Up @@ -58,6 +58,7 @@ describe('MdInput', function () {
MdInputPasswordTestController,
MdInputNumberTestController,
MdTextareaWithBindings,
MdInputWithFormControl,
],
});

Expand Down Expand Up @@ -621,6 +622,27 @@ describe('MdInput', function () {
expect(inputElement.name).toBe('some-name');
});

it('toggles the disabled state when used with a FormControl', () => {
let fixture = TestBed.createComponent(MdInputWithFormControl);

fixture.detectChanges();

let input: MdInput = fixture.debugElement.query(By.directive(MdInput)).componentInstance;
let testComponent: MdInputWithFormControl = fixture.debugElement.componentInstance;

expect(input.disabled).toBe(false);

testComponent.formControl.disable();
fixture.detectChanges();

expect(input.disabled).toBe(true);

testComponent.formControl.enable();
fixture.detectChanges();

expect(input.disabled).toBe(false);
});

describe('md-textarea', () => {
it('supports the rows, cols, and wrap attributes', () => {
let fixture = TestBed.createComponent(MdTextareaWithBindings);
Expand Down Expand Up @@ -807,6 +829,11 @@ class MdInputNumberTestController {
placeholder: string = '';
}

@Component({template: `<md-input [formControl]="formControl"></md-input>`})
class MdInputWithFormControl {
formControl = new FormControl();
}

@Component({template:
`<md-textarea [rows]="rows" [cols]="cols" [wrap]="wrap" placeholder="Snacks"></md-textarea>`})
class MdTextareaWithBindings {
Expand Down
7 changes: 7 additions & 0 deletions src/lib/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
this._onTouchedCallback = fn;
}

/**
* Implemented as a part of ControlValueAccessor.
*/
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}

/** TODO: internal */
ngAfterContentInit() {
this._validateConstraints();
Expand Down
54 changes: 48 additions & 6 deletions src/lib/radio/radio.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {NgControl, FormsModule} from '@angular/forms';
import {NgControl, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdRadioGroup, MdRadioButton, MdRadioChange, MdRadioModule} from './radio';
Expand All @@ -9,10 +9,11 @@ describe('MdRadio', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdRadioModule.forRoot(), FormsModule],
imports: [MdRadioModule.forRoot(), FormsModule, ReactiveFormsModule],
declarations: [
RadiosInsideRadioGroup,
RadioGroupWithNgModel,
RadioGroupWithFormControl,
StandaloneRadioButtons,
],
});
Expand Down Expand Up @@ -152,7 +153,7 @@ describe('MdRadio', () => {
expect(spies[1]).toHaveBeenCalledTimes(1);
});

it(`should not emit a change event from the radio group when change group value
it(`should not emit a change event from the radio group when change group value
programmatically`, () => {
expect(groupInstance.value).toBeFalsy();

Expand Down Expand Up @@ -246,7 +247,7 @@ describe('MdRadio', () => {
}
}));

it(`should update the group's selected radio to null when unchecking that radio
it(`should update the group's selected radio to null when unchecking that radio
programmatically`, () => {
let changeSpy = jasmine.createSpy('radio-group change listener');
groupInstance.change.subscribe(changeSpy);
Expand Down Expand Up @@ -420,6 +421,36 @@ describe('MdRadio', () => {
});
});

describe('group with FormControl', () => {
let fixture: ComponentFixture<RadioGroupWithFormControl>;
let groupDebugElement: DebugElement;
let groupInstance: MdRadioGroup;
let testComponent: RadioGroupWithFormControl;

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

testComponent = fixture.debugElement.componentInstance;
groupDebugElement = fixture.debugElement.query(By.directive(MdRadioGroup));
groupInstance = groupDebugElement.injector.get(MdRadioGroup);
});

it('should toggle the disabled state', () => {
expect(groupInstance.disabled).toBeFalsy();

testComponent.formControl.disable();
fixture.detectChanges();

expect(groupInstance.disabled).toBeTruthy();

testComponent.formControl.enable();
fixture.detectChanges();

expect(groupInstance.disabled).toBeFalsy();
});
});

describe('as standalone', () => {
let fixture: ComponentFixture<StandaloneRadioButtons>;
let radioDebugElements: DebugElement[];
Expand Down Expand Up @@ -548,11 +579,11 @@ class RadiosInsideRadioGroup {
<md-radio-button name="season" value="spring">Spring</md-radio-button>
<md-radio-button name="season" value="summer">Summer</md-radio-button>
<md-radio-button name="season" value="autum">Autumn</md-radio-button>

<md-radio-button name="weather" value="warm">Spring</md-radio-button>
<md-radio-button name="weather" value="hot">Summer</md-radio-button>
<md-radio-button name="weather" value="cool">Autumn</md-radio-button>

<span id="xyz">Baby Banana</span>
<md-radio-button name="fruit" value="banana" aria-label="Banana" aria-labelledby="xyz">
</md-radio-button>
Expand Down Expand Up @@ -581,6 +612,17 @@ class RadioGroupWithNgModel {
lastEvent: MdRadioChange;
}

@Component({
template: `
<md-radio-group [formControl]="formControl">
<md-radio-button value="1">One</md-radio-button>
</md-radio-group>
`
})
class RadioGroupWithFormControl {
formControl = new FormControl();
}

// TODO(jelbourn): remove everything below when Angular supports faking events.

/**
Expand Down
7 changes: 7 additions & 0 deletions src/lib/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor {
registerOnTouched(fn: any) {
this.onTouched = fn;
}

/**
* Implemented as a part of ControlValueAccessor.
*/
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
}


Expand Down
Loading