Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 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