UNPKG

n4s

Version:

typed schema validation version of enforce

245 lines (202 loc) 7.45 kB
import { describe, it, expect } from 'vitest'; import { enforce } from '../n4s'; describe('Eager API - Integration Tests', () => { describe('Basic behavior', () => { it('throws when a rule fails', () => { expect(() => enforce([]).isString()).toThrow(); expect(() => enforce(1).greaterThan(1)).toThrow(); expect(() => enforce('hi').matches(/[0-9]/)).toThrow(); }); it('returns silently when rule passes', () => { enforce(1).isNumber(); enforce(1).greaterThan(0); enforce('1984').matches(/[0-9]/); }); it('includes a helpful failure message (rule name and value)', () => { expect(() => enforce('a').greaterThan('b')).toThrow( /enforce\/greaterThan failed with "a"/, ); expect(() => enforce(['x']).shorterThan(0)).toThrow( /enforce\/shorterThan failed with \["x"\]/, ); }); }); describe('Chaining', () => { it('chains rules of the same type', () => { // String chaining enforce('hello') .isString() .longerThan(2) .shorterThan(10) .matches(/^h/) .startsWith('he'); // Number chaining enforce(5).isNumber().greaterThan(0).lessThan(10).isOdd(); // Array chaining enforce([1, 2, 3]).isArray().lengthEquals(3).isNotEmpty(); }); it('chains across different rule categories', () => { // Mix type checks with value checks enforce('hello').isString().isNotEmpty().longerThan(3); enforce(42).isNumber().isPositive().isEven(); enforce([1, 2]).isArray().longerThan(1).includes(1); }); it('stops at the first failing rule in a chain', () => { // After first failure, a throw occurs; later rules are not evaluated expect(() => enforce('a').isString().equals('a').lessThan('a')).toThrow(); expect(() => enforce(5).isNumber().greaterThan(10).lessThan(20), ).toThrow(); // fails at greaterThan(10) }); it('handles complex real-world validation chains', () => { // Username validation enforce('john_doe_123') .isString() .isNotEmpty() .longerThan(5) .shorterThan(20) .matches(/^[a-zA-Z0-9_]+$/); // Price validation enforce(99.99).isNumber().isPositive().greaterThan(0).lessThan(1000); // Email-like string validation enforce('test@example.com') .isString() .isNotEmpty() .matches(/@/) .matches(/\./) .longerThan(5); }); }); describe('Type coercion and comparisons', () => { it('handles numeric coercion', () => { enforce('10').isNumeric().greaterThan(5); enforce(900).greaterThan('100'); enforce('42').numberEquals(42); }); it('handles strict equality', () => { enforce(1).equals(1); enforce('hello').equals('hello'); const a = [1, 2, 3]; enforce(a).equals(a); expect(() => enforce('1').equals(1)).toThrow(); expect(() => enforce([1, 2, 3]).equals([1, 2, 3])).toThrow(); }); }); describe('Truthiness and emptiness', () => { it('validates truthy values', () => { enforce('hi').isTruthy(); enforce(1).isTruthy(); enforce([]).isTruthy(); enforce({}).isTruthy(); expect(() => enforce(0).isTruthy()).toThrow(); expect(() => enforce('').isTruthy()).toThrow(); expect(() => enforce(null).isTruthy()).toThrow(); }); it('validates falsy values', () => { enforce('').isFalsy(); enforce(0).isFalsy(); enforce(false).isFalsy(); enforce(null).isFalsy(); enforce(undefined).isFalsy(); enforce(NaN).isFalsy(); expect(() => enforce(1).isFalsy()).toThrow(); expect(() => enforce('hi').isFalsy()).toThrow(); }); it('validates empty and non-empty', () => { enforce('').isEmpty(); enforce([]).isEmpty(); enforce('text').isNotEmpty(); enforce([1]).isNotEmpty(); expect(() => enforce('text').isEmpty()).toThrow(); expect(() => enforce('').isNotEmpty()).toThrow(); }); }); describe('Object membership', () => { it('validates key membership', () => { const obj = { a: 1, b: 2, c: 3 }; enforce('a').isKeyOf(obj); enforce('z').isNotKeyOf(obj); }); it('validates value membership', () => { const obj = { a: 1, b: 2, c: 3 } as const; enforce(1).isValueOf(obj); enforce(4).isNotValueOf(obj); }); }); describe('Container membership', () => { it('validates string contains substring', () => { enforce('a').inside('cat'); enforce('at').inside('cat'); enforce('da').inside('tru dat.'); expect(() => enforce('ad').inside('tru dat.')).toThrow(); expect(() => enforce('x').inside('cat')).toThrow(); }); it('validates array contains element', () => { enforce('x').inside(['x', 'y', 'z']); enforce(1).inside([1, 2, 3]); enforce(['x', 'y']).inside(['x', 'y', 'z']); expect(() => enforce('w').inside(['x', 'y', 'z'])).toThrow(); expect(() => enforce(4).inside([1, 2, 3])).toThrow(); }); it('validates notInside', () => { enforce('ad').notInside('tru dat.'); enforce('w').notInside(['x', 'y', 'z']); enforce(['x', 'w']).notInside(['x', 'y', 'z']); expect(() => enforce('x').notInside(['x', 'y', 'z'])).toThrow(); expect(() => enforce('da').notInside('tru dat.')).toThrow(); }); }); describe('Array includes', () => { it('validates array includes element', () => { enforce([1, 2, 3]).includes(1); enforce([1, 2, 3]).includes(2); enforce(['a', 'b', 'c']).includes('b'); expect(() => enforce([1, 2, 3]).includes(4)).toThrow(); expect(() => enforce(['a', 'b']).includes('c')).toThrow(); expect(() => enforce([]).includes(1)).toThrow(); }); }); describe('Edge cases with falsy values', () => { it('handles falsy values correctly', () => { enforce(0).equals(0); enforce(false).equals(false); enforce('').equals(''); enforce(null).equals(null); enforce(undefined).equals(undefined); enforce(0).isFalsy(); enforce(false).isFalsy(); enforce('').isFalsy(); }); it('distinguishes between different falsy types', () => { enforce(null).isNull(); enforce(undefined).isUndefined(); enforce(null).isNullish(); enforce(undefined).isNullish(); expect(() => enforce(0).isNull()).toThrow(); expect(() => enforce('').isNullish()).toThrow(); expect(() => enforce(false).isNullish()).toThrow(); }); }); describe('Type assertions', () => { it('validates all basic types', () => { enforce('text').isString(); enforce(42).isNumber(); enforce(true).isBoolean(); enforce([]).isArray(); enforce(NaN).isNaN(); expect(() => enforce('42').isNumber()).toThrow(); expect(() => enforce(42).isString()).toThrow(); expect(() => enforce('true').isBoolean()).toThrow(); expect(() => enforce({}).isArray()).toThrow(); }); it('validates negative type checks', () => { enforce('text').isNotNumber(); enforce(42).isNotString(); enforce(true).isNotArray(); enforce(42).isNotNaN(); expect(() => enforce(42).isNotNumber()).toThrow(); expect(() => enforce('text').isNotString()).toThrow(); expect(() => enforce(NaN).isNotNaN()).toThrow(); }); }); });