BREAKING CHANGE:
- Renderer:
  * renderComponent method is removed form `Renderer`, only present on `RootRenderer`
  * Renderer.setDebugInfo is removed. Renderer.createElement / createText / createTemplateAnchor
    now take the DebugInfo directly.
- Query semantics:
  * Queries don't work with dynamically loaded components.
  * e.g. for router-outlet: loaded components can't be queries via @ViewQuery,
    but router-outlet emits an event `activate` now that emits the activated component
- Exception classes and the context inside changed (renamed fields)
- DebugElement.attributes is an Object and not a Map in JS any more
- ChangeDetectorGenConfig was renamed into CompilerConfig
- AppViewManager.createEmbeddedViewInContainer / AppViewManager.createHostViewInContainer
  are removed, use the methods in ViewContainerRef instead
- Change detection order changed:
  * 1. dirty check component inputs
  * 2. dirty check content children
  * 3. update render nodes
Closes #6301
Closes #6567
		
	
			
		
			
				
	
	
		
			494 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			494 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   ddescribe,
 | |
|   describe,
 | |
|   fakeAsync,
 | |
|   flushMicrotasks,
 | |
|   it,
 | |
|   iit,
 | |
|   xit,
 | |
|   expect,
 | |
|   beforeEach,
 | |
|   afterEach,
 | |
|   el,
 | |
|   AsyncTestCompleter,
 | |
|   inject,
 | |
|   tick
 | |
| } from 'angular2/testing_internal';
 | |
| 
 | |
| import {SpyNgControl, SpyValueAccessor} from '../spies';
 | |
| 
 | |
| import {
 | |
|   ControlGroup,
 | |
|   Control,
 | |
|   NgControlName,
 | |
|   NgControlGroup,
 | |
|   NgFormModel,
 | |
|   ControlValueAccessor,
 | |
|   Validators,
 | |
|   NgForm,
 | |
|   NgModel,
 | |
|   NgFormControl,
 | |
|   NgControl,
 | |
|   DefaultValueAccessor,
 | |
|   CheckboxControlValueAccessor,
 | |
|   SelectControlValueAccessor,
 | |
|   Validator
 | |
| } from 'angular2/common';
 | |
| 
 | |
| 
 | |
| import {selectValueAccessor, composeValidators} from 'angular2/src/common/forms/directives/shared';
 | |
| import {TimerWrapper} from 'angular2/src/facade/async';
 | |
| import {PromiseWrapper} from 'angular2/src/facade/promise';
 | |
| import {SimpleChange} from 'angular2/src/core/change_detection';
 | |
| 
 | |
| class DummyControlValueAccessor implements ControlValueAccessor {
 | |
|   writtenValue;
 | |
| 
 | |
|   registerOnChange(fn) {}
 | |
|   registerOnTouched(fn) {}
 | |
| 
 | |
|   writeValue(obj: any): void { this.writtenValue = obj; }
 | |
| }
 | |
| 
 | |
| class CustomValidatorDirective implements Validator {
 | |
|   validate(c: Control): {[key: string]: any} { return {"custom": true}; }
 | |
| }
 | |
| 
 | |
| function asyncValidator(expected, timeout = 0) {
 | |
|   return (c) => {
 | |
|     var completer = PromiseWrapper.completer();
 | |
|     var res = c.value != expected ? {"async": true} : null;
 | |
|     if (timeout == 0) {
 | |
|       completer.resolve(res);
 | |
|     } else {
 | |
|       TimerWrapper.setTimeout(() => { completer.resolve(res); }, timeout);
 | |
|     }
 | |
|     return completer.promise;
 | |
|   };
 | |
| }
 | |
| 
 | |
