UNPKG

@unito/integration-sdk

Version:

Integration SDK

51 lines (43 loc) 1.93 kB
import { strict as assert } from 'node:assert'; import type { RichTextNode, RichTextRoot } from '@unito/integration-api'; import type { CanRender } from '../richTextCodec.js'; // A richText codec for one field: parse provider wire to canonical AST, // serialize canonical AST back to wire, and the renderability predicate // the serializer uses to decide what gets sentinel-encoded. type Field = { canRender: CanRender; parse: (wire: string) => RichTextRoot; serialize: (root: RichTextRoot) => string; }; export type ConformanceField = { key: string; module: Field }; // `{type:'break'}` and `{type:'break',children:[]}` are the same canonical // node; integrations differ on whether they carry the empty array. Compare // modulo that variant so conformance flags data loss, not representation. function canonicalize(node: RichTextNode): RichTextNode { const out: RichTextNode = { ...node }; if (out.children) { if (out.children.length === 0) { delete out.children; } else { out.children = out.children.map(canonicalize); } } return out; } function normalize(root: RichTextRoot): RichTextRoot { return { type: 'root', children: (root.children ?? []).map(canonicalize) }; } // Asserts one canonical fixture round-trips through a field's codec — // serialize→parse must not throw and must reproduce the original AST. When // `divergence` names a documented loss, the fixture is instead asserted to // STILL diverge, so a later codec fix fails this assertion until the now-stale // divergence is removed. export function assertConforms(field: ConformanceField, ast: RichTextRoot, divergence?: string): void { const got = normalize(field.module.parse(field.module.serialize(ast))); const want = normalize(ast); if (divergence) { assert.notDeepEqual(got, want, `no longer diverges — remove divergence (${divergence})`); } else { assert.deepEqual(got, want); } }