import {Component, Directive, View} from 'angular2/angular2'; import { afterEach, AsyncTestCompleter, TestComponentBuilder, By, beforeEach, ddescribe, describe, dispatchEvent, fakeAsync, tick, expect, it, inject, iit, xit, browserDetection } from 'angular2/test_lib'; import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {NgIf, NgFor} from 'angular2/directives'; import { Control, ControlGroup, NgForm, FORM_DIRECTIVES, Validators, NgControl, ControlValueAccessor } from 'angular2/forms'; 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((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"login": new Control("loginValue")}); rootTC.detectChanges(); var input = rootTC.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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.query(By.css("input")); input.nativeElement.value = "updatedValue"; dispatchEvent(input.nativeElement, "change"); expect(form.value).toEqual({"login": "updatedValue"}); async.done(); }); })); it("should emit ng-submit event on submit", inject( [TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
{{name}}
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => { rootTC = root; }); tick(); rootTC.componentInstance.form = new ControlGroup({}); rootTC.componentInstance.name = 'old'; tick(); var form = rootTC.query(By.css("form")); dispatchEvent(form.nativeElement, "submit"); tick(); expect(rootTC.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((rootTC) => { rootTC.componentInstance.form = control; rootTC.detectChanges(); var input = rootTC.query(By.css("input")); expect(input.nativeElement.value).toEqual("loginValue"); input.nativeElement.value = "updatedValue"; dispatchEvent(input.nativeElement, "change"); 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((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"login": new Control("oldValue")}); rootTC.detectChanges(); rootTC.componentInstance.form = new ControlGroup({"login": new Control("newValue")}); rootTC.detectChanges(); var input = rootTC.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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); login.updateValue("newValue"); rootTC.detectChanges(); var input = rootTC.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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var loginEl = rootTC.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((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"text": new Control("old")}); rootTC.detectChanges(); var input = rootTC.query(By.css("input")); expect(input.nativeElement.value).toEqual("old"); input.nativeElement.value = "new"; dispatchEvent(input.nativeElement, "input"); expect(rootTC.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((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"text": new Control("old")}); rootTC.detectChanges(); var input = rootTC.query(By.css("input")); expect(input.nativeElement.value).toEqual("old"); input.nativeElement.value = "new"; dispatchEvent(input.nativeElement, "input"); expect(rootTC.componentInstance.form.value).toEqual({"text": "new"}); async.done(); }); })); it("should support `; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"text": new Control('old')}); rootTC.detectChanges(); var textarea = rootTC.query(By.css("textarea")); expect(textarea.nativeElement.value).toEqual("old"); textarea.nativeElement.value = "new"; dispatchEvent(textarea.nativeElement, "input"); expect(rootTC.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((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"checkbox": new Control(true)}); rootTC.detectChanges(); var input = rootTC.query(By.css("input")); expect(input.nativeElement.checked).toBe(true); input.nativeElement.checked = false; dispatchEvent(input.nativeElement, "change"); expect(rootTC.componentInstance.form.value).toEqual({"checkbox": false}); async.done(); }); })); it("should support `; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"city": new Control("SF")}); rootTC.detectChanges(); var select = rootTC.query(By.css("select")); var sfOption = rootTC.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(rootTC.componentInstance.form.value).toEqual({"city": 'NYC'}); expect(sfOption.nativeElement.selected).toBe(false); async.done(); }); })); it("should support `; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"city": new Control("NYC")}); rootTC.componentInstance.data = ['SF', 'NYC']; rootTC.detectChanges(); var select = rootTC.query(By.css('select')); expect(select.nativeElement.value).toEqual('NYC'); async.done(); }); })); it("should support custom value accessors", inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.form = new ControlGroup({"name": new Control("aa")}); rootTC.detectChanges(); var input = rootTC.query(By.css("input")); expect(input.nativeElement.value).toEqual("!aa!"); input.nativeElement.value = "!bb!"; dispatchEvent(input.nativeElement, "change"); expect(rootTC.componentInstance.form.value).toEqual({"name": "bb"}); async.done(); }); })); }); describe("validations", () => { it("should use validators defined in html", inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var form = new ControlGroup({"login": new Control("aa")}); var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); expect(form.valid).toEqual(true); var input = rootTC.query(By.css("input")); input.nativeElement.value = ""; dispatchEvent(input.nativeElement, "change"); expect(form.valid).toEqual(false); async.done(); }); })); it("should use 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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); expect(form.valid).toEqual(true); var input = rootTC.query(By.css("input")); input.nativeElement.value = ""; dispatchEvent(input.nativeElement, "change"); expect(form.valid).toEqual(false); async.done(); }); })); }); 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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.query(By.css("input")); input.nativeElement.value = "updatedValue"; dispatchEvent(input.nativeElement, "change"); expect(form.value).toEqual({"nested": {"login": "updatedValue"}}); async.done(); }); })); }); it("should support ng-model for complex forms", inject( [TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new ControlGroup({"name": new Control("")}); var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = 'oldValue'; rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; dispatchEvent(input, "change"); tick(); expect(rootTC.componentInstance.name).toEqual("updatedValue"); }))); it("should support ng-model for single fields", inject( [TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var form = new Control(""); var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => { rootTC = root; }); tick(); rootTC.componentInstance.form = form; rootTC.componentInstance.name = "oldValue"; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; dispatchEvent(input, "change"); tick(); expect(rootTC.componentInstance.name).toEqual("updatedValue"); }))); describe("template-driven forms", () => { it("should add new controls and control groups", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = null; rootTC.detectChanges(); var form = rootTC.componentViewChildren[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 ng-submit event on submit", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = 'old'; var form = rootTC.query(By.css("form")); dispatchEvent(form.nativeElement, "submit"); tick(); expect(rootTC.componentInstance.name).toEqual("updated"); }))); it("should not create a template-driven form when ng-no-form is used", inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.name = null; rootTC.detectChanges(); expect(rootTC.componentViewChildren.length).toEqual(0); async.done(); }); })); it("should remove controls", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = 'show'; rootTC.detectChanges(); tick(); var form = rootTC.componentViewChildren[0].inject(NgForm); expect(form.controls['login']).toBeDefined(); rootTC.componentInstance.name = 'hide'; rootTC.detectChanges(); tick(); expect(form.controls['login']).not.toBeDefined(); }))); it("should remove control groups", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = 'show'; rootTC.detectChanges(); tick(); var form = rootTC.componentViewChildren[0].inject(NgForm); expect(form.controls['user']).toBeDefined(); rootTC.componentInstance.name = 'hide'; rootTC.detectChanges(); tick(); expect(form.controls['user']).not.toBeDefined(); }))); it("should support ng-model for complex forms", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = "oldValue"; rootTC.detectChanges(); tick(); var input = rootTC.query(By.css("input")).nativeElement; expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; dispatchEvent(input, "change"); tick(); expect(rootTC.componentInstance.name).toEqual("updatedValue"); }))); it("should support ng-model for single fields", inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { var t = `
`; var rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.name = "oldValue"; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(input.value).toEqual("oldValue"); input.value = "updatedValue"; dispatchEvent(input, "change"); tick(); expect(rootTC.componentInstance.name).toEqual("updatedValue"); }))); }); 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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(DOM.classList(input)) .toEqual(['ng-binding', 'ng-invalid', 'ng-pristine', 'ng-untouched']); dispatchEvent(input, "blur"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; dispatchEvent(input, "change"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-touched", "ng-dirty", "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((rootTC) => { rootTC.componentInstance.form = form; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-untouched"]); dispatchEvent(input, "blur"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; dispatchEvent(input, "change"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-touched", "ng-dirty", "ng-valid"]); async.done(); }); })); it("should work with ng-model", inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { var t = `
`; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { rootTC.componentInstance.name = ""; rootTC.detectChanges(); var input = rootTC.query(By.css("input")).nativeElement; expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-untouched"]); dispatchEvent(input, "blur"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]); input.value = "updatedValue"; dispatchEvent(input, "change"); rootTC.detectChanges(); expect(DOM.classList(input)) .toEqual(["ng-binding", "ng-touched", "ng-dirty", "ng-valid"]); async.done(); }); })); }); describe("ng-model 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 rootTC; tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then( (root) => { rootTC = root; }); tick(); rootTC.componentInstance.form = form; rootTC.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, rootTC.nativeElement); } var input = rootTC.query(By.css("input")).nativeElement; input.value = "aa"; input.selectionStart = 1; dispatchEvent(input, "change"); tick(); rootTC.detectChanges(); // selection start has not changed because we did not reset the value expect(input.selectionStart).toEqual(1); }))); }); }); } @Directive({ selector: '[wrapped-value]', host: {'(change)': 'handleOnChange($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) {} handleOnChange(value) { this.onChange(value.substring(1, value.length - 1)); } } @Component({selector: "my-comp"}) @View({directives: [FORM_DIRECTIVES, WrappedValue, NgIf, NgFor]}) class MyComp { form: any; name: string; data: any; }