diff --git a/src/lib/checkbox/checkbox.spec.ts b/src/lib/checkbox/checkbox.spec.ts index ce900d1c9900..58bc06cab807 100644 --- a/src/lib/checkbox/checkbox.spec.ts +++ b/src/lib/checkbox/checkbox.spec.ts @@ -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'; @@ -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, @@ -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 = fixture.componentInstance.form.controls['checker']; + inputElement = 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. */ @@ -539,6 +579,24 @@ class CheckboxWithFormDirectives { isGood: boolean = false; } +/** Simple component for testing an MdCheckbox with ReactiveForms. */ +@Component({ + template: ` +
+ Check me +
+ `, +}) +class CheckboxWithReactiveFormsDirectives { + form: FormGroup; + + constructor(builder: FormBuilder) { + this.form = builder.group({ + checker: [{value: '', disabled: false}] + }); + } +} + /** Simple test component with multiple checkboxes. */ @Component(({ template: ` diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index 39959ada95af..3d64ff3b22af 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -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; diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts index 2c3146293d00..61130c962798 100644 --- a/src/lib/input/input.spec.ts +++ b/src/lib/input/input.spec.ts @@ -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'; @@ -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, @@ -57,6 +64,7 @@ describe('MdInput', function () { MdInputTextTestController, MdInputPasswordTestController, MdInputNumberTestController, + MdInputReactiveFormsTestController, ], }); @@ -608,6 +616,39 @@ describe('MdInput', function () { expect(inputElement.name).toBe('some-name'); }); + + describe('with reactive forms', () => { + let fixture: ComponentFixture; + 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 = 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(() => { + // 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'); + }); + }); + })); + }); }); @Component({template: ``}) @@ -780,3 +821,18 @@ class MdInputPasswordTestController { class MdInputNumberTestController { placeholder: string = ''; } + +@Component({template: ` +
+ +
+`}) +class MdInputReactiveFormsTestController { + form: FormGroup; + + constructor(builder: FormBuilder) { + this.form = builder.group({ + text: [{value: '', disabled: false}] + }); + } +} diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index e90a76d64d00..858dd20aeb7f 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -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 diff --git a/src/lib/radio/radio.spec.ts b/src/lib/radio/radio.spec.ts index af7303fa1939..2dafd200d772 100644 --- a/src/lib/radio/radio.spec.ts +++ b/src/lib/radio/radio.spec.ts @@ -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'; @@ -9,11 +16,12 @@ describe('MdRadio', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [MdRadioModule.forRoot(), FormsModule], + imports: [MdRadioModule.forRoot(), FormsModule, ReactiveFormsModule], declarations: [ RadiosInsideRadioGroup, RadioGroupWithNgModel, StandaloneRadioButtons, + RadioGroupWithReactiveForms, ], }); @@ -424,6 +432,40 @@ describe('MdRadio', () => { expect(fruitRadioNativeInputs[0].getAttribute('aria-labelledby')).toBe('uvw'); }); }); + + describe('with reactive forms', () => { + let fixture: ComponentFixture; + let formControl: FormControl; + let radioInputs: Array = []; + let nativeRadioInputs: Array = []; + + 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( element.nativeElement.querySelector('input')); + } + + 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`)); + }); + }); }); @@ -484,6 +526,33 @@ class RadioGroupWithNgModel { lastEvent: MdRadioChange; } +@Component({ + template: ` +
+ + + {{option.label}} + + +
+ ` +}) +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. /** diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index 1f9db8306946..8c1585c51d0a 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -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; + } }