import { describe, test } from "node:test"; import assert from "node:assert/strict"; import { renderKilder } from "../src/kilder.js"; import type { LivedSpecifics, Slot } from "../src/binding.js"; import type { Bank, Specific } from "../src/types.js"; function mkSpecific(over: Partial & { id: string }): Specific { return { id: over.id, type: over.type ?? "other", content: over.content ?? "x", topicTags: over.topicTags ?? ["t"], provenance: over.provenance ?? { capturedAt: "2026-06-20", source: "test" }, verification: over.verification ?? "n/a", status: over.status ?? "active", }; } const mkBank = (specifics: Specific[]): Bank => ({ schemaVersion: 1, specifics }); const ls = (slots: Slot[]): LivedSpecifics => ({ slots, status: "pending" }); describe("renderKilder — the NN-kilder.md artifact", () => { test("golden render of a mixed edition (specific-number-unverified + abstrakt + ekstern + unresolved)", () => { const bank = mkBank([ mkSpecific({ id: "num1", type: "number", verification: "unverified", content: "Sparte to dager i uka.", topicTags: ["saksbehandling", "tid"], provenance: { capturedAt: "2026-06-19", source: "seres/05" }, }), ]); const livedSpecifics = ls([ { slotId: "kp1", kind: "key-point", label: "Saksbehandling ble raskere", binding: { type: "specific", specificId: "num1" }, }, { slotId: "kp2", kind: "key-point", label: "Verktøyet bærer faget", binding: { type: "abstrakt", rationale: "Poenget er prinsippet, ikke et enkelttilfelle." }, }, { slotId: "kp3", kind: "key-point", label: "LinkedIn ranker på relevans", binding: { type: "ekstern" }, }, { slotId: "kp4", kind: "key-point", label: "Noe uavklart", binding: { type: "unresolved" }, }, ]); const expected = `# Kilder — Maskinrommet (05) > Auto-generert fra edition-state \`livedSpecifics\` + specifics-bank. Per lastbærende påstand: hva backer den. > Levd materiale er KTGs egne ord, aldri AI-oppdiktet (retning §3). Tall er uverifiserte til fakta-sjekket (regel 6/7). ## Lastbærende påstander ### kp1 · Saksbehandling ble raskere - **Binding:** levd materiale (number) · \`num1\` · uverifisert ⚠ - **Materiale:** «Sparte to dager i uka.» - **Opphav:** seres/05, fanget 2026-06-19 - **Tema-tagger:** saksbehandling, tid ### kp2 · Verktøyet bærer faget - **Binding:** abstrakt (med vilje) — Poenget er prinsippet, ikke et enkelttilfelle. ### kp3 · LinkedIn ranker på relevans - **Binding:** ekstern kilde — (fylles av research, Step 2) ### kp4 · Noe uavklart - **Binding:** ⛔ uavklart — må bindes før skjelett-gaten (vaghet avvises) ## Dekning - Lastbærende slots: 4 - Levd materiale: 1 - Abstrakt (escape): 1 - Ekstern: 1 - Uavklart (BLOCK): 1 Tall som må verifiseres (regel 6/7): kp1 `; assert.equal(renderKilder({ articleId: "05", title: "Maskinrommet", livedSpecifics, bank }), expected); }); test("a verified named-case renders ✓ and no number-verification line", () => { const bank = mkBank([ mkSpecific({ id: "c1", type: "named-case", verification: "verified", content: "Etat X." }), ]); const out = renderKilder({ articleId: "01", title: "T", livedSpecifics: ls([ { slotId: "kp1", kind: "key-point", label: "L", binding: { type: "specific", specificId: "c1" } }, ]), bank, }); assert.match(out, /levd materiale \(named-case\) · `c1` · verifisert ✓/); assert.match(out, /Tall som må verifiseres \(regel 6\/7\): ingen/); }); test("a dangling specific id renders an explicit dangling note", () => { const out = renderKilder({ articleId: "01", title: "T", livedSpecifics: ls([ { slotId: "kp1", kind: "key-point", label: "L", binding: { type: "specific", specificId: "ghost" } }, ]), bank: mkBank([]), }); assert.match(out, /⛔ levd materiale — id `ghost` mangler i banken \(dangling\)/); }); test("an empty slot-map renders the 'ingen slots' placeholder, not an empty section", () => { const out = renderKilder({ articleId: "01", title: "T", livedSpecifics: ls([]), bank: mkBank([]) }); assert.match(out, /_Ingen lastbærende slots bundet ennå\._/); assert.match(out, /- Lastbærende slots: 0/); }); });