import {Component, Directive, Output, EventEmitter} from 'angular2/core'; import {ComponentFixture, afterEach, AsyncTestCompleter, TestComponentBuilder, beforeEach, ddescribe, describe, dispatchEvent, fakeAsync, tick, flushMicrotasks, expect, it, inject, iit, xit, browserDetection} from 'angular2/testing_internal'; import {DOM} from 'angular2/src/platform/dom/dom_adapter'; import {Control, ControlGroup, ControlValueAccessor, FORM_DIRECTIVES, NG_VALIDATORS, NG_ASYNC_VALIDATORS, NgControl, NgIf, NgFor, NgForm, Validators, Validator, RadioButtonState} from 'angular2/common'; import {Provider, forwardRef, Input} from 'angular2/core'; import {By} from 'angular2/platform/browser'; import {ListWrapper} from 'angular2/src/facade/collection'; import {ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async'; import {CONST_EXPR} from 'angular2/src/facade/lang'; import {PromiseWrapper} from 'angular2/src/facade/promise'; export function main() { describe('integration tests', () => { it('should initialize DOM elements with the given form object', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'login': new Control('loginValue')}); fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.value).toEqual('loginValue'); async.done(); }); })); it('should update the control group values on DOM change', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({'login': new Control('oldValue')}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'updatedValue'; dispatchEvent(input.nativeElement, 'input'); expect(form.value).toEqual({'login': 'updatedValue'}); async.done(); }); })); it('should ignore the change event for ', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({'login': new Control('oldValue')}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'updatedValue'; ObservableWrapper.subscribe( form.valueChanges, (value) => { throw 'Should not happen'; }); dispatchEvent(input.nativeElement, 'change'); async.done(); }); })); it('should emit ngSubmit event on submit', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
{{name}}
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.debugElement.componentInstance.form = new ControlGroup({}); 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 work with single controls', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var control = new Control('loginValue'); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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 control group', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'login': new Control('oldValue')}); fixture.detectChanges(); fixture.debugElement.componentInstance.form = new ControlGroup({'login': new Control('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) => { var login = new Control('oldValue'); var form = new ControlGroup({'login': login}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); login.updateValue('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) => { var login = new Control('oldValue'); var form = new ControlGroup({'login': login}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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(); }); })); describe('different control types', () => { it('should support ', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'text': new Control('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) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'text': new Control('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(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'text': new Control('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) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'checkbox': new Control(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) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'num': new Control(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 ', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({ 'foodChicken': new Control(new RadioButtonState(false, 'chicken')), 'foodFish': new Control(new RadioButtonState(true, 'fish')) }); fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.checked).toEqual(false); dispatchEvent(input.nativeElement, 'change'); fixture.detectChanges(); let value = fixture.debugElement.componentInstance.form.value; expect(value['foodChicken'].checked).toEqual(true); expect(value['foodFish'].checked).toEqual(false); async.done(); }); })); describe('should support `; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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) => { var t = ``; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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 ngControl', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'city': new Control('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, 'input'); expect(fixture.debugElement.componentInstance.form.value).toEqual({ 'city': 'NYC' }); expect(sfOption.nativeElement.selected).toBe(false); async.done(); }); })); it('with a dynamic list of options', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (compFixture) => fixture = compFixture); tick(); fixture.debugElement.componentInstance.form = new ControlGroup({'city': new Control('NYC')}); fixture.debugElement.componentInstance.data = ['SF', 'NYC']; fixture.detectChanges(); tick(); var select = fixture.debugElement.query(By.css('select')); expect(select.nativeElement.value).toEqual('NYC'); }))); it('with option values that are objects', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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]; expect(select.nativeElement.value).toEqual('1: Object'); expect(nycOption.nativeElement.selected).toBe(true); select.nativeElement.value = '2: Object'; dispatchEvent(select.nativeElement, 'input'); fixture.detectChanges(); TimerWrapper.setTimeout(() => { expect(testComp.selectedCity['name']).toEqual('Buffalo'); async.done(); }, 0); }); })); it('when new options are added', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { var testComp: MyComp = 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(); 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); async.done(); }); })); it('when options are removed', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { var testComp: MyComp = fixture.debugElement.componentInstance; testComp.list = [{'name': 'SF'}, {'name': 'NYC'}]; testComp.selectedCity = testComp.list[1]; fixture.detectChanges(); var select = fixture.debugElement.query(By.css('select')); expect(select.nativeElement.value).toEqual('1: Object'); testComp.list.pop(); fixture.detectChanges(); expect(select.nativeElement.value).not.toEqual('1: Object'); async.done(); }); })); it('when option values change identity while tracking by index', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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(); 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); async.done(); }); })); it('with duplicate option values', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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(); 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); async.done(); }); })); it('when option values have same content, but different identities', inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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(); 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); async.done(); }); })); }); it('should support custom value accessors', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'name': new Control('aa')}); fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.value).toEqual('!aa!'); input.nativeElement.value = '!bb!'; dispatchEvent(input.nativeElement, 'input'); expect(fixture.debugElement.componentInstance.form.value).toEqual({'name': 'bb'}); async.done(); }); })); 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) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = new ControlGroup({'name': new Control('aa')}); fixture.detectChanges(); var input = fixture.debugElement.query(By.css('my-input')); expect(input.componentInstance.value).toEqual('!aa!'); input.componentInstance.value = '!bb!'; ObservableWrapper.subscribe(input.componentInstance.onInput, (value) => { expect(fixture.debugElement.componentInstance.form.value).toEqual({'name': 'bb'}); async.done(); }); input.componentInstance.dispatchChangeEvent(); }); })); }); describe('validations', () => { it('should use sync validators defined in html', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup( {'login': new Control(''), 'min': new Control(''), 'max': new Control('')}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var required = fixture.debugElement.query(By.css('[required]')); var minLength = fixture.debugElement.query(By.css('[minlength]')); var maxLength = fixture.debugElement.query(By.css('[maxlength]')); 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); 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); async.done(); }); })); it('should use async validators defined in the html', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new ControlGroup({'login': new Control('')}); var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => rootTC = root); tick(); rootTC.debugElement.componentInstance.form = form; rootTC.detectChanges(); expect(form.pending).toEqual(true); tick(100); expect(form.hasError('uniqLogin', ['login'])).toEqual(true); var input = rootTC.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) => { var form = new ControlGroup({'login': new Control('aa', Validators.required)}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); expect(form.valid).toEqual(true); 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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var control = new Control('', Validators.required, uniqLoginAsyncValidator('expected')); var form = new ControlGroup({'login': control}); var t = `
`; var fixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => fixture = root); tick(); fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); expect(form.hasError('required', ['login'])).toEqual(true); var input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'wrong value'; dispatchEvent(input.nativeElement, 'input'); expect(form.pending).toEqual(true); tick(); expect(form.hasError('uniqLogin', ['login'])).toEqual(true); input.nativeElement.value = 'expected'; dispatchEvent(input.nativeElement, 'input'); tick(); expect(form.valid).toEqual(true); }))); }); describe('nested forms', () => { it('should init DOM with the given form object', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({'nested': new ControlGroup({'login': new Control('value')})}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.value).toEqual('value'); async.done(); }); })); it('should update the control group values on DOM change', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({'nested': new ControlGroup({'login': new Control('value')})}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')); input.nativeElement.value = 'updatedValue'; dispatchEvent(input.nativeElement, 'input'); expect(form.value).toEqual({'nested': {'login': 'updatedValue'}}); async.done(); }); })); }); it('should support ngModel for complex forms', inject( [TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new ControlGroup({'name': new Control('')}); var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); 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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new Control(''); var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); 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('template-driven forms', () => { it('should add new controls and control groups', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.debugElement.componentInstance.name = null; fixture.detectChanges(); var form = fixture.debugElement.children[0].inject(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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); 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 not create a template-driven form when ngNoForm is used', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.name = null; fixture.detectChanges(); expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); async.done(); }); })); it('should remove controls', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.debugElement.componentInstance.name = 'show'; fixture.detectChanges(); tick(); var form = fixture.debugElement.children[0].inject(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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.debugElement.componentInstance.name = 'show'; fixture.detectChanges(); tick(); var form = fixture.debugElement.children[0].inject(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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); 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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); 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'); }))); it('should support ', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((f) => { fixture = f; }); tick(); fixture.debugElement.componentInstance.data = { 'chicken1': new RadioButtonState(false, 'chicken'), 'fish1': new RadioButtonState(true, 'fish'), 'chicken2': new RadioButtonState(false, 'chicken'), 'fish2': new RadioButtonState(true, 'fish') }; fixture.detectChanges(); tick(); var input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.checked).toEqual(false); dispatchEvent(input.nativeElement, 'change'); tick(); let data = fixture.debugElement.componentInstance.data; expect(data['chicken1']).toEqual(new RadioButtonState(true, 'chicken')); expect(data['fish1']).toEqual(new RadioButtonState(false, 'fish')); expect(data['chicken2']).toEqual(new RadioButtonState(false, 'chicken')); expect(data['fish2']).toEqual(new RadioButtonState(true, 'fish')); }))); }); describe('setting status classes', () => { it('should work with single fields', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new Control('', Validators.required); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')).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 complex model-driven forms', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({'name': new Control('', Validators.required)}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => { fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); var input = fixture.debugElement.query(By.css('input')).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 ngModel', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new Control(''); var t = `
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.debugElement.componentInstance.form = form; fixture.detectChanges(); // In Firefox, effective text selection in the real DOM requires an actual focus // of the field. This is not an issue in a new HTML document. if (browserDetection.isFirefox) { var fakeDoc = DOM.createHtmlDocument(); DOM.appendChild(fakeDoc.body, fixture.debugElement.nativeElement); } var input = fixture.debugElement.query(By.css('input')).nativeElement; input.value = 'aa'; input.selectionStart = 1; dispatchEvent(input, 'input'); 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', inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = ``; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); 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'); tick(); fixture.detectChanges(); expect(fixture.debugElement.componentInstance.name).toEqual('aa'); // Programatically update the input value to be "bb". fixture.debugElement.componentInstance.name = 'bb'; tick(); fixture.detectChanges(); expect(input.value).toEqual('bb'); // Programatically set it back to "aa". fixture.debugElement.componentInstance.name = 'aa'; tick(); fixture.detectChanges(); expect(input.value).toEqual('aa'); }))); it('should not crash when validity is checked from a binding', inject([TestComponentBuilder], fakeAsync((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. var t = `
{{x.valid}}
`; var fixture: ComponentFixture; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { fixture = root; }); tick(); fixture.detectChanges(); }))); }); }); } @Directive({ selector: '[wrapped-value]', host: {'(input)': 'handleOnInput($event.target.value)', '[value]': 'value'} }) class WrappedValue implements ControlValueAccessor { value; onChange: Function; constructor(cd: NgControl) { cd.valueAccessor = this; } writeValue(value) { this.value = `!${value}!`; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) {} handleOnInput(value) { this.onChange(value.substring(1, value.length - 1)); } } @Component({selector: 'my-input', template: ''}) class MyInput implements ControlValueAccessor { @Output('input') onInput: EventEmitter = new EventEmitter(); value: string; constructor(cd: NgControl) { cd.valueAccessor = this; } writeValue(value) { this.value = `!${value}!`; } registerOnChange(fn) { ObservableWrapper.subscribe(this.onInput, fn); } registerOnTouched(fn) {} dispatchChangeEvent() { ObservableWrapper.callEmit(this.onInput, this.value.substring(1, this.value.length - 1)); } } function uniqLoginAsyncValidator(expectedValue: string) { return (c) => { var completer = PromiseWrapper.completer(); var res = (c.value == expectedValue) ? null : {'uniqLogin': true}; completer.resolve(res); return completer.promise; }; } function loginIsEmptyGroupValidator(c: ControlGroup) { return c.controls['login'].value == '' ? {'loginIsEmpty': true} : null; } @Directive({ selector: '[login-is-empty-validator]', providers: [new Provider(NG_VALIDATORS, {useValue: loginIsEmptyGroupValidator, multi: true})] }) class LoginIsEmptyValidator { } @Directive({ selector: '[uniq-login-validator]', providers: [new Provider( NG_ASYNC_VALIDATORS, {useExisting: forwardRef(() => UniqLoginValidator), multi: true})] }) class UniqLoginValidator implements Validator { @Input('uniq-login-validator') expected; validate(c) { return uniqLoginAsyncValidator(this.expected)(c); } } @Component({ selector: 'my-comp', template: '', directives: [ FORM_DIRECTIVES, WrappedValue, MyInput, NgIf, NgFor, LoginIsEmptyValidator, UniqLoginValidator ] }) class MyComp { form: any; name: string; data: any; list: any[]; selectedCity: any; customTrackBy(index: number, obj: any): number { return index; }; } function sortedClassList(el) { var l = DOM.classList(el); ListWrapper.sort(l); return l; }