Files
snippets/serializer/test/api.test.ts
T

162 lines
3.8 KiB
TypeScript

import { test, expect } from 'vitest';
import {
type,
oneOf,
router,
u53,
f64,
str,
bool,
list,
opt,
enumOf,
flags,
tuple,
f64Array,
clearRegistry,
Writer,
Reader,
} from '../plugin/index.ts';
test('type() — flat schema round-trip', () => {
clearRegistry();
const Ticker = type('Ticker', {
symbol: str,
last: f64,
volume: f64,
});
type Ticker = typeof Ticker.$infer;
const v: Ticker = { symbol: 'BTC-USD', last: 100.5, volume: 1234 };
const bytes = Ticker.encode(v);
expect(Ticker.decode(bytes)).toEqual(v);
});
test('type() — nested object via inline reference', () => {
clearRegistry();
const Price = type('Price', { value: f64, scale: u53 });
const Order = type('Order', {
id: u53,
price: Price,
qty: f64,
});
const v = { id: 42, price: { value: 100.5, scale: 2 }, qty: 0.5 };
expect(Order.decode(Order.encode(v))).toEqual(v);
});
test('type() — anonymous (no name) still works', () => {
clearRegistry();
const Anon = type({ x: u53, y: f64 });
const v = { x: 1, y: 2.5 };
expect(Anon.decode(Anon.encode(v))).toEqual(v);
});
test('list, opt, enumOf, flags, tuple — combinators', () => {
clearRegistry();
const T = type('Combo', {
tags: list(str),
maybe: opt(f64),
side: enumOf(['buy', 'sell'] as const),
f: flags(['ioc', 'post_only'] as const),
point: tuple(f64, f64),
});
const v = {
tags: ['a', 'b'],
maybe: 3.14,
side: 'buy' as const,
f: { ioc: true, post_only: false },
point: [1, 2] as [number, number],
};
expect(T.decode(T.encode(v))).toEqual(v);
const v2 = { ...v, maybe: undefined };
expect(T.decode(T.encode(v2))).toEqual(v2);
});
test('type() — typed array field', () => {
clearRegistry();
const Signal = type('Signal', { name: str, samples: f64Array });
const v = { name: 'x', samples: new Float64Array([1, 2, 3, 4]) };
const back = Signal.decode(Signal.encode(v));
expect(back.name).toBe('x');
expect(back.samples).toBeInstanceOf(Float64Array);
expect(Array.from(back.samples)).toEqual([1, 2, 3, 4]);
});
test('oneOf() — discriminated union', () => {
clearRegistry();
const Event = oneOf('Event', 'kind', {
fill: { price: f64, qty: f64 },
cancel: { reason: str },
});
const a = { kind: 'fill', price: 100, qty: 0.5 };
const b = { kind: 'cancel', reason: 'user' };
expect(Event.decode(Event.encode(a as never))).toEqual(a);
expect(Event.decode(Event.encode(b as never))).toEqual(b);
});
test('encodeInto / decodeFrom — pooled writer hot path', () => {
clearRegistry();
const T = type('Pooled', { x: u53, y: f64 });
const w = new Writer(256);
const v = { x: 42, y: 3.14 };
w.reset();
T.encodeInto(v, w);
const bytes = w.bytes();
expect(bytes.length).toBeGreaterThan(0);
const r = new Reader(bytes);
expect(T.decodeFrom(r)).toEqual(v);
});
test('router() — framed multi-type dispatch', () => {
clearRegistry();
const A = type('A', { x: u53 });
const B = type('B', { y: str });
const proto = router(A, B);
const bytesA = proto.encode({ x: 7 }, A);
const bytesB = proto.encode({ y: 'hi' }, B);
expect(proto.decode(bytesA)).toEqual({ x: 7 });
expect(proto.decode(bytesB)).toEqual({ y: 'hi' });
});
test('typeof T.$infer — TS inference works at compile time', () => {
clearRegistry();
const Order = type('OrderInf', {
id: u53,
price: f64,
side: enumOf(['buy', 'sell'] as const),
active: bool,
tags: list(str),
});
type Order = typeof Order.$infer;
const v: Order = {
id: 1,
price: 100,
side: 'buy',
active: true,
tags: ['a'],
};
expect(Order.decode(Order.encode(v))).toEqual(v);
});
test('codec id is deterministic by name', () => {
clearRegistry();
const A = type('Same', { x: u53 });
clearRegistry();
const B = type('Same', { y: f64 });
expect(A.id).toBe(B.id);
});