diff --git a/modules/@angular/forms/test/integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts similarity index 64% rename from modules/@angular/forms/test/integration_spec.ts rename to modules/@angular/forms/test/reactive_integration_spec.ts index 1bdeece80f..c63365fc30 100644 --- a/modules/@angular/forms/test/integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -10,7 +10,7 @@ import {NgFor, NgIf} from '@angular/common'; import {Component, Directive, EventEmitter, Input, Output, forwardRef} from '@angular/core'; import {ComponentFixture, TestComponentBuilder, configureModule, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; -import {ControlValueAccessor, FormArray, FormControl, FormGroup, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NgControl, NgForm, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; +import {ControlValueAccessor, FormArray, FormControl, FormGroup, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NgControl, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; @@ -20,7 +20,7 @@ import {ListWrapper} from '../src/facade/collection'; import {PromiseWrapper} from '../src/facade/promise'; export function main() { - describe('integration tests', () => { + describe('reactive forms integration tests', () => { beforeEach(() => { configureModule({modules: [FormsModule, ReactiveFormsModule]}); }); @@ -128,31 +128,6 @@ export function main() { expect(fixture.debugElement.componentInstance.name).toEqual('updated'); }))); - it('should mark NgForm as submitted on submit event', - inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { - const t = `
-
- {{data}} -
`; - - var fixture: ComponentFixture; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { - fixture = root; - }); - tick(); - - fixture.debugElement.componentInstance.data = false; - - tick(); - - var form = fixture.debugElement.query(By.css('form')); - dispatchEvent(form.nativeElement, 'submit'); - - tick(); - expect(fixture.debugElement.componentInstance.data).toEqual(true); - }))); - it('should mark formGroup as submitted on submit event', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { const t = `
@@ -748,7 +723,7 @@ export function main() { }); })); - it('with ngControl', + it('with formControlName', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { @@ -806,205 +781,6 @@ export function main() { var select = fixture.debugElement.query(By.css('select')); expect(select.nativeElement.value).toEqual('NYC'); }))); - - it('with option values that are objects', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp = fixture.debugElement.componentInstance; - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}]; - testComp.selectedCity = testComp.list[1]; - fixture.detectChanges(); - - var select = fixture.debugElement.query(By.css('select')); - var nycOption = fixture.debugElement.queryAll(By.css('option'))[1]; - - tick(); - expect(select.nativeElement.value).toEqual('1: Object'); - expect(nycOption.nativeElement.selected).toBe(true); - - select.nativeElement.value = '2: Object'; - dispatchEvent(select.nativeElement, 'change'); - fixture.detectChanges(); - tick(); - expect(testComp.selectedCity['name']).toEqual('Buffalo'); - }); - }))); - - it('when new options are added (selection through the model)', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp: MyComp8 = fixture.debugElement.componentInstance; - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; - testComp.selectedCity = testComp.list[1]; - fixture.detectChanges(); - - testComp.list.push({'name': 'Buffalo'}); - testComp.selectedCity = testComp.list[2]; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - var buffalo = fixture.debugElement.queryAll(By.css('option'))[2]; - expect(select.nativeElement.value).toEqual('2: Object'); - expect(buffalo.nativeElement.selected).toBe(true); - }); - }))); - - it('when new options are added (selection through the UI)', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp: MyComp8 = fixture.debugElement.componentInstance; - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; - testComp.selectedCity = testComp.list[0]; - fixture.detectChanges(); - - var select = fixture.debugElement.query(By.css('select')); - var ny = fixture.debugElement.queryAll(By.css('option'))[1]; - - select.nativeElement.value = '1: Object'; - dispatchEvent(select.nativeElement, 'change'); - testComp.list.push({'name': 'Buffalo'}); - fixture.detectChanges(); - - expect(select.nativeElement.value).toEqual('1: Object'); - expect(ny.nativeElement.selected).toBe(true); - async.done(); - }); - })); - - - it('when options are removed', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp: MyComp8 = fixture.debugElement.componentInstance; - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; - testComp.selectedCity = testComp.list[1]; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - expect(select.nativeElement.value).toEqual('1: Object'); - - testComp.list.pop(); - fixture.detectChanges(); - tick(); - - expect(select.nativeElement.value).not.toEqual('1: Object'); - }); - }))); - - it('when option values change identity while tracking by index', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp = fixture.debugElement.componentInstance; - - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; - testComp.selectedCity = testComp.list[0]; - fixture.detectChanges(); - - testComp.list[1] = 'Buffalo'; - testComp.selectedCity = testComp.list[1]; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - var buffalo = fixture.debugElement.queryAll(By.css('option'))[1]; - - expect(select.nativeElement.value).toEqual('1: Buffalo'); - expect(buffalo.nativeElement.selected).toBe(true); - }); - }))); - - it('with duplicate option values', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp = fixture.debugElement.componentInstance; - - testComp.list = [{'name': 'NYC'}, {'name': 'SF'}, {'name': 'SF'}]; - testComp.selectedCity = testComp.list[0]; - fixture.detectChanges(); - - testComp.selectedCity = testComp.list[1]; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - var firstSF = fixture.debugElement.queryAll(By.css('option'))[1]; - - expect(select.nativeElement.value).toEqual('1: Object'); - expect(firstSF.nativeElement.selected).toBe(true); - }); - }))); - - it('when option values have same content, but different identities', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - - var testComp = fixture.debugElement.componentInstance; - testComp.list = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}]; - testComp.selectedCity = testComp.list[0]; - fixture.detectChanges(); - - testComp.selectedCity = testComp.list[2]; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - var secondNYC = fixture.debugElement.queryAll(By.css('option'))[2]; - - expect(select.nativeElement.value).toEqual('2: Object'); - expect(secondNYC.nativeElement.selected).toBe(true); - }); - }))); }); it('should support custom value accessors', @@ -1296,366 +1072,6 @@ export function main() { expect(fixture.debugElement.componentInstance.name).toEqual('updatedValue'); }))); - describe('template-driven forms', () => { - it('should add new controls and control groups', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
-
- -
-
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = null; - fixture.detectChanges(); - - var form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.controls['user']).not.toBeDefined(); - - tick(); - - expect(form.controls['user']).toBeDefined(); - expect(form.controls['user'].controls['login']).toBeDefined(); - }))); - - it('should emit ngSubmit event on submit', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'old'; - var form = fixture.debugElement.query(By.css('form')); - - dispatchEvent(form.nativeElement, 'submit'); - tick(); - - expect(fixture.debugElement.componentInstance.name).toEqual('updated'); - }))); - - it('should reset the form to empty when reset button is clicked', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = ` -
- -
- `; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'should be cleared'; - fixture.detectChanges(); - tick(); - - const form = fixture.debugElement.children[0].injector.get(NgForm); - const formEl = fixture.debugElement.query(By.css('form')); - - dispatchEvent(formEl.nativeElement, 'reset'); - fixture.detectChanges(); - tick(); - - expect(fixture.debugElement.componentInstance.name).toBe(null); - expect(form.value.name).toEqual(null); - }))); - - - it('should emit valueChanges and statusChanges on init', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - const form = fixture.debugElement.children[0].injector.get(NgForm); - fixture.debugElement.componentInstance.name = 'aa'; - fixture.detectChanges(); - - expect(form.valid).toEqual(true); - expect(form.value).toEqual({}); - - let formValidity: string; - let formValue: Object; - - ObservableWrapper.subscribe( - form.statusChanges, (status: string) => { formValidity = status; }); - - ObservableWrapper.subscribe( - form.valueChanges, (value: string) => { formValue = value; }); - - tick(); - - expect(formValidity).toEqual('INVALID'); - expect(formValue).toEqual({first: 'aa'}); - }))); - - it('should not create a template-driven form when ngNoForm is used', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.name = null; - fixture.detectChanges(); - - expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); - async.done(); - }); - })); - - it('should remove controls', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
-
- -
-
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'show'; - fixture.detectChanges(); - tick(); - var form = fixture.debugElement.children[0].injector.get(NgForm); - - - expect(form.controls['login']).toBeDefined(); - - fixture.debugElement.componentInstance.name = 'hide'; - fixture.detectChanges(); - tick(); - - expect(form.controls['login']).not.toBeDefined(); - }))); - - it('should remove control groups', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
-
- -
-
`; - - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'show'; - fixture.detectChanges(); - tick(); - var form = fixture.debugElement.children[0].injector.get(NgForm); - - expect(form.controls['user']).toBeDefined(); - - fixture.debugElement.componentInstance.name = 'hide'; - fixture.detectChanges(); - tick(); - - expect(form.controls['user']).not.toBeDefined(); - }))); - - it('should support ngModel for complex forms', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'oldValue'; - fixture.detectChanges(); - tick(); - - var input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(input.value).toEqual('oldValue'); - - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - tick(); - - expect(fixture.debugElement.componentInstance.name).toEqual('updatedValue'); - }))); - - - it('should support ngModel for single fields', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'oldValue'; - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')).nativeElement; - - tick(); - expect(input.value).toEqual('oldValue'); - - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - tick(); - - expect(fixture.debugElement.componentInstance.name).toEqual('updatedValue'); - }))); - - it('should support ngModel registration with a parent form', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = ` -
- -
- `; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = 'Nancy'; - fixture.detectChanges(); - var form = fixture.debugElement.children[0].injector.get(NgForm); - - tick(); - expect(form.value).toEqual({first: 'Nancy'}); - expect(form.valid).toBe(false); - - }))); - - - it('should throw if ngModel has a parent form but no name attr or standalone label', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp(`name attribute must be set`)); - async.done(); - }); - })); - - it('should not throw if ngModel has a parent form, no name attr, and a standalone label', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()).not.toThrow(); - async.done(); - }); - })); - - - it('should override name attribute with ngModelOptions name if provided', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = ` -
- -
- `; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.data = 'some data'; - fixture.detectChanges(); - const form = fixture.debugElement.children[0].injector.get(NgForm); - - tick(); - expect(form.value).toEqual({two: 'some data'}); - }))); - - it('should not register standalone ngModels with parent form', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = ` -
- - -
- `; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.data = 'some data'; - fixture.debugElement.componentInstance.list = 'should not show'; - fixture.detectChanges(); - const form = fixture.debugElement.children[0].injector.get(NgForm); - const inputs = fixture.debugElement.queryAll(By.css('input')); - - tick(); - expect(form.value).toEqual({one: 'some data'}); - expect(inputs[1].nativeElement.value).toEqual('should not show'); - }))); - - - it('should support ', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- - -
`; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - - fixture.debugElement.componentInstance.data = {food: 'fish'}; - fixture.detectChanges(); - tick(); - - const inputs = fixture.debugElement.queryAll(By.css('input')); - expect(inputs[0].nativeElement.checked).toEqual(false); - expect(inputs[1].nativeElement.checked).toEqual(true); - - dispatchEvent(inputs[0].nativeElement, 'change'); - tick(); - - const data = fixture.debugElement.componentInstance.data; - - expect(data.food).toEqual('chicken'); - expect(inputs[1].nativeElement.checked).toEqual(false); - }))); - - it('should support multiple named groups', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- - - - -
`; - - const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - - fixture.debugElement.componentInstance.data = {food: 'fish', drink: 'sprite'}; - fixture.detectChanges(); - tick(); - - const inputs = fixture.debugElement.queryAll(By.css('input')); - expect(inputs[0].nativeElement.checked).toEqual(false); - expect(inputs[1].nativeElement.checked).toEqual(true); - expect(inputs[2].nativeElement.checked).toEqual(false); - expect(inputs[3].nativeElement.checked).toEqual(true); - - dispatchEvent(inputs[0].nativeElement, 'change'); - tick(); - - const data = fixture.debugElement.componentInstance.data; - - expect(data.food).toEqual('chicken'); - expect(data.drink).toEqual('sprite'); - expect(inputs[1].nativeElement.checked).toEqual(false); - expect(inputs[2].nativeElement.checked).toEqual(false); - expect(inputs[3].nativeElement.checked).toEqual(true); - - }))); - - }); - - describe('setting status classes', () => { it('should work with single fields', inject( @@ -1719,108 +1135,33 @@ export function main() { dispatchEvent(input, 'input'); fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - async.done(); - }); - })); - - it('should work with ngModel', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.name = ''; - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); - - dispatchEvent(input, 'blur'); - fixture.detectChanges(); - - expect(sortedClassList(input)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-touched' - ]); - - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); async.done(); }); })); }); - describe('ngModel corner cases', () => { - it('should not update the view when the value initially came from the view', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - var form = new FormControl(''); + it('should not update the view when the value initially came from the view', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + var form = new FormControl(''); - const t = `
`; - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); + const t = `
`; + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')).nativeElement; - input.value = 'aa'; - input.setSelectionRange(1, 2); - dispatchEvent(input, 'input'); + var input = fixture.debugElement.query(By.css('input')).nativeElement; + input.value = 'aa'; + input.setSelectionRange(1, 2); + dispatchEvent(input, 'input'); - tick(); - fixture.detectChanges(); + tick(); + fixture.detectChanges(); - // selection start has not changed because we did not reset the value - expect(input.selectionStart).toEqual(1); - }))); - - it('should update the view when the model is set back to what used to be in the view', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = ``; - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.name = ''; - fixture.detectChanges(); - - // Type "aa" into the input. - var input = fixture.debugElement.query(By.css('input')).nativeElement; - input.value = 'aa'; - input.selectionStart = 1; - dispatchEvent(input, 'input'); - - fixture.detectChanges(); - tick(); - expect(fixture.debugElement.componentInstance.name).toEqual('aa'); - - // Programmatically update the input value to be "bb". - fixture.debugElement.componentInstance.name = 'bb'; - fixture.detectChanges(); - tick(); - expect(input.value).toEqual('bb'); - - // Programatically set it back to "aa". - fixture.debugElement.componentInstance.name = 'aa'; - fixture.detectChanges(); - tick(); - expect(input.value).toEqual('aa'); - }))); - it('should not crash when validity is checked from a binding', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - // {{x.valid}} used to crash because valid() tried to read a property - // from form.control before it was set. This test verifies this bug is - // fixed. - const t = `
-
{{x.valid}}
`; - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.detectChanges(); - }))); - }); + // selection start has not changed because we did not reset the value + expect(input.selectionStart).toEqual(1); + }))); }); } diff --git a/modules/@angular/forms/test/template_integration_spec.ts b/modules/@angular/forms/test/template_integration_spec.ts new file mode 100644 index 0000000000..ac8533b768 --- /dev/null +++ b/modules/@angular/forms/test/template_integration_spec.ts @@ -0,0 +1,699 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgFor, NgIf} from '@angular/common'; +import {Component} from '@angular/core'; +import {ComponentFixture, TestComponentBuilder, configureModule, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; +import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; +import {FormsModule, NgForm} from '@angular/forms'; +import {By} from '@angular/platform-browser/src/dom/debug/by'; +import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; +import {dispatchEvent} from '@angular/platform-browser/testing/browser_util'; +import {ObservableWrapper} from '../src/facade/async'; +import {ListWrapper} from '../src/facade/collection'; + +export function main() { + describe('template-driven forms integration tests', () => { + + beforeEach(() => { configureModule({modules: [FormsModule]}); }); + + it('should support ngModel for single fields', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
`; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'oldValue'; + fixture.detectChanges(); + + var input = fixture.debugElement.query(By.css('input')).nativeElement; + + tick(); + expect(input.value).toEqual('oldValue'); + + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + tick(); + + expect(fixture.debugElement.componentInstance.name).toEqual('updatedValue'); + }))); + + + it('should support ngModel registration with a parent form', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = ` +
+ +
+ `; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'Nancy'; + fixture.detectChanges(); + var form = fixture.debugElement.children[0].injector.get(NgForm); + + tick(); + expect(form.value).toEqual({first: 'Nancy'}); + expect(form.valid).toBe(false); + + }))); + + + it('should add new controls and control groups', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+
+ +
+
`; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = null; + fixture.detectChanges(); + + var form = fixture.debugElement.children[0].injector.get(NgForm); + expect(form.controls['user']).not.toBeDefined(); + + tick(); + + expect(form.controls['user']).toBeDefined(); + expect(form.controls['user'].controls['login']).toBeDefined(); + }))); + + it('should emit ngSubmit event on submit', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
`; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'old'; + var form = fixture.debugElement.query(By.css('form')); + + dispatchEvent(form.nativeElement, 'submit'); + tick(); + + expect(fixture.debugElement.componentInstance.name).toEqual('updated'); + }))); + + it('should mark NgForm as submitted on submit event', + inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { + const t = `
+
+ {{data}} +
`; + + var fixture: ComponentFixture; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { + fixture = root; + }); + tick(); + + fixture.debugElement.componentInstance.data = false; + + tick(); + + var form = fixture.debugElement.query(By.css('form')); + dispatchEvent(form.nativeElement, 'submit'); + + tick(); + expect(fixture.debugElement.componentInstance.data).toEqual(true); + }))); + + + it('should reset the form to empty when reset button is clicked', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = ` +
+ +
+ `; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'should be cleared'; + fixture.detectChanges(); + tick(); + + const form = fixture.debugElement.children[0].injector.get(NgForm); + const formEl = fixture.debugElement.query(By.css('form')); + + dispatchEvent(formEl.nativeElement, 'reset'); + fixture.detectChanges(); + tick(); + + expect(fixture.debugElement.componentInstance.name).toBe(null); + expect(form.value.name).toEqual(null); + }))); + + + it('should emit valueChanges and statusChanges on init', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + const form = fixture.debugElement.children[0].injector.get(NgForm); + fixture.debugElement.componentInstance.name = 'aa'; + fixture.detectChanges(); + + expect(form.valid).toEqual(true); + expect(form.value).toEqual({}); + + let formValidity: string; + let formValue: Object; + + ObservableWrapper.subscribe( + form.statusChanges, (status: string) => { formValidity = status; }); + + ObservableWrapper.subscribe(form.valueChanges, (value: string) => { formValue = value; }); + + tick(); + + expect(formValidity).toEqual('INVALID'); + expect(formValue).toEqual({first: 'aa'}); + }))); + + it('should not create a template-driven form when ngNoForm is used', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.debugElement.componentInstance.name = null; + fixture.detectChanges(); + + expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); + async.done(); + }); + })); + + it('should remove controls', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+
+ +
+
`; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'show'; + fixture.detectChanges(); + tick(); + var form = fixture.debugElement.children[0].injector.get(NgForm); + + + expect(form.controls['login']).toBeDefined(); + + fixture.debugElement.componentInstance.name = 'hide'; + fixture.detectChanges(); + tick(); + + expect(form.controls['login']).not.toBeDefined(); + }))); + + it('should remove control groups', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+
+ +
+
`; + + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'show'; + fixture.detectChanges(); + tick(); + var form = fixture.debugElement.children[0].injector.get(NgForm); + + expect(form.controls['user']).toBeDefined(); + + fixture.debugElement.componentInstance.name = 'hide'; + fixture.detectChanges(); + tick(); + + expect(form.controls['user']).not.toBeDefined(); + }))); + + it('should support ngModel for complex forms', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = 'oldValue'; + fixture.detectChanges(); + tick(); + + var input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(input.value).toEqual('oldValue'); + + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + tick(); + + expect(fixture.debugElement.componentInstance.name).toEqual('updatedValue'); + }))); + + + it('should throw if ngModel has a parent form but no name attr or standalone label', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + expect(() => fixture.detectChanges()) + .toThrowError(new RegExp(`name attribute must be set`)); + async.done(); + }); + })); + + it('should not throw if ngModel has a parent form, no name attr, and a standalone label', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + expect(() => fixture.detectChanges()).not.toThrow(); + async.done(); + }); + })); + + it('should override name attribute with ngModelOptions name if provided', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = ` +
+ +
+ `; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.data = 'some data'; + fixture.detectChanges(); + const form = fixture.debugElement.children[0].injector.get(NgForm); + + tick(); + expect(form.value).toEqual({two: 'some data'}); + }))); + + it('should not register standalone ngModels with parent form', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = ` +
+ + +
+ `; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.data = 'some data'; + fixture.debugElement.componentInstance.list = 'should not show'; + fixture.detectChanges(); + const form = fixture.debugElement.children[0].injector.get(NgForm); + const inputs = fixture.debugElement.queryAll(By.css('input')); + + tick(); + expect(form.value).toEqual({one: 'some data'}); + expect(inputs[1].nativeElement.value).toEqual('should not show'); + }))); + + it('should set status classes with ngModel', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.debugElement.componentInstance.name = ''; + fixture.detectChanges(); + + var input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual([ + 'ng-invalid', 'ng-pristine', 'ng-untouched' + ]); + + dispatchEvent(input, 'blur'); + fixture.detectChanges(); + + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); + + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + async.done(); + }); + })); + + describe('radio value accessor', () => { + it('should support ', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ + +
`; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + + fixture.debugElement.componentInstance.data = {food: 'fish'}; + fixture.detectChanges(); + tick(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + expect(inputs[0].nativeElement.checked).toEqual(false); + expect(inputs[1].nativeElement.checked).toEqual(true); + + dispatchEvent(inputs[0].nativeElement, 'change'); + tick(); + + const data = fixture.debugElement.componentInstance.data; + + expect(data.food).toEqual('chicken'); + expect(inputs[1].nativeElement.checked).toEqual(false); + }))); + + it('should support multiple named groups', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ + + + +
`; + + const fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + + fixture.debugElement.componentInstance.data = {food: 'fish', drink: 'sprite'}; + fixture.detectChanges(); + tick(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + expect(inputs[0].nativeElement.checked).toEqual(false); + expect(inputs[1].nativeElement.checked).toEqual(true); + expect(inputs[2].nativeElement.checked).toEqual(false); + expect(inputs[3].nativeElement.checked).toEqual(true); + + dispatchEvent(inputs[0].nativeElement, 'change'); + tick(); + + const data = fixture.debugElement.componentInstance.data; + + expect(data.food).toEqual('chicken'); + expect(data.drink).toEqual('sprite'); + expect(inputs[1].nativeElement.checked).toEqual(false); + expect(inputs[2].nativeElement.checked).toEqual(false); + expect(inputs[3].nativeElement.checked).toEqual(true); + + }))); + }); + + describe('select value accessor', () => { + it('with option values that are objects', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp = fixture.debugElement.componentInstance; + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}]; + testComp.selectedCity = testComp.list[1]; + fixture.detectChanges(); + + var select = fixture.debugElement.query(By.css('select')); + var nycOption = fixture.debugElement.queryAll(By.css('option'))[1]; + + tick(); + expect(select.nativeElement.value).toEqual('1: Object'); + expect(nycOption.nativeElement.selected).toBe(true); + + select.nativeElement.value = '2: Object'; + dispatchEvent(select.nativeElement, 'change'); + fixture.detectChanges(); + tick(); + expect(testComp.selectedCity['name']).toEqual('Buffalo'); + }); + }))); + + it('when new options are added (selection through the model)', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp: MyComp8 = fixture.debugElement.componentInstance; + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; + testComp.selectedCity = testComp.list[1]; + fixture.detectChanges(); + + testComp.list.push({'name': 'Buffalo'}); + testComp.selectedCity = testComp.list[2]; + fixture.detectChanges(); + tick(); + + var select = fixture.debugElement.query(By.css('select')); + var buffalo = fixture.debugElement.queryAll(By.css('option'))[2]; + expect(select.nativeElement.value).toEqual('2: Object'); + expect(buffalo.nativeElement.selected).toBe(true); + }); + }))); + + it('when new options are added (selection through the UI)', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp: MyComp8 = fixture.debugElement.componentInstance; + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; + testComp.selectedCity = testComp.list[0]; + fixture.detectChanges(); + + var select = fixture.debugElement.query(By.css('select')); + var ny = fixture.debugElement.queryAll(By.css('option'))[1]; + + select.nativeElement.value = '1: Object'; + dispatchEvent(select.nativeElement, 'change'); + testComp.list.push({'name': 'Buffalo'}); + fixture.detectChanges(); + + expect(select.nativeElement.value).toEqual('1: Object'); + expect(ny.nativeElement.selected).toBe(true); + async.done(); + }); + })); + + + it('when options are removed', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp: MyComp8 = fixture.debugElement.componentInstance; + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; + testComp.selectedCity = testComp.list[1]; + fixture.detectChanges(); + tick(); + + var select = fixture.debugElement.query(By.css('select')); + expect(select.nativeElement.value).toEqual('1: Object'); + + testComp.list.pop(); + fixture.detectChanges(); + tick(); + + expect(select.nativeElement.value).not.toEqual('1: Object'); + }); + }))); + + it('when option values change identity while tracking by index', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp = fixture.debugElement.componentInstance; + + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; + testComp.selectedCity = testComp.list[0]; + fixture.detectChanges(); + + testComp.list[1] = 'Buffalo'; + testComp.selectedCity = testComp.list[1]; + fixture.detectChanges(); + tick(); + + var select = fixture.debugElement.query(By.css('select')); + var buffalo = fixture.debugElement.queryAll(By.css('option'))[1]; + + expect(select.nativeElement.value).toEqual('1: Buffalo'); + expect(buffalo.nativeElement.selected).toBe(true); + }); + }))); + + it('with duplicate option values', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp = fixture.debugElement.componentInstance; + + testComp.list = [{'name': 'NYC'}, {'name': 'SF'}, {'name': 'SF'}]; + testComp.selectedCity = testComp.list[0]; + fixture.detectChanges(); + + testComp.selectedCity = testComp.list[1]; + fixture.detectChanges(); + tick(); + + var select = fixture.debugElement.query(By.css('select')); + var firstSF = fixture.debugElement.queryAll(By.css('option'))[1]; + + expect(select.nativeElement.value).toEqual('1: Object'); + expect(firstSF.nativeElement.selected).toBe(true); + }); + }))); + + it('when option values have same content, but different identities', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + + var testComp = fixture.debugElement.componentInstance; + testComp.list = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}]; + testComp.selectedCity = testComp.list[0]; + fixture.detectChanges(); + + testComp.selectedCity = testComp.list[2]; + fixture.detectChanges(); + tick(); + + var select = fixture.debugElement.query(By.css('select')); + var secondNYC = fixture.debugElement.queryAll(By.css('option'))[2]; + + expect(select.nativeElement.value).toEqual('2: Object'); + expect(secondNYC.nativeElement.selected).toBe(true); + }); + }))); + }); + + describe('ngModel corner cases', () => { + it('should update the view when the model is set back to what used to be in the view', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + const t = ``; + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.debugElement.componentInstance.name = ''; + fixture.detectChanges(); + + // Type "aa" into the input. + var input = fixture.debugElement.query(By.css('input')).nativeElement; + input.value = 'aa'; + input.selectionStart = 1; + dispatchEvent(input, 'input'); + + fixture.detectChanges(); + tick(); + expect(fixture.debugElement.componentInstance.name).toEqual('aa'); + + // Programmatically update the input value to be "bb". + fixture.debugElement.componentInstance.name = 'bb'; + fixture.detectChanges(); + tick(); + expect(input.value).toEqual('bb'); + + // Programatically set it back to "aa". + fixture.debugElement.componentInstance.name = 'aa'; + fixture.detectChanges(); + tick(); + expect(input.value).toEqual('aa'); + }))); + + it('should not crash when validity is checked from a binding', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + // {{x.valid}} used to crash because valid() tried to read a property + // from form.control before it was set. This test verifies this bug is + // fixed. + const t = `
+
{{x.valid}}
`; + let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); + tick(); + fixture.detectChanges(); + }))); + }); + + }); +}; + +@Component({selector: 'my-comp', template: '', directives: [NgIf, NgFor]}) +class MyComp8 { + form: any; + name: string; + data: any; + list: any[]; + selectedCity: any; + customTrackBy(index: number, obj: any): number { return index; }; +} + +function sortedClassList(el: any /** TODO #9100 */) { + var l = getDOM().classList(el); + ListWrapper.sort(l); + return l; +}