v8n
Version:
Dead simple fluent JavaScript validation library
1,798 lines (1,548 loc) • 53.4 kB
JavaScript
import v8n from './v8n';
import Rule from './Rule';
import ValidationError from './ValidationError';
beforeEach(() => {
v8n.clearCustomRules();
});
describe('chaining', () => {
let validation;
beforeEach(() => {
validation = v8n()
.string()
.not.every.lowercase()
.not.null()
.first('a')
.last('e')
.some.equal('l')
.length(3, 5);
});
it('should chain rules', () => {
expect(debugRules(validation)).toEqual([
'string()',
'not.every.lowercase()',
'not.null()',
'first("a")',
'last("e")',
'some.equal("l")',
'length(3, 5)',
]);
});
});
describe("the 'validation' object", () => {
it('should be immutable', () => {
const a = v8n().number();
const b = a.not.equal(10);
expect(a).not.toBe(b);
expect(a.test(10)).toBeTruthy();
expect(b.test(10)).toBeFalsy();
});
});
describe('execution functions', () => {
describe("the 'test' function", () => {
let validation;
beforeEach(() => {
validation = v8n()
.number()
.between(10, 20)
.not.odd();
});
it('should return false for invalid value', () => {
expect(validation.test('Hello')).toBeFalsy();
expect(validation.test(22)).toBeFalsy();
expect(validation.test(13)).toBeFalsy();
});
it('should return true for valid value', () => {
expect(validation.test(12)).toBeTruthy();
});
});
describe("the 'testAll' function", () => {
let validation;
beforeEach(() => {
validation = v8n()
.string()
.last('o')
.not.includes('a');
});
it('should return an array with a ValidationException for each failed rule', () => {
const result = validation.testAll(100);
expect(result).toHaveLength(2);
for (let i = 0; i < result.length; i++) {
expect(result[i].rule).toBe(validation.chain[i]);
expect(result[i].value).toBe(100);
}
});
it('should return an empty array if all rules passed', () => {
expect(validation.testAll('Hello')).toHaveLength(0);
});
});
describe("the 'check' function", () => {
let validation;
beforeEach(() => {
validation = v8n()
.string()
.maxLength(3);
});
it('should throw exception for invalid value', () => {
expect(() => validation.check('abcd')).toThrow();
});
it('should pass through for valid value', () => {
expect(() => validation.check('abc')).not.toThrow();
});
describe('the thrown exception', () => {
let exception;
beforeEach(() => {
try {
validation.check('Hello');
} catch (ex) {
exception = ex;
}
});
it('should have rule object', () => {
expect(exception.rule).toBeInstanceOf(Rule);
});
it('should have the validated value', () => {
expect(exception.value).toBe('Hello');
});
});
});
describe("the 'testAsync' function", () => {
beforeEach(() => {
v8n.extend({ asyncRule });
});
it('should return a promise', () => {
const validation = v8n()
.minLength(2)
.asyncRule('Hello');
expect(validation.testAsync('Hello')).toBeInstanceOf(Promise);
});
it('should execute rules in sequence', async () => {
const validation = v8n()
.minLength(2)
.asyncRule('Hi')
.asyncRule('Hello');
expect.assertions(4);
try {
await validation.testAsync('Hello');
} catch (ex) {
expect(ex.rule.name).toBe('asyncRule');
expect(ex.value).toBe('Hello');
}
try {
await validation.testAsync('Hi');
} catch (ex) {
expect(ex.rule.name).toBe('asyncRule');
expect(ex.value).toBe('Hi');
}
});
describe('working with modifiers', () => {
it("should work with the 'not' modifier", async () => {
const validation = v8n()
.minLength(2)
.not.asyncRule('Hello');
expect.assertions(2);
try {
await validation.testAsync('Hello');
} catch (ex) {
expect(ex.rule.name).toEqual('asyncRule');
expect(ex.value).toEqual('Hello');
}
});
it('should work with mixed modifiers', async () => {
const val1 = v8n()
.some.equal('a')
.not.every.vowel()
.length(3);
await expect(val1.testAsync('abc')).resolves.toBe('abc');
await expect(val1.testAsync('aei')).rejects.toBeDefined();
await expect(val1.testAsync('efg')).rejects.toBeDefined();
await expect(val1.testAsync('abcd')).rejects.toBeDefined();
const val2 = v8n()
.some.even()
.some.odd()
.not.length(2);
await expect(val2.testAsync([1, 2, 3])).resolves.toEqual([1, 2, 3]);
await expect(val2.testAsync([1, 2])).rejects.toBeDefined();
await expect(val2.testAsync([2, 4, 6])).rejects.toBeDefined();
await expect(val2.testAsync([1, 3, 5])).rejects.toBeDefined();
const val3 = v8n()
.length(3)
.every.even()
.not.some.equal(2);
await expect(val3.testAsync([4, 6, 8])).resolves.toEqual([4, 6, 8]);
await expect(val3.testAsync([4, 6, 7])).rejects.toBeDefined();
await expect(val3.testAsync([4, 5, 6, 7])).rejects.toBeDefined();
await expect(val3.testAsync([2, 4, 6])).rejects.toBeDefined();
const val4 = v8n()
.not.every.lowercase()
.not.every.uppercase();
await expect(val4.testAsync('abc')).rejects.toBeDefined();
await expect(val4.testAsync('ABU')).rejects.toBeDefined();
await expect(val4.testAsync('aBc')).resolves.toEqual('aBc');
await expect(val4.testAsync('AbC')).resolves.toEqual('AbC');
});
});
describe("working with schema's", () => {
it('should handle async rule within a schema', async () => {
v8n.extend({ asyncRule });
const validation = v8n().schema({
item: v8n()
.number()
.asyncRule([10, 17, 20]),
});
await expect(
validation.testAsync({ item: '10' }),
).rejects.toBeDefined();
await expect(validation.testAsync({ item: 11 })).rejects.toBeDefined();
await expect(validation.testAsync({ item: 17 })).resolves.toEqual({
item: 17,
});
});
});
describe('the returned Promise', () => {
it('should resolves when valid', async () => {
const validation = v8n()
.string()
.minLength(3)
.asyncRule('Hello');
const result = await validation.testAsync('Hello');
expect(result).toEqual('Hello');
});
it('should rejects with ValidationException when invalid', async () => {
const validation = v8n()
.minLength(2)
.asyncRule('Hello');
expect.assertions(2);
try {
await validation.testAsync('Hi');
} catch (ex) {
expect(ex.rule.name).toBe('asyncRule');
expect(ex.value).toBe('Hi');
}
});
it('should rejects with with ValidationException when exception occurs', async () => {
const validation = v8n()
.number()
.between(0, 50)
.includes('a');
expect.assertions(3);
try {
await validation.testAsync(10);
} catch (ex) {
expect(ex.rule.name).toBe('includes');
expect(ex.value).toBe(10);
expect(ex.cause).toBeDefined();
}
});
it('should get correct ValidationException from composite failure', async () => {
function asyncCompositeRule() {
return value =>
new Promise(resolve => {
v8n()
.schema({
a: v8n().string(),
b: v8n().number(),
})
.check(value);
resolve(true);
});
}
v8n.extend({ asyncCompositeRule });
const validation = v8n().asyncCompositeRule();
expect.assertions(4);
try {
await validation.testAsync({ a: 'one', b: 'two' });
} catch (ex) {
expect(ex.rule.name).toBe('asyncCompositeRule');
expect(ex.value).toEqual({ a: 'one', b: 'two' });
expect(ex.cause.rule.name).toBe('schema');
expect(ex.cause.value).toEqual({ a: 'one', b: 'two' });
}
});
});
});
});
describe('modifiers', () => {
describe("the 'not' modifier", () => {
it('should invert the next rule meaning', () => {
const validation = v8n()
.number()
.not.between(2, 8)
.not.even();
expect(validation.test(4)).toBeFalsy();
expect(validation.test(6)).toBeFalsy();
expect(validation.test(11)).toBeTruthy();
expect(() => validation.check(4)).toThrow();
expect(() => validation.check(6)).toThrow();
expect(() => validation.check(11)).not.toThrow();
});
test('double negative', () => {
const validation = v8n()
.not.not.number()
.not.not.positive();
expect(validation.test(1)).toBeTruthy();
expect(() => validation.check(12)).not.toThrow();
expect(validation.test('12')).toBeFalsy();
expect(() => validation.check(-1)).toThrow();
});
});
describe("the 'some' modifier", () => {
it('expect that rule passes on some array value', async () => {
const validation = v8n().some.positive();
expect(validation.test([-1, -2, -3])).toBeFalsy();
expect(validation.test(10)).toBeFalsy();
expect(validation.test([-1, -2, 1])).toBeTruthy();
expect(validation.test([1, 2, 3])).toBeTruthy();
expect(
v8n()
.some.schema({ str: v8n().string() })
.test([true, { str: 'hello' }, 12]),
).toBeTruthy();
expect(
v8n()
.some.schema({ str: v8n().string() })
.test(['hello', { str: true }, 12]),
).toBeFalsy();
expect(() =>
v8n()
.some.schema({ str: v8n().string() })
.check([true, { str: 'hello' }, 12]),
).not.toThrow();
expect(() =>
v8n()
.some.schema({ str: v8n().string() })
.check(['hello', { str: true }, 12]),
).toThrow();
await expect(
v8n()
.some.schema({ str: v8n().string() })
.testAsync([true, { str: 'hello' }, 12]),
).resolves.toEqual([true, { str: 'hello' }, 12]);
await expect(
v8n()
.some.schema({ str: v8n().string() })
.testAsync([false, { str: true }, 12]),
).rejects.toBeDefined();
});
});
describe("the 'every' modifier", () => {
it('expect that rule passes for every array value', async () => {
const validation = v8n().every.positive();
expect(validation.test([1, 2, 3, -1])).toBeFalsy();
expect(validation.test(10)).toBeFalsy();
expect(validation.test([1, 2, 3])).toBeTruthy();
expect(validation.test([1, 2, 3])).toBeTruthy();
expect(
v8n()
.every.schema({ str: v8n().string() })
.test([{ str: 'Hello' }]),
).toBeTruthy();
expect(() =>
v8n()
.every.schema({ str: v8n().string() })
.check([{ str: 'Hello' }]),
).not.toThrow();
expect(() =>
v8n()
.every.schema({ str: v8n().string() })
.check([{ str: true }]),
).toThrow();
await expect(
v8n()
.every.schema({ str: v8n().string() })
.testAsync([{ str: 'Hello' }]),
).resolves.toEqual([{ str: 'Hello' }]);
await expect(
v8n()
.every.schema({ str: v8n().string() })
.testAsync([{ str: true }]),
).rejects.toBeDefined();
});
it('expect that error if formatted correctly', async () => {
const validation = v8n().schema({
item: v8n().every.schema({
a: v8n().string(),
}),
});
const data = { item: [{ a: 1 }] };
const result = validation.testAll(data);
expect(result[0].rule.name).toBe('schema');
expect(result[0].cause).toHaveLength(1);
expect(result[0].cause[0].rule.name).toBe('schema');
expect(result[0].cause[0].cause).toHaveLength(1);
expect(result[0].cause[0].target).toBe('item');
expect(result[0].cause[0].cause[0].rule.name).toBe('string');
expect(result[0].cause[0].cause[0].target).toBe('a');
});
});
describe("the 'strict' modifier", () => {
it('makes the schema rule strict', () => {
const validation = v8n().strict.schema({
id: v8n()
.number()
.positive(),
name: v8n()
.string()
.minLength(4),
});
expect(
validation.test({
id: 1,
name: 'Luke',
}),
).toBeTruthy();
expect(
validation.test({
id: 1,
}),
).toBeFalsy();
expect(
validation.test({
id: 1,
name: 'Luke',
lastname: 'Skywalker',
}),
).toBeFalsy();
});
it('makes the schema rule strict asynchronously', async () => {
v8n.extend({ asyncRule });
const validation = v8n().strict.schema({
id: v8n()
.number()
.asyncRule([10, 20]),
});
const valid = {
id: 10,
};
const invalidValue = {
id: 1,
};
const invalidExtraProp = {
id: 1,
name: 'Luke',
};
await expect(validation.testAsync(valid)).resolves.toBe(valid);
await expect(validation.testAsync(invalidValue)).rejects.toBeInstanceOf(
ValidationError,
);
await expect(
validation.testAsync(invalidExtraProp),
).rejects.toBeInstanceOf(ValidationError);
});
it('does not affect non-schema validations', () => {
expect(
v8n()
.strict.number()
.test(1),
).toEqual(
v8n()
.number()
.test(1),
);
expect(
v8n()
.strict.lowercase()
.test('abc'),
).toEqual(
v8n()
.lowercase()
.test('abc'),
);
});
it('can be chained with other modifiers', () => {
const schema = {
id: v8n()
.number()
.positive(),
};
expect(
v8n()
.not.strict.schema(schema)
.test({
id: 1,
}),
).toBeFalsy();
expect(
v8n()
.not.strict.schema(schema)
.test({
id: 1,
name: 'abc',
}),
).toBeTruthy();
expect(
v8n()
.some.strict.schema(schema)
.test([
{
id: 1,
name: 'abc',
},
{
id: 1,
},
]),
).toBeTruthy();
expect(
v8n()
.some.strict.schema(schema)
.test([
{
id: 1,
name: 'abc',
},
{
id: -1,
},
]),
).toBeFalsy();
expect(
v8n()
.not.some.strict.schema(schema)
.test([
{
id: 1,
name: 'abc',
},
{
id: 1,
},
]),
).toBeFalsy();
});
});
test('should be able to mix modifiers', () => {
const validation = v8n().not.some.positive();
expect(validation.test([-1, -2, 1])).toBeFalsy();
expect(validation.test([-1, -2, -3])).toBeTruthy();
expect(validation.test([-5, -5, -1])).toBeTruthy();
});
test('fluency', async () => {
// some item should not be 2
const a = v8n().some.not.exact(2);
expect(a.test([2, 2, 3])).toBeTruthy();
expect(a.test([2, 2, 2])).toBeFalsy();
// all items should not be 2
const b = v8n().not.some.exact(2);
expect(b.test([2, 3, 3])).toBeFalsy();
expect(b.test([3, 3, 3])).toBeTruthy();
const c = v8n()
.not.every.even()
.some.not.exact(3);
expect(c.test([2, 4, 6])).toBeFalsy();
expect(c.test([3, 3, 3])).toBeFalsy();
expect(c.test([2, 3, 4])).toBeTruthy();
expect(c.test([2, 4, 5])).toBeTruthy();
});
});
describe('rules', () => {
test('equal', () => {
const is = v8n().equal('123');
expect(is.test('123')).toBeTruthy();
expect(is.test(123)).toBeTruthy();
expect(is.test('Hello')).toBeFalsy();
const not = v8n().not.equal(123);
expect(not.test('123')).toBeFalsy();
expect(not.test(123)).toBeFalsy();
expect(not.test('Hello')).toBeTruthy();
});
test('exact', () => {
const is = v8n().exact('123');
expect(is.test('123')).toBeTruthy();
expect(is.test(123)).toBeFalsy();
expect(is.test('Hello')).toBeFalsy();
const not = v8n().not.exact(123);
expect(not.test(123)).toBeFalsy();
expect(not.test('123')).toBeTruthy();
expect(not.test('Hello')).toBeTruthy();
});
test('pattern', () => {
const validation = v8n().pattern(/^[a-z]+$/);
expect(validation.test('a')).toBeTruthy();
expect(validation.test('ab')).toBeTruthy();
expect(validation.test(' ')).toBeFalsy();
expect(validation.test('A')).toBeFalsy();
expect(validation.test('Ab')).toBeFalsy();
});
test('string', () => {
const validation = v8n().string();
expect(validation.test('hello')).toBeTruthy();
expect(validation.test('')).toBeTruthy();
expect(validation.test(' ')).toBeTruthy();
expect(validation.test(123)).toBeFalsy();
expect(validation.test(true)).toBeFalsy();
expect(validation.test(false)).toBeFalsy();
expect(validation.test(undefined)).toBeFalsy();
expect(validation.test()).toBeFalsy();
expect(validation.test(null)).toBeFalsy();
});
test('undefined', () => {
const is = v8n().undefined();
expect(is.test()).toBeTruthy();
expect(is.test(undefined)).toBeTruthy();
expect(is.test(null)).toBeFalsy();
expect(is.test('')).toBeFalsy();
expect(is.test(0)).toBeFalsy();
expect(is.test(false)).toBeFalsy();
const not = v8n().not.undefined();
expect(not.test()).toBeFalsy();
expect(not.test(undefined)).toBeFalsy();
expect(not.test(null)).toBeTruthy();
expect(not.test('')).toBeTruthy();
expect(not.test(0)).toBeTruthy();
expect(not.test(false)).toBeTruthy();
});
test('null', () => {
const is = v8n().null();
expect(is.test(null)).toBeTruthy();
expect(is.test()).toBeFalsy();
expect(is.test(undefined)).toBeFalsy();
expect(is.test('')).toBeFalsy();
expect(is.test(0)).toBeFalsy();
expect(is.test(false)).toBeFalsy();
const not = v8n().not.null();
expect(not.test(null)).toBeFalsy();
expect(not.test()).toBeTruthy();
expect(not.test(undefined)).toBeTruthy();
expect(not.test('')).toBeTruthy();
expect(not.test(0)).toBeTruthy();
expect(not.test(false)).toBeTruthy();
});
test('array', () => {
const validation = v8n().array();
expect(validation.test([])).toBeTruthy();
expect(validation.test([1, 2])).toBeTruthy();
expect(validation.test(new Array())).toBeTruthy();
expect(validation.test(null)).toBeFalsy();
expect(validation.test(undefined)).toBeFalsy();
expect(validation.test('string')).toBeFalsy();
});
test('object', () => {
const validation = v8n().object();
expect(validation.test([])).toBeTruthy();
expect(validation.test({})).toBeTruthy();
expect(validation.test(null)).toBeTruthy();
expect(validation.test(() => {})).toBeFalsy();
expect(validation.test(undefined)).toBeFalsy();
expect(validation.test('string')).toBeFalsy();
expect(validation.test(2)).toBeFalsy();
});
test('instanceOf', () => {
const validationDate = v8n().instanceOf(Date);
expect(validationDate.test(new Date())).toBeTruthy();
expect(validationDate.test({})).toBeFalsy();
expect(validationDate.test(null)).toBeFalsy();
expect(validationDate.test('string')).toBeFalsy();
const validationObject = v8n().instanceOf(Object);
expect(validationObject.test(new Date())).toBeTruthy();
expect(validationObject.test({})).toBeTruthy();
expect(validationObject.test(null)).toBeFalsy();
expect(validationObject.test('string')).toBeFalsy();
});
test('number', () => {
const noFlag = v8n().number();
expect(noFlag.test(34)).toBeTruthy();
expect(noFlag.test(-10)).toBeTruthy();
expect(noFlag.test('1')).toBeFalsy();
expect(noFlag.test(null)).toBeFalsy();
expect(noFlag.test(undefined)).toBeFalsy();
expect(noFlag.test(NaN)).toBeTruthy();
expect(noFlag.test(Infinity)).toBeTruthy();
expect(noFlag.test(-Infinity)).toBeTruthy();
const flag = v8n().number(false);
expect(flag.test(34)).toBeTruthy();
expect(flag.test(-10)).toBeTruthy();
expect(flag.test('1')).toBeFalsy();
expect(flag.test(null)).toBeFalsy();
expect(flag.test(undefined)).toBeFalsy();
expect(flag.test(NaN)).toBeFalsy();
expect(flag.test(Infinity)).toBeFalsy();
expect(flag.test(-Infinity)).toBeFalsy();
});
test('numeric', () => {
const validation = v8n().numeric();
expect(validation.test('-10')).toBeTruthy();
expect(validation.test('0')).toBeTruthy();
expect(validation.test(0xff)).toBeTruthy();
expect(validation.test('0xFF')).toBeTruthy();
expect(validation.test('8e5')).toBeTruthy();
expect(validation.test('3.1415')).toBeTruthy();
expect(validation.test(+10)).toBeTruthy();
expect(validation.test('-0x42')).toBeFalsy();
expect(validation.test('7.2acdgs')).toBeFalsy();
expect(validation.test('')).toBeFalsy();
expect(validation.test({})).toBeFalsy();
expect(validation.test(NaN)).toBeFalsy();
expect(validation.test(null)).toBeFalsy();
expect(validation.test(true)).toBeFalsy();
expect(validation.test(Infinity)).toBeFalsy();
expect(validation.test(undefined)).toBeFalsy();
});
test('boolean', () => {
const validation = v8n().boolean();
expect(validation.test(true)).toBeTruthy();
expect(validation.test(false)).toBeTruthy();
expect(validation.test(1)).toBeFalsy();
expect(validation.test(0)).toBeFalsy();
expect(validation.test(null)).toBeFalsy();
expect(validation.test(undefined)).toBeFalsy();
});
test('lowercase', () => {
const validation = v8n().lowercase();
expect(validation.test('')).toBeFalsy();
expect(validation.test(' ')).toBeFalsy();
expect(validation.test('aBc')).toBeFalsy();
expect(validation.test('abc')).toBeTruthy();
expect(validation.test('abc def g')).toBeTruthy();
expect(validation.test(true)).toBeTruthy();
expect(validation.test(1)).toBeFalsy();
});
test('uppercase', () => {
const validation = v8n().uppercase();
expect(validation.test('')).toBeFalsy();
expect(validation.test(' ')).toBeFalsy();
expect(validation.test('A')).toBeTruthy();
expect(validation.test('ABC')).toBeTruthy();
expect(validation.test('ABC DEF G')).toBeTruthy();
expect(validation.test('abc')).toBeFalsy();
expect(validation.test('Abc')).toBeFalsy();
});
test('first', () => {
const letter = v8n().first('n');
expect(letter.test('n')).toBeTruthy();
expect(letter.test('nice')).toBeTruthy();
expect(letter.test(null)).toBeFalsy();
expect(letter.test('N')).toBeFalsy();
expect(letter.test('wrong')).toBeFalsy();
expect(letter.test(undefined)).toBeFalsy();
expect(letter.test(['n', 'i', 'c', 'e'])).toBeTruthy();
expect(letter.test(['a', 'b', 'c'])).toBeFalsy();
const number = v8n().first(2);
expect(number.test(20)).toBeFalsy();
expect(number.test(12)).toBeFalsy();
expect(number.test([2, 3])).toBeTruthy();
expect(number.test([1, 2])).toBeFalsy();
});
test('last', () => {
const letter = v8n().last('d');
expect(letter.test('d')).toBeTruthy();
expect(letter.test('old')).toBeTruthy();
expect(letter.test(undefined)).toBeFalsy();
expect(letter.test('D')).toBeFalsy();
expect(letter.test("don't")).toBeFalsy();
expect(letter.test(null)).toBeFalsy();
const number = v8n().last(2);
expect(number.test(32)).toBeFalsy();
expect(number.test(23)).toBeFalsy();
expect(number.test([3, 2])).toBeTruthy();
expect(number.test([2, 3])).toBeFalsy();
});
test('vowel', () => {
const validation = v8n().vowel();
expect(validation.test('aeiou')).toBeTruthy();
expect(validation.test('AEIOU')).toBeTruthy();
expect(validation.test('abcde')).toBeFalsy();
expect(validation.test('ABCDE')).toBeFalsy();
});
test('consonant', () => {
const validation = v8n().consonant();
expect(validation.test('abcde')).toBeFalsy();
expect(validation.test('bcdf')).toBeTruthy();
expect(validation.test('^')).toBeFalsy();
expect(validation.test('ç')).toBeFalsy();
});
test('empty', () => {
const validation = v8n().empty();
expect(validation.test('')).toBeTruthy();
expect(validation.test(' ')).toBeFalsy();
expect(validation.test('ab')).toBeFalsy();
expect(validation.test([])).toBeTruthy();
// eslint-disable-next-line no-sparse-arrays
expect(validation.test([, ,])).toBeFalsy();
expect(validation.test([1, 2])).toBeFalsy();
});
test('length', () => {
const minAndMax = v8n().length(3, 4);
expect(minAndMax.test('ab')).toBeFalsy();
expect(minAndMax.test('abc')).toBeTruthy();
expect(minAndMax.test('abcd')).toBeTruthy();
expect(minAndMax.test('abcde')).toBeFalsy();
expect(minAndMax.test([1, 2])).toBeFalsy();
expect(minAndMax.test([1, 2, 3])).toBeTruthy();
expect(minAndMax.test([1, 2, 3, 4])).toBeTruthy();
expect(minAndMax.test([1, 2, 3, 4, 5])).toBeFalsy();
const exact = v8n().length(3);
expect(exact.test('ab')).toBeFalsy();
expect(exact.test('abc')).toBeTruthy();
expect(exact.test('abcd')).toBeFalsy();
expect(exact.test([1, 2])).toBeFalsy();
expect(exact.test([1, 2, 3])).toBeTruthy();
expect(exact.test([1, 2, 3, 4])).toBeFalsy();
});
test('minLength', () => {
const validation = v8n().minLength(2);
expect(validation.test('a')).toBeFalsy();
expect(validation.test('ab')).toBeTruthy();
expect(validation.test('abc')).toBeTruthy();
expect(validation.test('abcd')).toBeTruthy();
});
test('maxLength', () => {
const validation = v8n().maxLength(3);
expect(validation.test('a')).toBeTruthy();
expect(validation.test('ab')).toBeTruthy();
expect(validation.test('abc')).toBeTruthy();
expect(validation.test('abcd')).toBeFalsy();
});
test('negative', () => {
const validation = v8n().negative();
expect(validation.test(-1)).toBeTruthy();
expect(validation.test(0)).toBeFalsy();
expect(validation.test(1)).toBeFalsy();
});
test('positive', () => {
const validation = v8n().positive();
expect(validation.test(-1)).toBeFalsy();
expect(validation.test(0)).toBeTruthy();
expect(validation.test(1)).toBeTruthy();
});
test('lessThan', () => {
const is = v8n().lessThan(3);
expect(is.test(1)).toBeTruthy();
expect(is.test(2)).toBeTruthy();
expect(is.test(-4)).toBeTruthy();
expect(is.test(3)).toBeFalsy();
expect(is.test(4)).toBeFalsy();
expect(is.test(-Infinity)).toBeTruthy();
expect(is.test(Infinity)).toBeFalsy();
const not = v8n().not.lessThan(3);
expect(not.test(1)).toBeFalsy();
expect(not.test(2)).toBeFalsy();
expect(not.test(-4)).toBeFalsy();
expect(not.test(3)).toBeTruthy();
expect(not.test(4)).toBeTruthy();
expect(not.test(-Infinity)).toBeFalsy();
expect(not.test(Infinity)).toBeTruthy();
expect(
v8n()
.lessThan(-Infinity)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.lessThan(-Infinity)
.test(Infinity),
).toBeFalsy();
expect(
v8n()
.lessThan(Infinity)
.test(-Infinity),
).toBeTruthy();
expect(
v8n()
.lessThan(Number.MIN_SAFE_INTEGER)
.test(-Infinity),
).toBeTruthy();
});
test('lessThanOrEqualTo', () => {
const is = v8n().lessThanOrEqual(3);
expect(is.test(-4)).toBeTruthy();
expect(is.test(-3)).toBeTruthy();
expect(is.test(1)).toBeTruthy();
expect(is.test(2)).toBeTruthy();
expect(is.test(3)).toBeTruthy();
expect(is.test(4)).toBeFalsy();
expect(is.test(-Infinity)).toBeTruthy();
expect(is.test(Infinity)).toBeFalsy();
const not = v8n().not.lessThanOrEqual(3);
expect(not.test(-4)).toBeFalsy();
expect(not.test(-3)).toBeFalsy();
expect(not.test(1)).toBeFalsy();
expect(not.test(2)).toBeFalsy();
expect(not.test(3)).toBeFalsy();
expect(not.test(4)).toBeTruthy();
expect(not.test(-Infinity)).toBeFalsy();
expect(not.test(Infinity)).toBeTruthy();
expect(
v8n()
.lessThanOrEqual(-Infinity)
.test(-Infinity),
).toBeTruthy();
expect(
v8n()
.lessThanOrEqual(-Infinity)
.test(Infinity),
).toBeFalsy();
expect(
v8n()
.lessThanOrEqual(Infinity)
.test(-Infinity),
).toBeTruthy();
expect(
v8n()
.lessThanOrEqual(Number.MIN_SAFE_INTEGER)
.test(-Infinity),
).toBeTruthy();
});
test('greaterThan', () => {
const is = v8n().greaterThan(3);
expect(is.test(2)).toBeFalsy();
expect(is.test(-3)).toBeFalsy();
expect(is.test(3)).toBeFalsy();
expect(is.test(4)).toBeTruthy();
expect(is.test(-Infinity)).toBeFalsy();
expect(is.test(Infinity)).toBeTruthy();
const not = v8n().not.greaterThan(3);
expect(not.test(2)).toBeTruthy();
expect(not.test(-3)).toBeTruthy();
expect(not.test(3)).toBeTruthy();
expect(not.test(4)).toBeFalsy();
expect(not.test(-Infinity)).toBeTruthy();
expect(not.test(Infinity)).toBeFalsy();
expect(
v8n()
.greaterThan(-Infinity)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.greaterThan(-Infinity)
.test(Infinity),
).toBeTruthy();
expect(
v8n()
.greaterThan(Infinity)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.greaterThan(Number.MIN_SAFE_INTEGER)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.greaterThan(Number.MAX_SAFE_INTEGER)
.test(Infinity),
).toBeTruthy();
});
test('greaterThanOrEqual', () => {
const is = v8n().greaterThanOrEqual(3);
expect(is.test(2)).toBeFalsy();
expect(is.test(-3)).toBeFalsy();
expect(is.test(3)).toBeTruthy();
expect(is.test(4)).toBeTruthy();
expect(is.test(-Infinity)).toBeFalsy();
expect(is.test(Infinity)).toBeTruthy();
const not = v8n().not.greaterThanOrEqual(3);
expect(not.test(2)).toBeTruthy();
expect(not.test(-3)).toBeTruthy();
expect(not.test(3)).toBeFalsy();
expect(not.test(4)).toBeFalsy();
expect(not.test(-Infinity)).toBeTruthy();
expect(not.test(Infinity)).toBeFalsy();
expect(
v8n()
.greaterThanOrEqual(-Infinity)
.test(-Infinity),
).toBeTruthy();
expect(
v8n()
.greaterThanOrEqual(-Infinity)
.test(Infinity),
).toBeTruthy();
expect(
v8n()
.greaterThanOrEqual(Infinity)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.greaterThanOrEqual(Number.MIN_SAFE_INTEGER)
.test(-Infinity),
).toBeFalsy();
expect(
v8n()
.greaterThanOrEqual(Number.MAX_SAFE_INTEGER)
.test(Infinity),
).toBeTruthy();
expect(
v8n()
.greaterThanOrEqual(Infinity)
.test(Infinity),
).toBeTruthy();
});
test('range', () => {
const is = v8n().range(2, 4);
expect(is.test(1)).toBeFalsy();
expect(is.test(5)).toBeFalsy();
expect(is.test(2)).toBeTruthy();
expect(is.test(3)).toBeTruthy();
expect(is.test(4)).toBeTruthy();
const not = v8n().not.range(2, 4);
expect(not.test(1)).toBeTruthy();
expect(not.test(5)).toBeTruthy();
expect(not.test(2)).toBeFalsy();
expect(not.test(3)).toBeFalsy();
expect(not.test(4)).toBeFalsy();
});
test('even', () => {
const validation = v8n().even();
expect(validation.test(-2)).toBeTruthy();
expect(validation.test(-1)).toBeFalsy();
expect(validation.test(0)).toBeTruthy();
expect(validation.test(1)).toBeFalsy();
expect(validation.test(2)).toBeTruthy();
});
test('odd', () => {
const validation = v8n().odd();
expect(validation.test(-2)).toBeFalsy();
expect(validation.test(-1)).toBeTruthy();
expect(validation.test(0)).toBeFalsy();
expect(validation.test(1)).toBeTruthy();
expect(validation.test(2)).toBeFalsy();
});
test('between', () => {
const is = v8n().between(3, 5);
expect(is.test(2)).toBeFalsy();
expect(is.test(3)).toBeTruthy();
expect(is.test(4)).toBeTruthy();
expect(is.test(5)).toBeTruthy();
expect(is.test(6)).toBeFalsy();
const not = v8n().not.between(3, 5);
expect(not.test(2)).toBeTruthy();
expect(not.test(3)).toBeFalsy();
expect(not.test(4)).toBeFalsy();
expect(not.test(5)).toBeFalsy();
expect(not.test(6)).toBeTruthy();
});
test('includes', () => {
const is = v8n().includes('2');
expect(is.test(['1', '2', '3'])).toBeTruthy();
expect(is.test(['1', '3'])).toBeFalsy();
expect(is.test(['1', '2'])).toBeTruthy();
expect(is.test('123')).toBeTruthy();
expect(is.test('13')).toBeFalsy();
expect(is.test([1, 2, 3])).toBeFalsy();
expect(is.test(2)).toBeFalsy();
const not = v8n().not.includes('2');
expect(not.test(['1', '2', '3'])).toBeFalsy();
expect(not.test(['1', '3'])).toBeTruthy();
expect(not.test(['1', '2'])).toBeFalsy();
expect(not.test('123')).toBeFalsy();
expect(not.test('13')).toBeTruthy();
expect(not.test([1, 2, 3])).toBeTruthy();
expect(not.test(2)).toBeTruthy();
});
test('integer', () => {
const is = v8n().integer();
expect(is.test(0)).toBeTruthy();
expect(is.test(12)).toBeTruthy();
expect(is.test(99999999999)).toBeTruthy();
expect(is.test(-100000)).toBeTruthy();
expect(is.test('12')).toBeFalsy();
expect(is.test(3.14)).toBeFalsy();
expect(is.test(NaN)).toBeFalsy();
expect(is.test(Infinity)).toBeFalsy();
const not = v8n().not.integer();
expect(not.test(0)).toBeFalsy();
expect(not.test(12)).toBeFalsy();
expect(not.test(99999999999)).toBeFalsy();
expect(not.test(-100000)).toBeFalsy();
expect(not.test('12')).toBeTruthy();
expect(not.test(3.14)).toBeTruthy();
expect(not.test(NaN)).toBeTruthy();
expect(not.test(Infinity)).toBeTruthy();
});
describe('schema', () => {
let is, not, validObj, invalidObj;
beforeEach(() => {
is = v8n().schema({
one: v8n().equal(1),
two: v8n().schema({
three: v8n().equal(3),
four: v8n().equal(4),
five: v8n().schema({
six: v8n().equal(6),
}),
}),
seven: v8n().schema({
eight: v8n().not.equal(8),
}),
});
not = v8n().not.schema({
one: v8n().equal(1),
two: v8n().schema({
three: v8n().equal(3),
four: v8n().equal(4),
five: v8n().schema({
six: v8n().equal(6),
}),
}),
seven: v8n().schema({
eight: v8n().not.equal(8),
}),
});
validObj = { one: 1, two: { three: 3, four: 4, five: { six: 6 } } };
invalidObj = { one: 'Hello' };
});
it('should work with validation', () => {
const result = is.testAll(invalidObj);
expect(result[0].cause).toHaveLength(2);
expect(result[0].cause[0].rule.name).toBe('equal');
expect(result[0].cause[1].rule.name).toBe('schema');
expect(result[0].cause[1].cause).toHaveLength(3);
expect(result[0].cause[1].cause[2].rule.name).toBe('schema');
expect(result[0].cause[1].cause[2].cause[0].target).toBe('six');
expect(is.test(validObj)).toBeTruthy();
expect(is.test(invalidObj)).toBeFalsy();
expect(not.test(validObj)).toBeFalsy();
expect(not.test(invalidObj)).toBeTruthy();
});
it('should work with nested validations', () => {
expect.assertions(12);
try {
is.check(invalidObj);
} catch (ex) {
expect(ex.cause).toHaveLength(2);
expect(ex.cause[0].rule.name).toBe('equal');
expect(ex.cause[0].value).toBe(invalidObj.one);
expect(ex.cause[1].rule.name).toBe('schema');
expect(ex.cause[1].cause).toHaveLength(3);
expect(ex.cause[1].cause[0].rule.name).toBe('equal');
expect(ex.cause[1].cause[1].rule.name).toBe('equal');
expect(ex.cause[1].cause[2].rule.name).toBe('schema');
expect(ex.cause[1].cause[2].cause[0].target).toBe('six');
}
expect(() => is.check(validObj)).not.toThrow();
expect(() => not.check(invalidObj)).not.toThrow();
expect(() => not.check(validObj)).toThrow();
});
});
describe('passesAnyOf', () => {
it('should pass if any of the received validation is valid', () => {
const is = v8n().passesAnyOf(
v8n().number(),
v8n().schema({
id: v8n().string(),
}),
);
expect(is.test(true)).toBeFalsy();
expect(is.test(undefined)).toBeFalsy();
expect(is.test('Hello')).toBeFalsy();
expect(is.test(null)).toBeFalsy();
expect(is.test({})).toBeFalsy();
expect(is.test(11)).toBeTruthy();
expect(is.test({ id: 'ef13c' })).toBeTruthy();
const not = v8n().not.passesAnyOf(
v8n().number(),
v8n().schema({
id: v8n().string(),
}),
);
expect(not.test(true)).toBeTruthy();
expect(not.test(undefined)).toBeTruthy();
expect(not.test('Hello')).toBeTruthy();
expect(not.test(null)).toBeTruthy();
expect(not.test({})).toBeTruthy();
expect(not.test(11)).toBeFalsy();
expect(not.test({ id: 'ef13c' })).toBeFalsy();
});
it("should fail if there's no validation specified", () => {
expect(
v8n()
.passesAnyOf()
.test('Foo'),
).toBeFalsy();
});
it('should work together with other rules', () => {
const validation = v8n()
.string()
.passesAnyOf(v8n().every.lowercase(), v8n().every.uppercase());
expect(validation.test('HELLO')).toBeTruthy();
expect(validation.test('hello')).toBeTruthy();
expect(validation.test('Hello')).toBeFalsy();
expect(validation.test({})).toBeFalsy();
});
test('composition', () => {
const validation = v8n().passesAnyOf(
v8n().passesAnyOf(v8n().null(), v8n().undefined()),
v8n().passesAnyOf(v8n().number(), v8n().boolean()),
);
expect(validation.test(null)).toBeTruthy();
expect(validation.test(undefined)).toBeTruthy();
expect(validation.test(12)).toBeTruthy();
expect(validation.test(false)).toBeTruthy();
expect(validation.test('Hello')).toBeFalsy();
});
});
describe('optional', () => {
it('should pass when validation passes', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
);
expect(optional.test(1)).toBe(true);
expect(optional.test(2)).toBe(true);
expect(optional.test(1000)).toBe(true);
expect(
v8n()
.optional(v8n().string())
.test(''),
).toBe(true);
});
it('should fail when validation fails', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
);
expect(optional.test(-1)).toBe(false);
expect(optional.test(-2)).toBe(false);
expect(optional.test(-100)).toBe(false);
});
it('should pass for null and undefined', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
);
expect(optional.test(null)).toBe(true);
expect(optional.test(undefined)).toBe(true);
});
it("should work with the 'string' rule", () => {
expect(
v8n()
.optional(v8n().string(), false)
.test(''),
).toBe(true);
expect(
v8n()
.optional(v8n().string(), false)
.test('hello'),
).toBe(true);
expect(
v8n()
.optional(v8n().string(), true)
.test(''),
).toBe(true);
expect(
v8n()
.optional(v8n().string(), true)
.test('hello'),
).toBe(true);
expect(
v8n()
.optional(v8n().string(), true)
.test('10'),
).toBe(true);
expect(
v8n()
.optional(v8n().string(), true)
.test(10),
).toBe(false);
expect(
v8n()
.optional(v8n().string(), true)
.test(),
).toBe(true);
});
it('should not consider trimmed empty string valid by default', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
);
expect(optional.test('')).toBe(false);
expect(optional.test(' ')).toBe(false);
});
it('should consider trimmed empty string valid when it is set to true', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
true,
);
expect(optional.test('')).toBe(true);
expect(optional.test(' ')).toBe(true);
expect(optional.test(-1)).toBe(false);
expect(optional.test('hello')).toBe(false);
});
it('should not consider trimmed empty string valid when it is set to false', () => {
const optional = v8n().optional(
v8n()
.number()
.positive(),
false,
);
expect(optional.test('')).toBe(false);
expect(optional.test(' ')).toBe(false);
});
it('should correctly validate asynchronous rules', async () => {
v8n.extend({ asyncRule });
const optional = v8n().optional(
v8n()
.number()
.asyncRule([10, 20]),
);
await expect(optional.testAsync(10)).resolves.toBe(10);
await expect(optional.testAsync('abc')).rejects.toBeInstanceOf(
ValidationError,
);
await expect(optional.testAsync(15)).rejects.toBeInstanceOf(
ValidationError,
);
await expect(optional.testAsync(undefined)).resolves.toBe(undefined);
});
});
});
describe('validation composition', () => {
let complex, validObj, invalidObj, causes;
beforeEach(() => {
// A complex schema
complex = v8n().schema({
one: v8n().equal('one'),
two: v8n().schema({
two_one: v8n().equal('two_one'),
two_two: v8n().not.schema({
two_two_one: v8n().equal('two_two_one'),
}),
}),
three: v8n().schema({
three_one: v8n().schema({
three_one_one: v8n().not.equal('three_one_one'),
}),
three_two: v8n().not.schema({
three_two_one: v8n().not.equal('three_two_one'),
}),
}),
});
validObj = {
one: 'one',
two: {
two_one: 'two_one',
two_two: 'two_two',
},
three: {
three_one: {
three_one_one: 311,
},
three_two: {
three_two_one: 'three_two_one',
},
},
};
invalidObj = {
one: 'one',
two: {
two_one: 21,
two_two: {
two_two_one: 221,
},
},
three: {
three_two: {
three_two_one: 321,
},
},
};
causes = [
{
target: 'two',
cause: [{ target: 'two_one', rule: { name: 'equal' } }],
},
{
target: 'three',
cause: [{ target: 'three_two', cause: null }],
},
];
});
it("should work with 'test' function", () => {
expect(complex.test(validObj)).toBeTruthy();
expect(complex.test(invalidObj)).toBeFalsy();
});
it("should work with 'check' function", () => {
expect.assertions(2);
expect(() => complex.check(validObj)).not.toThrow();
try {
complex.check(invalidObj);
} catch (ex) {
expect(ex.cause).toMatchObject(causes);
}
});
it("should work with 'testAll' function", () => {
expect(complex.testAll(validObj)).toHaveLength(0);
expect(complex.testAll(invalidObj)).toMatchObject([{ cause: causes }]);
});
it("should work with 'testAsync' function", async () => {
expect.assertions(2);
await expect(complex.testAsync(validObj)).resolves.toEqual(validObj);
try {
await complex.testAsync(invalidObj);
} catch (ex) {
expect(ex.cause).toMatchObject(causes);
}
});
});
describe('custom rules', () => {
it('should be chainable', () => {
v8n.extend({
newRule: () => () => true,
});
const validation = v8n()
.string()
.newRule()
.lowercase();
expect(debugRules(validation)).toEqual([
'string()',
'newRule()',
'lowercase()',
]);
});
it('should be used in validation', () => {
v8n.extend({
or: (a, b) => value => value === a || value === b,
});
const validation = v8n()
.string()
.or('one', 'two');
expect(validation.test('one')).toBeTruthy();
expect(validation.test('two')).toBeTruthy();
expect(validation.test('three')).toBeFalsy();
});
it("should be inverted by 'not' modifier", () => {
v8n.extend({
exact: it => value => value === it,
});
const validation = v8n()
.string()
.not.exact('hello');
expect(validation.test('hi')).toBeTruthy();
expect(validation.test('nice')).toBeTruthy();
expect(validation.test('hello')).toBeFalsy();
});
test('extend should be able to call multiple times', () => {
v8n.extend({
one: () => () => true,
});
v8n.extend({
two: () => () => true,
});
const validation = v8n()
.one()
.two();
expect(debugRules(validation)).toEqual(['one()', 'two()']);
});
describe("the 'clearCustomRules' function", () => {
beforeEach(() => {
v8n.extend({
asyncRule,
});
});
it('should clear custom rules', () => {
expect(v8n().asyncRule).toBeDefined();
v8n.clearCustomRules();
expect(v8n().asyncRule).toBeUndefined();
});
});
});
describe('fluency', () => {
test('fluency test 1', () => {
const validation = v8n()
.array()
.some.positive()
.some.negative()
.not.every.even()
.includes(6);
expect(validation.test(10)).toBeFalsy();
expect(validation.test([1, 2, 3, 6])).toBeFalsy();
expect(validation.test([-1, -2, -3])).toBeFalsy();
expect(validation.test([2, -2, 4, 6])).toBeFalsy();
expect(validation.test([2, -2, 4, 6, 7])).toBeTruthy();
});
test('fluency test 2', () => {
const validation = v8n()
.some.odd()
.some.not.odd()
.length(3);
expect(validation.test([1, 3, 5])).toBeFalsy();
expect(validation.test([1, 2, 3])).toBeTruthy();
expect(validation.test([1, 2, 3, 4])).toBeFalsy();
});
test('fluency test 3', () => {
const validation = v8n()
.not.every.positive()
.some.not.even()
.not.some.equal(3);
expect(validation.test([1, 2, 4])).toBeFalsy();
expect(validation.test([-2, 2, 3])).toBeFalsy();
expect(validation.test([-2, 2, 4])).toBeFalsy();
expect(validation.test([-2, 2, 5])).toBeTruthy();
});
test('fluency test 4', () => {
const validation = v8n()
.not.every.equal(2)
.every.positive()
.every.even();
expect(validation.test([2, 2, 2])).toBeFalsy();
expect(validation.test([2, 2, -4])).toBeFalsy();
expect(validation.test([4, 4, 4])).toBeTruthy();
});
test('fluency test 5', () => {
const validation = v8n()
.string()
.first('H')
.not.last('o')
.not.every.consonant()
.minLength(3);
expect(validation.test('Hello')).toBeFalsy();
expect(validation.test('Hi')).toBeFalsy();
expect(validation.test('Hbrn')).toBeFalsy();
expect(validation.test('Hbon')).toBeTruthy();
});
});
describe('random tests', () => {
test('random test 1', () => {
const validation = v8n()
.number()
.even()
.positive();
expect(validation.test(-2)).toBeFalsy();
expect(validation.test(-1)).toBeFalsy();
expect(validation.test(0)).toBeTruthy();
expect(validation.test(1)).toBeFalsy();
expect(validation.test(2)).toBeTruthy();
});
test('random test 2', () => {
const validation = v8n()
.string()
.minLength(2)
.maxLength(5)
.lowercase()
.first('b')
.last('o');
expect(validation.test('bruno')).toBeTruthy();
expect(validation.test('bruna')).toBeFalsy();
expect(validation.test('druno')).toBeFalsy();
expect(validation.test('Bruno')).toBeFalsy();
expect(validation.test('Bruno')).toBeFalsy();
expect(validation.test('brunno')).toBeFalsy();
});
test('random test 3', () => {
const validation = v8n()
.array()
.minLength(3)
.maxLength(4)
.first(2)
.last('o');
expect(validation.test([2, 'tree', 'four', 'lo'])).toBeFalsy();
expect(validation.test([2, 'tree', 'four', 'o'])).toBeTruthy();
expect(validation.test([2, 'tree', 'four', 'five', 'o'])).toBeFalsy();
expect(validation.test([2, 'o'])).toBeFalsy();
expect(validation.test('234o')).toBeFalsy();
});
test('random test 4', () => {
const validation = v8n()
.between(10, 20)
.not.between(12, 14)
.not.between(16, 18);
expect(validation.test(9)).toBeFalsy();
expec