n4s
Version:
typed schema validation version of enforce
202 lines (163 loc) • 5.7 kB
text/typescript
import { describe, expect, it } from 'vitest';
import { enforce } from '../../n4s';
// This test suite verifies that compound and schema rule types are properly defined
// and can be used for type inference via the .infer property
describe('Compound and Schema Rule Types', () => {
it('should properly type allOf rules', () => {
const rule = enforce.allOf(
enforce.isNumber(),
enforce.isNumber().greaterThan(0),
);
type InferredType = typeof rule.infer;
const value: InferredType = 5;
void value;
// Type test: string is not assignable to number
const badValue: InferredType = 'test';
void badValue;
expect(rule.run(5).pass).toBe(true);
expect(rule.run(-1).pass).toBe(false);
});
it('should properly type anyOf rules', () => {
const rule = enforce.anyOf(enforce.isString(), enforce.isNumber());
type InferredType = typeof rule.infer;
const str: InferredType = 'test';
const num: InferredType = 123;
void str;
void num;
// Type test: boolean is not string | number
// @ts-expect-error - boolean is not string | number
const badValue: InferredType = true;
void badValue;
expect(rule.run('test').pass).toBe(true);
expect(rule.run(123).pass).toBe(true);
// @ts-expect-error - boolean is not string | number
expect(rule.run(true).pass).toBe(false);
});
it('should properly type noneOf rules', () => {
const rule = enforce.noneOf(enforce.isString());
type InferredType = typeof rule.infer;
const value: InferredType = 'test';
void value;
expect(rule.run(123).pass).toBe(true);
expect(rule.run('test').pass).toBe(false);
});
it('should properly type oneOf rules', () => {
const rule = enforce.oneOf(
enforce.isString(),
enforce.isNumber(),
enforce.isBoolean(),
);
type InferredType = typeof rule.infer;
const str: InferredType = 'test';
const num: InferredType = 123;
const bool: InferredType = true;
void str;
void num;
void bool;
expect(rule.run('test').pass).toBe(true);
expect(rule.run(123).pass).toBe(true);
});
it('should properly type optional rules', () => {
const rule = enforce.optional(enforce.isString());
type InferredType = typeof rule.infer;
const str: InferredType = 'test';
const undef: InferredType = undefined;
const nul: InferredType = null;
void str;
void undef;
void nul;
// Type test: number is not string | undefined | null
// @ts-expect-error - number is not string | undefined | null
const badValue: InferredType = 123;
void badValue;
expect(rule.run('test').pass).toBe(true);
expect(rule.run(undefined).pass).toBe(true);
expect(rule.run(null).pass).toBe(true);
// @ts-expect-error - number is not string | undefined | null
expect(rule.run(123).pass).toBe(false);
});
it('should properly type isArrayOf rules', () => {
const rule = enforce.isArrayOf(enforce.isString());
type InferredType = typeof rule.infer;
const arr: InferredType = ['a', 'b', 'c'];
void arr;
// Type test: number[] is not string[]
// @ts-expect-error - number is not assignable to string
const badArr: InferredType = [1, 2, 3];
void badArr;
expect(rule.run(['a', 'b']).pass).toBe(true);
// @ts-expect-error - number is not assignable to string
expect(rule.run([1, 2]).pass).toBe(false);
});
it('should properly type shape rules', () => {
const rule = enforce.shape({
name: enforce.isString(),
age: enforce.isNumber(),
});
type InferredType = typeof rule.infer;
const obj: InferredType = { name: 'John', age: 30 };
void obj;
// Type test: age must be number
// @ts-expect-error - age must be number
const badObj: InferredType = { name: 'John', age: 'thirty' };
void badObj;
expect(rule.run({ name: 'John', age: 30 }).pass).toBe(true);
// @ts-expect-error - age must be number
expect(rule.run({ name: 'John', age: 'thirty' }).pass).toBe(false);
});
it('should properly type loose rules', () => {
const rule = enforce.loose({
name: enforce.isString(),
});
type InferredType = typeof rule.infer;
const obj: InferredType = { name: 'John', extraProp: true };
void obj;
expect(rule.run({ name: 'John', extraProp: true }).pass).toBe(true);
});
it('should properly type partial rules', () => {
const rule = enforce.partial({
name: enforce.isString(),
age: enforce.isNumber(),
});
type InferredType = typeof rule.infer;
const obj1: InferredType = { name: 'John' };
const obj2: InferredType = { age: 30 };
const obj3: InferredType = {};
void obj1;
void obj2;
void obj3;
expect(rule.run({ name: 'John' }).pass).toBe(true);
expect(rule.run({ age: 30 }).pass).toBe(true);
expect(rule.run({}).pass).toBe(true);
});
it('should work with complex nested compositions', () => {
const userRule = enforce.shape({
id: enforce.isNumber(),
profile: enforce.shape({
name: enforce.isString(),
email: enforce.optional(enforce.isString()),
}),
tags: enforce.optional(enforce.isArrayOf(enforce.isString())),
});
type User = typeof userRule.infer;
const validUser: User = {
id: 1,
profile: {
name: 'John',
email: 'john@example.com',
},
tags: ['developer', 'admin'],
};
void validUser;
expect(
userRule.run({
id: 1,
profile: {
name: 'John',
email: 'john@example.com',
},
tags: ['developer', 'admin'],
}).pass,
).toBe(true);
});
});