Skip to content
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

Fix: Input/Radio should be disabled when disabling the formcontrol #1242

Closed
wants to merge 4 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
60 changes: 59 additions & 1 deletion src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import {
NgControl,
FormsModule,
FormBuilder,
FormGroup,
ReactiveFormsModule, FormControl,
} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
Expand All @@ -21,10 +24,11 @@ describe('MdCheckbox', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdCheckboxModule.forRoot(), FormsModule],
imports: [MdCheckboxModule.forRoot(), FormsModule, ReactiveFormsModule],
declarations: [
SingleCheckbox,
CheckboxWithFormDirectives,
CheckboxWithReactiveFormsDirectives,
MultipleCheckboxes,
CheckboxWithTabIndex,
CheckboxWithAriaLabel,
Expand Down Expand Up @@ -494,6 +498,42 @@ describe('MdCheckbox', () => {
expect(inputElement.getAttribute('name')).toBe('test-name');
});
});

describe('with reactive forms', () => {
let checkboxElement: MdCheckbox;
let formControl: FormControl;
let inputElement: HTMLInputElement;

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

fixture.whenStable().then(() => {
checkboxElement = fixture.debugElement.query(By.directive(MdCheckbox)).componentInstance;
formControl = <FormControl> fixture.componentInstance.form.controls['checker'];
inputElement = <HTMLInputElement>fixture.nativeElement.querySelector('input');
});
}));

it('should be disabled when the form-control is set disabled', async(() => {
expect(inputElement.disabled).toBeFalsy();
expect(checkboxElement.disabled).toBeFalsy();

formControl.disable();

fixture.detectChanges();

fixture.whenStable().then(() => {
// Temporary workaround, see https://github.com/angular/angular/issues/10148
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(checkboxElement.disabled).toBeTruthy('Component should be disabled');
// Somehow fails :-/
expect(inputElement.disabled).toBeTruthy('InputElement should be disabled');
});
});
}));
});
});

/** Simple component for testing a single checkbox. */
Expand Down Expand Up @@ -539,6 +579,24 @@ class CheckboxWithFormDirectives {
isGood: boolean = false;
}

/** Simple component for testing an MdCheckbox with ReactiveForms. */
@Component({
template: `
<form [formGroup]="form">
<md-checkbox formControlName="checker">Check me</md-checkbox>
</form>
`,
})
class CheckboxWithReactiveFormsDirectives {
form: FormGroup;

constructor(builder: FormBuilder) {
this.form = builder.group({
checker: [{value: '', disabled: false}]
});
}
}

