n4s
Version:
typed schema validation version of enforce
368 lines (305 loc) • 10.8 kB
text/typescript
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);
});
});
});