/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {fakeAsync, flushMicrotasks, tick,} from '@angular/core/testing'; import {SpyNgControl, SpyValueAccessor} from '../spies'; import {ControlGroup, Control, NgControlName, NgControlGroup, NgFormModel, ControlValueAccessor, Validators, NgForm, NgModel, NgFormControl, NgControl, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, Validator} from '@angular/common/src/forms-deprecated'; import {selectValueAccessor, composeValidators} from '@angular/common/src/forms-deprecated/directives/shared'; import {TimerWrapper} from '../../src/facade/async'; import {PromiseWrapper} from '../../src/facade/promise'; import {SimpleChange} from '@angular/core/src/change_detection'; class DummyControlValueAccessor implements ControlValueAccessor { writtenValue: any /** TODO #9100 */; registerOnChange(fn: any /** TODO #9100 */) {} registerOnTouched(fn: any /** TODO #9100 */) {} writeValue(obj: any): void { this.writtenValue = obj; } } class CustomValidatorDirective implements Validator { validate(c: Control): {[key: string]: any} { return {'custom': true}; } } function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) { return (c: any /** TODO #9100 */) => { 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: DefaultValueAccessor; beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); }); describe('shared', () => { describe('selectValueAccessor', () => { var dir: NgControl; beforeEach(() => { dir = 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 = new SpyValueAccessor(); expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError(); }); }); describe('composeValidators', () => { it('should compose functions', () => { var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); var dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true}); var v = composeValidators([dummy1, dummy2]); expect(v(new Control(''))).toEqual({'dummy1': true, 'dummy2': true}); }); it('should compose validator directives', () => { var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); var v = composeValidators([dummy1, new CustomValidatorDirective()]); expect(v(new Control(''))).toEqual({'dummy1': true, 'custom': true}); }); }); }); describe('NgFormModel', () => { var form: any /** TODO #9100 */; var formModel: ControlGroup; var loginControlDir: any /** TODO #9100 */; 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 with name: '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 form control with name: 'login'`)); }); it('should throw when no value accessor with path', () => { const group = new NgControlGroup(form, null, null); const dir = new NgControlName(group, null, null, null); group.name = 'passwords'; dir.name = 'password'; expect(() => form.addControl(dir)) .toThrowError(new RegExp( `No value accessor for form control with path: 'passwords -> password'`)); }); 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); (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', () => { (formModel.find(['login'])).updateValue('initValue'); form.addControl(loginControlDir); expect((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: any /** TODO #9100 */) => { 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); (formModel.find(['passwords', 'password'])).updateValue('somePassword'); (formModel.find([ 'passwords', 'passwordConfirm' ])).updateValue('someOtherPassword'); // sync validators are set expect(formModel.hasError('differentPasswords', ['passwords'])).toEqual(true); (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); (formModel.find(['login'])).updateValue('new value'); form.ngOnChanges({}); expect((loginControlDir.valueAccessor).writtenValue).toEqual('new value'); }); it('should set up a sync validator', () => { var formValidator = (c: any /** TODO #9100 */) => ({'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: any /** TODO #9100 */; var formModel: ControlGroup; var loginControlDir: any /** TODO #9100 */; var personControlGroupDir: any /** TODO #9100 */; 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: any /** TODO #9100 */) => ({'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: any /** TODO #9100 */; var controlGroupDir: any /** TODO #9100 */; 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: any /** TODO #9100 */; var control: any /** TODO #9100 */; var checkProperties = function(control: any /** TODO #9100 */) { 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: any /** TODO #9100 */; 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 throw when no value accessor with unnamed control', () => { const unnamedDir = new NgModel(null, null, null); expect(() => unnamedDir.ngOnChanges({})) .toThrowError(new RegExp(`No value accessor for form control with unspecified name`)); }); 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: any /** TODO #9100 */; var controlNameDir: any /** TODO #9100 */; 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); }); }); }); }