From 680f7e02990ffcdc081e864c7e7bfe598a03250d Mon Sep 17 00:00:00 2001 From: erictsangx Date: Fri, 20 Nov 2015 17:19:04 +0800 Subject: [PATCH] fix(core/forms): input[type=text] .valueChanges fires unexpectedly Closes #4768, #5284 Closes #5401 --- .../directives/default_value_accessor.ts | 8 +- .../test/common/forms/integration_spec.ts | 84 ++++++++++++------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/modules/angular2/src/common/forms/directives/default_value_accessor.ts b/modules/angular2/src/common/forms/directives/default_value_accessor.ts index 81ecc6c640..de9a5c1ba4 100644 --- a/modules/angular2/src/common/forms/directives/default_value_accessor.ts +++ b/modules/angular2/src/common/forms/directives/default_value_accessor.ts @@ -20,11 +20,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider( // TODO: vsavkin replace the above selector with the one below it once // https://github.com/angular/angular/issues/3011 is implemented // selector: '[ng-control],[ng-model],[ng-form-control]', - host: { - '(change)': 'onChange($event.target.value)', - '(input)': 'onChange($event.target.value)', - '(blur)': 'onTouched()' - }, + host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'}, bindings: [DEFAULT_VALUE_ACCESSOR] }) export class DefaultValueAccessor implements ControlValueAccessor { @@ -40,4 +36,4 @@ export class DefaultValueAccessor implements ControlValueAccessor { registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } -} \ No newline at end of file +} diff --git a/modules/angular2/test/common/forms/integration_spec.ts b/modules/angular2/test/common/forms/integration_spec.ts index 6ce963cdf0..a21d2e13ff 100644 --- a/modules/angular2/test/common/forms/integration_spec.ts +++ b/modules/angular2/test/common/forms/integration_spec.ts @@ -74,13 +74,37 @@ export function main() { var input = fixture.debugElement.query(By.css("input")); input.nativeElement.value = "updatedValue"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(form.value).toEqual({"login": "updatedValue"}); async.done(); }); })); + it("should ignore the change event for ", + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + var form = new ControlGroup({"login": new Control("oldValue")}); + + var t = `
+ +
`; + + tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + var input = fixture.debugElement.query(By.css("input")); + + input.nativeElement.value = "updatedValue"; + + ObservableWrapper.subscribe(form.valueChanges, + (value) => { throw 'Should not happen'; }); + dispatchEvent(input.nativeElement, "change"); + + async.done(); + }); + })); + + it("should emit ng-submit event on submit", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
@@ -120,7 +144,7 @@ export function main() { expect(input.nativeElement.value).toEqual("loginValue"); input.nativeElement.value = "updatedValue"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(control.value).toEqual("updatedValue"); async.done(); @@ -298,7 +322,7 @@ export function main() { expect(input.nativeElement.value).toEqual("10"); input.nativeElement.value = "20"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(fixture.debugElement.componentInstance.form.value).toEqual({"num": 20}); async.done(); @@ -370,7 +394,7 @@ export function main() { expect(input.nativeElement.value).toEqual("!aa!"); input.nativeElement.value = "!bb!"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"}); async.done(); @@ -391,7 +415,7 @@ export function main() { expect(input.componentInstance.value).toEqual("!aa!"); input.componentInstance.value = "!bb!"; - ObservableWrapper.subscribe(input.componentInstance.onChange, (value) => { + ObservableWrapper.subscribe(input.componentInstance.onInput, (value) => { expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"}); async.done(); }); @@ -424,9 +448,9 @@ export function main() { required.nativeElement.value = ""; minLength.nativeElement.value = "1"; maxLength.nativeElement.value = "1234"; - dispatchEvent(required.nativeElement, "change"); - dispatchEvent(minLength.nativeElement, "change"); - dispatchEvent(maxLength.nativeElement, "change"); + dispatchEvent(required.nativeElement, "input"); + dispatchEvent(minLength.nativeElement, "input"); + dispatchEvent(maxLength.nativeElement, "input"); expect(form.hasError("required", ["login"])).toEqual(true); expect(form.hasError("minlength", ["min"])).toEqual(true); @@ -437,9 +461,9 @@ export function main() { required.nativeElement.value = "1"; minLength.nativeElement.value = "123"; maxLength.nativeElement.value = "123"; - dispatchEvent(required.nativeElement, "change"); - dispatchEvent(minLength.nativeElement, "change"); - dispatchEvent(maxLength.nativeElement, "change"); + dispatchEvent(required.nativeElement, "input"); + dispatchEvent(minLength.nativeElement, "input"); + dispatchEvent(maxLength.nativeElement, "input"); expect(form.valid).toEqual(true); @@ -470,7 +494,7 @@ export function main() { var input = rootTC.debugElement.query(By.css("input")); input.nativeElement.value = "expected"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); tick(100); expect(form.valid).toEqual(true); @@ -492,7 +516,7 @@ export function main() { var input = fixture.debugElement.query(By.css("input")); input.nativeElement.value = ""; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(form.valid).toEqual(false); async.done(); @@ -521,7 +545,7 @@ export function main() { var input = fixture.debugElement.query(By.css("input")); input.nativeElement.value = "wrong value"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(form.pending).toEqual(true); tick(); @@ -529,7 +553,7 @@ export function main() { expect(form.hasError("uniqLogin", ["login"])).toEqual(true); input.nativeElement.value = "expected"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); tick(); expect(form.valid).toEqual(true); @@ -575,7 +599,7 @@ export function main() { var input = fixture.debugElement.query(By.css("input")); input.nativeElement.value = "updatedValue"; - dispatchEvent(input.nativeElement, "change"); + dispatchEvent(input.nativeElement, "input"); expect(form.value).toEqual({"nested": {"login": "updatedValue"}}); async.done(); @@ -604,7 +628,7 @@ export function main() { expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); @@ -629,7 +653,7 @@ export function main() { expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); @@ -763,7 +787,7 @@ export function main() { expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); @@ -785,7 +809,7 @@ export function main() { expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); @@ -813,7 +837,7 @@ export function main() { expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); fixture.detectChanges(); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); @@ -840,7 +864,7 @@ export function main() { expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); fixture.detectChanges(); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); @@ -865,7 +889,7 @@ export function main() { expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); fixture.detectChanges(); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); @@ -898,7 +922,7 @@ export function main() { var input = fixture.debugElement.query(By.css("input")).nativeElement; input.value = "aa"; input.selectionStart = 1; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); fixture.detectChanges(); @@ -921,7 +945,7 @@ export function main() { var input = fixture.debugElement.query(By.css("input")).nativeElement; input.value = "aa"; input.selectionStart = 1; - dispatchEvent(input, "change"); + dispatchEvent(input, "input"); tick(); fixture.detectChanges(); @@ -958,7 +982,7 @@ export function main() { @Directive({ selector: '[wrapped-value]', - host: {'(change)': 'handleOnChange($event.target.value)', '[value]': 'value'} + host: {'(input)': 'handleOnInput($event.target.value)', '[value]': 'value'} }) class WrappedValue implements ControlValueAccessor { value; @@ -971,24 +995,24 @@ class WrappedValue implements ControlValueAccessor { registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) {} - handleOnChange(value) { this.onChange(value.substring(1, value.length - 1)); } + handleOnInput(value) { this.onChange(value.substring(1, value.length - 1)); } } @Component({selector: "my-input", template: ''}) class MyInput implements ControlValueAccessor { - @Output('change') onChange: EventEmitter = new EventEmitter(); + @Output('input') onInput: EventEmitter = new EventEmitter(); value: string; constructor(cd: NgControl) { cd.valueAccessor = this; } writeValue(value) { this.value = `!${value}!`; } - registerOnChange(fn) { ObservableWrapper.subscribe(this.onChange, fn); } + registerOnChange(fn) { ObservableWrapper.subscribe(this.onInput, fn); } registerOnTouched(fn) {} dispatchChangeEvent() { - ObservableWrapper.callEmit(this.onChange, this.value.substring(1, this.value.length - 1)); + ObservableWrapper.callEmit(this.onInput, this.value.substring(1, this.value.length - 1)); } }