Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/user-guide/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The following table lists all validation functions provided by **ngrx-forms**.
|`lessThanOrEqualTo`|Requires the `number` value to be less than or equal to another number|
|`greaterThan`|Requires the `number` value to be greater than another number|
|`greaterThanOrEqualTo`|Requires the `number` value to be greater than or equal to another number|
|`exclusiveBetween`|Requires the `number` value to be between min and max numbers|
|`inclusiveBetween`|Requires the `number` value to be between or equal to min and max numbers|
|`minLength`|Requires a `string` or `array` value to have a minimum length. Empty strings and arrays are always valid to allow for optional form controls. Use this function together with `required` if those values should not be valid|
|`maxLength`|Requires a `string` or `array` value to have a maximum length|
|`email`|Requires a `string` value to be a valid e-mail address. Empty strings are always valid to allow for optional form controls. Use this function together with `required` if empty strings should not be valid|
Expand Down
2 changes: 2 additions & 0 deletions validation/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
*/
export { email, EmailValidationError } from './src/email';
export { equalTo, EqualToValidationError } from './src/equal-to';
export { exclusiveBetween, ExclusiveBetweenValidationError } from './src/exclusive-between';
export { greaterThan, GreaterThanValidationError } from './src/greater-than';
export { greaterThanOrEqualTo, GreaterThanOrEqualToValidationError } from './src/greater-than-or-equal-to';
export { inclusiveBetween, InclusiveBetweenValidationError } from './src/inclusive-between';
export { lessThan, LessThanValidationError } from './src/less-than';
export { lessThanOrEqualTo, LessThanOrEqualToValidationError } from './src/less-than-or-equal-to';
export { maxLength, MaxLengthValidationError } from './src/max-length';
Expand Down
155 changes: 155 additions & 0 deletions validation/src/exclusive-between.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { AbstractControlState, box, unbox, validate } from 'ngrx-forms';
import { exclusiveBetween } from './exclusive-between';

describe(exclusiveBetween.name, () => {
it('should throw for null min parameter', () => {
expect(() => exclusiveBetween(null as any, 100)).toThrow();
});

it('should throw for undefined min parameter', () => {
expect(() => exclusiveBetween(undefined as any, 100)).toThrow();
});

it('should throw for null max parameter', () => {
expect(() => exclusiveBetween(0, null as any)).toThrow();
});

it('should throw for undefined max parameter', () => {
expect(() => exclusiveBetween(0, undefined as any)).toThrow();
});

it('should not return an error for null', () => {
expect(exclusiveBetween(0, 100)(null)).toEqual({});
});

it('should not return an error for undefined', () => {
expect(exclusiveBetween(0, 100)(undefined)).toEqual({});
});

it('should not return an error for non-numeric value', () => {
expect(exclusiveBetween(0, 100)('string' as any)).toEqual({});
});

it('should not return an error if value is greater than min and less than max', () => {
expect(exclusiveBetween(0, 100)(50)).toEqual({});
});

it('should return errors with min, max and actual properties if less than min', () => {
const min = 0;
const max = 100;
const actual = -1;
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual,
},
});
});

it('should return an errors with min, max and actual properties if equal to min', () => {
const min = 0;
const max = 100;
const actual = 0;
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual,
},
});
});

it('should return errors with min, max and actual properties if greater than max', () => {
const min = 0;
const max = 100;
const actual = 101;
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual,
},
});
});

it('should return an errors with min, max and actual properties if equal to max', () => {
const min = 0;
const max = 100;
const actual = 100;
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual,
},
});
});

it('should not return an error if boxed value is greater than min and less than max', () => {
expect(exclusiveBetween(0, 100)(box(50))).toEqual({});
});

