UNPKG

n4s

Version:

typed schema validation version of enforce

1,331 lines (1,181 loc) 42.2 kB
// @ts-nocheck /* eslint-disable @typescript-eslint/no-unused-vars */ import { isObject } from 'vest-utils'; import { describe, it, expect, beforeEach, vi } from 'vitest'; describe('enforce.extend', () => { let enforce: any; beforeEach(async () => { vi.resetModules(); vi.clearAllMocks(); ({ enforce } = await import('../n4s')); }); describe('Basic functionality', () => { describe('Boolean return values', () => { beforeEach(() => { enforce.extend({ isValidEmail: (value: string) => value.indexOf('@') > -1, hasKey: (value: Record<string, any>, key: string) => value.hasOwnProperty(key), }); }); it('Should allow custom rule with boolean return value (true)', () => { expect(() => enforce('test@example.com').isValidEmail()).not.toThrow(); }); it('Should allow custom rule with boolean return value (false)', () => { expect(() => enforce('invalid-email').isValidEmail()).toThrow(); }); it('Should work with multiple arguments', () => { expect(() => enforce({ name: 'John' }).hasKey('name')).not.toThrow(); expect(() => enforce({ name: 'John' }).hasKey('age')).toThrow(); }); it('Should work in lazy mode with .run()', () => { expect(enforce.isValidEmail().run('test@example.com').pass).toBe(true); expect(enforce.isValidEmail().run('invalid-email').pass).toBe(false); }); it('Should work in lazy mode with .run() returning full result', () => { expect(enforce.isValidEmail().run('test@example.com')).toEqual({ pass: true, type: 'test@example.com', }); expect(enforce.isValidEmail().run('invalid-email')).toEqual({ pass: false, type: 'invalid-email', }); }); }); describe('Object return values with pass and message', () => { beforeEach(() => { enforce.extend({ isValidEmail: (value: string) => ({ pass: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), message: () => `${value} is not a valid email address`, }), isWithinRange: (value: number, floor: number, ceiling: number) => { const pass = value >= floor && value <= ceiling; return { pass, message: () => pass ? `expected ${value} not to be within range ${floor} - ${ceiling}` : `expected ${value} to be within range ${floor} - ${ceiling}`, }; }, }); }); it('Should handle object return with pass: true', () => { expect(() => enforce('test@example.com').isValidEmail()).not.toThrow(); }); it('Should handle object return with pass: false and use custom message', () => { expect(() => enforce('invalid').isValidEmail()).toThrow( 'invalid is not a valid email address', ); }); it('Should work with multiple arguments', () => { expect(() => enforce(5).isWithinRange(1, 10)).not.toThrow(); expect(() => enforce(15).isWithinRange(1, 10)).toThrow( 'expected 15 to be within range 1 - 10', ); }); it('Should work in lazy mode with .run()', () => { expect(enforce.isValidEmail().run('test@example.com').pass).toBe(true); expect(enforce.isValidEmail().run('invalid').pass).toBe(false); }); it('Should work in lazy mode with .run() returning full result', () => { expect(enforce.isValidEmail().run('test@example.com')).toEqual({ pass: true, type: 'test@example.com', }); expect(enforce.isValidEmail().run('invalid')).toEqual({ pass: false, type: 'invalid', message: 'invalid is not a valid email address', }); }); }); describe('Object return with just message string', () => { beforeEach(() => { enforce.extend({ customRule: () => ({ pass: false, message: 'Static error message', }), }); }); it('Should handle message as string instead of function', () => { expect(() => enforce('value').customRule()).toThrow( 'Static error message', ); }); it('Should work with .run()', () => { expect(enforce.customRule().run('value')).toEqual({ pass: false, type: 'value', message: 'Static error message', }); }); }); }); describe('Chaining custom rules', () => { beforeEach(() => { enforce.extend({ startsWithUnderscore: (value: string) => ({ pass: value.startsWith('_'), message: () => `${value} does not start with underscore`, }), hasLength: (value: string, length: number) => value.length === length, isLowerCase: (value: string) => value === value.toLowerCase(), }); }); it('Should allow chaining custom rules with built-in rules', () => { expect(() => enforce('_test').startsWithUnderscore().isString(), ).not.toThrow(); }); it('Should allow chaining multiple custom rules', () => { expect(() => enforce('_test').startsWithUnderscore().hasLength(5).isLowerCase(), ).not.toThrow(); }); it('Should throw on first failed rule in chain', () => { expect(() => enforce('test').startsWithUnderscore().hasLength(4)).toThrow( 'test does not start with underscore', ); }); it('Should work with lazy evaluation', () => { expect( enforce.startsWithUnderscore().hasLength(5).isLowerCase().run('_test') .pass, ).toBe(true); expect( enforce.startsWithUnderscore().hasLength(5).isLowerCase().run('_TEST') .pass, ).toBe(false); }); }); describe('Edge cases and error handling', () => { it('Should handle custom rule that returns undefined', () => { enforce.extend({ returnsUndefined: () => undefined, }); expect(() => enforce('test').returnsUndefined()).toThrow(); }); it('Should handle custom rule that returns null', () => { enforce.extend({ returnsNull: () => null, }); expect(() => enforce('test').returnsNull()).toThrow(); }); it('Should handle custom rule that throws an error', () => { enforce.extend({ throwsError: () => { throw new Error('Custom error'); }, }); expect(() => enforce('test').throwsError()).toThrow('Custom error'); }); it('Should handle custom rule with no arguments', () => { enforce.extend({ alwaysTrue: () => true, alwaysFalse: () => false, }); expect(() => enforce('test').alwaysTrue()).not.toThrow(); expect(() => enforce('test').alwaysFalse()).toThrow(); }); it('Should handle custom rule receiving different value types', () => { enforce.extend({ checkType: (value: any) => ({ pass: true, message: () => `Type: ${typeof value}`, }), }); expect(() => enforce(123).checkType()).not.toThrow(); expect(() => enforce('string').checkType()).not.toThrow(); expect(() => enforce(null).checkType()).not.toThrow(); expect(() => enforce(undefined).checkType()).not.toThrow(); expect(() => enforce({}).checkType()).not.toThrow(); expect(() => enforce([]).checkType()).not.toThrow(); }); it('Should handle custom rules with many arguments', () => { enforce.extend({ sumEquals: (value: number, ...args: number[]) => value === args.reduce((sum, n) => sum + n, 0), }); expect(() => enforce(10).sumEquals(1, 2, 3, 4)).not.toThrow(); expect(() => enforce(10).sumEquals(1, 2, 3)).toThrow(); }); }); describe('Multiple extend calls', () => { it('Should allow multiple extend calls to add different rules', () => { enforce.extend({ customRule1: () => true, }); enforce.extend({ customRule2: () => true, }); expect(() => enforce('test').customRule1().customRule2()).not.toThrow(); }); it('Should allow overriding existing custom rules', () => { enforce.extend({ toggleRule: () => true, }); expect(() => enforce('test').toggleRule()).not.toThrow(); enforce.extend({ toggleRule: () => false, }); expect(() => enforce('test').toggleRule()).toThrow(); }); it('Should not affect built-in rules', () => { enforce.extend({ customRule: () => true, }); // Built-in rules should still work expect(() => enforce('test').isString()).not.toThrow(); expect(() => enforce(123).isNumber()).not.toThrow(); }); }); describe('Integration with schema rules', () => { beforeEach(() => { enforce.extend({ isEmail: (value: string) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), isAdult: (value: number) => value >= 18, }); }); it('Should work within shape()', () => { const schema = enforce.shape({ email: enforce.isString().isEmail(), age: enforce.isNumber().isAdult(), }); expect(schema.run({ email: 'test@example.com', age: 25 }).pass).toBe( true, ); expect(schema.run({ email: 'invalid', age: 16 }).pass).toBe(false); }); it('Should work within loose()', () => { const schema = enforce.loose({ email: enforce.isString().isEmail(), }); expect( schema.run({ email: 'test@example.com', extra: 'field' }).pass, ).toBe(true); }); it('Should work within isArrayOf()', () => { const rule = enforce.isArrayOf(enforce.isString().isEmail()); expect(rule.run(['test@example.com', 'another@example.com']).pass).toBe( true, ); expect(rule.run(['test@example.com', 'invalid']).pass).toBe(false); }); it('Should work within optional()', () => { const schema = enforce.shape({ email: enforce.optional(enforce.isString().isEmail()), }); expect(schema.run({ email: 'test@example.com' }).pass).toBe(true); expect(schema.run({}).pass).toBe(true); }); }); describe('Integration with compound rules', () => { beforeEach(() => { enforce.extend({ isPositive: (value: number) => value > 0, isEven: (value: number) => value % 2 === 0, isOdd: (value: number) => value % 2 !== 0, }); }); it('Should work within allOf()', () => { const ok = enforce .allOf(enforce.isNumber(), enforce.isPositive(), enforce.isEven()) .run(4).pass; expect(ok).toBe(true); const notOk = enforce .allOf(enforce.isNumber(), enforce.isPositive(), enforce.isEven()) .run(-4).pass; expect(notOk).toBe(false); }); it('Should work within anyOf()', () => { const res = enforce.anyOf(enforce.isEven(), enforce.isOdd()).run(3).pass; expect(res).toBe(true); }); it('Should work within noneOf()', () => { const res = enforce .noneOf(enforce.isNumber(), enforce.isPositive()) .run('string').pass; expect(res).toBe(true); }); it('Should work within oneOf()', () => { expect(() => enforce(3).oneOf(enforce.isEven(), enforce.isOdd(), enforce.isNumber()), ).toThrow(); // Fails because two rules pass (isOdd and isNumber) }); }); describe('Custom rules with enforce.context()', () => { beforeEach(() => { enforce.extend({ contextAware: (value: string) => { const context = enforce.context(); return !!context; }, accessParent: (value: string) => { const context = enforce.context(); return context?.parent !== undefined; }, accessMeta: (value: string) => { const context = enforce.context(); return context?.meta !== undefined; }, }); }); it('Should have access to context', () => { expect(() => enforce('test').contextAware()).not.toThrow(); }); it('Should have access to parent in context', () => { expect(() => enforce('test').accessParent()).not.toThrow(); }); it('Should have access to meta in context', () => { expect(() => enforce('test').accessMeta()).not.toThrow(); }); it('Should allow traversing parent values', () => { enforce.extend({ isFriendTheSameAsUser: (value: string) => { const context = enforce.context(); if (value === context?.parent()?.parent()?.value.username) { return { pass: false, message: () => 'Friend cannot be the same as username', }; } return true; }, }); expect(() => enforce({ username: 'johndoe', friends: ['Mike', 'Jim'], }).shape({ username: enforce.isString(), friends: enforce.isArrayOf( enforce.isString().isFriendTheSameAsUser(), ), }), ).not.toThrow(); expect(() => enforce({ username: 'johndoe', friends: ['Mike', 'Jim', 'johndoe'], }).shape({ username: enforce.isString(), friends: enforce.isArrayOf( enforce.isString().isFriendTheSameAsUser(), ), }), ).toThrow(/Friend cannot be the same as username|enforce/); }); }); describe('Custom rules with enforce.message()', () => { beforeEach(() => { enforce.extend({ ruleWithMessage: () => ({ pass: false, message: () => 'Original message', }), ruleWithoutMessage: () => false, }); }); it('Should allow overriding custom rule message', () => { expect(() => enforce('test').message('Custom message').ruleWithMessage(), ).toThrow('Custom message'); }); it('Should allow overriding message on rules without explicit message', () => { expect(() => enforce('test').message('Custom message').ruleWithoutMessage(), ).toThrow('Custom message'); }); it('Should work with message as function', () => { expect(() => enforce('test') .message(() => 'Custom message') .ruleWithMessage(), ).toThrow('Custom message'); }); }); describe('Type coercion and edge cases', () => { beforeEach(() => { enforce.extend({ checksEquality: (value: any, expected: any) => value === expected, checksLooseEquality: (value: any, expected: any) => value == expected, }); }); it('Should handle strict equality', () => { expect(() => enforce(1).checksEquality(1)).not.toThrow(); expect(() => enforce(1).checksEquality('1')).toThrow(); expect(() => enforce(true).checksEquality(1)).toThrow(); expect(() => enforce(null).checksEquality(undefined)).toThrow(); }); it('Should handle loose equality', () => { expect(() => enforce(1).checksLooseEquality('1')).not.toThrow(); expect(() => enforce(true).checksLooseEquality(1)).not.toThrow(); expect(() => enforce(null).checksLooseEquality(undefined)).not.toThrow(); }); it('Should handle falsy values correctly', () => { enforce.extend({ isFalsy: (value: any) => !value, isTruthy: (value: any) => !!value, }); expect(() => enforce(0).isFalsy()).not.toThrow(); expect(() => enforce('').isFalsy()).not.toThrow(); expect(() => enforce(false).isFalsy()).not.toThrow(); expect(() => enforce(null).isFalsy()).not.toThrow(); expect(() => enforce(undefined).isFalsy()).not.toThrow(); expect(() => enforce(1).isTruthy()).not.toThrow(); expect(() => enforce('text').isTruthy()).not.toThrow(); expect(() => enforce(true).isTruthy()).not.toThrow(); expect(() => enforce({}).isTruthy()).not.toThrow(); expect(() => enforce([]).isTruthy()).not.toThrow(); }); }); describe('Complex real-world scenarios', () => { it('Should support password validation rules', () => { enforce.extend({ hasMinLength: (value: string, length: number) => value.length >= length, hasUpperCase: (value: string) => /[A-Z]/.test(value), hasLowerCase: (value: string) => /[a-z]/.test(value), hasNumber: (value: string) => /[0-9]/.test(value), hasSpecialChar: (value: string) => /[!@#$%^&*(),.?":{}|<>]/.test(value), }); const password = 'SecureP@ss123'; expect(() => enforce(password) .hasMinLength(8) .hasUpperCase() .hasLowerCase() .hasNumber() .hasSpecialChar(), ).not.toThrow(); expect(() => enforce('weak').hasMinLength(8)).toThrow(); expect(() => enforce('alllowercase').hasUpperCase()).toThrow(); }); it('Should support conditional validation', () => { enforce.extend({ passwordsMatch: (passConfirm: string, password: string) => passConfirm === password, isValidIf: (value: any, condition: boolean, validator: () => boolean) => !condition || validator(), }); expect(() => enforce('pass123').passwordsMatch('pass123')).not.toThrow(); expect(() => enforce('pass123').passwordsMatch('different')).toThrow(); }); it('Should support date range validation', () => { enforce.extend({ isAfter: (value: Date, date: Date) => value > date, isBefore: (value: Date, date: Date) => value < date, isBetween: (value: Date, start: Date, end: Date) => value >= start && value <= end, }); const now = new Date(); const yesterday = new Date(now.getTime() - 86400000); const tomorrow = new Date(now.getTime() + 86400000); expect(() => enforce(now).isAfter(yesterday)).not.toThrow(); expect(() => enforce(now).isBefore(tomorrow)).not.toThrow(); expect(() => enforce(now).isBetween(yesterday, tomorrow)).not.toThrow(); }); }); describe('Performance and stress tests', () => { it('Should handle many custom rules', () => { const rules: Record<string, () => boolean> = {}; for (let i = 0; i < 100; i++) { rules[`rule${i}`] = () => true; } enforce.extend(rules); expect(() => enforce('test').rule0().rule50().rule99()).not.toThrow(); }); it('Should handle deeply nested validation with custom rules', () => { enforce.extend({ isEmail: (value: string) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), isAdult: (value: number) => value >= 18, }); const schema = enforce.shape({ user: enforce.shape({ profile: enforce.shape({ contact: enforce.shape({ email: enforce.isString().isEmail(), }), age: enforce.isNumber().isAdult(), }), }), }); expect( schema.run({ user: { profile: { contact: { email: 'test@example.com' }, age: 25 }, }, }).pass, ).toBe(true); }); }); describe('Error message customization', () => { it('Should support dynamic error messages based on input', () => { enforce.extend({ isInRange: (value: number, min: number, max: number) => ({ pass: value >= min && value <= max, message: () => `Value ${value} is outside the allowed range of ${min}-${max}`, }), }); expect(() => enforce(15).isInRange(1, 10)).toThrow( 'Value 15 is outside the allowed range of 1-10', ); }); it('Should support error messages with context', () => { enforce.extend({ notSameAsField: (value: string, fieldName: string) => { const context = enforce.context(); const parentValue = context?.parent()?.value; return { pass: value !== parentValue?.[fieldName], message: () => `Value cannot be the same as ${fieldName}: ${parentValue?.[fieldName]}`, }; }, }); expect(() => enforce({ username: 'johndoe', displayName: 'johndoe', }).shape({ username: enforce.isString(), displayName: enforce.isString().notSameAsField('username'), }), ).toThrow(/Value cannot be the same as username|enforce/); }); }); describe('Async behavior considerations', () => { it('Should handle custom rules that might be used async (returning promises should fail sync)', () => { enforce.extend({ // This simulates someone accidentally returning a promise asyncRule: () => Promise.resolve(true), }); // In n4spath, invalid return values should throw expect(() => enforce('test').asyncRule()).toThrow(); }); }); describe('Cleanup and isolation', () => { it('Should not leak custom rules between test runs', () => { // This test ensures that custom rules don't persist unexpectedly // Note: In actual implementation, this might require cleanup between tests enforce.extend({ temporaryRule: () => true, }); expect(enforce.temporaryRule).toBeDefined(); }); }); describe('Integration with condition()', () => { it('Should work alongside enforce.condition()', () => { enforce.extend({ isEven: (value: number) => value % 2 === 0, }); expect(enforce.condition(() => true).run(4).pass).toBe(true); expect(enforce.condition(() => false).run(3).pass).toBe(false); }); }); describe('Comprehensive Lazy API tests', () => { describe('Boolean return values with lazy API', () => { beforeEach(() => { enforce.extend({ isValidEmail: (value: string) => value.indexOf('@') > -1, hasKey: (value: Record<string, any>, key: string) => value.hasOwnProperty(key), isPositive: (value: number) => value > 0, isEven: (value: number) => value % 2 === 0, isLengthBetween: (value: string, min: number, max: number) => value.length >= min && value.length <= max, }); }); it('Should work with single argument - passing case', () => { const result = enforce.isValidEmail().run('test@example.com'); expect(result).toEqual({ pass: true, type: 'test@example.com', }); }); it('Should work with single argument - failing case', () => { const result = enforce.isValidEmail().run('invalid-email'); expect(result).toEqual({ pass: false, type: 'invalid-email', }); }); it('Should work with multiple arguments - passing case', () => { const result = enforce.hasKey('name').run({ name: 'John', age: 30 }); expect(result).toEqual({ pass: true, type: { name: 'John', age: 30 }, }); }); it('Should work with multiple arguments - failing case', () => { const result = enforce.hasKey('email').run({ name: 'John', age: 30 }); expect(result).toEqual({ pass: false, type: { name: 'John', age: 30 }, }); }); it('Should work with numeric validations', () => { expect(enforce.isPositive().run(5)).toEqual({ pass: true, type: 5, }); expect(enforce.isPositive().run(-5)).toEqual({ pass: false, type: -5, }); expect(enforce.isEven().run(4)).toEqual({ pass: true, type: 4, }); expect(enforce.isEven().run(3)).toEqual({ pass: false, type: 3, }); }); it('Should work with multiple arguments and complex types', () => { expect(enforce.isLengthBetween(3, 10).run('hello')).toEqual({ pass: true, type: 'hello', }); expect(enforce.isLengthBetween(3, 10).run('hi')).toEqual({ pass: false, type: 'hi', }); expect(enforce.isLengthBetween(3, 10).run('this is too long')).toEqual({ pass: false, type: 'this is too long', }); }); it('Should handle different data types', () => { enforce.extend({ alwaysTrue: () => true, alwaysFalse: () => false, }); // String expect(enforce.alwaysTrue().run('string')).toEqual({ pass: true, type: 'string', }); // Number expect(enforce.alwaysTrue().run(42)).toEqual({ pass: true, type: 42, }); // Boolean expect(enforce.alwaysTrue().run(true)).toEqual({ pass: true, type: true, }); // Object const obj = { key: 'value' }; expect(enforce.alwaysTrue().run(obj)).toEqual({ pass: true, type: obj, }); // Array const arr = [1, 2, 3]; expect(enforce.alwaysTrue().run(arr)).toEqual({ pass: true, type: arr, }); // Null expect(enforce.alwaysTrue().run(null)).toEqual({ pass: true, type: null, }); // Undefined expect(enforce.alwaysTrue().run(undefined)).toEqual({ pass: true, type: undefined, }); }); }); describe('Object return values with lazy API', () => { beforeEach(() => { enforce.extend({ isValidEmailWithMessage: (value: string) => ({ pass: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), message: () => `${value} is not a valid email address`, }), isWithinRange: (value: number, floor: number, ceiling: number) => { const pass = value >= floor && value <= ceiling; return { pass, message: () => pass ? `expected ${value} not to be within range ${floor} - ${ceiling}` : `expected ${value} to be within range ${floor} - ${ceiling}`, }; }, hasExactLength: (value: string, length: number) => ({ pass: value.length === length, message: () => `Expected length ${length}, got ${value.length}`, }), }); }); it('Should work with object return - passing case', () => { const result = enforce .isValidEmailWithMessage() .run('test@example.com'); expect(result).toEqual({ pass: true, type: 'test@example.com', }); }); it('Should work with object return - failing case', () => { const result = enforce.isValidEmailWithMessage().run('invalid'); expect(result).toEqual({ pass: false, type: 'invalid', message: 'invalid is not a valid email address', }); }); it('Should work with multiple arguments and object return', () => { expect(enforce.isWithinRange(1, 10).run(5)).toEqual({ pass: true, type: 5, }); expect(enforce.isWithinRange(1, 10).run(15)).toEqual({ pass: false, type: 15, message: 'expected 15 to be within range 1 - 10', }); }); it('Should work with complex validation scenarios', () => { expect(enforce.hasExactLength(5).run('hello')).toEqual({ pass: true, type: 'hello', }); expect(enforce.hasExactLength(5).run('hi')).toEqual({ pass: false, type: 'hi', message: 'Expected length 5, got 2', }); }); }); describe('Chaining with lazy API', () => { beforeEach(() => { enforce.extend({ startsWithUnderscore: (value: string) => value.startsWith('_'), hasMinLength: (value: string, length: number) => value.length >= length, isLowerCase: (value: string) => value === value.toLowerCase(), isAlphanumeric: (value: string) => /^[a-zA-Z0-9]+$/.test(value), }); }); it('Should work with chained custom rules', () => { const result = enforce .startsWithUnderscore() .hasMinLength(5) .isLowerCase() .run('_test'); expect(result).toEqual({ pass: true, type: '_test', }); }); it('Should fail on first failed rule in chain', () => { const result = enforce .startsWithUnderscore() .hasMinLength(5) .isLowerCase() .run('test'); expect(result).toEqual({ pass: false, type: 'test', }); }); it('Should work with mix of custom and built-in rules', () => { const result = enforce .isString() .startsWithUnderscore() .hasMinLength(3) .run('_ab'); expect(result).toEqual({ pass: true, type: '_ab', }); }); it('Should work with complex chaining scenarios', () => { expect( enforce.isString().hasMinLength(1).isAlphanumeric().run('test123'), ).toEqual({ pass: true, type: 'test123', }); expect( enforce.isString().hasMinLength(1).isAlphanumeric().run('test-123'), ).toEqual({ pass: false, type: 'test-123', }); }); }); describe('Error handling in lazy API', () => { beforeEach(() => { enforce.extend({ throwsError: () => { throw new Error('Custom validation error'); }, returnsUndefined: () => undefined, returnsNull: () => null, returnsInvalidObject: () => ({ invalid: true }), }); }); it('Should handle rules that throw errors', () => { expect(() => enforce.throwsError().run('test')).toThrow( 'Custom validation error', ); }); it('Should handle rules that return undefined', () => { const result = enforce.returnsUndefined().run('test'); expect(result).toEqual({ pass: false, type: 'test', }); }); it('Should handle rules that return null', () => { const result = enforce.returnsNull().run('test'); expect(result).toEqual({ pass: false, type: 'test', }); }); it('Should handle rules that return invalid objects', () => { const result = enforce.returnsInvalidObject().run('test'); expect(result).toEqual({ pass: false, type: 'test', }); }); }); describe('Type coercion and edge cases in lazy API', () => { beforeEach(() => { enforce.extend({ strictEquals: (value: any, expected: any) => value === expected, looseEquals: (value: any, expected: any) => value == expected, isTruthy: (value: any) => !!value, isFalsy: (value: any) => !value, typeCheck: (value: any, expectedType: string) => typeof value === expectedType, }); }); it('Should handle strict equality comparisons', () => { expect(enforce.strictEquals(1).run(1)).toEqual({ pass: true, type: 1, }); expect(enforce.strictEquals(1).run('1')).toEqual({ pass: false, type: '1', }); expect(enforce.strictEquals(true).run(1)).toEqual({ pass: false, type: 1, }); }); it('Should handle loose equality comparisons', () => { expect(enforce.looseEquals(1).run('1')).toEqual({ pass: true, type: '1', }); expect(enforce.looseEquals(true).run(1)).toEqual({ pass: true, type: 1, }); expect(enforce.looseEquals(null).run(undefined)).toEqual({ pass: true, type: undefined, }); }); it('Should handle truthy/falsy checks', () => { // Truthy values expect(enforce.isTruthy().run(1)).toEqual({ pass: true, type: 1 }); expect(enforce.isTruthy().run('text')).toEqual({ pass: true, type: 'text', }); expect(enforce.isTruthy().run(true)).toEqual({ pass: true, type: true, }); expect(enforce.isTruthy().run({})).toEqual({ pass: true, type: {} }); expect(enforce.isTruthy().run([])).toEqual({ pass: true, type: [] }); // Falsy values expect(enforce.isFalsy().run(0)).toEqual({ pass: true, type: 0 }); expect(enforce.isFalsy().run('')).toEqual({ pass: true, type: '' }); expect(enforce.isFalsy().run(false)).toEqual({ pass: true, type: false, }); expect(enforce.isFalsy().run(null)).toEqual({ pass: true, type: null }); expect(enforce.isFalsy().run(undefined)).toEqual({ pass: true, type: undefined, }); }); it('Should handle type checking', () => { expect(enforce.typeCheck('string').run('hello')).toEqual({ pass: true, type: 'hello', }); expect(enforce.typeCheck('number').run(42)).toEqual({ pass: true, type: 42, }); expect(enforce.typeCheck('boolean').run(true)).toEqual({ pass: true, type: true, }); expect(enforce.typeCheck('object').run({})).toEqual({ pass: true, type: {}, }); expect(enforce.typeCheck('object').run(null)).toEqual({ pass: true, type: null, }); expect(enforce.typeCheck('undefined').run(undefined)).toEqual({ pass: true, type: undefined, }); // Type mismatches expect(enforce.typeCheck('string').run(42)).toEqual({ pass: false, type: 42, }); expect(enforce.typeCheck('number').run('hello')).toEqual({ pass: false, type: 'hello', }); }); }); describe('Complex scenarios with lazy API', () => { beforeEach(() => { enforce.extend({ isEmail: (value: string) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), isStrongPassword: (value: string) => { const hasLength = value.length >= 8; const hasUpper = /[A-Z]/.test(value); const hasLower = /[a-z]/.test(value); const hasNumber = /[0-9]/.test(value); const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(value); return hasLength && hasUpper && hasLower && hasNumber && hasSpecial; }, isPhoneNumber: (value: string) => /^\+?[\d\s\-\(\)]{10,}$/.test(value), isValidAge: (value: number) => value >= 0 && value <= 150, isValidDate: (value: Date) => value instanceof Date && !isNaN(value.getTime()), }); }); it('Should work with email validation', () => { expect(enforce.isEmail().run('test@example.com')).toEqual({ pass: true, type: 'test@example.com', }); expect(enforce.isEmail().run('invalid-email')).toEqual({ pass: false, type: 'invalid-email', }); }); it('Should work with password validation', () => { expect(enforce.isStrongPassword().run('StrongP@ss123')).toEqual({ pass: true, type: 'StrongP@ss123', }); expect(enforce.isStrongPassword().run('weak')).toEqual({ pass: false, type: 'weak', }); }); it('Should work with phone number validation', () => { expect(enforce.isPhoneNumber().run('+1234567890')).toEqual({ pass: true, type: '+1234567890', }); expect(enforce.isPhoneNumber().run('123')).toEqual({ pass: false, type: '123', }); }); it('Should work with age validation', () => { expect(enforce.isValidAge().run(25)).toEqual({ pass: true, type: 25, }); expect(enforce.isValidAge().run(-5)).toEqual({ pass: false, type: -5, }); expect(enforce.isValidAge().run(200)).toEqual({ pass: false, type: 200, }); }); it('Should work with date validation', () => { const validDate = new Date('2023-01-01'); const invalidDate = new Date('invalid'); expect(enforce.isValidDate().run(validDate)).toEqual({ pass: true, type: validDate, }); expect(enforce.isValidDate().run(invalidDate)).toEqual({ pass: false, type: invalidDate, }); }); }); describe('Integration with schema rules in lazy API', () => { beforeEach(() => { enforce.extend({ isEmail: (value: string) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), isAdult: (value: number) => value >= 18, hasValidPassword: (value: string) => { return ( value.length >= 8 && /[A-Z]/.test(value) && /[0-9]/.test(value) ); }, }); }); it('Should work within shape() using lazy API', () => { const schema = enforce.shape({ email: enforce.isString().isEmail(), age: enforce.isNumber().isAdult(), password: enforce.isString().hasValidPassword(), }); const validData = { email: 'test@example.com', age: 25, password: 'SecurePass123', }; const invalidData = { email: 'invalid-email', age: 16, password: 'weak', }; expect(schema.run(validData)).toEqual({ pass: true, type: validData, }); const res = schema.run(invalidData); expect(res).toMatchObject({ pass: false, type: invalidData }); }); it('Should work within isArrayOf() using lazy API', () => { const emailArrayRule = enforce.isArrayOf(enforce.isString().isEmail()); const validEmails = ['test@example.com', 'another@example.com']; const invalidEmails = ['test@example.com', 'invalid-email']; expect(emailArrayRule.run(validEmails)).toEqual({ pass: true, type: validEmails, }); const res2 = emailArrayRule.run(invalidEmails); expect(res2).toMatchObject({ pass: false }); }); it('Should work within optional() using lazy API', () => { const schema = enforce.shape({ email: enforce.optional(enforce.isString().isEmail()), age: enforce.optional(enforce.isNumber().isAdult()), }); expect(schema.run({ email: 'test@example.com', age: 25 })).toEqual({ pass: true, type: { email: 'test@example.com', age: 25 }, }); expect(schema.run({ email: 'test@example.com' })).toEqual({ pass: true, type: { email: 'test@example.com' }, }); expect(schema.run({})).toEqual({ pass: true, type: {}, }); }); }); describe('Performance and edge cases in lazy API', () => { it('Should handle many custom rules with lazy API', () => { const rules: Record<string, () => boolean> = {}; for (let i = 0; i < 50; i++) { rules[`rule${i}`] = () => true; } enforce.extend(rules); const result = enforce.rule0().rule25().rule49().run('test'); expect(result).toEqual({ pass: true, type: 'test', }); }); it('Should handle rules with many arguments in lazy API', () => { enforce.extend({ sumEquals: (value: number, ...args: number[]) => value === args.reduce((sum, n) => sum + n, 0), concatenatesTo: (value: string, ...parts: string[]) => value === parts.join(''), }); expect(enforce.sumEquals(1, 2, 3, 4).run(10)).toEqual({ pass: true, type: 10, }); expect(enforce.sumEquals(1, 2, 3).run(10)).toEqual({ pass: false, type: 10, }); expect( enforce.concatenatesTo('hello', 'world').run('helloworld'), ).toEqual({ pass: true, type: 'helloworld', }); expect( enforce.concatenatesTo('hello', 'world').run('hello world'), ).toEqual({ pass: false, type: 'hello world', }); }); it('Should handle complex nested objects and arrays', () => { enforce.extend({ hasProperty: (value: any, prop: string) => value && value.hasOwnProperty(prop), arrayContains: (value: any[], item: any) => value.some(v => v && item && isObject(v) && isObject(item) ? JSON.stringify(v) === JSON.stringify(item) : v === item, ), objectDeepEquals: (value: any, expected: any) => JSON.stringify(value) === JSON.stringify(expected), }); const complexObject = { nested: { deep: { value: 'test' } }, array: [1, 2, 3], mixed: [{ id: 1 }, { id: 2 }], }; expect(enforce.hasProperty('nested').run(complexObject)).toEqual({ pass: true, type: complexObject, }); expect( enforce.arrayContains({ id: 1 }).run(complexObject.mixed), ).toEqual({ pass: true, type: complexObject.mixed, }); const expectedObject = { a: 1, b: 2 }; expect( enforce.objectDeepEquals(expectedObject).run({ a: 1, b: 2 }), ).toEqual({ pass: true, type: { a: 1, b: 2 }, }); }); }); }); });