112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { encode } from "./encoder.js";
|
|
import { tryEncodeFloat16 } from "./half-float.js";
|
|
|
|
const hex = (b: Uint8Array): string =>
|
|
Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
|
|
describe("encoder — bigint range branches", () => {
|
|
it("encodes -1n as major-1 minor=0", { timeout: 5000 }, () => {
|
|
// -1n is in [-u64, -1]: encoded as major 1, value = -(-1) - 1 = 0
|
|
expect(hex(encode(-1n))).toBe("20");
|
|
});
|
|
|
|
it("encodes -100n via major-1", { timeout: 5000 }, () => {
|
|
// -100n encodes as major 1 minor=24 0x63 (= -1 - 99 = -100)
|
|
expect(hex(encode(-100n))).toBe("3863");
|
|
});
|
|
|
|
it("encodes negative on the u64 boundary", { timeout: 5000 }, () => {
|
|
// -2^64 in range? value >= -0x10000000000000000n is true for value=-2^64
|
|
// -value - 1n = 2^64 - 1 = 0xffffffffffffffff fits in 8 bytes
|
|
expect(hex(encode(-(2n ** 64n)))).toBe("3bffffffffffffffff");
|
|
});
|
|
});
|
|
|
|
describe("encoder — half-float edge branches", () => {
|
|
it("subnormal reconstruction mismatch returns undefined", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
// 1.5 * 2^-24 — mant rounds to 2, reconstructed = 2^-23 ≠ original
|
|
expect(tryEncodeFloat16(1.5 * 2 ** -24)).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when rollup overflows exponent", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
// Value such that mantissa rounds up to 1024, pushing biasedExp from
|
|
// 30 (0x1e) to 31 (0x1f) — overflow → undefined.
|
|
// 65520 has mantissa-bits 1023.5 which rounds to 1024.
|
|
expect(tryEncodeFloat16(65520)).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when normal-path reconstruction mismatch", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
// 1.0001 — fraction rounds to a mantissa whose reconstructed value
|
|
// differs from the input by a tiny amount.
|
|
expect(tryEncodeFloat16(1.0001)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("encoder — Map with Tagged keys", () => {
|
|
it("deterministic mode sorts mixed-type keys by canonical bytes", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
// Keys: "b" (62), 1 (01), 0xff (18ff)
|
|
const m = new Map<unknown, number>([
|
|
["b", 1],
|
|
[1, 2],
|
|
[255, 3],
|
|
]);
|
|
// Canonical-byte sort: 01 < 18ff < 6162. Expected order: 1, 255, "b"
|
|
const out = hex(encode(m, { deterministic: true }));
|
|
expect(out.startsWith("a3")).toBe(true);
|
|
// Body: 0102 18ff03 616201
|
|
expect(out).toBe("a3010218ff036162 01".replace(/\s/g, ""));
|
|
});
|
|
});
|
|
|
|
describe("encoder — depth boundary", () => {
|
|
it("encodes at exact maxDepth without throw", { timeout: 5000 }, () => {
|
|
let nested: unknown = 1;
|
|
for (let i = 0; i < 5; i++) nested = [nested];
|
|
// maxDepth=5 should accept depth-5; depth-6 throws
|
|
expect(() => encode(nested, { maxDepth: 5 })).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("encoder — depth on bigint outside u64", () => {
|
|
it("encodes large positive bignum via tag 2", { timeout: 5000 }, () => {
|
|
// 2^128 → tag 2 + 16-byte payload
|
|
expect(hex(encode(2n ** 128n)).startsWith("c25")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("encoder — float branch coverage", () => {
|
|
it("deterministic mode encodes NaN via float32 (NaN-NaN equality path)", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const out = encode(Number.NaN, { deterministic: true });
|
|
expect(out[0]).toBe(0xfa); // float32
|
|
});
|
|
|
|
it("preferFloat16=true falls through to float32 when f16 not exact", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
// 1 + 2^-23 — float32-exact; f16 cannot represent the fractional bit.
|
|
const value = 1 + 2 ** -23;
|
|
const out = encode(value, { preferFloat16: true });
|
|
expect(out[0]).toBe(0xfa); // float32
|
|
expect(out.length).toBe(5);
|
|
});
|
|
|
|
it("default float path emits float64 when not exact in float32", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const out = encode(0.1);
|
|
expect(out[0]).toBe(0xfb); // float64
|
|
expect(out.length).toBe(9);
|
|
});
|
|
});
|