it('should return errors with min, max and actual properties for boxed values if less than min', () => {
const min = 0;
const max = 100;
const actual = box(-1);
expect(exclusiveBetween(min, max)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should return an errors with min, max and actual properties for boxed values if equal to min', () => {
const min = 0;
const max = 100;
const actual = box(0);
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should return errors with min, max and actual properties for boxed values if greater than max', () => {
const min = 0;
const max = 100;
const actual = box(101);
expect(exclusiveBetween(min, max)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should return an errors with min, max and actual properties for boxed values if equal to max', () => {
const min = 0;
const max = 100;
const actual = box(100);
expect(exclusiveBetween(0, 100)(actual)).toEqual({
exclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should properly infer value type when used with validate update function', () => {
// this code is never meant to be executed, it should just pass the type checker
if (1 !== 1) {
// tslint:disable-next-line:no-non-null-assertion
const state: AbstractControlState<number> = undefined!;
const v = validate(state, exclusiveBetween(0, 100));
const v2: number = v.value;
console.log(v2);
}
});
});
70 changes: 70 additions & 0 deletions validation/src/exclusive-between.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Boxed, unbox, ValidationErrors } from 'ngrx-forms';

export interface ExclusiveBetweenValidationError {
min: number;
max: number;
actual: number;
}

// @ts-ignore
declare module 'ngrx-forms/src/state' {
export interface ValidationErrors {
exclusiveBetween?: ExclusiveBetweenValidationError;
}
}

/**
* A validation function that requires the value to be between the given min and max values.
* Considers `null`, `undefined` and non-numeric values as valid. Combine this function with the `required`
* validation function if `null` or `undefined` should be considered invalid.
*
* The validation error returned by this validation function has the following shape:
*
```typescript
{
exclusiveBetween: {
min: number;
max: number;
actual: number;
};
}
```
*
* Usually you would use this validation function in conjunction with the `validate`
* update function to perform synchronous validation in your reducer:
*
```typescript
updateGroup<MyFormValue>({
amount: validate(exclusiveBetween(0, 100)),
})
```
*
* Note that this function is generic to allow the compiler to properly infer the type
* of the `validate` function for both optional and non-optional controls.
*/
export function exclusiveBetween(min: number, max: number) {
// tslint:disable-next-line:strict-type-predicates (guard for users without strict type checking)
if (min === null || min === undefined || max === null || max === undefined) {
throw new Error(`The exclusiveBetween Validation function requires the min and max parameters to be a non-null number, got ${min} and ${max}!`);
}

return <T extends number | Boxed<number> | null | undefined>(value: T): ValidationErrors => {
value = unbox(value) as number | null | undefined as T;

if (value === null || value === undefined || typeof value !== 'number') {
return {};
}

if (min < value && value < max) {
return {};
}

return {
exclusiveBetween: {
min,
max,
actual: value as number,
},
};
};
}
119 changes: 119 additions & 0 deletions validation/src/inclusive-between.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { AbstractControlState, box, unbox, validate } from 'ngrx-forms';
import { inclusiveBetween } from './inclusive-between';

describe(inclusiveBetween.name, () => {
it('should throw for null min parameter', () => {
expect(() => inclusiveBetween(null as any, 100)).toThrow();
});

it('should throw for undefined min parameter', () => {
expect(() => inclusiveBetween(undefined as any, 100)).toThrow();
});

it('should throw for null max parameter', () => {
expect(() => inclusiveBetween(0, null as any)).toThrow();
});

it('should throw for undefined max parameter', () => {
expect(() => inclusiveBetween(0, undefined as any)).toThrow();
});

it('should not return an error for null', () => {
expect(inclusiveBetween(0, 100)(null)).toEqual({});
});

it('should not return an error for undefined', () => {
expect(inclusiveBetween(0, 100)(undefined)).toEqual({});
});

it('should not return an error for non-numeric value', () => {
expect(inclusiveBetween(0, 100)('string' as any)).toEqual({});
});

it('should not return an error if value is greater than min and less than max', () => {
expect(inclusiveBetween(0, 100)(50)).toEqual({});
});

it('should not return an error if value is equal to min', () => {
expect(inclusiveBetween(0, 100)(0)).toEqual({});
});

it('should not return an error if value is less than max', () => {
expect(inclusiveBetween(0, 100)(99)).toEqual({});
});

it('should return errors with min, max and actual properties if less than min', () => {
const min = 0;
const max = 100;
const actual = -1;
expect(inclusiveBetween(0, 100)(actual)).toEqual({
inclusiveBetween: {
min,
max,
actual,
},
});
});

it('should return errors with min, max and actual properties if greater than max', () => {
const min = 0;
const max = 100;
const actual = 101;
expect(inclusiveBetween(0, 100)(actual)).toEqual({
inclusiveBetween: {
min,
max,
actual,
},
});
});

it('should not return an error if boxed value is greater than min and less than max', () => {
expect(inclusiveBetween(0, 100)(box(50))).toEqual({});
});

it('should not return an error if boxed value is equal to min', () => {
expect(inclusiveBetween(0, 100)(box(0))).toEqual({});
});

it('should not return an error if boxed value is equal to max', () => {
expect(inclusiveBetween(0, 100)(box(100))).toEqual({});
});

it('should return errors with min, max and actual properties for boxed values if less than min', () => {
const min = 0;
const max = 100;
const actual = box(-1);
expect(inclusiveBetween(min, max)(actual)).toEqual({
inclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should return errors with min, max and actual properties for boxed values if greater than max', () => {
const min = 0;
const max = 100;
const actual = box(101);
expect(inclusiveBetween(min, max)(actual)).toEqual({
inclusiveBetween: {
min,
max,
actual: unbox(actual),
},
});
});

it('should properly infer value type when used with validate update function', () => {
// this code is never meant to be executed, it should just pass the type checker
if (1 !== 1) {
// tslint:disable-next-line:no-non-null-assertion
const state: AbstractControlState<number> = undefined!;
const v = validate(state, inclusiveBetween(0, 100));
const v2: number = v.value;
console.log(v2);
}
});
});
Loading