Skip to content

Commit

Permalink
fix(forms): fix Validators.min/maxLength with FormArray
Browse files Browse the repository at this point in the history
  • Loading branch information
Dzmitry Shylovich authored and Dzmitry Shylovich committed Nov 25, 2016
1 parent 7194fc2 commit 8b2e094
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 102 deletions.
16 changes: 13 additions & 3 deletions modules/@angular/forms/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {toPromise} from 'rxjs/operator/toPromise';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {StringMapWrapper} from './facade/collection';
import {isPresent} from './facade/lang';
import {AbstractControl} from './model';
import {AbstractControl, FormArray} from './model';
import {isPromise} from './private_import_core';

function isEmptyInputValue(value: any) {
Expand Down Expand Up @@ -72,7 +72,12 @@ export class Validators {
if (isEmptyInputValue(control.value)) {
return null; // don't validate empty values to allow optional controls
}
const length = typeof control.value === 'string' ? control.value.length : 0;
let length: number;
if (control instanceof FormArray) {
length = control.length;
} else {
length = typeof control.value === 'string' ? control.value.length : 0;
}
return length < minLength ?
{'minlength': {'requiredLength': minLength, 'actualLength': length}} :
null;
Expand All @@ -84,7 +89,12 @@ export class Validators {
*/
static maxLength(maxLength: number): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const length = typeof control.value === 'string' ? control.value.length : 0;
let length: number;
if (control instanceof FormArray) {
length = control.length;
} else {
length = typeof control.value === 'string' ? control.value.length : 0;
}
return length > maxLength ?
{'maxlength': {'requiredLength': maxLength, 'actualLength': length}} :
null;
Expand Down
221 changes: 122 additions & 99 deletions modules/@angular/forms/test/validators_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {fakeAsync, tick} from '@angular/core/testing';
import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {AbstractControl, FormControl, Validators} from '@angular/forms';
import {AbstractControl, FormArray, FormControl, Validators} from '@angular/forms';
import {Observable} from 'rxjs/Observable';

import {normalizeAsyncValidator} from '../src/directives/normalize_validator';
Expand Down Expand Up @@ -67,6 +67,18 @@ export function main() {
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
'minlength': {'requiredLength': 2, 'actualLength': 1}
});

it('should not error on FormArray', () => {
const fa = new FormArray([new FormControl(''), new FormControl('')]);
expect(Validators.minLength(2)(fa)).toBeNull();
});

it('should error on FormArray', () => {
const fa = new FormArray([new FormControl('')]);
expect(Validators.minLength(2)(fa)).toEqual({
'minlength': {'requiredLength': 2, 'actualLength': 1}
});
});
});
});

Expand All @@ -85,138 +97,149 @@ export function main() {
'maxlength': {'requiredLength': 2, 'actualLength': 3}
});
});
});
it('should not error when FormArray has valid length', () => {
const fa = new FormArray([new FormControl(''), new FormControl('')]);
expect(Validators.maxLength(2)(fa)).toBeNull();
});

describe('pattern', () => {
it('should not error on an empty string',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull(); });
it('should error when FormArray has invalid length', () => {
const fa = new FormArray([new FormControl(''), new FormControl('')]);
expect(Validators.maxLength(1)(fa)).toEqual({
'maxlength': {'requiredLength': 1, 'actualLength': 2}
});
});

it('should not error on null',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
describe('pattern', () => {
it('should not error on an empty string',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull(); });

it('should not error on undefined',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
it('should not error on null',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });

it('should not error on null value and "null" pattern',
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
it('should not error on undefined',
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });

it('should not error on valid strings',
() => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull());
it('should not error on null value and "null" pattern',
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });

it('should not error on valid strings',
() => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull());

it('should error on failure to match string', () => {
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
it('should error on failure to match string', () => {
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
});
});
});

it('should accept RegExp object', () => {
const pattern: RegExp = new RegExp('[a-zA-Z ]+');
expect(Validators.pattern(pattern)(new FormControl('aaAA'))).toBeNull();
});
it('should accept RegExp object', () => {
const pattern: RegExp = new RegExp('[a-zA-Z ]+');
expect(Validators.pattern(pattern)(new FormControl('aaAA'))).toBeNull();
});

