UNPKG

n4s

Version:

typed schema validation version of enforce

289 lines (242 loc) 8.8 kB
/** * Comprehensive TypeScript type tests for n4s * These tests verify type inference, type guards, and compile-time type safety */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'vitest'; import { enforce } from '../n4s'; // Wrap in a function so runtime won't execute; TypeScript still checks it. function typeChecks() { // ===== BASIC TYPE INFERENCE ===== // Type guards provide proper narrowing const test1 = enforce(1).isNumber().greaterThan(0); const test2 = enforce('hello').isString().startsWith('h'); const test3 = enforce([1, 2, 3]).isArray().includes(1); const test4 = enforce(true).isBoolean(); // These should cause type errors // Type test: - greaterThan should not be available on boolean // @ts-expect-error - greaterThan should not be available on boolean const test5 = enforce(true).greaterThan(5); // Type test: - startsWith should not be available on number // @ts-expect-error - startsWith should not be available on number const test6 = enforce(123).startsWith('1'); // Type test: - array includes() takes single value, not available on string // @ts-expect-error - array includes() takes single value, not available on string const test7 = enforce('hello').includes('h'); // ===== TYPE GUARDS ===== // isString type guard - valid chains const str1 = enforce('hello').isString().startsWith('h'); const str2 = enforce('hello').isString().endsWith('o'); const str3 = enforce('hello').isString().matches(/^h/); const str4 = enforce('hello').isString().longerThan(3); const str5 = enforce('hello').isString().minLength(1); // isNumber type guard - valid chains const num1 = enforce(42).isNumber().greaterThan(0); const num2 = enforce(42).isNumber().lessThan(100); const num3 = enforce(42).isNumber().isEven(); const num4 = enforce(42).isNumber().isPositive(); const num5 = enforce(42).isNumber().isBetween(0, 100); const num6 = enforce(42).isNumber().isNotNaN(); // isBoolean type guard - valid chains const bool1 = enforce(true).isBoolean().isTrue(); const bool2 = enforce(false).isBoolean().isFalse(); const bool3 = enforce(true).isBoolean().isTruthy(); const bool4 = enforce(false).isBoolean().isFalsy(); const bool5 = enforce(true).isBoolean().equals(true); // isArray type guard - valid chains const arr1 = enforce([1, 2, 3]).isArray().includes(1); const arr2 = enforce([1, 2, 3]).isArray().minLength(1); const arr3 = enforce([1, 2, 3]).isArray().maxLength(10); const arr4 = enforce([1, 2, 3]).isArray().lengthEquals(3); const arr5 = enforce([1, 2, 3]).isArray().isEmpty(); const arr6 = enforce([1, 2, 3]).isArray().longerThan(2); // isNumeric type guard - valid chains (works with numeric strings) const numeric1 = enforce('42').isNumeric().greaterThan(0); const numeric2 = enforce(42).isNumeric().lessThan(100); const numeric3 = enforce('42').isNumeric().isBetween(0, 100); const numeric4 = enforce('42').isNumeric().isPositive(); const numeric5 = enforce('42').isNumeric().isEven(); // isNull type guard const null1 = enforce(null).isNull(); // isUndefined type guard const undef1 = enforce(undefined).isUndefined(); // isNullish type guard const nullish1 = enforce(null).isNullish(); const nullish2 = enforce(undefined).isNullish(); // Type guards work with unknown const unknownValue: unknown = 42; const strUnknown = enforce(unknownValue).isString().startsWith('x'); const numUnknown = enforce(unknownValue).isNumber().isPositive(); const boolUnknown = enforce(unknownValue).isBoolean().isTrue(); const arrUnknown = enforce(unknownValue).isArray().includes(1); const numericUnknown = enforce(unknownValue).isNumeric().isPositive(); const nullUnknown = enforce(unknownValue).isNull(); const undefUnknown = enforce(unknownValue).isUndefined(); const nullishUnknown = enforce(unknownValue).isNullish(); // ===== SCHEMA RULES TYPE INFERENCE ===== // shape rule - infers exact type const shape1 = enforce({ name: 'John', age: 30 }).shape({ name: enforce.isString(), age: enforce.isNumber(), }); // optional rule - infers union with undefined and null const opt1 = enforce('hello').optional(enforce.isString()); const opt2 = enforce(undefined).optional(enforce.isString()); const opt3 = enforce(42).optional(enforce.isNumber().greaterThan(0)); // isArrayOf rule - infers array type const arrOf1 = enforce([1, 2, 3]).isArrayOf(enforce.isNumber()); const arrOf2 = enforce(['a', 'b']).isArrayOf(enforce.isString()); const arrOf3 = enforce([true, false]).isArrayOf(enforce.isBoolean()); // loose rule - allows extra properties const loose1 = enforce({ name: 'John', age: 30, extra: 'data' }).loose({ name: enforce.isString(), age: enforce.isNumber(), }); // partial rule - all properties optional const partial1 = enforce({ name: 'John' }).partial({ name: enforce.isString(), age: enforce.isNumber(), }); // ===== COMPOUND RULES TYPE INFERENCE ===== // allOf rule - must satisfy all rules const allOf1 = enforce(42).allOf( enforce.isNumber(), enforce.isNumber().greaterThan(0), enforce.isNumber().lessThan(100), ); // anyOf rule - must satisfy at least one rule (union type) const anyOf1 = enforce('42').anyOf(enforce.isString(), enforce.isNumber()); // noneOf rule - must satisfy none of the rules const noneOf1 = enforce(42).noneOf(enforce.isString(), enforce.isBoolean()); // oneOf rule - must satisfy exactly one rule const oneOf1 = enforce('hello').oneOf(enforce.isString(), enforce.isNumber()); // ===== COMPLEX CHAINING SCENARIOS ===== // Multiple type guards in sequence const complex1 = enforce(42).isNumber().greaterThan(0).isNumber().isEven(); // Type guard after schema rule const complex2 = enforce([1, 2, 3]) .isArrayOf(enforce.isNumber()) .minLength(1); // Chaining multiple validations on unknown const complex3 = enforce(unknownValue) .isNumber() .greaterThan(0) .lessThan(100) .isEven(); // ===== TYPE INFERENCE WITH .infer PROPERTY ===== // Primitive rules const stringRule = enforce.isString(); // @ts-expect-error type StringType = typeof stringRule.infer; // Should be string const numberRule = enforce.isNumber(); // @ts-expect-error type NumberType = typeof numberRule.infer; // Should be number const booleanRule = enforce.isBoolean(); // @ts-expect-error type BooleanType = typeof booleanRule.infer; // Should be boolean const arrayRule = enforce.isArray(); // @ts-expect-error type ArrayType = typeof arrayRule.infer; // Should be unknown[] // Schema rules const userRule = enforce.shape({ id: enforce.isNumber(), name: enforce.isString(), email: enforce.optional(enforce.isString()), }); // @ts-expect-error type UserType = typeof userRule.infer; // Compound rules const stringOrNumberRule = enforce.anyOf( enforce.isString(), enforce.isNumber(), ); // @ts-expect-error type StringOrNumber = typeof stringOrNumberRule.infer; // Should be string | number // Array of objects const usersRule = enforce.isArrayOf(userRule); // @ts-expect-error type UsersType = typeof usersRule.infer; // ===== CUSTOM RULES TYPE SAFETY ===== // Custom rules with extend should be type-safe // (This would require the n4s namespace declaration) // Mark all as used to avoid warnings void [ test1, test2, test3, test4, test5, test6, test7, str1, str2, str3, str4, str5, num1, num2, num3, num4, num5, num6, bool1, bool2, bool3, bool4, bool5, arr1, arr2, arr3, arr4, arr5, arr6, numeric1, numeric2, numeric3, numeric4, numeric5, null1, undef1, nullish1, nullish2, strUnknown, numUnknown, boolUnknown, arrUnknown, numericUnknown, nullUnknown, undefUnknown, nullishUnknown, shape1, opt1, opt2, opt3, arrOf1, arrOf2, arrOf3, loose1, partial1, allOf1, anyOf1, noneOf1, oneOf1, complex1, complex2, complex3, ]; } // Runtime test to verify the file is recognized by vitest describe('TypeScript Type Tests', () => { it('compiles without errors', () => { expect(true).toBe(true); }); it('ensures type guards work at runtime', () => { // Verify that type guards actually work expect(enforce.isNumber().test(42)).toBe(true); expect(enforce.isString().test('hello')).toBe(true); expect(enforce.isBoolean().test(true)).toBe(true); expect(enforce.isArray().test([])).toBe(true); }); }); // Mark unused function as referenced for TS noUnusedLocals void typeChecks;