UNPKG

n4s

Version:

typed schema validation version of enforce

368 lines (305 loc) 10.8 kB
import { describe, it, expect } from 'vitest'; import { enforce, compose } from '../n4s'; declare global { namespace n4s { interface EnforceMatchers { isPositive: (value: number) => boolean; isBetween: (value: number, min: number, max: number) => boolean; isEven: (value: number) => boolean; isPositiveNumber: (value: number) => boolean; isBetweenRange: (value: number, min: number, max: number) => boolean; } } } describe('Documentation Examples', () => { describe('compose examples', () => { it('should validate username with composed rule', () => { const isValidUsername = compose( enforce .isString() .longerThan(3) .shorterThan(20) .matches(/^[a-zA-Z0-9_]+$/), ); expect(isValidUsername.test('john_doe')).toBe(true); expect(isValidUsername.test('ab')).toBe(false); // too short expect(isValidUsername.test('john doe')).toBe(false); // contains space }); it('should use composed rule in schema validation', () => { const isValidUsername = compose( enforce .isString() .longerThan(3) .shorterThan(20) .matches(/^[a-zA-Z0-9_]+$/), ); const result = enforce({ username: 'john_doe' }).shape({ username: isValidUsername, }); expect(result.pass).toBe(true); }); }); describe('enforce examples', () => { it('should validate with eager API', () => { expect(() => { enforce('hello').isString().longerThan(3); }).not.toThrow(); expect(() => { enforce('hi').isString().longerThan(3); }).toThrow(); }); it('should validate with lazy API', () => { const stringRule = enforce.isString(); expect(stringRule.test('hello')).toBe(true); const result = stringRule.run('hello'); expect(result.pass).toBe(true); expect(result.type).toBe('hello'); }); it('should support custom messages', () => { expect(() => { enforce('').message('Field is required').isNotEmpty(); }).toThrow('Field is required'); }); it('should validate with schema', () => { const result = enforce({ name: 'John', age: 30 }).shape({ name: enforce.isString(), age: enforce.isNumber(), }); expect(result.pass).toBe(true); }); }); describe('enforce.context examples', () => { it('should access validation context', () => { const stringRule = enforce.isString(); const result = stringRule.run('test'); // Context is only available during rule execution expect(result.pass).toBe(true); }); }); describe('enforce.extend examples', () => { it('should extend with custom rules', () => { enforce.extend({ isPositive: (value: number) => value > 0, isBetween: (value: number, min: number, max: number) => value >= min && value <= max, }); // Eager API expect(() => { enforce(5).isPositive(); }).not.toThrow(); expect(() => { enforce(-5).isPositive(); }).toThrow(); // Lazy API const rule = enforce.isPositive(); expect(rule.test(5)).toBe(true); expect(rule.test(-3)).toBe(false); }); }); describe('RuleInstance examples', () => { it('should work with RuleInstance', () => { const stringRule = enforce.isString(); // test method returns boolean expect(stringRule.test('hello')).toBe(true); // @ts-expect-error - stringRule.test should accept string expect(stringRule.test(123)).toBe(false); // run method returns RuleRunReturn const result = stringRule.run('hello'); expect(result.pass).toBe(true); expect(result.type).toBe('hello'); }); it('should chain rules', () => { const rule = enforce.isString().longerThan(3); expect(rule.test('hello')).toBe(true); expect(rule.test('hi')).toBe(false); }); }); describe('RuleRunReturn examples', () => { it('should create passing result', () => { const rule = enforce.isString(); const result = rule.run('hello'); expect(result.pass).toBe(true); expect(result.type).toBe('hello'); expect(result.message).toBeUndefined(); }); it('should create failing result with message', () => { const rule = enforce.isString().message('Must be a string'); const result = rule.run(123); expect(result.pass).toBe(false); expect(result.message).toBe('Must be a string'); }); }); describe('schema rule examples', () => { it('should validate with shape', () => { const userSchema = enforce.shape({ name: enforce.isString(), age: enforce.isNumber(), email: enforce.isString(), }); expect( userSchema.test({ name: 'John', age: 30, email: 'john@example.com', }), ).toBe(true); expect( userSchema.test({ name: 'John', // @ts-expect-error - age should be number age: 'thirty', email: 'john@example.com', }), ).toBe(false); }); it('should validate with loose', () => { const schema = enforce.loose({ name: enforce.isString(), age: enforce.isNumber(), }); // Allows extra properties expect( schema.test({ name: 'John', age: 30, extra: 'allowed', }), ).toBe(true); }); it('should validate with partial', () => { const schema = enforce.partial({ name: enforce.isString(), age: enforce.isNumber(), }); // All properties optional expect(schema.test({})).toBe(true); expect(schema.test({ name: 'John' })).toBe(true); expect(schema.test({ age: 30 })).toBe(true); }); it('should validate with optional', () => { const schema = enforce.shape({ name: enforce.isString(), age: enforce.optional(enforce.isNumber()), }); expect(schema.test({ name: 'John' })).toBe(true); expect(schema.test({ name: 'John', age: 30 })).toBe(true); expect(schema.test({ name: 'John', age: undefined })).toBe(true); }); it('should validate with isArrayOf', () => { const numbersRule = enforce.isArrayOf(enforce.isNumber()); expect(numbersRule.test([1, 2, 3])).toBe(true); // @ts-expect-error - string is not assignable to number expect(numbersRule.test([1, 'two', 3])).toBe(false); }); }); describe('compound rule examples', () => { it('should validate with allOf', () => { const rule = enforce.allOf( enforce.isNumber().greaterThan(0).lessThan(100), ); expect(rule.test(50)).toBe(true); expect(rule.test(150)).toBe(false); }); it('should validate with anyOf', () => { const rule = enforce.anyOf(enforce.isString(), enforce.isNumber()); expect(rule.test('hello')).toBe(true); expect(rule.test(123)).toBe(true); // @ts-expect-error - boolean is not string | number expect(rule.test(true)).toBe(false); }); it('should validate with noneOf', () => { const rule = enforce.noneOf(enforce.isNull(), enforce.isUndefined()); expect(rule.test('hello')).toBe(true); expect(rule.test(null)).toBe(false); expect(rule.test(undefined)).toBe(false); }); it('should validate with oneOf', () => { const rule = enforce.oneOf(enforce.isString(), enforce.isNumber()); expect(rule.test('hello')).toBe(true); expect(rule.test(123)).toBe(true); }); }); describe('type validation examples', () => { it('should validate arrays', () => { const rule = enforce.isArray(); expect(rule.test([1, 2, 3])).toBe(true); // @ts-expect-error - rule.test should accept array expect(rule.test('not array')).toBe(false); }); it('should validate strings', () => { const rule = enforce.isString(); expect(rule.test('hello')).toBe(true); // @ts-expect-error - rule.test should accept string expect(rule.test(123)).toBe(false); }); it('should validate numbers', () => { const rule = enforce.isNumber(); expect(rule.test(123)).toBe(true); // @ts-expect-error - rule.test should accept number expect(rule.test('123')).toBe(false); }); it('should validate booleans', () => { const rule = enforce.isBoolean(); expect(rule.test(true)).toBe(true); // @ts-expect-error - rule.test should accept boolean expect(rule.test('true')).toBe(false); }); it('should validate null', () => { const rule = enforce.isNull(); expect(rule.test(null)).toBe(true); // @ts-expect-error - null should not accept undefined expect(rule.test(undefined)).toBe(false); }); it('should validate undefined', () => { const rule = enforce.isUndefined(); expect(rule.test(undefined)).toBe(true); // @ts-expect-error - undefined should not accept null expect(rule.test(null)).toBe(false); }); it('should validate nullish', () => { const rule = enforce.isNullish(); expect(rule.test(null)).toBe(true); expect(rule.test(undefined)).toBe(true); // @ts-expect-error - nullish should not accept number expect(rule.test(0)).toBe(false); }); it('should validate numeric', () => { const rule = enforce.isNumeric(); expect(rule.test(123)).toBe(true); expect(rule.test('123')).toBe(true); expect(rule.test('abc')).toBe(false); }); }); describe('extendEnforce examples', () => { it('should extend with custom rules and use in both APIs', () => { enforce.extend({ isEven: (value: number) => value % 2 === 0, }); // Eager API expect(() => { enforce(10).isEven(); }).not.toThrow(); expect(() => { enforce(11).isEven(); }).toThrow(); // Lazy API const evenRule = enforce.isEven(); expect(evenRule.test(10)).toBe(true); expect(evenRule.test(11)).toBe(false); }); it('should combine custom rules with built-in rules', () => { enforce.extend({ isPositiveNumber: (value: number) => value > 0, isBetweenRange: (value: number, min: number, max: number) => value >= min && value <= max, }); const schema = enforce.shape({ age: enforce.isNumber().isPositiveNumber().isBetweenRange(18, 100), score: enforce.isNumber().isEven(), }); expect(schema.test({ age: 25, score: 100 })).toBe(true); expect(schema.test({ age: -5, score: 100 })).toBe(false); expect(schema.test({ age: 25, score: 99 })).toBe(false); }); }); });