it('should error on failure to match RegExp object', () => {
const pattern: RegExp = new RegExp('^[a-zA-Z ]*$');
expect(Validators.pattern(pattern)(new FormControl('aaa0'))).toEqual({
'pattern': {'requiredPattern': '/^[a-zA-Z ]*$/', 'actualValue': 'aaa0'}
it('should error on failure to match RegExp object', () => {
const pattern: RegExp = new RegExp('^[a-zA-Z ]*$');
expect(Validators.pattern(pattern)(new FormControl('aaa0'))).toEqual({
'pattern': {'requiredPattern': '/^[a-zA-Z ]*$/', 'actualValue': 'aaa0'}
});
});
});

it('should not error on "null" pattern',
() => expect(Validators.pattern(null)(new FormControl('aaAA'))).toBeNull());
it('should not error on "null" pattern',
() => expect(Validators.pattern(null)(new FormControl('aaAA'))).toBeNull());

it('should not error on "undefined" pattern',
() => expect(Validators.pattern(undefined)(new FormControl('aaAA'))).toBeNull());
});
it('should not error on "undefined" pattern',
() => expect(Validators.pattern(undefined)(new FormControl('aaAA'))).toBeNull());
});

describe('compose', () => {
it('should return null when given null',
() => { expect(Validators.compose(null)).toBe(null); });
describe('compose', () => {
it('should return null when given null',
() => { expect(Validators.compose(null)).toBe(null); });

it('should collect errors from all the validators', () => {
const c = Validators.compose([validator('a', true), validator('b', true)]);
expect(c(new FormControl(''))).toEqual({'a': true, 'b': true});
});
it('should collect errors from all the validators', () => {
const c = Validators.compose([validator('a', true), validator('b', true)]);
expect(c(new FormControl(''))).toEqual({'a': true, 'b': true});
});

it('should run validators left to right', () => {
const c = Validators.compose([validator('a', 1), validator('a', 2)]);
expect(c(new FormControl(''))).toEqual({'a': 2});
});
it('should run validators left to right', () => {
const c = Validators.compose([validator('a', 1), validator('a', 2)]);
expect(c(new FormControl(''))).toEqual({'a': 2});
});

it('should return null when no errors', () => {
const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new FormControl(''))).toBeNull();
});
it('should return null when no errors', () => {
const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new FormControl(''))).toBeNull();
});

it('should ignore nulls', () => {
const c = Validators.compose([null, Validators.required]);
expect(c(new FormControl(''))).toEqual({'required': true});
it('should ignore nulls', () => {
const c = Validators.compose([null, Validators.required]);
expect(c(new FormControl(''))).toEqual({'required': true});
});
});
});

describe('composeAsync', () => {
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
return (c: any /** TODO #9100 */) => {
const emitter = new EventEmitter();
const res = c.value != expected ? response : null;
Promise.resolve(null).then(() => {
emitter.emit(res);
// this is required because of a bug in ObservableWrapper
// where callComplete can fire before callEmit
// remove this one the bug is fixed
setTimeout(() => { emitter.complete(); }, 0);
});
describe('composeAsync', () => {
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
return (c: any /** TODO #9100 */) => {
const emitter = new EventEmitter();
const res = c.value != expected ? response : null;
Promise.resolve(null).then(() => {
emitter.emit(res);
// this is required because of a bug in ObservableWrapper
// where callComplete can fire before callEmit
// remove this one the bug is fixed
setTimeout(() => { emitter.complete(); }, 0);
});

return emitter;
};
}
return emitter;
};
}

it('should return null when given null',
() => { expect(Validators.composeAsync(null)).toBeNull(); });
it('should return null when given null',
() => { expect(Validators.composeAsync(null)).toBeNull(); });

it('should collect errors from all the validators', fakeAsync(() => {
const c = Validators.composeAsync([
asyncValidator('expected', {'one': true}), asyncValidator('expected', {'two': true})
]);
it('should collect errors from all the validators', fakeAsync(() => {
const c = Validators.composeAsync([
asyncValidator('expected', {'one': true}),
asyncValidator('expected', {'two': true})
]);

let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);
let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);

tick(1);
tick(1);

expect(value).toEqual({'one': true, 'two': true});
}));
expect(value).toEqual({'one': true, 'two': true});
}));

it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const c = Validators.composeAsync(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]);
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const c = Validators.composeAsync(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]);

let value: any = null;
c(new FormControl()).then((v: any) => value = v);
tick(1);
let value: any = null;
c(new FormControl()).then((v: any) => value = v);
tick(1);

expect(value).toEqual({'one': true});
}));
expect(value).toEqual({'one': true});
}));

it('should return null when no errors', fakeAsync(() => {
const c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);
it('should return null when no errors', fakeAsync(() => {
const c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);

let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('expected'))).then(v => value = v);
tick(1);
let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('expected'))).then(v => value = v);
tick(1);

expect(value).toBeNull();
}));
expect(value).toBeNull();
}));

it('should ignore nulls', fakeAsync(() => {
const c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);
it('should ignore nulls', fakeAsync(() => {
const c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);

let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);
let value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new FormControl('invalid'))).then(v => value = v);

tick(1);
tick(1);

expect(value).toEqual({'one': true});
}));
expect(value).toEqual({'one': true});
}));
});
});
});
}

0 comments on commit 8b2e094

Please sign in to comment.