From 292ccf882ae2d3f0f816d81d3a19edff753393f7 Mon Sep 17 00:00:00 2001 From: Kara Date: Wed, 17 Aug 2016 17:10:56 -0700 Subject: [PATCH] test(forms): update reactive form integration tests to use TestBed (#10908) --- .../forms/test/reactive_integration_spec.ts | 2505 +++++++---------- 1 file changed, 1088 insertions(+), 1417 deletions(-) diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts index bd9d7c7e20..d8a61feceb 100644 --- a/modules/@angular/forms/test/reactive_integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -8,1029 +8,781 @@ import {NgFor, NgIf} from '@angular/common'; import {Component, Directive, EventEmitter, Input, Output, forwardRef} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; -import {AsyncTestCompleter, TestComponentBuilder, 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, ReactiveFormsModule, Validator, Validators} from '@angular/forms'; +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, 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'; import {ListWrapper} from '../src/facade/collection'; +import {AbstractControl} from '../src/model'; export function main() { describe('reactive forms integration tests', () => { beforeEach(() => { - TestBed.configureTestingModule({imports: [FormsModule, ReactiveFormsModule]}); + TestBed.configureTestingModule({ + imports: [FormsModule, ReactiveFormsModule], + declarations: [ + FormControlComp, FormGroupComp, FormArrayComp, FormArrayNestedGroup, + FormControlNameSelect, FormControlNumberInput, FormControlRadioButtons, WrappedValue, + WrappedValueForm, MyInput, MyInputForm, FormGroupNgModel, FormControlNgModel, + LoginIsEmptyValidator, LoginIsEmptyWrapper, UniqLoginValidator, UniqLoginWrapper + ] + }); + TestBed.compileComponents(); }); - it('should initialize DOM elements with the given form object', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'login': new FormControl('loginValue')}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('loginValue'); - async.done(); - }); - })); - - it('should update the form group values on DOM change', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = new FormGroup({'login': new FormControl('oldValue')}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')); - - input.nativeElement.value = 'updatedValue'; - 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: AsyncTestCompleter) => { - var form = new FormGroup({'login': new FormControl('oldValue')}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')); - - input.nativeElement.value = 'updatedValue'; - - - form.valueChanges.subscribe({next: (value) => { throw 'Should not happen'; }}); - dispatchEvent(input.nativeElement, 'change'); - - async.done(); - }); - })); - - it('should emit ngSubmit event on submit', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
-
- {{name}} -
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - - fixture.debugElement.componentInstance.form = new FormGroup({}); - fixture.debugElement.componentInstance.name = 'old'; - - tick(); - - var form = fixture.debugElement.query(By.css('form')); - dispatchEvent(form.nativeElement, 'submit'); - - tick(); - expect(fixture.debugElement.componentInstance.name).toEqual('updated'); - }))); - - it('should mark formGroup as submitted on submit event', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
-
- {{data}} -
`; - - var fixture: ComponentFixture; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { fixture = root; }); - tick(); - - fixture.debugElement.componentInstance.form = new FormGroup({}); - 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 work with single controls', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var control = new FormControl('loginValue'); - - const t = `
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = control; - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('loginValue'); - - input.nativeElement.value = 'updatedValue'; - dispatchEvent(input.nativeElement, 'input'); - - expect(control.value).toEqual('updatedValue'); - async.done(); - }); - })); - - it('should update DOM elements when rebinding the form group', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'login': new FormControl('oldValue')}); - fixture.detectChanges(); - - fixture.debugElement.componentInstance.form = - new FormGroup({'login': new FormControl('newValue')}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('newValue'); - async.done(); - }); - })); - - it('should update DOM elements when updating the value of a control', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var login = new FormControl('oldValue'); - var form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - login.setValue('newValue'); - - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('newValue'); - async.done(); - }); - })); - - it('should mark controls as touched after interacting with the DOM control', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var login = new FormControl('oldValue'); - var form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - var loginEl = fixture.debugElement.query(By.css('input')); - expect(login.touched).toBe(false); - - dispatchEvent(loginEl.nativeElement, 'blur'); - - expect(login.touched).toBe(true); - - async.done(); - }); - })); - - it('should mark controls as dirty before emitting a value change event', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const login = new FormControl('oldValue'); - const form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - login.valueChanges.subscribe(() => { - expect(login.dirty).toBe(true); - async.done(); - }); - - const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; - loginEl.value = 'newValue'; - - dispatchEvent(loginEl, 'input'); - }); - })); - - it('should clear value in UI when form resets programmatically', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const login = new FormControl('oldValue'); - const form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - login.setValue('new value'); - - const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; - expect(loginEl.value).toBe('new value'); - - form.reset(); - expect(loginEl.value).toBe(''); - async.done(); - }); - })); - - it('should set value in UI when form resets to that value programmatically', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const login = new FormControl('oldValue'); - const form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - login.setValue('new value'); - - const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; - expect(loginEl.value).toBe('new value'); - - form.reset({'login': 'oldValue'}); - - expect(loginEl.value).toBe('oldValue'); - async.done(); - }); - })); - - it('should mark control as pristine before emitting a value change event when resetting ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const login = new FormControl('oldValue'); - const form = new FormGroup({'login': login}); - - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; - loginEl.value = 'newValue'; - - dispatchEvent(loginEl, 'input'); - - expect(login.pristine).toBe(false); - - login.valueChanges.subscribe(() => { - expect(login.pristine).toBe(true); - async.done(); - }); - - form.reset(); - }); - })); - - it('should support form arrays', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); - const form = new FormGroup({cities: cityArray}); - - const t = `
-
-
- -
-
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.cityArray = cityArray; - fixture.detectChanges(); - - const inputs = fixture.debugElement.queryAll(By.css('input')); - expect(inputs[0].nativeElement.value).toEqual('SF'); - expect(inputs[1].nativeElement.value).toEqual('NY'); - expect(fixture.componentInstance.form.value).toEqual({cities: ['SF', 'NY']}); - - inputs[0].nativeElement.value = 'LA'; - dispatchEvent(inputs[0].nativeElement, 'input'); - - fixture.detectChanges(); - - expect(fixture.componentInstance.form.value).toEqual({cities: ['LA', 'NY']}); - async.done(); - }); - })); - - it('should support form groups nested in form arrays', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const cityArray = new FormArray([ - new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}), - new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')}) - ]); - const form = new FormGroup({cities: cityArray}); - - const t = `
-
-
- - -
-
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.cityArray = cityArray; - fixture.detectChanges(); - - const inputs = fixture.debugElement.queryAll(By.css('input')); - expect(inputs[0].nativeElement.value).toEqual('SF'); - expect(inputs[1].nativeElement.value).toEqual('CA'); - expect(inputs[2].nativeElement.value).toEqual('NY'); - expect(inputs[3].nativeElement.value).toEqual('NY'); - expect(fixture.componentInstance.form.value).toEqual({ - cities: [{town: 'SF', state: 'CA'}, {town: 'NY', state: 'NY'}] - }); - - inputs[0].nativeElement.value = 'LA'; - dispatchEvent(inputs[0].nativeElement, 'input'); - - fixture.detectChanges(); - - expect(fixture.componentInstance.form.value).toEqual({ - cities: [{town: 'LA', state: 'CA'}, {town: 'NY', state: 'NY'}] - }); - - async.done(); - }); - })); - - it('should support pushing new controls to form arrays', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); - const form = new FormGroup({cities: cityArray}); - - const t = `
-
-
- -
-
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.cityArray = cityArray; - fixture.detectChanges(); - tick(); - - cityArray.push(new FormControl('LA')); - fixture.detectChanges(); - tick(); - - const inputs = fixture.debugElement.queryAll(By.css('input')); - expect(inputs[2].nativeElement.value).toEqual('LA'); - expect(fixture.componentInstance.form.value).toEqual({cities: ['SF', 'NY', 'LA']}); - - }); - }))); - - describe('different control types', () => { - it('should support ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'text': new FormControl('old')}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('old'); - - input.nativeElement.value = 'new'; - dispatchEvent(input.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({'text': 'new'}); - async.done(); - }); - })); - - it('should support without type', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'text': new FormControl('old')}); - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('old'); - - input.nativeElement.value = 'new'; - dispatchEvent(input.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({'text': 'new'}); - async.done(); - }); - })); - - it('should support - `; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'text': new FormControl('old')}); - fixture.detectChanges(); - - var textarea = fixture.debugElement.query(By.css('textarea')); - expect(textarea.nativeElement.value).toEqual('old'); - - textarea.nativeElement.value = 'new'; - dispatchEvent(textarea.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({'text': 'new'}); - async.done(); - }); - })); - - it('should support ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'checkbox': new FormControl(true)}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.checked).toBe(true); - - input.nativeElement.checked = false; - dispatchEvent(input.nativeElement, 'change'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({ - 'checkbox': false - }); - async.done(); - }); - })); - - it('should support ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'num': new FormControl(10)}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('10'); - - input.nativeElement.value = '20'; - dispatchEvent(input.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({'num': 20}); - async.done(); - }); - })); - - it('should support when value is cleared in the UI', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'num': new FormControl(10)}); - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - input.nativeElement.value = ''; - dispatchEvent(input.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.valid).toBe(false); - expect(fixture.debugElement.componentInstance.form.value).toEqual({'num': null}); - - input.nativeElement.value = '0'; - dispatchEvent(input.nativeElement, 'input'); - - expect(fixture.debugElement.componentInstance.form.valid).toBe(true); - expect(fixture.debugElement.componentInstance.form.value).toEqual({'num': 0}); - async.done(); - }); - })); - - - it('should support when value is cleared programmatically', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = new FormGroup({'num': new FormControl(10)}); - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.data = null; - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual(''); - - async.done(); - }); - })); - - it('should support ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- - -
`; - - const ctrl = new FormControl('fish'); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = new FormGroup({'food': ctrl}); - fixture.detectChanges(); - - var 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'); - fixture.detectChanges(); - - let value = fixture.debugElement.componentInstance.form.value; - expect(value.food).toEqual('chicken'); - expect(inputs[1].nativeElement.checked).toEqual(false); - - ctrl.setValue('fish'); - fixture.detectChanges(); - - expect(inputs[0].nativeElement.checked).toEqual(false); - expect(inputs[1].nativeElement.checked).toEqual(true); - - async.done(); - }); - })); - - it('should use formControlName to group radio buttons when name is absent', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- - - - -
`; - - const foodCtrl = new FormControl('fish'); - const drinkCtrl = new FormControl('sprite'); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'food': foodCtrl, 'drink': drinkCtrl}); - fixture.detectChanges(); - - 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'); - inputs[0].nativeElement.checked = true; - fixture.detectChanges(); - - const value = fixture.debugElement.componentInstance.form.value; - expect(value.food).toEqual('chicken'); - expect(inputs[1].nativeElement.checked).toEqual(false); - expect(inputs[2].nativeElement.checked).toEqual(false); - expect(inputs[3].nativeElement.checked).toEqual(true); - - drinkCtrl.setValue('cola'); - fixture.detectChanges(); - - expect(inputs[0].nativeElement.checked).toEqual(true); - expect(inputs[1].nativeElement.checked).toEqual(false); - expect(inputs[2].nativeElement.checked).toEqual(true); - expect(inputs[3].nativeElement.checked).toEqual(false); - - async.done(); - }); - })); - - it('should support removing controls from ', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = ` - - -
-
- - -
-
`; - - const ctrl = new FormControl('fish'); - const showRadio = new FormControl('yes'); - const form = new FormGroup({'food': ctrl}); - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.showRadio = showRadio; - showRadio.valueChanges.subscribe((change) => { - (change === 'yes') ? form.addControl('food', new FormControl('fish')) : - form.removeControl('food'); - }); - fixture.detectChanges(); - - const input = fixture.debugElement.query(By.css('[value="no"]')); - dispatchEvent(input.nativeElement, 'change'); - - fixture.detectChanges(); - expect(form.value).toEqual({}); - async.done(); - }); - })); - - describe('should support - - - `; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.detectChanges(); - - var select = fixture.debugElement.query(By.css('select')); - var sfOption = fixture.debugElement.query(By.css('option')); - - expect(select.nativeElement.value).toEqual('SF'); - expect(sfOption.nativeElement.selected).toBe(true); - async.done(); - }); - })); - - it('with basic selection and value bindings', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = ``; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - var testComp = fixture.debugElement.componentInstance; - testComp.list = [{'id': '0', 'name': 'SF'}, {'id': '1', 'name': 'NYC'}]; - fixture.detectChanges(); - - var sfOption = fixture.debugElement.query(By.css('option')); - expect(sfOption.nativeElement.value).toEqual('0'); - - testComp.list[0]['id'] = '2'; - fixture.detectChanges(); - expect(sfOption.nativeElement.value).toEqual('2'); - async.done(); - }); - })); - - it('with formControlName', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'city': new FormControl('SF')}); - fixture.detectChanges(); - - var select = fixture.debugElement.query(By.css('select')); - var sfOption = fixture.debugElement.query(By.css('option')); - - - expect(select.nativeElement.value).toEqual('SF'); - expect(sfOption.nativeElement.selected).toBe(true); - - select.nativeElement.value = 'NYC'; - dispatchEvent(select.nativeElement, 'change'); - - expect(fixture.debugElement.componentInstance.form.value).toEqual({ - 'city': 'NYC' - }); - expect(sfOption.nativeElement.selected).toBe(false); - async.done(); - }); - })); - - it('with a dynamic list of options', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - const t = `
- -
`; - - var fixture: any /** TODO #9100 */; - tcb.overrideTemplate(MyComp8, t) - .createAsync(MyComp8) - .then((compFixture) => fixture = compFixture); - tick(); - - fixture.debugElement.componentInstance.form = - new FormGroup({'city': new FormControl('NYC')}); - - fixture.debugElement.componentInstance.data = ['SF', 'NYC']; - fixture.detectChanges(); - tick(); - - var select = fixture.debugElement.query(By.css('select')); - expect(select.nativeElement.value).toEqual('NYC'); - }))); + describe('basic functionality', () => { + it('should work with single controls', () => { + const fixture = TestBed.createComponent(FormControlComp); + const control = new FormControl('old value'); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); + + // model -> view + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('old value'); + + input.nativeElement.value = 'updated value'; + dispatchEvent(input.nativeElement, 'input'); + + // view -> model + expect(control.value).toEqual('updated value'); }); - it('should support custom value accessors', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should work with formGroups (model -> view)', () => { + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.form = + new FormGroup({'login': new FormControl('loginValue')}); + fixture.detectChanges(); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'name': new FormControl('aa')}); - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('!aa!'); + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('loginValue'); + }); - input.nativeElement.value = '!bb!'; - dispatchEvent(input.nativeElement, 'input'); + it('work with formGroups (view -> model)', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('oldValue')}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); - expect(fixture.debugElement.componentInstance.form.value).toEqual({'name': 'bb'}); - async.done(); - }); - })); + const input = fixture.debugElement.query(By.css('input')); + input.nativeElement.value = 'updatedValue'; + dispatchEvent(input.nativeElement, 'input'); - it('should support custom value accessors on non builtin input elements that fire a change event without a \'target\' property', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + expect(form.value).toEqual({'login': 'updatedValue'}); + }); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'name': new FormControl('aa')}); - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('my-input')); - expect(input.componentInstance.value).toEqual('!aa!'); + it('should update DOM elements when rebinding the form group', () => { + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.form = + new FormGroup({'login': new FormControl('oldValue')}); + fixture.detectChanges(); - input.componentInstance.value = '!bb!'; - input.componentInstance.onInput.subscribe({ - next: (value: any) => { - expect(fixture.debugElement.componentInstance.form.value).toEqual({ - 'name': 'bb' - }); - async.done(); - } - }); - input.componentInstance.dispatchChangeEvent(); - }); - })); + fixture.debugElement.componentInstance.form = + new FormGroup({'login': new FormControl('newValue')}); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('newValue'); + }); + }); + + describe('form arrays', () => { + it('should support form arrays', () => { + const fixture = TestBed.createComponent(FormArrayComp); + const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); + const form = new FormGroup({cities: cityArray}); + fixture.debugElement.componentInstance.form = form; + fixture.debugElement.componentInstance.cityArray = cityArray; + fixture.detectChanges(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + + // model -> view + expect(inputs[0].nativeElement.value).toEqual('SF'); + expect(inputs[1].nativeElement.value).toEqual('NY'); + expect(form.value).toEqual({cities: ['SF', 'NY']}); + + inputs[0].nativeElement.value = 'LA'; + dispatchEvent(inputs[0].nativeElement, 'input'); + fixture.detectChanges(); + + // view -> model + expect(form.value).toEqual({cities: ['LA', 'NY']}); + }); + + it('should support pushing new controls to form arrays', () => { + const fixture = TestBed.createComponent(FormArrayComp); + const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); + const form = new FormGroup({cities: cityArray}); + fixture.debugElement.componentInstance.form = form; + fixture.debugElement.componentInstance.cityArray = cityArray; + fixture.detectChanges(); + + cityArray.push(new FormControl('LA')); + fixture.detectChanges(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + expect(inputs[2].nativeElement.value).toEqual('LA'); + expect(form.value).toEqual({cities: ['SF', 'NY', 'LA']}); + }); + + it('should support form groups nested in form arrays', () => { + const fixture = TestBed.createComponent(FormArrayNestedGroup); + const cityArray = new FormArray([ + new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}), + new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')}) + ]); + const form = new FormGroup({cities: cityArray}); + fixture.debugElement.componentInstance.form = form; + fixture.debugElement.componentInstance.cityArray = cityArray; + fixture.detectChanges(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + expect(inputs[0].nativeElement.value).toEqual('SF'); + expect(inputs[1].nativeElement.value).toEqual('CA'); + expect(inputs[2].nativeElement.value).toEqual('NY'); + expect(inputs[3].nativeElement.value).toEqual('NY'); + expect(form.value).toEqual({ + cities: [{town: 'SF', state: 'CA'}, {town: 'NY', state: 'NY'}] + }); + + inputs[0].nativeElement.value = 'LA'; + dispatchEvent(inputs[0].nativeElement, 'input'); + fixture.detectChanges(); + + expect(form.value).toEqual({ + cities: [{town: 'LA', state: 'CA'}, {town: 'NY', state: 'NY'}] + }); + }); + + }); + + describe('programmatic changes', () => { + it('should update the value in the DOM when setValue is called', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('oldValue'); + const form = new FormGroup({'login': login}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + login.setValue('newValue'); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('newValue'); + }); + + }); + + describe('user input', () => { + + it('should mark controls as touched after interacting with the DOM control', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('oldValue'); + const form = new FormGroup({'login': login}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const loginEl = fixture.debugElement.query(By.css('input')); + expect(login.touched).toBe(false); + + dispatchEvent(loginEl.nativeElement, 'blur'); + + expect(login.touched).toBe(true); + }); + + }); + + describe('submit and reset events', () => { + it('should emit ngSubmit event on submit', () => { + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.form = + new FormGroup({'login': new FormControl('loginValue')}); + fixture.debugElement.componentInstance.data = 'should be changed'; + fixture.detectChanges(); + + const formEl = fixture.debugElement.query(By.css('form')).nativeElement; + dispatchEvent(formEl, 'submit'); + + fixture.detectChanges(); + expect(fixture.debugElement.componentInstance.data).toEqual('submitted'); + }); + + it('should mark formGroup as submitted on submit event', () => { + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.form = + new FormGroup({'login': new FormControl('loginValue')}); + fixture.detectChanges(); + + const formGroupDir = fixture.debugElement.children[0].injector.get(FormGroupDirective); + expect(formGroupDir.submitted).toBe(false); + + const formEl = fixture.debugElement.query(By.css('form')).nativeElement; + dispatchEvent(formEl, 'submit'); + + fixture.detectChanges(); + expect(formGroupDir.submitted).toEqual(true); + }); + + it('should set value in UI when form resets to that value programmatically', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('some value'); + const form = new FormGroup({'login': login}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + expect(loginEl.value).toBe('some value'); + + form.reset({'login': 'reset value'}); + expect(loginEl.value).toBe('reset value'); + }); + + it('should clear value in UI when form resets programmatically', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('some value'); + const form = new FormGroup({'login': login}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + expect(loginEl.value).toBe('some value'); + + form.reset(); + expect(loginEl.value).toBe(''); + }); + + }); + + describe('value changes and status changes', () => { + + it('should mark controls as dirty before emitting a value change event', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('oldValue'); + fixture.debugElement.componentInstance.form = new FormGroup({'login': login}); + fixture.detectChanges(); + + login.valueChanges.subscribe(() => { expect(login.dirty).toBe(true); }); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + loginEl.value = 'newValue'; + + dispatchEvent(loginEl, 'input'); + }); + + it('should mark control as pristine before emitting a value change event when resetting ', + () => { + const fixture = TestBed.createComponent(FormGroupComp); + const login = new FormControl('oldValue'); + const form = new FormGroup({'login': login}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + loginEl.value = 'newValue'; + dispatchEvent(loginEl, 'input'); + + expect(login.pristine).toBe(false); + + login.valueChanges.subscribe(() => { expect(login.pristine).toBe(true); }); + + form.reset(); + }); + + }); + + describe('setting status classes', () => { + it('should work with single fields', () => { + const fixture = TestBed.createComponent(FormControlComp); + const control = new FormControl('', Validators.required); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); + + const 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']); + }); + + it('should work with single fields in parent forms', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('', Validators.required)}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const 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']); + }); + + it('should work with formGroup', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('', Validators.required)}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')).nativeElement; + const formEl = fixture.debugElement.query(By.css('form')).nativeElement; + + expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); + + dispatchEvent(input, 'blur'); + fixture.detectChanges(); + + expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); + + expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); + + }); + + describe('value accessors', () => { + + it('should support without type', () => { + TestBed.overrideComponent( + FormControlComp, {set: {template: ``}}); + const fixture = TestBed.createComponent(FormControlComp); + const control = new FormControl('old'); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); + + // model -> view + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('old'); + + input.nativeElement.value = 'new'; + dispatchEvent(input.nativeElement, 'input'); + + // view -> model + expect(control.value).toEqual('new'); + }); + + it('should support ', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('old')}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + // model -> view + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.value).toEqual('old'); + + input.nativeElement.value = 'new'; + dispatchEvent(input.nativeElement, 'input'); + + // view -> model + expect(form.value).toEqual({'login': 'new'}); + }); + + it('should ignore the change event for ', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('oldValue')}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.css('input')); + form.valueChanges.subscribe({next: (value) => { throw 'Should not happen'; }}); + input.nativeElement.value = 'updatedValue'; + + dispatchEvent(input.nativeElement, 'change'); + }); + + it('should support `}}); + const fixture = TestBed.createComponent(FormControlComp); + const control = new FormControl('old'); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); + + // model -> view + const textarea = fixture.debugElement.query(By.css('textarea')); + expect(textarea.nativeElement.value).toEqual('old'); + + textarea.nativeElement.value = 'new'; + dispatchEvent(textarea.nativeElement, 'input'); + + // view -> model + expect(control.value).toEqual('new'); + }); + + it('should support ', () => { + TestBed.overrideComponent( + FormControlComp, {set: {template: ``}}); + const fixture = TestBed.createComponent(FormControlComp); + const control = new FormControl(true); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); + + // model -> view + const input = fixture.debugElement.query(By.css('input')); + expect(input.nativeElement.checked).toBe(true); + + input.nativeElement.checked = false; + dispatchEvent(input.nativeElement, 'change'); + + // view -> model + expect(control.value).toBe(false); + }); + + it('should support - - - `; + const required = fixture.debugElement.query(By.css('[required]')); + const minLength = fixture.debugElement.query(By.css('[minlength]')); + const maxLength = fixture.debugElement.query(By.css('[maxlength]')); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); + required.nativeElement.value = ''; + minLength.nativeElement.value = '1'; + maxLength.nativeElement.value = '1234'; - var required = fixture.debugElement.query(By.css('[required]')); - var minLength = fixture.debugElement.query(By.css('[minlength]')); - var maxLength = fixture.debugElement.query(By.css('[maxlength]')); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); - required.nativeElement.value = ''; - minLength.nativeElement.value = '1'; - maxLength.nativeElement.value = '1234'; - 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); + expect(form.hasError('maxlength', ['max'])).toEqual(true); + expect(form.hasError('loginIsEmpty')).toEqual(true); - expect(form.hasError('required', ['login'])).toEqual(true); - expect(form.hasError('minlength', ['min'])).toEqual(true); - expect(form.hasError('maxlength', ['max'])).toEqual(true); + required.nativeElement.value = '1'; + minLength.nativeElement.value = '123'; + maxLength.nativeElement.value = '123'; - expect(form.hasError('loginIsEmpty')).toEqual(true); + dispatchEvent(required.nativeElement, 'input'); + dispatchEvent(minLength.nativeElement, 'input'); + dispatchEvent(maxLength.nativeElement, 'input'); - required.nativeElement.value = '1'; - minLength.nativeElement.value = '123'; - maxLength.nativeElement.value = '123'; - dispatchEvent(required.nativeElement, 'input'); - dispatchEvent(minLength.nativeElement, 'input'); - dispatchEvent(maxLength.nativeElement, 'input'); + expect(form.valid).toEqual(true); + }); - expect(form.valid).toEqual(true); - - async.done(); - }); - })); - - it('should use async validators defined in the html', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - var form = new FormGroup({'login': new FormControl('')}); - - const t = `
- -
`; - - var rootTC: any /** TODO #9100 */; - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => rootTC = root); + it('should use async validators defined in the html', fakeAsync(() => { + const fixture = TestBed.createComponent(UniqLoginWrapper); + const form = new FormGroup({'login': new FormControl('')}); tick(); - - rootTC.debugElement.componentInstance.form = form; - rootTC.detectChanges(); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); expect(form.pending).toEqual(true); - tick(100); expect(form.hasError('uniqLogin', ['login'])).toEqual(true); - var input = rootTC.debugElement.query(By.css('input')); + const input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'expected'; dispatchEvent(input.nativeElement, 'input'); tick(100); expect(form.valid).toEqual(true); - }))); + })); - it('should use sync validators defined in the model', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = new FormGroup({'login': new FormControl('aa', Validators.required)}); + it('should use sync validators defined in the model', () => { + const fixture = TestBed.createComponent(FormGroupComp); + const form = new FormGroup({'login': new FormControl('aa', Validators.required)}); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + expect(form.valid).toEqual(true); - const t = `
- -
`; + const input = fixture.debugElement.query(By.css('input')); + input.nativeElement.value = ''; + dispatchEvent(input.nativeElement, 'input'); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - expect(form.valid).toEqual(true); + expect(form.valid).toEqual(false); + }); - var input = fixture.debugElement.query(By.css('input')); - - input.nativeElement.value = ''; - dispatchEvent(input.nativeElement, 'input'); - - expect(form.valid).toEqual(false); - async.done(); - }); - })); - - it('should use async validators defined in the model', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - var control = + it('should use async validators defined in the model', fakeAsync(() => { + const fixture = TestBed.createComponent(FormGroupComp); + const control = new FormControl('', Validators.required, uniqLoginAsyncValidator('expected')); - var form = new FormGroup({'login': control}); - - const t = `
- -
`; - - var fixture: any /** TODO #9100 */; - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => fixture = root); - tick(); - + const form = new FormGroup({'login': control}); fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); + tick(); expect(form.hasError('required', ['login'])).toEqual(true); - var input = fixture.debugElement.query(By.css('input')); + const input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'wrong value'; dispatchEvent(input.nativeElement, 'input'); @@ -1044,455 +796,216 @@ export function main() { tick(); expect(form.valid).toEqual(true); - }))); + })); + }); - describe('nested forms', () => { - it('should init DOM with the given form object', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = - new FormGroup({'nested': new FormGroup({'login': new FormControl('value')})}); - - const t = `
-
- -
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - var input = fixture.debugElement.query(By.css('input')); - expect(input.nativeElement.value).toEqual('value'); - async.done(); - }); - })); - - it('should update the control group values on DOM change', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = - new FormGroup({'nested': new FormGroup({'login': new FormControl('value')})}); - - const t = `
-
- -
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - var input = fixture.debugElement.query(By.css('input')); - - input.nativeElement.value = 'updatedValue'; - dispatchEvent(input.nativeElement, 'input'); - - expect(form.value).toEqual({'nested': {'login': 'updatedValue'}}); - async.done(); - }); - })); - }); - - it('should support ngModel for complex forms', - fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - var form = new FormGroup({'name': new FormControl('')}); - - const t = - `
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - - fixture.debugElement.componentInstance.name = 'oldValue'; - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - 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) => { - var form = new FormControl(''); - - const t = `
`; - - let fixture = tcb.overrideTemplate(MyComp8, t).createFakeAsync(MyComp8); - tick(); - fixture.debugElement.componentInstance.form = form; - fixture.debugElement.componentInstance.name = 'oldValue'; - fixture.detectChanges(); - - 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'); - }))); - - describe('setting status classes', () => { - it('should work with single fields', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - var form = new FormControl('', Validators.required); - - const t = `
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - 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(); - }); - })); - - it('should work with single fields in parent forms', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const form = new FormGroup({'name': new FormControl('', Validators.required)}); - - const t = - `
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - const 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(); - }); - })); - - it('should work with formGroup and formGroupName', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const form = new FormGroup( - {'person': new FormGroup({'name': new FormControl('', Validators.required)})}); - - const t = `
-
- -
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = form; - fixture.detectChanges(); - - const input = fixture.debugElement.query(By.css('input')).nativeElement; - const formGroup = - fixture.debugElement.query(By.css('[formGroupName]')).nativeElement; - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - - expect(sortedClassList(formGroup)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); - - expect(sortedClassList(formEl)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); - - dispatchEvent(input, 'blur'); - fixture.detectChanges(); - - expect(sortedClassList(formGroup)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-touched' - ]); - - expect(sortedClassList(formEl)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-touched' - ]); - - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); - - expect(sortedClassList(formGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - async.done(); - }); - })); - }); - - 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(); - - var input = fixture.debugElement.query(By.css('input')).nativeElement; - input.value = 'aa'; - input.setSelectionRange(1, 2); - dispatchEvent(input, 'input'); - - tick(); - fixture.detectChanges(); - - // selection start has not changed because we did not reset the value - expect(input.selectionStart).toEqual(1); - }))); - describe('errors', () => { - it('should throw if a form isn\'t passed into formGroup', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should throw if a form isn\'t passed into formGroup', () => { + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp(`formGroup expects a FormGroup instance`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError(new RegExp(`formGroup expects a FormGroup instance`)); + }); - it('should throw if formControlName is used without a control container', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = ``; + it('should throw if formControlName is used without a control container', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` + + ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `formControlName must be used with a parent formGroup directive`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formControlName must be used with a parent formGroup directive`)); + }); - it('should throw if formControlName is used with NgForm', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should throw if formControlName is used with NgForm', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `formControlName must be used with a parent formGroup directive.`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formControlName must be used with a parent formGroup directive.`)); + }); - it('should throw if formControlName is used with NgModelGroup', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
-
- -
-
`; + it('should throw if formControlName is used with NgModelGroup', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+
+ +
+
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError( - new RegExp(`formControlName cannot be used with an ngModelGroup parent.`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formControlName cannot be used with an ngModelGroup parent.`)); + }); - it('should throw if formGroupName is used without a control container', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should throw if formGroupName is used without a control container', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `formGroupName must be used with a parent formGroup directive`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formGroupName must be used with a parent formGroup directive`)); + }); - it('should throw if formGroupName is used with NgForm', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
+ it('should throw if formGroupName is used with NgForm', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
-
`; + + ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `formGroupName must be used with a parent formGroup directive.`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formGroupName must be used with a parent formGroup directive.`)); + }); - it('should throw if formArrayName is used without a control container', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should throw if formArrayName is used without a control container', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `formArrayName must be used with a parent formGroup directive`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`formArrayName must be used with a parent formGroup directive`)); + }); - it('should throw if ngModel is used alone under formGroup', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
+ it('should throw if ngModel is used alone under formGroup', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.myGroup = new FormGroup({}); + + expect(() => fixture.detectChanges()) + .toThrowError(new RegExp( + `ngModel cannot be used to register form controls with a parent formGroup directive.`)); + }); + + it('should not throw if ngModel is used alone under formGroup with standalone: true', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.myGroup = new FormGroup({}); + + expect(() => fixture.detectChanges()).not.toThrowError(); + }); + + it('should throw if ngModel is used alone with formGroupName', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+
-
`; +
+
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); + const myGroup = new FormGroup({person: new FormGroup({})}); + fixture.debugElement.componentInstance.myGroup = new FormGroup({person: new FormGroup({})}); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.myGroup = new FormGroup({}); - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `ngModel cannot be used to register form controls with a parent formGroup directive.`)); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError(new RegExp( + `ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.`)); + }); - it('should not throw if ngModel is used alone under formGroup with standalone: true', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; + it('should throw if ngModelGroup is used with formGroup', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+
+ +
+
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.myGroup = new FormGroup({}); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.myGroup = new FormGroup({}); - expect(() => fixture.detectChanges()).not.toThrowError(); - async.done(); - }); - })); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp(`ngModelGroup cannot be used with a parent formGroup directive`)); + }); - it('should throw if ngModel is used alone with formGroupName', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
-
- -
-
`; + it('should throw if radio button name does not match formControlName attr', () => { + TestBed.overrideComponent(FormGroupComp, { + set: { + template: ` +
+ +
+ ` + } + }); + const fixture = TestBed.createComponent(FormGroupComp); + fixture.debugElement.componentInstance.form = + new FormGroup({'food': new FormControl('fish')}); - const myGroup = new FormGroup({person: new FormGroup({})}); + expect(() => fixture.detectChanges()) + .toThrowError(new RegExp('If you define both a name and a formControlName')); + }); - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.myGroup = - new FormGroup({person: new FormGroup({})}); - - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.`)); - async.done(); - }); - })); - - it('should throw if ngModelGroup is used with formGroup', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
-
- -
-
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.myGroup = new FormGroup({}); - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp( - `ngModelGroup cannot be used with a parent formGroup directive`)); - async.done(); - }); - })); - - - it('should throw if radio button name does not match formControlName attr', - inject( - [TestComponentBuilder, AsyncTestCompleter], - (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { - const t = `
- -
`; - - tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { - fixture.debugElement.componentInstance.form = - new FormGroup({'food': new FormControl('fish')}); - expect(() => fixture.detectChanges()) - .toThrowError(new RegExp('If you define both a name and a formControlName')); - async.done(); - }); - })); }); }); } @@ -1502,19 +1015,17 @@ export function main() { host: {'(input)': 'handleOnInput($event.target.value)', '[value]': 'value'} }) class WrappedValue implements ControlValueAccessor { - value: any /** TODO #9100 */; + value: any; onChange: Function; constructor(cd: NgControl) { cd.valueAccessor = this; } - writeValue(value: any /** TODO #9100 */) { this.value = `!${value}!`; } + writeValue(value: any) { this.value = `!${value}!`; } - registerOnChange(fn: any /** TODO #9100 */) { this.onChange = fn; } - registerOnTouched(fn: any /** TODO #9100 */) {} + registerOnChange(fn: (value: any) => void) { this.onChange = fn; } + registerOnTouched(fn: any) {} - handleOnInput(value: any /** TODO #9100 */) { - this.onChange(value.substring(1, value.length - 1)); - } + handleOnInput(value: any) { this.onChange(value.substring(1, value.length - 1)); } } @Component({selector: 'my-input', template: ''}) @@ -1524,17 +1035,17 @@ class MyInput implements ControlValueAccessor { constructor(cd: NgControl) { cd.valueAccessor = this; } - writeValue(value: any /** TODO #9100 */) { this.value = `!${value}!`; } + writeValue(value: any) { this.value = `!${value}!`; } - registerOnChange(fn: any /** TODO #9100 */) { this.onInput.subscribe({next: fn}); } + registerOnChange(fn: (value: any) => void) { this.onInput.subscribe({next: fn}); } - registerOnTouched(fn: any /** TODO #9100 */) {} + registerOnTouched(fn: any) {} dispatchChangeEvent() { this.onInput.emit(this.value.substring(1, this.value.length - 1)); } } function uniqLoginAsyncValidator(expectedValue: string) { - return (c: any /** TODO #9100 */) => { + return (c: AbstractControl) => { var resolve: (result: any) => void; var promise = new Promise(res => { resolve = res; }); var res = (c.value == expectedValue) ? null : {'uniqLogin': true}; @@ -1565,25 +1076,185 @@ class LoginIsEmptyValidator { class UniqLoginValidator implements Validator { @Input('uniq-login-validator') expected: any /** TODO #9100 */; - validate(c: any /** TODO #9100 */) { return uniqLoginAsyncValidator(this.expected)(c); } + validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); } } -@Component({ - selector: 'my-comp', - template: '', - directives: [WrappedValue, MyInput, NgIf, NgFor, LoginIsEmptyValidator, UniqLoginValidator] -}) -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 */) { +function sortedClassList(el: HTMLElement) { var l = getDOM().classList(el); ListWrapper.sort(l); return l; } + +@Component({ + selector: 'form-control-comp', + template: ` + + ` +}) +class FormControlComp { + control: FormControl; +} + +@Component({ + selector: 'form-group-comp', + template: ` +
+ +
+ ` +}) +class FormGroupComp { + form: FormGroup; + data: string; +} + +@Component({ + selector: 'form-control-number-input', + template: ` + + ` +}) +class FormControlNumberInput { + control: FormControl; +} + +@Component({ + selector: 'form-control-radio-buttons', + template: ` +
+ + + + +
+ + + ` +}) +class FormControlRadioButtons { + form: FormGroup; + showRadio = new FormControl('yes'); +} + +@Component({ + selector: 'form-array-comp', + template: ` +
+
+
+ +
+
+
+ ` +}) +class FormArrayComp { + form: FormGroup; + cityArray: FormArray; +} + +@Component({ + selector: 'form-array-nested-group', + template: ` +
+
+
+ + +
+
+
+ ` +}) +class FormArrayNestedGroup { + form: FormGroup; + cityArray: FormArray; +} + +@Component({ + selector: 'form-control-name-select', + template: ` +
+ +
+ ` +}) +class FormControlNameSelect { + cities = ['SF', 'NY']; + form = new FormGroup({city: new FormControl('SF')}); +} + +@Component({ + selector: 'wrapped-value-form', + template: ` +
+ +
+ ` +}) +class WrappedValueForm { + form: FormGroup; +} + +@Component({ + selector: 'my-input-form', + template: ` +
+ +
+ ` +}) +class MyInputForm { + form: FormGroup; +} + +@Component({ + selector: 'form-group-ng-model', + template: ` +
+ +
+ ` +}) +class FormGroupNgModel { + form: FormGroup; + login: string; +} + +@Component({ + selector: 'form-control-ng-model', + template: ` + + ` +}) +class FormControlNgModel { + control: FormControl; + login: string; +} + +@Component({ + selector: 'login-is-empty-wrapper', + template: ` +
+ + + +
+ ` +}) +class LoginIsEmptyWrapper { + form: FormGroup; +} +@Component({ + selector: 'uniq-login-wrapper', + template: ` +
+ +
+ ` +}) +class UniqLoginWrapper { + form: FormGroup; +}