feat(forms): add support for async validations
This commit is contained in:
parent
39626a944d
commit
bb2b961f93
|
@ -1,6 +1,7 @@
|
||||||
library angular2.core.facade.promise;
|
library angular2.core.facade.promise;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:async' as async;
|
||||||
export 'dart:async' show Future;
|
export 'dart:async' show Future;
|
||||||
|
|
||||||
class PromiseWrapper {
|
class PromiseWrapper {
|
||||||
|
@ -29,6 +30,10 @@ class PromiseWrapper {
|
||||||
return promise.catchError(onError);
|
return promise.catchError(onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void scheduleMicrotask(fn) {
|
||||||
|
async.scheduleMicrotask(fn);
|
||||||
|
}
|
||||||
|
|
||||||
static PromiseCompleter<dynamic> completer() =>
|
static PromiseCompleter<dynamic> completer() =>
|
||||||
new PromiseCompleter(new Completer());
|
new PromiseCompleter(new Completer());
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,10 @@ export class PromiseWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static scheduleMicrotask(computation: () => any): void {
|
||||||
|
PromiseWrapper.then(PromiseWrapper.resolve(null), computation, (_) => {});
|
||||||
|
}
|
||||||
|
|
||||||
static completer(): PromiseCompleter<any> {
|
static completer(): PromiseCompleter<any> {
|
||||||
var resolve;
|
var resolve;
|
||||||
var reject;
|
var reject;
|
||||||
|
|
|
@ -105,7 +105,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
||||||
|
|
||||||
addControl(dir: NgControl): void {
|
addControl(dir: NgControl): void {
|
||||||
this._later(_ => {
|
PromiseWrapper.scheduleMicrotask(() => {
|
||||||
var container = this._findContainer(dir.path);
|
var container = this._findContainer(dir.path);
|
||||||
var ctrl = new Control();
|
var ctrl = new Control();
|
||||||
setUpControl(ctrl, dir);
|
setUpControl(ctrl, dir);
|
||||||
|
@ -117,7 +117,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
||||||
|
|
||||||
removeControl(dir: NgControl): void {
|
removeControl(dir: NgControl): void {
|
||||||
this._later(_ => {
|
PromiseWrapper.scheduleMicrotask(() => {
|
||||||
var container = this._findContainer(dir.path);
|
var container = this._findContainer(dir.path);
|
||||||
if (isPresent(container)) {
|
if (isPresent(container)) {
|
||||||
container.removeControl(dir.name);
|
container.removeControl(dir.name);
|
||||||
|
@ -127,7 +127,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
}
|
}
|
||||||
|
|
||||||
addControlGroup(dir: NgControlGroup): void {
|
addControlGroup(dir: NgControlGroup): void {
|
||||||
this._later(_ => {
|
PromiseWrapper.scheduleMicrotask(() => {
|
||||||
var container = this._findContainer(dir.path);
|
var container = this._findContainer(dir.path);
|
||||||
var group = new ControlGroup({});
|
var group = new ControlGroup({});
|
||||||
setUpControlGroup(group, dir);
|
setUpControlGroup(group, dir);
|
||||||
|
@ -137,7 +137,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeControlGroup(dir: NgControlGroup): void {
|
removeControlGroup(dir: NgControlGroup): void {
|
||||||
this._later(_ => {
|
PromiseWrapper.scheduleMicrotask(() => {
|
||||||
var container = this._findContainer(dir.path);
|
var container = this._findContainer(dir.path);
|
||||||
if (isPresent(container)) {
|
if (isPresent(container)) {
|
||||||
container.removeControl(dir.name);
|
container.removeControl(dir.name);
|
||||||
|
@ -151,7 +151,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateModel(dir: NgControl, value: any): void {
|
updateModel(dir: NgControl, value: any): void {
|
||||||
this._later(_ => {
|
PromiseWrapper.scheduleMicrotask(() => {
|
||||||
var ctrl = <Control>this.form.find(dir.path);
|
var ctrl = <Control>this.form.find(dir.path);
|
||||||
ctrl.updateValue(value);
|
ctrl.updateValue(value);
|
||||||
});
|
});
|
||||||
|
@ -167,7 +167,4 @@ export class NgForm extends ControlContainer implements Form {
|
||||||
path.pop();
|
path.pop();
|
||||||
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
|
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_later(fn): void { PromiseWrapper.then(PromiseWrapper.resolve(null), fn, (_) => {}); }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,8 +59,9 @@ export abstract class AbstractControl {
|
||||||
private _pristine: boolean = true;
|
private _pristine: boolean = true;
|
||||||
private _touched: boolean = false;
|
private _touched: boolean = false;
|
||||||
private _parent: ControlGroup | ControlArray;
|
private _parent: ControlGroup | ControlArray;
|
||||||
|
private _asyncValidationSubscription;
|
||||||
|
|
||||||
constructor(public validator: Function) {}
|
constructor(public validator: Function, public asyncValidator: Function) {}
|
||||||
|
|
||||||
get value(): any { return this._value; }
|
get value(): any { return this._value; }
|
||||||
|
|
||||||
|
@ -119,10 +120,14 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
this._updateValue();
|
this._updateValue();
|
||||||
|
|
||||||
this._errors = this.validator(this);
|
this._errors = this._runValidator();
|
||||||
this._controlsErrors = this._calculateControlsErrors();
|
this._controlsErrors = this._calculateControlsErrors();
|
||||||
this._status = this._calculateStatus();
|
this._status = this._calculateStatus();
|
||||||
|
|
||||||
|
if (this._status == VALID || this._status == PENDING) {
|
||||||
|
this._runAsyncValidator();
|
||||||
|
}
|
||||||
|
|
||||||
if (emitEvent) {
|
if (emitEvent) {
|
||||||
ObservableWrapper.callNext(this._valueChanges, this._value);
|
ObservableWrapper.callNext(this._valueChanges, this._value);
|
||||||
}
|
}
|
||||||
|
@ -132,6 +137,23 @@ export abstract class AbstractControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _runValidator() { return isPresent(this.validator) ? this.validator(this) : null; }
|
||||||
|
|
||||||
|
private _runAsyncValidator() {
|
||||||
|
if (isPresent(this.asyncValidator)) {
|
||||||
|
this._status = PENDING;
|
||||||
|
this._cancelExistingSubscription();
|
||||||
|
this._asyncValidationSubscription =
|
||||||
|
ObservableWrapper.subscribe(this.asyncValidator(this), res => this.setErrors(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cancelExistingSubscription(): void {
|
||||||
|
if (isPresent(this._asyncValidationSubscription)) {
|
||||||
|
ObservableWrapper.dispose(this._asyncValidationSubscription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets errors on a control.
|
* Sets errors on a control.
|
||||||
*
|
*
|
||||||
|
@ -190,13 +212,18 @@ export abstract class AbstractControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _calculateStatus(): string {
|
private _calculateStatus(): string {
|
||||||
return isPresent(this._errors) || isPresent(this._controlsErrors) ? INVALID : VALID;
|
if (isPresent(this._errors)) return INVALID;
|
||||||
|
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
|
||||||
|
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
|
||||||
|
return VALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
abstract _updateValue(): void;
|
abstract _updateValue(): void;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
abstract _calculateControlsErrors(): any;
|
abstract _calculateControlsErrors(): any;
|
||||||
|
/** @internal */
|
||||||
|
abstract _anyControlsHaveStatus(status: string): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,8 +246,8 @@ export class Control extends AbstractControl {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_onChange: Function;
|
_onChange: Function;
|
||||||
|
|
||||||
constructor(value: any = null, validator: Function = Validators.nullValidator) {
|
constructor(value: any = null, validator: Function = null, asyncValidator: Function = null) {
|
||||||
super(validator);
|
super(validator, asyncValidator);
|
||||||
this._value = value;
|
this._value = value;
|
||||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||||
this._valueChanges = new EventEmitter();
|
this._valueChanges = new EventEmitter();
|
||||||
|
@ -259,6 +286,11 @@ export class Control extends AbstractControl {
|
||||||
*/
|
*/
|
||||||
_calculateControlsErrors() { return null; }
|
_calculateControlsErrors() { return null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_anyControlsHaveStatus(status: string): boolean { return false; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a listener for change events.
|
* Register a listener for change events.
|
||||||
*/
|
*/
|
||||||
|
@ -282,9 +314,9 @@ export class ControlGroup extends AbstractControl {
|
||||||
private _optionals: {[key: string]: boolean};
|
private _optionals: {[key: string]: boolean};
|
||||||
|
|
||||||
constructor(public controls: {[key: string]: AbstractControl},
|
constructor(public controls: {[key: string]: AbstractControl},
|
||||||
optionals: {[key: string]: boolean} = null,
|
optionals: {[key: string]: boolean} = null, validator: Function = null,
|
||||||
validator: Function = Validators.nullValidator) {
|
asyncValidator: Function = null) {
|
||||||
super(validator);
|
super(validator, asyncValidator);
|
||||||
this._optionals = isPresent(optionals) ? optionals : {};
|
this._optionals = isPresent(optionals) ? optionals : {};
|
||||||
this._valueChanges = new EventEmitter();
|
this._valueChanges = new EventEmitter();
|
||||||
|
|
||||||
|
@ -348,6 +380,15 @@ export class ControlGroup extends AbstractControl {
|
||||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_anyControlsHaveStatus(status: string): boolean {
|
||||||
|
var res = false;
|
||||||
|
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||||
|
res = res || (this.contains(name) && control.status == status);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_reduceValue() {
|
_reduceValue() {
|
||||||
return this._reduceChildren({}, (acc, control, name) => {
|
return this._reduceChildren({}, (acc, control, name) => {
|
||||||
|
@ -396,8 +437,9 @@ export class ControlGroup extends AbstractControl {
|
||||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||||
*/
|
*/
|
||||||
export class ControlArray extends AbstractControl {
|
export class ControlArray extends AbstractControl {
|
||||||
constructor(public controls: AbstractControl[], validator: Function = Validators.nullValidator) {
|
constructor(public controls: AbstractControl[], validator: Function = null,
|
||||||
super(validator);
|
asyncValidator: Function = null) {
|
||||||
|
super(validator, asyncValidator);
|
||||||
|
|
||||||
this._valueChanges = new EventEmitter();
|
this._valueChanges = new EventEmitter();
|
||||||
|
|
||||||
|
@ -457,6 +499,12 @@ export class ControlArray extends AbstractControl {
|
||||||
return anyErrors ? res : null;
|
return anyErrors ? res : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_anyControlsHaveStatus(status: string): boolean {
|
||||||
|
return ListWrapper.any(this.controls, c => c.status == status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_setParentForControls(): void {
|
_setParentForControls(): void {
|
||||||
this.controls.forEach((control) => { control.setParent(this); });
|
this.controls.forEach((control) => { control.setParent(this); });
|
||||||
|
|
|
@ -50,12 +50,6 @@ export function main() {
|
||||||
expect(g.validator).toBe(Validators.nullValidator);
|
expect(g.validator).toBe(Validators.nullValidator);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use default validators when no validators are provided", () => {
|
|
||||||
var g = b.group({"login": "some value"});
|
|
||||||
expect(g.controls["login"].validator).toBe(Validators.nullValidator);
|
|
||||||
expect(g.validator).toBe(Validators.nullValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create control arrays", () => {
|
it("should create control arrays", () => {
|
||||||
var c = b.control("three");
|
var c = b.control("three");
|
||||||
var a = b.array(["one", ["two", Validators.required], c, b.array(['four'])]);
|
var a = b.array(["one", ["two", Validators.required], c, b.array(['four'])]);
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {By} from 'angular2/src/core/debug';
|
||||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||||
import {ObservableWrapper} from 'angular2/src/core/facade/async';
|
import {ObservableWrapper} from 'angular2/src/core/facade/async';
|
||||||
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
|
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
|
||||||
|
import {PromiseWrapper} from "angular2/src/core/facade/promise";
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe("integration tests", () => {
|
describe("integration tests", () => {
|
||||||
|
@ -445,7 +446,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("should use validators defined in the model",
|
it("should use sync validators defined in the model",
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
var form = new ControlGroup({"login": new Control("aa", Validators.required)});
|
var form = new ControlGroup({"login": new Control("aa", Validators.required)});
|
||||||
|
|
||||||
|
@ -467,6 +468,41 @@ export function main() {
|
||||||
async.done();
|
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 = `<div [ng-form-model]="form">
|
||||||
|
<input type="text" ng-control="login">
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
var rootTC;
|
||||||
|
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((root) => rootTC = root);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
rootTC.debugElement.componentInstance.form = form;
|
||||||
|
rootTC.detectChanges();
|
||||||
|
|
||||||
|
expect(form.hasError("required", ["login"])).toEqual(true);
|
||||||
|
|
||||||
|
var input = rootTC.debugElement.query(By.css("input"));
|
||||||
|
input.nativeElement.value = "wrong value";
|
||||||
|
dispatchEvent(input.nativeElement, "change");
|
||||||
|
|
||||||
|
expect(form.pending).toEqual(true);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(form.hasError("uniqLogin", ["login"])).toEqual(true);
|
||||||
|
|
||||||
|
input.nativeElement.value = "expected";
|
||||||
|
dispatchEvent(input.nativeElement, "change");
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(form.valid).toEqual(true);
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("nested forms", () => {
|
describe("nested forms", () => {
|
||||||
|
@ -923,6 +959,15 @@ class MyInput implements ControlValueAccessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uniqLoginAsyncValidator(expectedValue: string) {
|
||||||
|
return (c) => {
|
||||||
|
var e = new EventEmitter();
|
||||||
|
var res = (c.value == expectedValue) ? null : {"uniqLogin": true};
|
||||||
|
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callNext(e, res));
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function loginIsEmptyGroupValidator(c: ControlGroup) {
|
function loginIsEmptyGroupValidator(c: ControlGroup) {
|
||||||
return c.controls["login"].value == "" ? {"loginIsEmpty": true} : null;
|
return c.controls["login"].value == "" ? {"loginIsEmpty": true} : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,32 @@ import {
|
||||||
inject
|
inject
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
import {ControlGroup, Control, ControlArray, Validators} from 'angular2/core';
|
import {ControlGroup, Control, ControlArray, Validators} from 'angular2/core';
|
||||||
import {ObservableWrapper} from 'angular2/src/core/facade/async';
|
import {isPresent, CONST_EXPR} from 'angular2/src/core/facade/lang';
|
||||||
|
import {EventEmitter, TimerWrapper, ObservableWrapper} from 'angular2/src/core/facade/async';
|
||||||
import {IS_DART} from '../../platform';
|
import {IS_DART} from '../../platform';
|
||||||
|
import {PromiseWrapper} from "angular2/src/core/facade/promise";
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
function asyncValidator(expected, timeouts = CONST_EXPR({})) {
|
||||||
|
return (c) => {
|
||||||
|
var e = new EventEmitter();
|
||||||
|
var t = isPresent(timeouts[c.value]) ? timeouts[c.value] : 0;
|
||||||
|
var res = c.value != expected ? {"async": true} : null;
|
||||||
|
|
||||||
|
if (t == 0) {
|
||||||
|
PromiseWrapper.scheduleMicrotask(() => { ObservableWrapper.callNext(e, res); });
|
||||||
|
} else {
|
||||||
|
TimerWrapper.setTimeout(() => { ObservableWrapper.callNext(e, res); }, t);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("Form Model", () => {
|
describe("Form Model", () => {
|
||||||
describe("Control", () => {
|
describe("Control", () => {
|
||||||
it("should default the value to null", () => {
|
it("should default the value to null", () => {
|
||||||
var c = new Control();
|
var c = new Control();
|
||||||
expect(c.value).toBe(null);
|
expect(c.value).toBe(null);
|
||||||
expect(c.validator).toBe(Validators.nullValidator);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("validator", () => {
|
describe("validator", () => {
|
||||||
|
@ -44,6 +60,60 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("asyncValidator", () => {
|
||||||
|
it("should run validator with the initial value", fakeAsync(() => {
|
||||||
|
var c = new Control("value", null, asyncValidator("expected"));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(c.valid).toEqual(false);
|
||||||
|
expect(c.errors).toEqual({"async": true});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should rerun the validator when the value changes", fakeAsync(() => {
|
||||||
|
var c = new Control("value", null, asyncValidator("expected"));
|
||||||
|
|
||||||
|
c.updateValue("expected");
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(c.valid).toEqual(true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should run the async validator only when the sync validator passes", fakeAsync(() => {
|
||||||
|
var c = new Control("", Validators.required, asyncValidator("expected"));
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(c.errors).toEqual({"required": true});
|
||||||
|
|
||||||
|
c.updateValue("some value");
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(c.errors).toEqual({"async": true});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should mark the control as pending while running the async validation",
|
||||||
|
fakeAsync(() => {
|
||||||
|
var c = new Control("", null, asyncValidator("expected"));
|
||||||
|
|
||||||
|
expect(c.pending).toEqual(true);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(c.pending).toEqual(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should only use the latest async validation run", fakeAsync(() => {
|
||||||
|
var c =
|
||||||
|
new Control("", null, asyncValidator("expected", {"long": 200, "expected": 100}));
|
||||||
|
|
||||||
|
c.updateValue("long");
|
||||||
|
c.updateValue("expected");
|
||||||
|
|
||||||
|
tick(300);
|
||||||
|
|
||||||
|
expect(c.valid).toEqual(true);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
describe("dirty", () => {
|
describe("dirty", () => {
|
||||||
it("should be false after creating a control", () => {
|
it("should be false after creating a control", () => {
|
||||||
var c = new Control("value");
|
var c = new Control("value");
|
||||||
|
@ -154,7 +224,7 @@ export function main() {
|
||||||
|
|
||||||
describe("setErrors", () => {
|
describe("setErrors", () => {
|
||||||
it("should set errors on a control", () => {
|
it("should set errors on a control", () => {
|
||||||
var c = new Control("someValue", Validators.nullValidator);
|
var c = new Control("someValue");
|
||||||
|
|
||||||
c.setErrors({"someError": true});
|
c.setErrors({"someError": true});
|
||||||
|
|
||||||
|
@ -456,6 +526,42 @@ export function main() {
|
||||||
expect(g.getError("required", ["invalid"])).toEqual(null);
|
expect(g.getError("required", ["invalid"])).toEqual(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("asyncValidator", () => {
|
||||||
|
it("should run the async validator", fakeAsync(() => {
|
||||||
|
var c = new Control("value");
|
||||||
|
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
|
||||||
|
|
||||||
|
expect(g.pending).toEqual(true);
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(g.errors).toEqual({"async": true});
|
||||||
|
expect(g.pending).toEqual(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should set the parent group's status to pending", fakeAsync(() => {
|
||||||
|
var c = new Control("value", null, asyncValidator("expected"));
|
||||||
|
var g = new ControlGroup({"one": c});
|
||||||
|
|
||||||
|
expect(g.pending).toEqual(true);
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(g.pending).toEqual(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should run the parent group's async validator when children are pending",
|
||||||
|
fakeAsync(() => {
|
||||||
|
var c = new Control("value", null, asyncValidator("expected"));
|
||||||
|
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(g.errors).toEqual({"async": true});
|
||||||
|
expect(g.find(["one"]).errors).toEqual({"async": true});
|
||||||
|
}));
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ControlArray", () => {
|
describe("ControlArray", () => {
|
||||||
|
@ -697,6 +803,20 @@ export function main() {
|
||||||
expect(g.find(["array", 0]).value).toEqual("111");
|
expect(g.find(["array", 0]).value).toEqual("111");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("asyncValidator", () => {
|
||||||
|
it("should run the async validator", fakeAsync(() => {
|
||||||
|
var c = new Control("value");
|
||||||
|
var g = new ControlArray([c], null, asyncValidator("expected"));
|
||||||
|
|
||||||
|
expect(g.pending).toEqual(true);
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(g.errors).toEqual({"async": true});
|
||||||
|
expect(g.pending).toEqual(false);
|
||||||
|
}));
|
||||||
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ var NG_API = [
|
||||||
'AbstractControl.valid',
|
'AbstractControl.valid',
|
||||||
'AbstractControl.validator',
|
'AbstractControl.validator',
|
||||||
'AbstractControl.validator=',
|
'AbstractControl.validator=',
|
||||||
|
'AbstractControl.asyncValidator',
|
||||||
|
'AbstractControl.asyncValidator=',
|
||||||
'AbstractControl.value',
|
'AbstractControl.value',
|
||||||
'AbstractControl.valueChanges',
|
'AbstractControl.valueChanges',
|
||||||
'AbstractControlDirective',
|
'AbstractControlDirective',
|
||||||
|
@ -299,6 +301,8 @@ var NG_API = [
|
||||||
'Control.valid',
|
'Control.valid',
|
||||||
'Control.validator',
|
'Control.validator',
|
||||||
'Control.validator=',
|
'Control.validator=',
|
||||||
|
'Control.asyncValidator',
|
||||||
|
'Control.asyncValidator=',
|
||||||
'Control.value',
|
'Control.value',
|
||||||
'Control.valueChanges',
|
'Control.valueChanges',
|
||||||
'Control.setErrors()',
|
'Control.setErrors()',
|
||||||
|
@ -329,6 +333,8 @@ var NG_API = [
|
||||||
'ControlArray.valid',
|
'ControlArray.valid',
|
||||||
'ControlArray.validator',
|
'ControlArray.validator',
|
||||||
'ControlArray.validator=',
|
'ControlArray.validator=',
|
||||||
|
'ControlArray.asyncValidator',
|
||||||
|
'ControlArray.asyncValidator=',
|
||||||
'ControlArray.value',
|
'ControlArray.value',
|
||||||
'ControlArray.valueChanges',
|
'ControlArray.valueChanges',
|
||||||
'ControlArray.setErrors()',
|
'ControlArray.setErrors()',
|
||||||
|
@ -373,6 +379,8 @@ var NG_API = [
|
||||||
'ControlGroup.valid',
|
'ControlGroup.valid',
|
||||||
'ControlGroup.validator',
|
'ControlGroup.validator',
|
||||||
'ControlGroup.validator=',
|
'ControlGroup.validator=',
|
||||||
|
'ControlGroup.asyncValidator',
|
||||||
|
'ControlGroup.asyncValidator=',
|
||||||
'ControlGroup.value',
|
'ControlGroup.value',
|
||||||
'ControlGroup.valueChanges',
|
'ControlGroup.valueChanges',
|
||||||
'ControlGroup.setErrors()',
|
'ControlGroup.setErrors()',
|
||||||
|
|
Loading…
Reference in New Issue