import { describe, expect, it } from "vitest"; import type { Note } from "../model"; import { extractHeadings, extractWikilinkTitles, findBacklinks, parseWikilinks, renderWikilinks, resolveWikilink, slugify, } from "../wikilink"; const mockNotes: Note[] = [{ id: "note-1", owner_did: "did:plc:test", title: "First Note", body: "Content with [[Second Note]] link", tags: ["test"], visibility: { type: "Private" }, created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", }, { id: "note-2", owner_did: "did:plc:test", title: "Second Note", body: "This links to [[First Note]] and [[Third Note]]", tags: ["test"], visibility: { type: "Private" }, created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", }, { id: "note-3", owner_did: "did:plc:test", title: "Third Note", body: "No wikilinks here", tags: [], visibility: { type: "Private" }, created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", }]; describe("parseWikilinks", () => { it("extracts simple wikilinks", () => { const text = "Check out [[My Note]] for more info"; const links = parseWikilinks(text); expect(links).toHaveLength(1); expect(links[0].title).toBe("My Note"); expect(links[0].alias).toBeUndefined(); }); it("extracts wikilinks with aliases", () => { const text = "See [[Long Note Title|short alias]] here"; const links = parseWikilinks(text); expect(links).toHaveLength(1); expect(links[0].title).toBe("Long Note Title"); expect(links[0].alias).toBe("short alias"); }); it("extracts multiple wikilinks", () => { const text = "Both [[Note A]] and [[Note B|B]] are relevant"; const links = parseWikilinks(text); expect(links).toHaveLength(2); expect(links[0].title).toBe("Note A"); expect(links[1].title).toBe("Note B"); expect(links[1].alias).toBe("B"); }); it("returns empty array for no wikilinks", () => { const text = "Just regular text without links"; const links = parseWikilinks(text); expect(links).toHaveLength(0); }); it("includes position information", () => { const text = "Start [[Link]] end"; const links = parseWikilinks(text); expect(links[0].start).toBe(6); expect(links[0].end).toBe(14); expect(links[0].raw).toBe("[[Link]]"); }); }); describe("extractWikilinkTitles", () => { it("returns unique titles", () => { const text = "[[Note A]] and [[Note B]] and [[Note A]] again"; const titles = extractWikilinkTitles(text); expect(titles).toHaveLength(2); expect(titles).toContain("Note A"); expect(titles).toContain("Note B"); }); }); describe("resolveWikilink", () => { it("resolves exact match", () => { const note = resolveWikilink("First Note", mockNotes); expect(note?.id).toBe("note-1"); }); it("resolves case-insensitively", () => { const note = resolveWikilink("FIRST NOTE", mockNotes); expect(note?.id).toBe("note-1"); }); it("returns null for unresolved links", () => { const note = resolveWikilink("Nonexistent Note", mockNotes); expect(note).toBeNull(); }); it("trims whitespace", () => { const note = resolveWikilink(" First Note ", mockNotes); expect(note?.id).toBe("note-1"); }); }); describe("findBacklinks", () => { it("finds notes linking to target", () => { const backlinks = findBacklinks("Second Note", mockNotes); expect(backlinks).toHaveLength(1); expect(backlinks[0].id).toBe("note-1"); }); it("finds multiple backlinks", () => { const backlinks = findBacklinks("First Note", mockNotes); expect(backlinks).toHaveLength(1); expect(backlinks[0].id).toBe("note-2"); }); it("returns empty for notes with no backlinks", () => { const backlinks = findBacklinks("Third Note", mockNotes); expect(backlinks).toHaveLength(1); expect(backlinks[0].id).toBe("note-2"); }); it("is case-insensitive", () => { const backlinks = findBacklinks("SECOND NOTE", mockNotes); expect(backlinks).toHaveLength(1); }); }); describe("slugify", () => { it("converts to lowercase", () => { expect(slugify("Hello World")).toBe("hello-world"); }); it("removes special characters", () => { expect(slugify("Hello, World!")).toBe("hello-world"); }); it("collapses multiple dashes", () => { expect(slugify("Hello World")).toBe("hello-world"); }); it("handles empty string", () => { expect(slugify("")).toBe(""); }); }); describe("extractHeadings", () => { it("extracts H1-H6 headings", () => { const markdown = `# Heading 1 ## Heading 2 ### Heading 3 #### Heading 4 ##### Heading 5 ###### Heading 6`; const headings = extractHeadings(markdown); expect(headings).toHaveLength(6); expect(headings[0]).toEqual({ level: 1, text: "Heading 1", id: "heading-1" }); expect(headings[5]).toEqual({ level: 6, text: "Heading 6", id: "heading-6" }); }); it("handles special characters in headings", () => { const markdown = "## Hello, World!"; const headings = extractHeadings(markdown); expect(headings[0].id).toBe("hello-world"); }); it("returns empty array for no headings", () => { const markdown = "Just regular text"; expect(extractHeadings(markdown)).toHaveLength(0); }); }); describe("renderWikilinks", () => { it("renders resolved links as anchors", () => { const text = "See [[My Note]] here"; const result = renderWikilinks(text, () => "/notes/123"); expect(result).toBe("See My Note here"); }); it("renders unresolved links as spans", () => { const text = "See [[Missing]] here"; const result = renderWikilinks(text, () => null); expect(result).toBe("See Missing here"); }); it("uses alias for display text when provided", () => { const text = "See [[Long Title|alias]] here"; const result = renderWikilinks(text, () => "/notes/123"); expect(result).toBe("See alias here"); }); });