import assert from "node:assert/strict"; import test from "node:test"; import { resolveArchiveDescriptions } from "./flight-text.mjs"; function textRow(id, text) { return `${id}:T${Buffer.byteLength(text, "utf8").toString(16)},${text}`; } function flightPush(value) { return ``; } function builderHtml(...chunks) { return [flightPush([0]), ...chunks.map((chunk) => flightPush([1, chunk]))].join(""); } test("resolves exact Flight text references across split chunks using UTF-8 byte lengths", () => { const firstText = "Mycelial ring: café 🌱\nsecond line"; const secondText = "Spirit stable — restored"; const stream = `${textRow("17f", firstText)}${textRow("180", secondText)}`; const splitA = stream.slice(0, 8); const splitB = stream.slice(8, 36); const splitC = stream.slice(36); const classes = [{ classId: 12, untouched: { note: "unchanged" }, nodes: [ { id: 7221, description: "$17F", rankDescriptions: [{ rank: 1, spellId: 1, description: "$180" }], spellId: 1, }, { id: 7222, description: "$$180", rankDescriptions: [{ rank: 1, spellId: 2, description: "Literal $d placeholder" }], }, ], }]; const resolved = resolveArchiveDescriptions(classes, builderHtml(splitA, splitB, splitC)); assert.notEqual(resolved, classes); assert.notEqual(resolved[0], classes[0]); assert.equal(resolved[0].nodes[0].description, firstText); assert.equal(resolved[0].nodes[0].rankDescriptions[0].description, secondText); assert.equal(resolved[0].nodes[0].spellId, 1); assert.deepEqual(resolved[0].untouched, classes[0].untouched); assert.equal(resolved[0].nodes[1].description, "$$180"); assert.equal(resolved[0].nodes[1].rankDescriptions[0].description, "Literal $d placeholder"); assert.equal(classes[0].nodes[0].description, "$17F"); assert.equal(classes[0].nodes[0].rankDescriptions[0].description, "$180"); }); test("resolves node descriptions inside nested tab collections and preserves escaped dollar text", () => { const stream = textRow("abc", "\u003cstrong\u003eArchive text\u003c/strong\u003e"); const classes = [{ classId: 13, tabs: [{ tabId: 1, nodes: [{ id: 1, description: "$abc", rankDescriptions: [] }] }], }]; const resolved = resolveArchiveDescriptions(classes, builderHtml(stream)); assert.equal(resolved[0].tabs[0].nodes[0].description, "Archive text"); }); test("rejects missing exact references with their source field", () => { const classes = [{ classId: 12, nodes: [{ id: 7221, description: "$dead", rankDescriptions: [] }] }]; assert.throws( () => resolveArchiveDescriptions(classes, builderHtml(textRow("1", "one"))), /description reference \$dead at classes\[0\]\.nodes\[0\]\.description has no Flight text row/, ); }); test("rejects truncated UTF-8 text records and malformed Flight push JSON", () => { const classes = [{ classId: 12, nodes: [] }]; assert.throws(() => resolveArchiveDescriptions(classes, builderHtml("1:T20,short")), /text row 1 declares 20 bytes but is truncated/); assert.throws(() => resolveArchiveDescriptions(classes, ''), /push argument array is unterminated/); });