ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
471 lines (445 loc) • 18.4 kB
text/typescript
import {
required,
minLength,
maxLength,
minValue,
maxValue,
number,
regex,
email,
choices,
composeValidators,
combine2Validators,
} from './validate';
describe('Validators', () => {
const test = async (validator, inputs, message) => {
const validationResults = await Promise.all<Error | undefined>(
inputs.map(input => validator(input, null))
).then(results =>
results.map(error =>
error && error.message ? error.message : error
)
);
expect(validationResults).toEqual(
Array(...Array(inputs.length)).map(() => message)
);
};
describe('combine2Validators', () => {
it('should create a new validator that always return the result directly when both validator are synchronous', () => {
const includesFoo = value =>
value.match(/foo/) ? null : 'value must include foo';
const includesBar = value =>
value.match(/bar/) ? null : 'value must include bar';
const combinedValidator = combine2Validators(
includesFoo,
includesBar
);
expect(combinedValidator('foobar', null, null)).toBe(null);
expect(combinedValidator('bar', null, null)).toBe(
'value must include foo'
);
expect(combinedValidator('foo', null, null)).toBe(
'value must include bar'
);
expect(combinedValidator('', null, null)).toBe(
'value must include foo'
);
});
it('should create a new validator that always return a promise when both validator are asynchronous', async () => {
const includesFoo = value =>
Promise.resolve(
value.match(/foo/) ? null : 'value must include foo'
);
const includesBar = value =>
Promise.resolve(
value.match(/bar/) ? null : 'value must include bar'
);
const combinedValidator = combine2Validators(
includesFoo,
includesBar
);
const validPromise = combinedValidator('foobar', null, null);
expect(validPromise.then).toBeDefined();
expect(await validPromise).toBe(null);
const missingFooPromise = combinedValidator('bar', null, null);
expect(missingFooPromise.then).toBeDefined();
expect(await missingFooPromise).toBe('value must include foo');
const missingBarPromise = combinedValidator('foo', null, null);
expect(missingBarPromise.then).toBeDefined();
expect(await missingBarPromise).toBe('value must include bar');
const invalidPromise = combinedValidator('', null, null);
expect(invalidPromise.then).toBeDefined();
expect(await invalidPromise).toBe('value must include foo');
});
describe('synchronous validator + asynchronous validator', () => {
const includesFoo = value =>
value.match(/foo/) ? null : 'value must include foo';
const includesBar = value =>
Promise.resolve(
value.match(/bar/) ? null : 'value must include bar'
);
const combinedValidator = combine2Validators(
includesFoo,
includesBar
);
it('should return valid result inside a promise when both validators pass', async () => {
const promise = combinedValidator('foobar', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe(null);
});
it('should return invalid result directly when both validators fail', () => {
expect(combinedValidator('', null, null)).toBe(
'value must include foo'
);
});
it('should return invalid result directly when first validator fail', () => {
expect(combinedValidator('bar', null, null)).toBe(
'value must include foo'
);
});
it('should return invalid result inside a promise when second validator fail', async () => {
const promise = combinedValidator('foo', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe('value must include bar');
});
});
describe('asynchronous validator + synchronous validator', () => {
const includesFoo = value =>
Promise.resolve(
value.match(/foo/) ? null : 'value must include foo'
);
const includesBar = value =>
value.match(/bar/) ? null : 'value must include bar';
const combinedValidator = combine2Validators(
includesFoo,
includesBar
);
it('should return valid result inside a promise when both validators pass', async () => {
const promise = combinedValidator('foobar', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe(null);
});
it('should return valid result inside a promise when both validators fail', async () => {
const promise = combinedValidator('', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe('value must include foo');
});
it('should return invalid result in a promise when first validator fail', async () => {
const promise = combinedValidator('bar', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe('value must include foo');
});
it('should return invalid result inside a promise when second validator fail', async () => {
const promise = combinedValidator('foo', null, null);
expect(promise.then).toBeDefined();
expect(await promise).toBe('value must include bar');
});
});
});
describe('composeValidators', () => {
const asyncSuccessfullValidator = async () =>
new Promise(resolve => resolve(undefined));
const asyncFailedValidator = async () =>
new Promise(resolve => resolve('async'));
it('Correctly composes validators passed as an array', async () => {
await test(
composeValidators([
required(),
minLength(5),
asyncSuccessfullValidator,
]),
[''],
'ra.validation.required'
);
await test(
composeValidators([
required(),
asyncSuccessfullValidator,
minLength(5),
]),
['abcd'],
'ra.validation.minLength'
);
await test(
composeValidators([
required(),
asyncFailedValidator,
minLength(5),
]),
['abcde'],
'async'
);
await test(
composeValidators([
required(),
minLength(5),
asyncSuccessfullValidator,
]),
['abcde'],
undefined
);
});
it('Correctly composes validators passed as many arguments', async () => {
await test(
composeValidators(
required(),
minLength(5),
asyncSuccessfullValidator
),
[''],
'ra.validation.required'
);
await test(
composeValidators(
required(),
asyncSuccessfullValidator,
minLength(5)
),
['abcd'],
'ra.validation.minLength'
);
await test(
composeValidators(
required(),
asyncFailedValidator,
minLength(5)
),
['abcde'],
'async'
);
await test(
composeValidators(
required(),
minLength(5),
asyncSuccessfullValidator
),
['abcde'],
undefined
);
});
});
describe('required', () => {
it('should return undefined if the value is not empty', () => {
test(required(), ['foo', 12, [1]], undefined);
});
it('should return an error message if the value is empty', () => {
test(
required(),
[undefined, '', null, []],
'ra.validation.required'
);
});
it('should have a `isRequired` prop for allowing the UI to add a required marker', () => {
expect(required().isRequired).toEqual(true);
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.required');
test(
required(message),
[undefined, '', null, []],
'ra.validation.required'
);
expect(message).toHaveBeenCalledTimes(4);
expect(message).toHaveBeenLastCalledWith({
args: undefined,
value: [],
values: null,
});
});
});
describe('minLength', () => {
it('should return undefined if the value is empty', () => {
test(minLength(5), [undefined, '', null], undefined);
});
it('should return undefined if the value is not a string', () => {
test(minLength(5), [1234, 123456], undefined);
});
it('should return undefined if the value has equal or higher length than the given minimum', () => {
test(minLength(5), ['12345', '123456'], undefined);
});
it('should return an error message if the value has smaller length than the given minimum', () => {
test(minLength(5), ['1234', '12'], 'ra.validation.minLength');
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.minLength');
test(
minLength(5, message),
['1234', '12'],
'ra.validation.minLength'
);
expect(message).toHaveBeenCalledTimes(2);
expect(message).toHaveBeenLastCalledWith({
args: { min: 5 },
value: '12',
values: null,
});
});
});
describe('maxLength', () => {
it('should return undefined if the value is empty', () => {
test(maxLength(5), [undefined, '', null], undefined);
});
it('should return undefined if the value is not a string', () => {
test(maxLength(5), [1234, 123456], undefined);
});
it('should return undefined if the value has equal or smaller length than the given maximum', () => {
test(maxLength(5), ['12345', '123'], undefined);
});
it('should return an error message if the value has higher length than the given maximum', () => {
test(maxLength(10), ['12345678901'], 'ra.validation.maxLength');
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.maxLength');
test(
maxLength(10, message),
['12345678901'],
'ra.validation.maxLength'
);
expect(message).toHaveBeenCalledTimes(1);
expect(message).toHaveBeenLastCalledWith({
args: { max: 10 },
value: '12345678901',
values: null,
});
});
});
describe('minValue', () => {
it('should return undefined if the value is empty', () => {
test(minValue(5), [undefined, '', null], undefined);
});
it('should return undefined if the value is equal or higher than the given minimum', () => {
test(minValue(5), [5, 10, 5.5, '10'], undefined);
});
it('should return an error message if the value is lower than the given minimum', () => {
test(minValue(10), [1, 9.5, '5'], 'ra.validation.minValue');
});
it('should return an error message if the value is 0', () => {
test(minValue(10), [0], 'ra.validation.minValue');
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.minValue');
test(minValue(10, message), [0], 'ra.validation.minValue');
expect(message).toHaveBeenCalledTimes(1);
expect(message).toHaveBeenLastCalledWith({
args: { min: 10 },
value: 0,
values: null,
});
});
});
describe('maxValue', () => {
it('should return undefined if the value is empty', () => {
test(maxValue(5), [undefined, '', null], undefined);
});
it('should return undefined if the value is equal or less than the given maximum', () => {
test(maxValue(5), [5, 4, 4.5, '4'], undefined);
});
it('should return an error message if the value is higher than the given maximum', () => {
test(maxValue(10), [11, 10.5, '11'], 'ra.validation.maxValue');
});
it('should return undefined if the value is 0', () => {
test(maxValue(10), [0], undefined);
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.maxValue');
test(
maxValue(10, message),
[11, 10.5, '11'],
'ra.validation.maxValue'
);
expect(message).toHaveBeenCalledTimes(3);
expect(message).toHaveBeenLastCalledWith({
args: { max: 10 },
value: '11',
values: null,
});
});
});
describe('number', () => {
it('should return undefined if the value is empty', () => {
test(number(), [undefined, '', null], undefined);
});
it('should return undefined if the value is a number', () => {
test(number(), [123, '123', new Date(), 0, 2.5, -5], undefined);
});
it('should return an error message if the value is not a number', () => {
test(number(), ['foo'], 'ra.validation.number');
});
it('should allow message to be a callback', () => {
const message = jest.fn(() => 'ra.validation.number');
test(number(message), ['foo'], 'ra.validation.number');
expect(message).toHaveBeenCalledTimes(1);
expect(message).toHaveBeenLastCalledWith({
args: undefined,
value: 'foo',
values: null,
});
});
});
describe('regex', () => {
it('should return undefined if the value is empty', () => {
test(regex(/foo/, 'not foo'), [undefined, '', null], undefined);
});
it('should return undefined if the value is not a string', () => {
test(regex(/foo/, 'not foo'), [1234, new Date()], undefined);
});
it('should return undefined if the value matches the pattern', () => {
test(
regex(/foo/, 'not foo'),
['foobar', 'barfoo', 'barfoobar', 'foofoo'],
undefined
);
});
it('should return an error message if the value does not match the pattern', () => {
test(
regex(/foo/, 'not foo'),
['bar', 'barfo', 'hello, world'],
'not foo'
);
});
it('should memoize the validator when the regex pattern and message are the same', () => {
expect(regex(/foo/, 'placeholder')).toBe(
regex(/foo/, 'placeholder')
);
});
it('should create new validator when the regex pattern is different', () => {
expect(regex(/foo/, 'placeholder')).not.toBe(
regex(/notfoo/, 'placeholder')
);
});
it('should create new validator when message is different', () => {
expect(regex(/foo/, 'placeholder')).not.toBe(
regex(/foo/, 'another placeholder')
);
});
});
describe('email', () => {
it('should return undefined if the value is empty', () => {
test(email(), [undefined, '', null], undefined);
});
it('should return undefined if the value is not a string', () => {
test(email(), [1234, new Date()], undefined);
});
it('should return undefined if the value is a valid email', () => {
test(
email(),
['foo@bar.com', 'john.doe@mydomain.co.uk'],
undefined
);
});
it('should return an error if the value is not a valid email', () => {
test(email(), ['foo@bar', 'hello, world'], 'ra.validation.email');
});
});
describe('choices', () => {
it('should return undefined if the value is empty', () => {
test(choices([1, 2], 'error'), [undefined, '', null], undefined);
});
it('should return undefined if the value is in the choice list', () => {
test(choices([1, 2], 'error'), [1, 2], undefined);
});
it('should return an error message if the value is not in the choice list', () => {
test(choices([1, 2], 'error'), ['hello', 3], 'error');
});
});
});