From 9acc0dc091ec39848d54cdb5d098ffc8d387bd5e Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 8 Dec 2016 17:38:59 +0800 Subject: [PATCH] fix(select): clear select if no option matches value Closes #2109 --- src/demo-app/select/select-demo.html | 2 + src/lib/select/select.spec.ts | 65 +++++++++++++++++++++++----- src/lib/select/select.ts | 31 ++++++++++--- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index e48fdc5388bc..aae5985aae20 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -12,6 +12,7 @@

Status: {{ foodControl.status }}

+ @@ -30,6 +31,7 @@ + diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index d93404c6733e..cd973859983d 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -193,19 +193,19 @@ describe('MdSelect', () => { }); it('should focus the selected option if an option is selected', async(() => { - trigger.click(); - fixture.detectChanges(); - - const options = - overlayContainerElement.querySelectorAll('md-option') as NodeListOf; - options[1].click(); - fixture.detectChanges(); + // must wait for initial writeValue promise to finish + fixture.whenStable().then(() => { + fixture.componentInstance.control.setValue('pizza-1'); + fixture.detectChanges(); - trigger.click(); - fixture.detectChanges(); + trigger.click(); + fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(fixture.componentInstance.select._keyManager.focusedItemIndex).toEqual(1); + // must wait for animation to finish + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.componentInstance.select._keyManager.focusedItemIndex).toEqual(1); + }); }); })); @@ -307,6 +307,49 @@ describe('MdSelect', () => { .toEqual('steak-0', `Expected control's value to be set to the new option.`); }); + it('should clear the selection when a nonexistent option value is selected', () => { + fixture.componentInstance.control.setValue('pizza-1'); + fixture.detectChanges(); + + fixture.componentInstance.control.setValue('gibberish'); + fixture.detectChanges(); + + const value = fixture.debugElement.query(By.css('.md-select-value')); + expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`); + expect(trigger.textContent) + .not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`); + + trigger.click(); + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + expect(options[1].classList) + .not.toContain('md-selected', `Expected option with the old value not to be selected.`); + }); + + + it('should clear the selection when the control is reset', () => { + fixture.componentInstance.control.setValue('pizza-1'); + fixture.detectChanges(); + + fixture.componentInstance.control.reset(); + fixture.detectChanges(); + + const value = fixture.debugElement.query(By.css('.md-select-value')); + expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`); + expect(trigger.textContent) + .not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`); + + trigger.click(); + fixture.detectChanges(); + + const options = + overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + expect(options[1].classList) + .not.toContain('md-selected', `Expected option with the old value not to be selected.`); + }); + it('should set the control to touched when the select is touched', () => { expect(fixture.componentInstance.control.touched) .toEqual(false, `Expected the control to start off as untouched.`); diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index e4b2eb936a4c..98add447d51a 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -277,11 +277,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return; } - this.options.forEach((option: MdOption) => { - if (option.value === value) { - option.select(); - } - }); + this._setSelectionByValue(value); } /** @@ -378,6 +374,31 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr scrollContainer.scrollTop = this._scrollTop; } + /** + * Sets the selected option based on a value. If no option can be + * found with the designated value, the select trigger is cleared. + */ + private _setSelectionByValue(value: any): void { + this.options.forEach((option: MdOption) => { + if (option.value === value) { + option.select(); + } + }); + + if (this.selected && this.selected.value !== value) { + this._clearSelection(); + } + } + + /** Clears the select trigger and deselects every option in the list. */ + private _clearSelection(): void { + this._selected = null; + this._updateOptions(); + + // this must happen after change detection so the value doesn't animate + Promise.resolve(null).then(() => this._placeholderState = ''); + } + private _getTriggerRect(): ClientRect { return this.trigger.nativeElement.getBoundingClientRect(); }