/** * Metadata block parsing and rendering edge cases beyond the core round-trip * tests in utils.test.ts: audience normalization, link-list filtering, block * boundaries, "None"/"Unknown" sentinel values, and rendering defaults. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { parseMetadata, renderMetadata } from "../src/utils.ts"; import type { DocMetadata } from "../src/types.ts"; const META_BASE: DocMetadata = { purpose: "Purpose text", audience: "Both", sourceOfTruth: "Codebase (implementation is authoritative)", lastUpdated: "2026-01-01", dependsOn: [], referencedBy: [], }; // ── parseMetadata ───────────────────────────────────────────────────── test("parseMetadata parses a complete block, canonicalizing link lists", () => { const raw = [ "> **Purpose:** Functional spec", ">", "> **Audience:** AI", ">", "> **Source of Truth:** Codebase (implementation is authoritative)", ">", "> **Last Updated:** 2026-02-03", ">", "> **Depends On:** [AGENTS](../AGENTS.md), [ROADMAP](ROADMAP.md)", ">", "> **Referenced By:** Unknown", "", "## Body", "content", ].join("\n"); const meta = parseMetadata(raw, "docs/SPEC.md"); assert.equal(meta.purpose, "Functional spec"); assert.equal(meta.audience, "AI"); assert.equal(meta.lastUpdated, "2026-02-03"); assert.deepEqual(meta.dependsOn, ["AGENTS.md", "docs/ROADMAP.md"]); assert.deepEqual(meta.referencedBy, []); }); test("parseMetadata normalizes audience values case-insensitively", () => { const audience = (value: string) => parseMetadata(`> **Audience:** ${value}`, "docs/x.md").audience; assert.equal(audience("Both"), "Both"); assert.equal(audience("AI"), "AI"); assert.equal(audience("Human"), "Human"); assert.equal(audience("Both (human + AI)"), "Both"); assert.equal(audience("AI agents and tools"), "AI"); // Unrecognized values fall back to Human, matching the implementation. assert.equal(audience("Robots"), "Human"); }); test("parseMetadata defaults fields when absent and treats None/Unknown as empty lists", () => { const meta = parseMetadata( "> **Purpose:** only\n>\n> text\n\nbody", "docs/x.md", ); assert.equal(meta.audience, "Both"); assert.equal(meta.sourceOfTruth, "Codebase (implementation is authoritative)"); assert.equal(meta.lastUpdated, ""); assert.deepEqual(meta.dependsOn, []); assert.deepEqual(meta.referencedBy, []); const none = parseMetadata( "> **Depends On:** None\n> **Referenced By:** Unknown", "docs/x.md", ); assert.deepEqual(none.dependsOn, []); assert.deepEqual(none.referencedBy, []); }); test("parseMetadata drops external URLs and same-document anchors from link lists", () => { const raw = [ "> **Depends On:** [OpenAI](https://openai.com), [Section](#intro), [SPEC](SPEC.md), [api-docs](https://api.example.com/v1)", ].join("\n"); const meta = parseMetadata(raw, "docs/ARCHITECTURE.md"); assert.deepEqual(meta.dependsOn, ["docs/SPEC.md"]); }); test("parseMetadata ignores non-bold quoted lines but keeps scanning within the block", () => { const raw = [ "> **Purpose:** keep me", "> some prose without a bold key", "> **Depends On:** [SPEC](SPEC.md)", "", "## Body", ].join("\n"); const meta = parseMetadata(raw, "docs/ARCHITECTURE.md"); assert.equal(meta.purpose, "keep me"); assert.deepEqual(meta.dependsOn, ["docs/SPEC.md"]); }); test("parseMetadata stops at the first non-quote line", () => { const raw = [ "> **Purpose:** inside", "", "> **Depends On:** [SPEC](SPEC.md)", ].join("\n"); const meta = parseMetadata(raw, "docs/ARCHITECTURE.md"); assert.equal(meta.purpose, "inside"); assert.deepEqual(meta.dependsOn, []); }); test("parseMetadata returns defaults for a body without any metadata block", () => { const meta = parseMetadata("# Title\n\ntext", "README.md"); assert.deepEqual(meta, { purpose: "", audience: "Both", sourceOfTruth: "Codebase (implementation is authoritative)", lastUpdated: "", dependsOn: [], referencedBy: [], }); }); test("parseMetadata strips whitespace-only link entries", () => { const meta = parseMetadata( "> **Depends On:** [A](A.md), , [B](B.md)", "docs/ROOT.md", ); assert.deepEqual(meta.dependsOn, ["docs/A.md", "docs/B.md"]); }); // ── renderMetadata ──────────────────────────────────────────────────── test("renderMetadata emits None/Unknown sentinels for empty lists", () => { const rendered = renderMetadata(META_BASE, "docs/BACKLOG.md"); assert.match(rendered, /> \*\*Depends On:\*\* None/); assert.match(rendered, /> \*\*Referenced By:\*\* Unknown/); }); test("renderMetadata emits every expected key in order", () => { const rendered = renderMetadata(META_BASE, "docs/BACKLOG.md"); const lines = rendered.split("\n"); assert.match(lines[0]!, /^> \*\*Purpose:\*\*/); assert.ok(lines.includes(">"), "separator lines expected"); const expected = [ "**Purpose:**", "**Audience:**", "**Source of Truth:**", "**Last Updated:**", "**Depends On:**", "**Referenced By:**", ]; let cursor = -1; for (const key of expected) { const idx = rendered.indexOf(key); assert.ok(idx > cursor, `key ${key} should appear after previous keys`); cursor = idx; } }); test("renderMetadata → parseMetadata round-trips through a different source path", () => { const meta: DocMetadata = { ...META_BASE, dependsOn: ["docs/API.md", "docs/DESIGN.md"], referencedBy: ["AGENTS.md"], }; const rendered = renderMetadata(meta, "docs/ARCHITECTURE.md"); const parsed = parseMetadata(rendered, "docs/ARCHITECTURE.md"); assert.equal(parsed.purpose, meta.purpose); assert.equal(parsed.audience, meta.audience); assert.equal(parsed.sourceOfTruth, meta.sourceOfTruth); assert.equal(parsed.lastUpdated, meta.lastUpdated); assert.deepEqual(parsed.dependsOn, meta.dependsOn); assert.deepEqual(parsed.referencedBy, meta.referencedBy); }); test("renderMetadata is deterministic for a fixed metadata object", () => { const a = renderMetadata(META_BASE, "docs/BACKLOG.md"); const b = renderMetadata(META_BASE, "docs/BACKLOG.md"); assert.equal(a, b); });