/** Simple test component with multiple checkboxes. */
@Component(({
template: `
Expand Down
8 changes: 8 additions & 0 deletions src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ export class MdCheckbox implements ControlValueAccessor {
this.onTouched = fn;
}

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

private _transitionCheckState(newState: TransitionCheckState) {
let oldState = this._currentCheckState;
let renderer = this._renderer;
Expand Down
64 changes: 60 additions & 4 deletions src/lib/input/input.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import {
async,
TestBed,
async,
TestBed,
ComponentFixture,
} from '@angular/core/testing';
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {
FormsModule,
FormGroup,
FormBuilder,
ReactiveFormsModule,
FormControl
} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {MdInput, MdInputModule} from './input';

Expand All @@ -14,7 +21,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 @@ -57,6 +64,7 @@ describe('MdInput', function () {
MdInputTextTestController,
MdInputPasswordTestController,
MdInputNumberTestController,
MdInputReactiveFormsTestController,
],
});

Expand Down Expand Up @@ -608,6 +616,39 @@ describe('MdInput', function () {

expect(inputElement.name).toBe('some-name');
});

describe('with reactive forms', () => {
let fixture: ComponentFixture<MdInputReactiveFormsTestController>;
let inputElement: HTMLInputElement;
let mdInput: MdInput;
let formControl: FormControl;

beforeEach(() => {
fixture = TestBed.createComponent(MdInputReactiveFormsTestController);
inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
fixture.detectChanges();

mdInput = fixture.debugElement.query(By.directive(MdInput)).componentInstance;
formControl = <FormControl> fixture.componentInstance.form.controls['text'];
});

it('should be disabled when the form-control is set disabled', async(() => {
expect(inputElement.disabled).toBeFalsy();
expect(mdInput.disabled).toBeFalsy();

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

fixture.whenStable().then(() => {
Copy link
Member

@jelbourn jelbourn Sep 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kara, is using whenStable supposed to be necessary for changing disabled state?
(here and elsewhere)

// Temporary workaround, see https://github.com/angular/angular/issues/10148
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(inputElement.disabled).toBeTruthy('InputElement should be disabled');
expect(mdInput.disabled).toBeTruthy('Component should be disabled');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I had no idea you could pass a message like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It helps alot when looking for errors :)

});
});
}));
});
});

@Component({template: `<md-input id="test-id"></md-input>`})
Expand Down Expand Up @@ -780,3 +821,18 @@ class MdInputPasswordTestController {
class MdInputNumberTestController {
placeholder: string = '';
}

@Component({template: `
<form [formGroup]="form">
<md-input type="text" formControlName="text"></md-input>
</form>
`})
class MdInputReactiveFormsTestController {
form: FormGroup;

constructor(builder: FormBuilder) {
this.form = builder.group({
text: [{value: '', disabled: false}]
});
}
}
8 changes: 8 additions & 0 deletions src/lib/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
this._validateConstraints();
}

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

/**
* Convert the value passed in to a value that is expected from the type of the md-input.
* This is normally performed by the *_VALUE_ACCESSOR in forms, but since the type is bound
Expand Down
73 changes: 71 additions & 2 deletions src/lib/radio/radio.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NgControl, FormsModule} from '@angular/forms';
import {
NgControl,
FormsModule,
FormControl,
FormBuilder,
FormGroup,
ReactiveFormsModule
} 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,11 +16,12 @@ describe('MdRadio', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdRadioModule.forRoot(), FormsModule],
imports: [MdRadioModule.forRoot(), FormsModule, ReactiveFormsModule],
declarations: [
RadiosInsideRadioGroup,
RadioGroupWithNgModel,
StandaloneRadioButtons,
RadioGroupWithReactiveForms,
],
});

Expand Down Expand Up @@ -424,6 +432,40 @@ describe('MdRadio', () => {
expect(fruitRadioNativeInputs[0].getAttribute('aria-labelledby')).toBe('uvw');
});
});

describe('with reactive forms', () => {
let fixture: ComponentFixture<RadioGroupWithReactiveForms>;
let formControl: FormControl;
let radioInputs: Array<MdRadioButton> = [];
let nativeRadioInputs: Array<HTMLInputElement> = [];

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

let inputs = fixture.debugElement.queryAll(By.directive(MdRadioButton));

for (let element of inputs) {
radioInputs.push(element.componentInstance);
nativeRadioInputs.push(<HTMLInputElement> element.nativeElement.querySelector('input'));
}

formControl = <FormControl> fixture.componentInstance.form.controls['radio'];
});

it('should be disabled when the form-control is set disabled', () => {
radioInputs.forEach(input => expect(input.disabled).toBeFalsy());
nativeRadioInputs.forEach(input => expect(input.disabled).toBeFalsy());

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

radioInputs.forEach(input =>
expect(input.disabled).toBeTruthy(`${input.value} should be disabled`));
nativeRadioInputs.forEach(input =>
expect(input.disabled).toBeTruthy(`Native ${input.id} should be disabled`));
});
});
});


Expand Down Expand Up @@ -484,6 +526,33 @@ class RadioGroupWithNgModel {
lastEvent: MdRadioChange;
}

@Component({
template: `
<form [formGroup]="form">
<md-radio-group formControlName="radio">
<md-radio-button *ngFor="let option of options" [value]="option.value">
{{option.label}}
</md-radio-button>
</md-radio-group>
</form>
`
})
class RadioGroupWithReactiveForms {
options = [
{label: 'Vanilla', value: 'vanilla'},
{label: 'Chocolate', value: 'chocolate'},
{label: 'Strawberry', value: 'strawberry'},
];

form: FormGroup;

constructor(builder: FormBuilder) {
this.form = builder.group({
radio: [{value: '', disabled: false}]
});
}
}

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

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

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


Expand Down