| export function main() {
 | |
|   describe("Form Directives", () => {
 | |
|     var defaultAccessor;
 | |
| 
 | |
|     beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
 | |
| 
 | |
|     describe("shared", () => {
 | |
|       describe("selectValueAccessor", () => {
 | |
|         var dir: NgControl;
 | |
| 
 | |
|         beforeEach(() => { dir = <any>new SpyNgControl(); });
 | |
| 
 | |
|         it("should throw when given an empty array",
 | |
|            () => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
 | |
| 
 | |
|         it("should return the default value accessor when no other provided",
 | |
|            () => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
 | |
| 
 | |
|         it("should return checkbox accessor when provided", () => {
 | |
|           var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
 | |
|           expect(selectValueAccessor(dir, [defaultAccessor, checkboxAccessor]))
 | |
|               .toEqual(checkboxAccessor);
 | |
|         });
 | |
| 
 | |
|         it("should return select accessor when provided", () => {
 | |
|           var selectAccessor = new SelectControlValueAccessor(null, null);
 | |
|           expect(selectValueAccessor(dir, [defaultAccessor, selectAccessor]))
 | |
|               .toEqual(selectAccessor);
 | |
|         });
 | |
| 
 | |
|         it("should throw when more than one build-in accessor is provided", () => {
 | |
|           var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
 | |
|           var selectAccessor = new SelectControlValueAccessor(null, null);
 | |
|           expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
 | |
|         });
 | |
| 
 | |
|         it("should return custom accessor when provided", () => {
 | |
|           var customAccessor = new SpyValueAccessor();
 | |
|           var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
 | |
|           expect(selectValueAccessor(dir, [defaultAccessor, customAccessor, checkboxAccessor]))
 | |
|               .toEqual(customAccessor);
 | |
|         });
 | |
| 
 | |
|         it("should throw when more than one custom accessor is provided", () => {
 | |
|           var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
 | |
|           expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe("composeValidators", () => {
 | |
|         it("should compose functions", () => {
 | |
|           var dummy1 = (_) => ({"dummy1": true});
 | |
|           var dummy2 = (_) => ({"dummy2": true});
 | |
|           var v = composeValidators([dummy1, dummy2]);
 | |
|           expect(v(new Control(""))).toEqual({"dummy1": true, "dummy2": true});
 | |
|         });
 | |
| 
 | |
|         it("should compose validator directives", () => {
 | |
|           var dummy1 = (_) => ({"dummy1": true});
 | |
|           var v = composeValidators([dummy1, new CustomValidatorDirective()]);
 | |
|           expect(v(new Control(""))).toEqual({"dummy1": true, "custom": true});
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe("NgFormModel", () => {
 | |
|       var form;
 | |
|       var formModel: ControlGroup;
 | |
|       var loginControlDir;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         form = new NgFormModel([], []);
 | |
|         formModel = new ControlGroup({
 | |
|           "login": new Control(),
 | |
|           "passwords":
 | |
|               new ControlGroup({"password": new Control(), "passwordConfirm": new Control()})
 | |
|         });
 | |
|         form.form = formModel;
 | |
| 
 | |
|         loginControlDir = new NgControlName(form, [Validators.required],
 | |
|                                             [asyncValidator("expected")], [defaultAccessor]);
 | |
|         loginControlDir.name = "login";
 | |
|         loginControlDir.valueAccessor = new DummyControlValueAccessor();
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => {
 | |
|         expect(form.control).toBe(formModel);
 | |
|         expect(form.value).toBe(formModel.value);
 | |
|         expect(form.valid).toBe(formModel.valid);
 | |
|         expect(form.errors).toBe(formModel.errors);
 | |
|         expect(form.pristine).toBe(formModel.pristine);
 | |
|         expect(form.dirty).toBe(formModel.dirty);
 | |
|         expect(form.touched).toBe(formModel.touched);
 | |
|         expect(form.untouched).toBe(formModel.untouched);
 | |
|       });
 | |
| 
 | |
|       describe("addControl", () => {
 | |
|         it("should throw when no control found", () => {
 | |
|           var dir = new NgControlName(form, null, null, [defaultAccessor]);
 | |
|           dir.name = "invalidName";
 | |
| 
 | |
|           expect(() => form.addControl(dir))
 | |
|               .toThrowError(new RegExp("Cannot find control 'invalidName'"));
 | |
|         });
 | |
| 
 | |
|         it("should throw when no value accessor", () => {
 | |
|           var dir = new NgControlName(form, null, null, null);
 | |
|           dir.name = "login";
 | |
| 
 | |
|           expect(() => form.addControl(dir))
 | |
|               .toThrowError(new RegExp("No value accessor for 'login'"));
 | |
|         });
 | |
| 
 | |
|         it("should set up validators", fakeAsync(() => {
 | |
|              form.addControl(loginControlDir);
 | |
| 
 | |
|              // sync validators are set
 | |
|              expect(formModel.hasError("required", ["login"])).toBe(true);
 | |
|              expect(formModel.hasError("async", ["login"])).toBe(false);
 | |
| 
 | |
|              (<Control>formModel.find(["login"])).updateValue("invalid value");
 | |
| 
 | |
|              // sync validator passes, running async validators
 | |
|              expect(formModel.pending).toBe(true);
 | |
| 
 | |
|              tick();
 | |
| 
 | |
|              expect(formModel.hasError("required", ["login"])).toBe(false);
 | |
|              expect(formModel.hasError("async", ["login"])).toBe(true);
 | |
|            }));
 | |
| 
 | |
|         it("should write value to the DOM", () => {
 | |
|           (<Control>formModel.find(["login"])).updateValue("initValue");
 | |
| 
 | |
|           form.addControl(loginControlDir);
 | |
| 
 | |
|           expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("initValue");
 | |
|         });
 | |
| 
 | |
|         it("should add the directive to the list of directives included in the form", () => {
 | |
|           form.addControl(loginControlDir);
 | |
|           expect(form.directives).toEqual([loginControlDir]);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe("addControlGroup", () => {
 | |
|         var matchingPasswordsValidator = (g) => {
 | |
|           if (g.controls["password"].value != g.controls["passwordConfirm"].value) {
 | |
|             return {"differentPasswords": true};
 | |
|           } else {
 | |
|             return null;
 | |
|           }
 | |
|         };
 | |
| 
 | |
|         it("should set up validator", fakeAsync(() => {
 | |
|              var group = new NgControlGroup(form, [matchingPasswordsValidator],
 | |
|                                             [asyncValidator('expected')]);
 | |
|              group.name = "passwords";
 | |
|              form.addControlGroup(group);
 | |
| 
 | |
|              (<Control>formModel.find(["passwords", "password"])).updateValue("somePassword");
 | |
|              (<Control>formModel.find(["passwords", "passwordConfirm"]))
 | |
|                  .updateValue("someOtherPassword");
 | |
| 
 | |
|              // sync validators are set
 | |
|              expect(formModel.hasError("differentPasswords", ["passwords"])).toEqual(true);
 | |
| 
 | |
|              (<Control>formModel.find(["passwords", "passwordConfirm"]))
 | |
|                  .updateValue("somePassword");
 | |
| 
 | |
|              // sync validators pass, running async validators
 | |
|              expect(formModel.pending).toBe(true);
 | |
| 
 | |
|              tick();
 | |
| 
 | |
|              expect(formModel.hasError("async", ["passwords"])).toBe(true);
 | |
|            }));
 | |
|       });
 | |
| 
 | |
|       describe("removeControl", () => {
 | |
|         it("should remove the directive to the list of directives included in the form", () => {
 | |
|           form.addControl(loginControlDir);
 | |
|           form.removeControl(loginControlDir);
 | |
|           expect(form.directives).toEqual([]);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       describe("ngOnChanges", () => {
 | |
|         it("should update dom values of all the directives", () => {
 | |
|           form.addControl(loginControlDir);
 | |
| 
 | |
|           (<Control>formModel.find(["login"])).updateValue("new value");
 | |
| 
 | |
|           form.ngOnChanges({});
 | |
| 
 | |
|           expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("new value");
 | |
|         });
 | |
| 
 | |
|         it("should set up a sync validator", () => {
 | |
|           var formValidator = (c) => ({"custom": true});
 | |
|           var f = new NgFormModel([formValidator], []);
 | |
|           f.form = formModel;
 | |
|           f.ngOnChanges({"form": new SimpleChange(null, null)});
 | |
| 
 | |
|           expect(formModel.errors).toEqual({"custom": true});
 | |
|         });
 | |
| 
 | |
|         it("should set up an async validator", fakeAsync(() => {
 | |
|              var f = new NgFormModel([], [asyncValidator("expected")]);
 | |
|              f.form = formModel;
 | |
|              f.ngOnChanges({"form": new SimpleChange(null, null)});
 | |
| 
 | |
|              tick();
 | |
| 
 | |
|              expect(formModel.errors).toEqual({"async": true});
 | |
|            }));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe("NgForm", () => {
 | |
|       var form;
 | |
|       var formModel: ControlGroup;
 | |
|       var loginControlDir;
 | |
|       var personControlGroupDir;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         form = new NgForm([], []);
 | |
|         formModel = form.form;
 | |
| 
 | |
|         personControlGroupDir = new NgControlGroup(form, [], []);
 | |
|         personControlGroupDir.name = "person";
 | |
| 
 | |
|         loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
 | |
|         loginControlDir.name = "login";
 | |
|         loginControlDir.valueAccessor = new DummyControlValueAccessor();
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => {
 | |
|         expect(form.control).toBe(formModel);
 | |
|         expect(form.value).toBe(formModel.value);
 | |
|         expect(form.valid).toBe(formModel.valid);
 | |
|         expect(form.errors).toBe(formModel.errors);
 | |
|         expect(form.pristine).toBe(formModel.pristine);
 | |
|         expect(form.dirty).toBe(formModel.dirty);
 | |
|         expect(form.touched).toBe(formModel.touched);
 | |
|         expect(form.untouched).toBe(formModel.untouched);
 | |
|       });
 | |
| 
 | |
|       describe("addControl & addControlGroup", () => {
 | |
|         it("should create a control with the given name", fakeAsync(() => {
 | |
|              form.addControlGroup(personControlGroupDir);
 | |
|              form.addControl(loginControlDir);
 | |
| 
 | |
|              flushMicrotasks();
 | |
| 
 | |
|              expect(formModel.find(["person", "login"])).not.toBeNull;
 | |
|            }));
 | |
| 
 | |
|         // should update the form's value and validity
 | |
|       });
 | |
| 
 | |
|       describe("removeControl & removeControlGroup", () => {
 | |
|         it("should remove control", fakeAsync(() => {
 | |
|              form.addControlGroup(personControlGroupDir);
 | |
|              form.addControl(loginControlDir);
 | |
| 
 | |
|              form.removeControlGroup(personControlGroupDir);
 | |
|              form.removeControl(loginControlDir);
 | |
| 
 | |
|              flushMicrotasks();
 | |
| 
 | |
|              expect(formModel.find(["person"])).toBeNull();
 | |
|              expect(formModel.find(["person", "login"])).toBeNull();
 | |
|            }));
 | |
| 
 | |
|         // should update the form's value and validity
 | |
|       });
 | |
| 
 | |
|       it("should set up sync validator", fakeAsync(() => {
 | |
|            var formValidator = (c) => ({"custom": true});
 | |
|            var f = new NgForm([formValidator], []);
 | |
| 
 | |
|            tick();
 | |
| 
 | |
|            expect(f.form.errors).toEqual({"custom": true});
 | |
|          }));
 | |
| 
 | |
|       it("should set up async validator", fakeAsync(() => {
 | |
|            var f = new NgForm([], [asyncValidator("expected")]);
 | |
| 
 | |
|            tick();
 | |
| 
 | |
|            expect(f.form.errors).toEqual({"async": true});
 | |
|          }));
 | |
|     });
 | |
| 
 | |
|     describe("NgControlGroup", () => {
 | |
|       var formModel;
 | |
|       var controlGroupDir;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         formModel = new ControlGroup({"login": new Control(null)});
 | |
| 
 | |
|         var parent = new NgFormModel([], []);
 | |
|         parent.form = new ControlGroup({"group": formModel});
 | |
|         controlGroupDir = new NgControlGroup(parent, [], []);
 | |
|         controlGroupDir.name = "group";
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => {
 | |
|         expect(controlGroupDir.control).toBe(formModel);
 | |
|         expect(controlGroupDir.value).toBe(formModel.value);
 | |
|         expect(controlGroupDir.valid).toBe(formModel.valid);
 | |
|         expect(controlGroupDir.errors).toBe(formModel.errors);
 | |
|         expect(controlGroupDir.pristine).toBe(formModel.pristine);
 | |
|         expect(controlGroupDir.dirty).toBe(formModel.dirty);
 | |
|         expect(controlGroupDir.touched).toBe(formModel.touched);
 | |
|         expect(controlGroupDir.untouched).toBe(formModel.untouched);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe("NgFormControl", () => {
 | |
|       var controlDir;
 | |
|       var control;
 | |
|       var checkProperties = function(control) {
 | |
|         expect(controlDir.control).toBe(control);
 | |
|         expect(controlDir.value).toBe(control.value);
 | |
|         expect(controlDir.valid).toBe(control.valid);
 | |
|         expect(controlDir.errors).toBe(control.errors);
 | |
|         expect(controlDir.pristine).toBe(control.pristine);
 | |
|         expect(controlDir.dirty).toBe(control.dirty);
 | |
|         expect(controlDir.touched).toBe(control.touched);
 | |
|         expect(controlDir.untouched).toBe(control.untouched);
 | |
|       };
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
 | |
|         controlDir.valueAccessor = new DummyControlValueAccessor();
 | |
| 
 | |
|         control = new Control(null);
 | |
|         controlDir.form = control;
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => { checkProperties(control); });
 | |
| 
 | |
|       it("should reexport new control properties", () => {
 | |
|         var newControl = new Control(null);
 | |
|         controlDir.form = newControl;
 | |
|         controlDir.ngOnChanges({"form": new SimpleChange(control, newControl)});
 | |
| 
 | |
|         checkProperties(newControl);
 | |
|       });
 | |
| 
 | |
|       it("should set up validator", () => {
 | |
|         expect(control.valid).toBe(true);
 | |
| 
 | |
|         // this will add the required validator and recalculate the validity
 | |
|         controlDir.ngOnChanges({"form": new SimpleChange(null, control)});
 | |
| 
 | |
|         expect(control.valid).toBe(false);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe("NgModel", () => {
 | |
|       var ngModel;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         ngModel =
 | |
|             new NgModel([Validators.required], [asyncValidator("expected")], [defaultAccessor]);
 | |
|         ngModel.valueAccessor = new DummyControlValueAccessor();
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => {
 | |
|         var control = ngModel.control;
 | |
|         expect(ngModel.control).toBe(control);
 | |
|         expect(ngModel.value).toBe(control.value);
 | |
|         expect(ngModel.valid).toBe(control.valid);
 | |
|         expect(ngModel.errors).toBe(control.errors);
 | |
|         expect(ngModel.pristine).toBe(control.pristine);
 | |
|         expect(ngModel.dirty).toBe(control.dirty);
 | |
|         expect(ngModel.touched).toBe(control.touched);
 | |
|         expect(ngModel.untouched).toBe(control.untouched);
 | |
|       });
 | |
| 
 | |
|       it("should set up validator", fakeAsync(() => {
 | |
|            // this will add the required validator and recalculate the validity
 | |
|            ngModel.ngOnChanges({});
 | |
|            tick();
 | |
| 
 | |
|            expect(ngModel.control.errors).toEqual({"required": true});
 | |
| 
 | |
|            ngModel.control.updateValue("someValue");
 | |
|            tick();
 | |
| 
 | |
|            expect(ngModel.control.errors).toEqual({"async": true});
 | |
|          }));
 | |
|     });
 | |
| 
 | |
|     describe("NgControlName", () => {
 | |
|       var formModel;
 | |
|       var controlNameDir;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         formModel = new Control("name");
 | |
| 
 | |
|         var parent = new NgFormModel([], []);
 | |
|         parent.form = new ControlGroup({"name": formModel});
 | |
|         controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
 | |
|         controlNameDir.name = "name";
 | |
|       });
 | |
| 
 | |
|       it("should reexport control properties", () => {
 | |
|         expect(controlNameDir.control).toBe(formModel);
 | |
|         expect(controlNameDir.value).toBe(formModel.value);
 | |
|         expect(controlNameDir.valid).toBe(formModel.valid);
 | |
|         expect(controlNameDir.errors).toBe(formModel.errors);
 | |
|         expect(controlNameDir.pristine).toBe(formModel.pristine);
 | |
|         expect(controlNameDir.dirty).toBe(formModel.dirty);
 | |
|         expect(controlNameDir.touched).toBe(formModel.touched);
 | |
|         expect(controlNameDir.untouched).toBe(formModel.untouched);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| }
 |