valirator
Version:
Powerful javascript by schema validation tool
1,414 lines (1,226 loc) • 32 kB
JavaScript
import { formatMessage } from '../lib/utils';
import { registerRule, hasRule, getRule, overrideRule, overrideRuleMessage } from '../lib/storage';
import { validate, validateSync, validateRule, validateValue } from '../lib/core';
import ValidationSchema from '../lib/validationSchema';
describe('validation', () => {
describe('registerRule', () => {
it('should register rule', () => {
registerRule('myRule', () => true, 'error');
expect(hasRule('myRule')).toBe(true);
});
});
describe('overrideRule', () => {
const { check: originalRule } = getRule('required');
beforeEach(() => {
overrideRule('required', (actual, expected) => {
return actual % expected === 0;
});
});
afterEach(() => {
overrideRule('required', originalRule);
});
it('should fail on overridden required rule', done => {
validateValue(5, { required: 2 }).then(errors => {
expect(errors.hasErrors()).toBe(true);
done();
});
});
it('should pass on overridden required rule', done => {
validateValue(5, { required: 5 }).then(errors => {
expect(errors.hasErrors()).toBe(false);
done();
});
});
});
describe('overrideRuleMessage', () => {
const { message: originalMessage } = getRule('required');
beforeEach(() => {
overrideRuleMessage('required', 'custom required message');
});
afterEach(() => {
overrideRuleMessage('required', originalMessage);
});
it('should use overridden required message', done => {
validateValue(null, { required: true }).then(errors => {
expect(errors.required).toBe('custom required message');
done();
});
});
});
describe('formatMessage', () => {
it('should format text', done => {
formatMessage('%{actual} === %{expected}', 5, 5).then(formattedMessage => {
expect(formattedMessage).toBe('5 === 5');
done();
});
});
it('should accept function', done => {
formatMessage(
(actual, expected) => {
return `${actual} === ${expected}`;
},
5,
5,
).then(formattedMessage => {
expect(formattedMessage).toBe('5 === 5');
done();
});
});
it('should have default message', done => {
formatMessage().then(formattedMessage => {
expect(formattedMessage).toBeDefined();
done();
});
});
});
describe('validateRule', () => {
it('should validate separate rule', done => {
validateRule('required', true, null).then(error => {
expect(error).toBeDefined();
done();
});
});
});
describe('validateValue', () => {
it('should validate separate value with list of rules', done => {
validateValue('John', { required: true, minLength: 5 }).then(errors => {
expect(errors).toBeDefined();
done();
});
});
});
describe('validateObject', () => {});
describe('validateArray', () => {});
describe('validate', () => {
it('should validate required rule', done => {
const obj = {
FirstName: null,
};
const schema = {
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.FirstName.required).toBeDefined();
done();
});
});
it('should override default global message', done => {
const obj = {
FirstName: null,
};
const schema = {
overrides: {
messages: {
required: 'Field is required',
},
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.required).toBe('Field is required');
done();
});
});
it('should override default required rule (allow empty, for example)', done => {
const obj = {
FirstName: '',
};
const schema = {
overrides: {
rules: {
required: value => {
return value !== undefined && value !== null;
},
},
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.hasErrors()).toBe(false);
done();
});
});
it('should support nested schemas', done => {
const obj = {
Person: {
FirstName: null,
},
};
const schema = {
properties: {
Person: {
rules: {
required: true,
},
properties: {
FirstName: {
rules: {
required: true,
hasPersonObj: (value, exp, obj, property, schema, defaultRule, initial) => !!initial.Person,
},
},
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.Person.hasErrors()).toBe(true);
expect(errors.Person.FirstName.hasErrors()).toBe(true);
expect(errors.Person.FirstName.required).toBeDefined();
expect(errors.Person.FirstName.hasPersonObj).not.toBeDefined();
done();
});
});
it('should support high level object validation', done => {
const obj = {
FirstName: null,
LastName: 'John',
};
const schema = {
rules: {
required: (value, a, b, c, d, requiredRule) => {
return requiredRule(value) && requiredRule(value.FirstName) && requiredRule(value.LastName);
},
},
properties: {
FirstName: {
rules: {
required: true,
},
},
LastName: {
required: true,
pattern: [/\d+/, /\w+/],
},
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.FirstName.hasErrors()).toBe(true);
expect(errors.FirstName.required).toBeDefined();
expect(errors.LastName.hasErrors()).toBe(false);
done();
});
});
it('should support only rules definitions, if there are no messages or properties', done => {
const obj = {
FirstName: null,
};
const schema = {
properties: {
FirstName: {
required: true,
},
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.FirstName.required).toBeDefined();
done();
});
});
it('should pass validation for nested schemas', done => {
const obj = {
Person: {
FirstName: 'John',
},
};
const schema = {
properties: {
Person: {
rules: {
required: true,
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(false);
expect(errors.Person.hasErrors()).toBe(false);
expect(errors.Person.FirstName.hasErrors()).toBe(false);
expect(errors.Person.FirstName.required).not.toBeDefined();
done();
});
});
it('should support array schemas', done => {
const obj = {
Persons: [
{
FirstName: 'John',
},
{
FirstName: null,
},
{
FirstName: 'Bob',
},
],
};
const schema = {
properties: {
Persons: {
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
done();
});
});
it('should support high level array schemas', done => {
const obj = [
{
FirstName: 'John',
},
{
FirstName: null,
},
{
FirstName: 'Bob',
},
];
const schema = {
FirstName: {
rules: {
required: true,
},
},
};
validate(schema, obj).then(errors => {
expect(errors[0].FirstName.hasErrors()).toBe(false);
expect(errors[1].FirstName.hasErrors()).toBe(true);
expect(errors[2].FirstName.hasErrors()).toBe(false);
done();
});
});
it('should support primitive validation', done => {
const obj = null;
const schema = {
rules: {
required: true,
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.required).toBeDefined();
done();
});
});
it('should support __isArray__ param', done => {
const obj = null;
const schema = {
__isArray__: true,
rules: {
required: true,
},
};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(true);
expect(errors.hasErrorsOfTypes('required')).toBe(true);
done();
});
});
it('should support array validation and schemas as well', done => {
const obj = {
Persons: [
{
FirstName: 'John',
},
{
FirstName: null,
},
{
FirstName: 'Bob',
},
],
};
const schema = {
properties: {
Persons: {
__isArray__: true,
rules: {
minLength: 5,
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.Persons.hasErrors()).toBe(true);
expect(errors.Persons.hasErrorsOfTypes('minLength')).toBe(true);
expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
done();
});
});
it('should support custom rule', done => {
const obj = {
FirstName: 2,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
return actual === expected * 2;
},
},
messages: {
myRule: '%{actual} !== %{expected} * 2',
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
done();
});
});
it('should support error result for custom rule', done => {
const obj = {
FirstName: 4,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
if (actual === expected * 2) {
return 'not valid!';
}
return true;
},
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('not valid!');
done();
});
});
it('should support async rule', done => {
const obj = {
FirstName: 2,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(actual === expected * 2);
}, 10);
});
},
},
messages: {
myRule: '%{actual} !== %{expected} * 2',
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
done();
});
});
it('should support async message', done => {
const obj = {
FirstName: 2,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
return actual === expected * 2;
},
},
messages: {
myRule: (actual, expected) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`${actual} !== ${expected} * 2`);
}, 10);
});
},
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
done();
});
});
it('should not fail on empty schema', done => {
const obj = {
FirstName: 2,
};
const schema = {};
validate(schema, obj).then(errors => {
expect(errors.hasErrors()).toBe(false);
done();
});
});
it('should not fail on empty obj', done => {
const obj = {};
const schema = {
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.required).toBeDefined();
done();
});
});
it('should not fail on null obj', done => {
const obj = null;
const schema = {
__isObject__: true,
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.required).toBeDefined();
done();
});
});
it('should support high level schema', done => {
const obj = {
FirstName: null,
};
const schema = {
FirstName: {
rules: {
required: true,
},
},
};
validate(schema, obj).then(errors => {
expect(errors.FirstName.required).toBeDefined();
done();
});
});
it('should be fast', done => {
console.time('should be fast');
const obj = {
Id: '9131',
AccountId: '1',
UserId: null,
FirstName: 'Test',
LastName: 'Test',
Line1: '15625 Alton Pkwy',
Line2: 'Suite 200',
City: 'Irvine',
State: 'CA',
Zip: '92620',
Country: 'US',
Phone: '+1-2345678901',
Phone2: '2342342341',
Company: 'Test',
Email: 'test@example.com',
Type: 'primary',
};
const schema = {
messages: {
required: 'validation.required',
},
properties: {
FirstName: {
rules: {
type: 'string',
required: true,
maxLength: 45,
pattern: /^[a-zA-Z0-9]+$/,
},
messages: {
pattern: 'validation.firstName.pattern',
},
},
LastName: {
rules: {
type: 'string',
required: true,
maxLength: 45,
pattern: /^[a-zA-Z0-9]+$/,
},
messages: {
pattern: 'validation.firstName.pattern',
},
},
Email: {
rules: {
type: 'string',
required: true,
maxLength: 50,
format: 'email',
},
messages: {
format: 'validation.email.format',
},
},
Phone: {
rules: {
type: 'string',
required: true,
},
},
Line1: {
rules: {
type: 'string',
required: true,
maxLength: 100,
},
},
Line2: {
rules: {
type: 'string',
maxLength: 100,
},
},
Country: {
rules: {
type: 'string',
required: true,
},
},
State: {
rules: {
type: 'string',
required: true,
maxLength: 50,
},
},
City: {
rules: {
type: 'string',
required: true,
maxLength: 50,
},
},
Zip: {
rules: {
type: 'string',
required: true,
maxLength: 15,
},
},
},
};
validate(schema, obj).then(() => {
console.timeEnd('should be fast');
done();
});
});
it('should be fast with array', done => {
console.time('should be fast with array');
const obj = {
Id: '1',
Name: 'CalAmp Account',
ParentAccount: '0',
SolomonId: 'GAC22222222',
CreditTerms: 'COD',
SalesTerritory: 'US West',
Language: 'english',
PartnerBranding: 'calamp',
CreditPurchaseAuthorized: '0',
ActivationDate: null,
DeactivationDate: null,
IsDisabled: 'no',
IsMarkedArchive: 'no',
CanViewSubaccounts: '1',
PartnerLogo: 'calamp_logo_slogan_1_125px.jpg',
ShowPoweredByLogo: '1',
AllowNewDeviceUseFromParent: '0',
CanViewAirtimeStore: '1',
CanViewHardwareStore: '0',
LastChangeDate: '2016-08-12 05:37:24',
AllowCommandAutoRetry: '1',
SkipInstallProcess: '0',
AllowAirtimeAutoRenew: '1',
InvoiceAccount: '0',
EmailNotificationOnInstall: '1',
MandatoryInstallOdometer: '0',
RenewalPlanId: '4',
RenewalPlanPrice: null,
EnableLocationValidationReport: '1',
ParentName: 'N/A',
MaxScheduleActions: 12,
is_cac_account: false,
CustomUserAttributeDefinitions: [],
CustomVehicleAttributeDefinitions: [],
AirTimePlan: [
{
RenewalPlanSKU: 'rp1111',
RenewalPlanPrice: '33.33',
},
{
RenewalPlanSKU: 'rp2222',
RenewalPlanPrice: '444.44',
},
{
RenewalPlanSKU: 'aa',
RenewalPlanPrice: '',
},
{
RenewalPlanSKU: '',
RenewalPlanPrice: 'aa',
},
{
RenewalPlanSKU: '',
RenewalPlanPrice: '',
},
],
};
const airTimePlanRowRequired = (
actual,
expected,
property,
{ RenewalPlanSKU, RenewalPlanPrice },
schema,
defaultRule,
) => {
let isRequired = {
allowEmpty: true,
};
if (RenewalPlanSKU || RenewalPlanPrice) {
isRequired = true;
}
return defaultRule(actual, isRequired);
};
const schema = {
messages: {
required: 'validation.required',
},
properties: {
Name: {
rules: {
type: 'string',
required: true,
maxLength: 64,
},
},
Language: {
rules: {
type: 'string',
required: true,
},
},
AirTimePlan: {
rules: {
required: true,
},
properties: {
RenewalPlanSKU: {
rules: {
required: airTimePlanRowRequired,
pattern: /^RP|rp[0-9]{4}$/,
},
messages: {
pattern: 'validation.renewalPlanSKU.pattern',
},
},
RenewalPlanPrice: {
rules: {
required: airTimePlanRowRequired,
pattern: /^[0-9]+\.?[0-9]{2}$/,
},
messages: {
pattern: 'validation.renewalPlanPrice.pattern',
},
},
},
},
},
};
validate(schema, obj).then(() => {
console.timeEnd('should be fast with array');
done();
});
});
});
describe('validateSync', () => {
it('should validate required rule', () => {
const obj = {
FirstName: null,
};
const schema = {
FirstName: {
required: true,
},
};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(true);
expect(errors.FirstName.required).toBeDefined();
});
it('should override default global message', () => {
const obj = {
FirstName: null,
};
const schema = {
overrides: {
messages: {
required: 'Field is required',
},
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.required).toBe('Field is required');
});
it('should override default required rule (allow empty, for example)', () => {
const obj = {
FirstName: '',
};
const schema = {
overrides: {
rules: {
required: value => {
return value !== undefined && value !== null;
},
},
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.hasErrors()).toBe(false);
});
it('should support nested schemas', () => {
const obj = {
Person: {
FirstName: null,
},
};
const schema = {
Person: {
FirstName: {
required: true,
},
},
};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(true);
expect(errors.Person.hasErrors()).toBe(true);
expect(errors.Person.FirstName.hasErrors()).toBe(true);
expect(errors.Person.FirstName.required).toBeDefined();
});
it('should pass validation for nested schemas', () => {
const obj = {
Person: {
FirstName: 'John',
},
};
const schema = {
properties: {
Person: {
rules: {
required: true,
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(false);
expect(errors.Person.hasErrors()).toBe(false);
expect(errors.Person.FirstName.hasErrors()).toBe(false);
expect(errors.Person.FirstName.required).not.toBeDefined();
});
it('should support array schemas', () => {
const obj = {
Persons: [
{
FirstName: 'John',
},
{
FirstName: null,
},
{
FirstName: 'Bob',
},
],
};
const schema = {
properties: {
Persons: {
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
});
it('should support array validation and schemas as well', () => {
const obj = {
Persons: [
{
FirstName: 'John',
},
{
FirstName: null,
},
{
FirstName: 'Bob',
},
],
};
const schema = {
properties: {
Persons: {
rules: {
minLength: 5,
},
properties: {
FirstName: {
rules: {
required: true,
},
},
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.Persons.hasErrors()).toBe(true);
expect(errors.Persons.hasErrorsOfTypes('minLength')).toBe(true);
expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
});
it('should support custom rule', () => {
const obj = {
FirstName: 2,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
return actual === expected * 2;
},
},
messages: {
myRule: '%{actual} !== %{expected} * 2',
},
},
properties: {
FirstName: {
min: 6,
myRule: 2,
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
});
it('should support error result for custom rule', () => {
const obj = {
FirstName: 4,
};
const schema = {
overrides: {
rules: {
myRule: (actual, expected) => {
if (actual === expected * 2) {
return 'not valid!';
}
return true;
},
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('not valid!');
});
it('should not fail on empty schema', () => {
const obj = {
FirstName: 2,
};
const schema = {};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(false);
});
it('should not fail on empty obj', () => {
const obj = {};
const schema = {
properties: {
FirstName: {
rules: {
required: true,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.required).toBeDefined();
});
it('should support high level schema', () => {
const obj = {
FirstName: null,
};
const schema = {
FirstName: {
rules: {
required: true,
},
},
};
const errors = validateSync(schema, obj);
expect(errors.FirstName.required).toBeDefined();
});
});
describe('ValidationSchema', () => {
it('should support ValidationSchema for multiple validations', done => {
const obj = {
FirstName: 2,
};
const schema = new ValidationSchema({
overrides: {
rules: {
myRule: (actual, expected) => {
return actual === expected * 2;
},
},
messages: {
myRule: (actual, expected) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`${actual} !== ${expected} * 2`);
}, 10);
});
},
},
},
properties: {
FirstName: {
rules: {
min: 6,
myRule: 2,
},
},
},
});
schema.validate(obj).then(errors => {
expect(errors.FirstName.min).toBeDefined();
expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
done();
});
});
});
describe('validate simple array', () => {
it('should pass validation', () => {
const obj = {
categories: [1, 2, 3],
};
const schema = {
categories: {
rules: {
required: true,
minItems: 2,
},
},
};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(false);
});
it('should faild validation', () => {
const obj = {
categories: [1, 2, 3],
};
const schema = {
categories: {
rules: {
required: true,
maxItems: 2,
},
},
};
const errors = validateSync(schema, obj);
expect(errors.getFirstErrors().categories).toBeDefined();
});
});
describe('validate complex array', () => {
it('should pass validation', () => {
const obj = {
categories: [
{
id: 1,
},
{
id: 2,
},
{
id: 3,
},
],
};
const schema = {
categories: {
rules: {
required: true,
minItems: 2,
},
properties: {
id: {
required: true,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.hasErrors()).toBe(false);
});
it('should faild validation', () => {
const obj = {
categories: [
{
id: 1,
},
{
id: 2,
},
{
id: 3,
},
],
};
const schema = {
categories: {
rules: {
required: true,
minItems: 2,
},
properties: {
id: {
required: true,
},
name: {
required: true,
},
},
},
};
const errors = validateSync(schema, obj);
expect(errors.getFirstErrors().categories).toBeDefined();
});
});
});