You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/arts/sium/test/lazy.test.ts

241 lines
5.4 KiB

import { describe, expect, it } from 'vitest';
import { array, lazy, object, optional, string } from '../core';
import { createSchema } from '../core/schema';
import type { Schema } from '../core/types';
import { SiumAsyncSchemaError, SiumLazyResolvingError } from '../errors';
type TreeNode = {
value: string;
children?: TreeNode[];
};
type Alpha = {
name: string;
beta?: Beta;
};
type Beta = {
label: string;
alpha?: Alpha;
};
describe('lazy()', () => {
it('delegates primitive decode/encode through the cached schema', () => {
const schema = lazy(() => string());
expect(schema.decodeSync('x')).toBe('x');
expect(schema.encode('x')).toBe('x');
});
it('decodes self-referential trees to arbitrary input depth', () => {
const TreeSchema: Schema<TreeNode, TreeNode> = lazy(() =>
object({
value: string(),
children: optional(array(TreeSchema))
})
);
const tree = TreeSchema.decodeSync({
value: 'root',
children: [
{
value: 'branch',
children: [{ value: 'leaf' }]
}
]
});
expect(tree).toEqual({
value: 'root',
children: [
{
value: 'branch',
children: [{ value: 'leaf', children: undefined }]
}
]
});
});
it('supports mutual recursion through lazy forward references', () => {
const AlphaSchema: Schema<Alpha, Alpha> = lazy(() =>
object({
name: string(),
beta: optional(BetaSchema)
})
);
const BetaSchema: Schema<Beta, Beta> = lazy(() =>
object({
label: string(),
alpha: optional(AlphaSchema)
})
);
expect(
AlphaSchema.decodeSync({
name: 'a1',
beta: {
label: 'b1',
alpha: {
name: 'a2'
}
}
})
).toEqual({
name: 'a1',
beta: {
label: 'b1',
alpha: {
name: 'a2',
beta: undefined
}
}
});
});
it('round-trips recursive trees through decode + encode', () => {
const TreeSchema: Schema<TreeNode, TreeNode> = lazy(() =>
object({
value: string(),
children: optional(array(TreeSchema))
})
);
const input = {
value: 'root',
children: [{ value: 'leaf' }]
};
expect(TreeSchema.encode(TreeSchema.decodeSync(input))).toEqual({
value: 'root',
children: [
{
value: 'leaf',
children: undefined
}
]
});
});
it('inherits async from the resolved inner schema', () => {
const schema = lazy(() =>
createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
})
);
expect(schema['~sium'].async).toBe(true);
});
it('reports the inner kind transparently after resolution', () => {
const schema = lazy(() => string());
expect(schema['~sium'].kind).toBe('string');
});
it('passes through the inner shape transparently after resolution', () => {
const inner = object({ value: string() });
const schema = lazy(() => inner);
expect(schema['~sium'].shape).toBe(inner['~sium'].shape);
});
it('invokes the factory thunk once and caches the resolved schema', async () => {
let calls = 0;
const schema = lazy(() => {
calls += 1;
return string();
});
expect(schema.decodeSync('a')).toBe('a');
expect(schema.encode('b')).toBe('b');
await expect(schema.validate('c')).resolves.toEqual({ ok: true, value: 'c' });
expect(schema['~sium'].kind).toBe('string');
expect(calls).toBe(1);
});
it('delegates the Standard Schema contract to the resolved inner schema', async () => {
const schema = lazy(() => string());
expect(schema['~standard'].version).toBe(1);
expect(schema['~standard'].vendor).toBe('sium');
await expect(schema['~standard'].validate('ok')).resolves.toEqual({ value: 'ok' });
});
it('preserves validation issue paths from recursive objects', async () => {
const TreeSchema: Schema<TreeNode, TreeNode> = lazy(() =>
object({
value: string(),
children: optional(array(TreeSchema))
})
);
await expect(
TreeSchema.validate({
value: 42 as never,
children: [{ value: 'leaf' }]
})
).resolves.toEqual({
ok: false,
issues: [
{
path: ['value'],
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: {
expected: 'string',
actual: 'number'
}
}
]
});
});
it('resolves recursion based on the input shape rather than schema depth', () => {
const TreeSchema: Schema<TreeNode, TreeNode> = lazy(() =>
object({
value: string(),
children: optional(array(TreeSchema))
})
);
expect(TreeSchema.decodeSync({ value: 'leaf' })).toEqual({
value: 'leaf',
children: undefined
});
});
it('rethrows SiumAsyncSchemaError from decodeSync when the inner schema is async', () => {
const schema = lazy(() =>
createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
})
);
expect(() => schema.decodeSync('x')).toThrow(SiumAsyncSchemaError);
});
it('throws SiumLazyResolvingError when the thunk uses the proxy while resolving', () => {
const schema: Schema<string, string> = lazy(() => {
schema.decodeSync('x');
return string();
});
expect(() => schema.decodeSync('x')).toThrow(SiumLazyResolvingError);
});
it('throws SiumLazyResolvingError when the thunk returns the same lazy proxy', () => {
const schema: Schema<string, string> = lazy(() => schema);
expect(() => schema.decodeSync('x')).toThrow(SiumLazyResolvingError);
});
it('is re-exported by the core barrel', () => {
expect(lazy).toBeTypeOf('function');
});
});

Powered by TurnKey Linux.