From 8af16e8926b1e878bced8afa8eb54884549366bd Mon Sep 17 00:00:00 2001 From: Robert Messerle Date: Tue, 17 May 2016 18:33:32 -0700 Subject: [PATCH] fix(radio): unsetting the model will deselect all radio buttons (#441) closes #327 --- src/components/radio/radio.spec.ts | 98 +++++++++++++++++++----------- src/components/radio/radio.ts | 23 ++++--- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/components/radio/radio.spec.ts b/src/components/radio/radio.spec.ts index ca408650f200..19bdb2af78b5 100644 --- a/src/components/radio/radio.spec.ts +++ b/src/components/radio/radio.spec.ts @@ -22,8 +22,8 @@ export function main() { builder = tcb; })); - it('should have same name as radio group', (done: () => void) => { - builder + it('should have same name as radio group', () => { + return builder .overrideTemplate(TestApp, ` @@ -34,11 +34,11 @@ export function main() { fixture.detectChanges(); expect(button.componentInstance.name).toBe('my_group'); - }).then(done); + }); }); - it('should not allow click selection if disabled', (done: () => void) => { - builder + it('should not allow click selection if disabled', () => { + return builder .overrideTemplate(TestApp, '') .createAsync(TestApp) .then(fixture => { @@ -49,11 +49,11 @@ export function main() { button.nativeElement.click(); expect(button.componentInstance.checked).toBe(false); - }).then(done); + }); }); - it('should be disabled if radio group disabled', (done: () => void) => { - builder + it('should be disabled if radio group disabled', () => { + return builder .overrideTemplate(TestApp, ` @@ -64,11 +64,11 @@ export function main() { fixture.detectChanges(); expect(button.componentInstance.disabled).toBe(true); - }).then(done); + }); }); - it('updates parent group value when selected and value changed', (done: () => void) => { - builder + it('updates parent group value when selected and value changed', () => { + return builder .overrideTemplate(TestApp, ` @@ -86,11 +86,11 @@ export function main() { button.componentInstance.value = '2'; fixture.detectChanges(); expect(radioGroupInstance.value).toBe('2'); - }).then(done); + }); }); - it('should be checked after input change event', (done: () => void) => { - builder + it('should be checked after input change event', () => { + return builder .overrideTemplate(TestApp, '') .createAsync(TestApp) .then(fixture => { @@ -103,11 +103,11 @@ export function main() { let event = createEvent('change'); input.nativeElement.dispatchEvent(event); expect(button.componentInstance.checked).toBe(true); - }).then(done); + }); }); - it('should emit event when checked', (done: () => void) => { - builder + it('should emit event when checked', () => { + return builder .overrideTemplate(TestApp, '') .createAsync(TestApp) .then(fixture => { @@ -124,11 +124,11 @@ export function main() { expect(changeEvent).not.toBe(null); expect(changeEvent.source).toBe(button.componentInstance); }); - }).then(done); + }); }); - it('should be focusable', (done: () => void) => { - builder + it('should be focusable', () => { + return builder .overrideTemplate(TestApp, '') .createAsync(TestApp) .then(fixture => { @@ -147,7 +147,7 @@ export function main() { input.nativeElement.dispatchEvent(event); fixture.detectChanges(); expect(button.nativeElement.classList.contains('md-radio-focused')).toBe(false); - }).then(done); + }); }); }); @@ -183,8 +183,8 @@ export function main() { builder = tcb; })); - it('can select by value', (done: () => void) => { - builder + it('can select by value', () => { + return builder .overrideTemplate(TestApp, ` @@ -203,11 +203,11 @@ export function main() { fixture.detectChanges(); expect(radioGroupInstance.selected).toBe(buttons[1].componentInstance); - }).then(done); + }); }); - it('should select uniquely', (done: () => void) => { - builder + it('should select uniquely', () => { + return builder .overrideTemplate(TestApp, ` @@ -229,11 +229,11 @@ export function main() { radioGroupInstance.selected = buttons[1].componentInstance; fixture.detectChanges(); expect(isSinglySelected(buttons[1], buttons)).toBe(true); - }).then(done); + }); }); - it('should emit event when value changes', (done: () => void) => { - builder + it('should emit event when value changes', () => { + return builder .overrideTemplate(TestApp, ` @@ -258,11 +258,11 @@ export function main() { expect(changeEvent).not.toBe(null); expect(changeEvent.source).toBe(buttons[1].componentInstance); }); - }).then(done); + }); }); - it('should bind value to model without initial value', (done: () => void) => { - builder + it('should bind value to model without initial value', () => { + return builder .overrideTemplate(TestApp, ` @@ -290,11 +290,11 @@ export function main() { expect(isSinglySelected(buttons[1], buttons)).toBe(true); expect(fixture.componentInstance.choice).toBe(1); }); - }).then(done); + }); }); - it('should bind value to model with initial value', (done: () => void) => { - builder + it('should bind value to model with initial value', () => { + return builder .overrideTemplate(TestAppWithInitialValue, ` @@ -321,7 +321,29 @@ export function main() { expect(isSinglySelected(buttons[1], buttons)).toBe(true); expect(fixture.componentInstance.choice).toBe(1); }); - }).then(done); + }); + }); + + it('should deselect all buttons when model is null or undefined', () => { + return builder + .overrideTemplate(TestAppWithInitialValue, ` + + + + + `) + .createAsync(TestAppWithInitialValue) + .then(fixture => { + let buttons = fixture.debugElement.queryAll(By.css('md-radio-button')); + + fixture.componentInstance.choice = 0; + fixture.detectChanges(); + expect(isSinglySelected(buttons[0], buttons)).toBe(true); + + fixture.componentInstance.choice = null; + fixture.detectChanges(); + expect(allDeselected(buttons)).toBe(true); + }); }); }); @@ -336,6 +358,10 @@ function isSinglySelected(button: DebugElement, buttons: DebugElement[]): boolea return component.checked && otherSelectedButtons.length == 0; } +function allDeselected(buttons: DebugElement[]): boolean { + return buttons.every(debugEl => !debugEl.componentInstance.checked); +} + /** Browser-agnostic function for creating an event. */ function createEvent(name: string): Event { let ev: Event; diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index 55f4a6207aaf..72247ed96b72 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -150,12 +150,14 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { }); if (matched.length == 0) { - // Didn't find a button that matches this value, return early without setting. - return; + // When the value of the group is cleared to null, deselect all radio button in the group. + if (this.value == null) { + this.selected = null; + this._radios.forEach(radio => radio.checked = false); + } + } else { + this.selected = matched[0]; } - - // Change the selection immediately. - this.selected = matched[0]; } } @@ -173,10 +175,15 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { } set selected(selected: MdRadioButton) { - this._selected = selected; - this.value = selected.value; + if (selected) { + this._selected = selected; + this.value = selected.value; - selected.checked = true; + selected.checked = true; + } else { + this._selected = null; + this._value = null; + } } /** Implemented as part of ControlValueAccessor. */