From 062b8d90afaf60e0066568d64188ca074001986f Mon Sep 17 00:00:00 2001 From: Oussama Ben Brahim <14368835+benbraou@users.noreply.github.com> Date: Mon, 13 Jul 2020 01:35:09 +0200 Subject: [PATCH] refactor(forms): refactor common validators used in unit tests (#38020) A util file is added to forms test package: - it exposes simpleAsyncValidator, asyncValidator and asyncValidatorReturningObservable validators - it refactors simpleAsyncValidator and asyncValidator to use common promise creation code - it exposes currentStateOf allowing to get the validation state of a list of AbstractControl Closes #37831 PR Close #38020 --- packages/forms/test/directives_spec.ts | 19 +----- packages/forms/test/form_array_spec.ts | 22 +----- packages/forms/test/form_control_spec.ts | 33 +-------- packages/forms/test/form_group_spec.ts | 65 +----------------- packages/forms/test/util.ts | 87 ++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 133 deletions(-) create mode 100644 packages/forms/test/util.ts diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 0f395e52cf..dd497dc30c 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -12,6 +12,7 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin import {AbstractControl, CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, ValidationErrors, Validator, Validators} from '@angular/forms'; import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared'; import {SpyNgControl, SpyValueAccessor} from './spies'; +import {asyncValidator} from './util'; class DummyControlValueAccessor implements ControlValueAccessor { writtenValue: any; @@ -30,24 +31,6 @@ class CustomValidatorDirective implements Validator { } } -function asyncValidator(expected: any, timeout = 0) { - return (c: AbstractControl): any => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const res = c.value != expected ? {'async': true} : null; - if (timeout == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, timeout); - } - return promise; - }; -} - { describe('Form Directives', () => { let defaultAccessor: DefaultValueAccessor; diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 68477d384a..7159500b47 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -11,29 +11,9 @@ import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/cor import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn} from '@angular/forms'; import {Validators} from '@angular/forms/src/validators'; import {of} from 'rxjs'; +import {asyncValidator} from './util'; (function() { -function asyncValidator(expected: string, timeouts = {}) { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - describe('FormArray', () => { describe('adding/removing', () => { let a: FormArray; diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 46a65e743b..d9eaafdcd4 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -6,43 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {EventEmitter} from '@angular/core'; import {fakeAsync, tick} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal'; -import {AbstractControl, AsyncValidatorFn, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; import {FormArray} from '@angular/forms/src/model'; +import {asyncValidator, asyncValidatorReturningObservable} from './util'; (function() { -function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - -function asyncValidatorReturningObservable(c: AbstractControl) { - const e = new EventEmitter>(); - Promise.resolve(null).then(() => { - e.emit({'async': true}); - }); - return e; -} - function otherAsyncValidator() { return Promise.resolve({'other': true}); } diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index c46026e515..9addf0dd37 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -6,80 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {EventEmitter} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal'; import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; import {of} from 'rxjs'; +import {asyncValidator, asyncValidatorReturningObservable, currentStateOf, simpleAsyncValidator} from './util'; + (function() { function simpleValidator(c: AbstractControl): ValidationErrors|null { return c.get('one')!.value === 'correct' ? null : {'broken': true}; } -function asyncValidator(expected: string, timeouts = {}) { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - -function simpleAsyncValidator({ - timeout = 0, - shouldFail, - customError = - { - async: true - } -}: {timeout?: number, shouldFail: boolean, customError?: any}) { - return (c: AbstractControl) => { - const res = shouldFail ? customError : null; - - if (timeout === 0) { - return of(res); - } - - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - - setTimeout(() => { - resolve(res); - }, timeout); - - return promise; - }; -} - -function currentStateOf(controls: AbstractControl[]): - {errors: any; pending: boolean; status: string;}[] { - return controls.map(c => ({errors: c.errors, pending: c.pending, status: c.status})); -} - -function asyncValidatorReturningObservable(c: AbstractControl) { - const e = new EventEmitter(); - Promise.resolve(null).then(() => { - e.emit({'async': true}); - }); - return e; -} - function otherObservableValidator() { return of({'other': true}); } diff --git a/packages/forms/test/util.ts b/packages/forms/test/util.ts new file mode 100644 index 0000000000..148f398a3e --- /dev/null +++ b/packages/forms/test/util.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright Google LLC 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 {EventEmitter} from '@angular/core'; +import {AbstractControl, AsyncValidatorFn, ValidationErrors} from '@angular/forms'; +import {of} from 'rxjs'; + +function createValidationPromise( + result: ValidationErrors|null, timeout: number): Promise { + return new Promise(resolve => { + if (timeout == 0) { + resolve(result); + } else { + setTimeout(() => { + resolve(result); + }, timeout); + } + }); +} + +/** + * Returns a promise-based async validator that emits, after a delay, either: + * - an error `{async: true}` if the control value does not match the expected value + * - or null, otherwise + * The delay is either: + * - defined in `timeouts` parameter, as the association to the control value + * - or 0ms otherwise + * + * @param expected The expected control value + * @param timeouts A dictionary associating a control value to when the validation will trigger for + * that value + */ +export function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn { + return (control: AbstractControl) => { + const timeout = (timeouts as any)[control.value] ?? 0; + const result = control.value != expected ? {async: true} : null; + return createValidationPromise(result, timeout); + }; +} + +/** + * Returns an async validator that emits null or a custom error after a specified delay. + * If the delay is set to 0ms, the validator emits synchronously. + * + * @param timeout Indicates when the validator will emit + * @param shouldFail When true, a validation error is emitted, otherwise null is emitted + * @param customError When supplied, overrides the default error `{async: true}` + */ +export function simpleAsyncValidator({ + timeout = 0, + shouldFail, + customError = + { + async: true + } +}: {timeout?: number, shouldFail: boolean, customError?: any}): AsyncValidatorFn { + const result = shouldFail ? customError : null; + return (c: AbstractControl) => + timeout === 0 ? of(result) : createValidationPromise(result, timeout); +} + +/** + * Returns the asynchronous validation state of each provided control + * @param controls A collection of controls + */ +export function currentStateOf(controls: AbstractControl[]): + {errors: any; pending: boolean; status: string;}[] { + return controls.map(c => ({errors: c.errors, pending: c.pending, status: c.status})); +} + +/** + * Returns an `EventEmitter` emitting the default error `{'async': true}` + * + * @param c The control instance + */ +export function asyncValidatorReturningObservable(c: AbstractControl): EventEmitter { + const e = new EventEmitter(); + Promise.resolve(null).then(() => { + e.emit({'async': true}); + }); + return e; +}