UNPKG

n4s

Version:

typed schema validation version of enforce

285 lines (233 loc) 9.56 kB
import { describe, expect, it } from 'vitest'; import { StandardSchemaV1 } from 'vest-utils/standardSchemaSpec'; import { enforce } from '../n4s'; declare global { namespace n4s { interface EnforceMatchers { toNumberForParse: (value: unknown) => { message: string; pass: boolean; type: number; }; } } } /** * Helper type that combines both branches of StandardSchemaV1.Result * into a single type where both `value` and `issues` are optional. * This allows direct property access in test assertions without * requiring type narrowing or `as any` casts. */ type ValidateResult<T = unknown> = { readonly value?: T; readonly issues?: ReadonlyArray<StandardSchemaV1.Issue>; }; /** * Casts a StandardSchemaV1.Result (or its Promise-union variant) to * a ValidateResult so both `.value` and `.issues` are accessible. */ function asResult<T>( result: StandardSchemaV1.Result<T> | Promise<StandardSchemaV1.Result<T>>, ): ValidateResult<T> { return result as ValidateResult<T>; } describe('n4s StandardSchema Support', () => { describe('Lazy Interface', () => { it('Should have the "~standard" property', () => { const validator = enforce.isString(); expect(validator).toHaveProperty('~standard'); }); it('Should have ~standard.version === 1', () => { const validator = enforce.isString(); expect(validator['~standard'].version).toBe(1); }); it('Should have ~standard.vendor === "n4s"', () => { const validator = enforce.isString(); expect(validator['~standard'].vendor).toBe('n4s'); }); it('Should have ~standard.validate function', () => { const validator = enforce.isString(); expect(typeof validator['~standard'].validate).toBe('function'); }); it('Should implement validate method matching StandardSchema spec - success case', () => { const validator = enforce.isString(); const validResult = asResult(validator['~standard'].validate('hello')); expect(validResult).toHaveProperty('value'); expect(validResult.value).toBe('hello'); expect(validResult.issues).toBeUndefined(); }); it('Should implement validate method matching StandardSchema spec - failure case', () => { const validator = enforce.isString(); const invalidResult = asResult(validator['~standard'].validate(123)); expect(invalidResult).toHaveProperty('issues'); expect(invalidResult).not.toHaveProperty('value'); expect(Array.isArray(invalidResult.issues)).toBe(true); expect(invalidResult.issues?.length).toBeGreaterThan(0); }); it('Should include message in issues array', () => { const validator = enforce.isString(); const invalidResult = asResult(validator['~standard'].validate(123)); expect(invalidResult.issues).toBeDefined(); expect(invalidResult.issues?.[0]).toHaveProperty('message'); expect(typeof invalidResult.issues?.[0].message).toBe('string'); }); it('Should include path in issues array', () => { const validator = enforce.isString(); const invalidResult = asResult(validator['~standard'].validate(123)); expect(invalidResult.issues).toBeDefined(); expect(invalidResult.issues?.[0]).toHaveProperty('path'); expect(Array.isArray(invalidResult.issues?.[0].path)).toBe(true); }); }); it('should expose .parse() and return transformed value when valid', async () => { const { enforce } = await import('../n4s'); enforce.extend({ toNumberForParse: (value: any) => { const parsed = Number(value); return { pass: !Number.isNaN(parsed), type: parsed, message: 'not numeric', }; }, }); expect(enforce.toNumberForParse().parse('12')).toBe(12); }); describe('Direct validate() method usage', () => { it('should expose .validate() as a direct method', () => { const validator = enforce.equals(5); expect(typeof validator.validate).toBe('function'); }); it('should return success result for valid input', () => { const validator = enforce.equals(5); const res = asResult(validator.validate(5)); expect(res.value).toBe(5); expect(res.issues).toBeUndefined(); }); it('should return failure result for invalid input', () => { const validator = enforce.equals(5); const res = asResult(validator.validate(10)); expect(res.value).toBeUndefined(); expect(res.issues).toBeDefined(); expect(res.issues?.length).toBeGreaterThan(0); }); }); describe('Shape/Schema Rules', () => { it('enforce.shape should have ~standard property', () => { const shape = enforce.shape({ name: enforce.isString(), age: enforce.isNumber(), }); expect(shape['~standard']).toBeDefined(); expect(shape['~standard'].version).toBe(1); expect(shape['~standard'].vendor).toBe('n4s'); }); it('enforce.shape should validate successfully with valid data', () => { const shape = enforce.shape({ name: enforce.isString(), age: enforce.isNumber(), }); const res = asResult( shape['~standard'].validate({ name: 'Bob', age: 30 }), ); expect(res.value).toEqual({ name: 'Bob', age: 30 }); expect(res.issues).toBeUndefined(); }); it('enforce.shape should fail with invalid data', () => { const shape = enforce.shape({ name: enforce.isString(), age: enforce.isNumber(), }); const res = asResult( shape['~standard'].validate({ name: 'Bob', age: 'thirty' }), ); expect(res.value).toBeUndefined(); expect(res.issues).toBeDefined(); expect(res.issues?.length).toBeGreaterThan(0); }); it('enforce.loose should have ~standard property', () => { const loose = enforce.loose({ name: enforce.isString(), }); expect(loose['~standard']).toBeDefined(); expect(loose['~standard'].version).toBe(1); }); it('enforce.partial should have ~standard property', () => { const partial = enforce.partial({ name: enforce.isString(), age: enforce.isNumber(), }); expect(partial['~standard']).toBeDefined(); expect(partial['~standard'].version).toBe(1); }); }); describe('Compound Rules', () => { it('enforce.anyOf should have ~standard property', () => { const validator = enforce.anyOf(enforce.isNumber(), enforce.isString()); expect(validator['~standard']).toBeDefined(); expect(validator['~standard'].version).toBe(1); expect(validator['~standard'].vendor).toBe('n4s'); }); it('enforce.anyOf should validate correctly', () => { const validator = enforce.anyOf(enforce.isNumber(), enforce.isString()); const stringResult = asResult(validator['~standard'].validate('hello')); expect(stringResult.value).toBe('hello'); expect(stringResult.issues).toBeUndefined(); const numberResult = asResult(validator['~standard'].validate(42)); expect(numberResult.value).toBe(42); expect(numberResult.issues).toBeUndefined(); const invalidResult = asResult(validator['~standard'].validate(true)); expect(invalidResult.value).toBeUndefined(); expect(invalidResult.issues).toBeDefined(); }); it('enforce.allOf should have ~standard property', () => { const validator = enforce.allOf( enforce.isNumber(), enforce.isNumber().greaterThan(0), ); expect(validator['~standard']).toBeDefined(); expect(validator['~standard'].version).toBe(1); }); it('enforce.oneOf should have ~standard property', () => { const validator = enforce.oneOf(enforce.isNumber(), enforce.isString()); expect(validator['~standard']).toBeDefined(); expect(validator['~standard'].version).toBe(1); }); }); describe('Chained Rules', () => { it('should support chained rules with ~standard', () => { const validator = enforce.isNumber().greaterThan(0).lessThan(100); expect(validator['~standard']).toBeDefined(); expect(validator['~standard'].version).toBe(1); }); it('should validate chained rules correctly', () => { const validator = enforce.isNumber().greaterThan(0).lessThan(100); const validResult = asResult(validator['~standard'].validate(50)); expect(validResult.value).toBe(50); expect(validResult.issues).toBeUndefined(); const invalidResult = asResult(validator['~standard'].validate(150)); expect(invalidResult.value).toBeUndefined(); expect(invalidResult.issues).toBeDefined(); }); }); describe('Array Validators', () => { it('enforce.isArrayOf should have ~standard property', () => { const validator = enforce.isArrayOf(enforce.isString()); expect(validator['~standard']).toBeDefined(); expect(validator['~standard'].version).toBe(1); }); it('enforce.isArrayOf should validate correctly', () => { const validator = enforce.isArrayOf(enforce.isString()); const validResult = asResult( validator['~standard'].validate(['a', 'b', 'c']), ); expect(validResult.value).toEqual(['a', 'b', 'c']); expect(validResult.issues).toBeUndefined(); const invalidResult = asResult( validator['~standard'].validate([1, 2, 3]), ); expect(invalidResult.value).toBeUndefined(); expect(invalidResult.issues).toBeDefined(); }); }); });