import { describe, expect, test } from "bun:test"; import { extractHeadings, makeAnchor } from "../../src/lib/headings.ts"; describe("makeAnchor", () => { test("slugifies plain text", () => { const seen = new Map(); expect(makeAnchor("Hello World", seen)).toBe("hello-world"); }); test("suffixes repeats with -1, -2, ...", () => { const seen = new Map(); expect(makeAnchor("Intro", seen)).toBe("intro"); expect(makeAnchor("Intro", seen)).toBe("intro-1"); expect(makeAnchor("Intro", seen)).toBe("intro-2"); }); test("returns empty for text that slugifies to empty", () => { const seen = new Map(); expect(makeAnchor(" ", seen)).toBe(""); }); }); describe("extractHeadings", () => { test("returns empty for no headings", () => { expect(extractHeadings("just paragraphs\n\nmore text")).toEqual([]); expect(extractHeadings("")).toEqual([]); }); test("captures level, text, and anchor", () => { const md = "# One\n\n## Two\n\n### Three"; expect(extractHeadings(md)).toEqual([ { text: "One", level: 1, anchor: "one" }, { text: "Two", level: 2, anchor: "two" }, { text: "Three", level: 3, anchor: "three" }, ]); }); test("strips trailing ATX closing hashes", () => { expect(extractHeadings("# Title ##")[0]?.text).toBe("Title"); }); test("requires a space after the hashes", () => { expect(extractHeadings("#notaheading")).toEqual([]); }); test("ignores headings inside fenced code blocks", () => { const md = "# Real\n\n```\n# Fake\n```\n\n## AlsoReal"; const out = extractHeadings(md); expect(out.map((h) => h.text)).toEqual(["Real", "AlsoReal"]); }); test("handles tilde fences", () => { const md = "~~~\n# Fake\n~~~\n# Real"; expect(extractHeadings(md).map((h) => h.text)).toEqual(["Real"]); }); test("ignores YAML comments in frontmatter", () => { const md = "---\n# not a heading\ntitle: x\n---\n\n# Real"; expect(extractHeadings(md).map((h) => h.text)).toEqual(["Real"]); }); test("disambiguates duplicate anchors", () => { const md = "# Intro\n\n## Intro\n\n### Intro"; expect(extractHeadings(md).map((h) => h.anchor)).toEqual([ "intro", "intro-1", "intro-2", ]); }); });