import { describe, it, expect } from "vitest"; import { parseWikiLinks, WikiLink } from "./wiki-link-parser"; describe("Wiki Link Parser", () => { describe("Basic Parsing", () => { it("should parse a simple wiki link", () => { const content = "This is a [[Page Name]] in text."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0]).toEqual({ raw: "[[Page Name]]", target: "Page Name", displayText: "Page Name", start: 10, end: 23, }); }); it("should parse multiple wiki links", () => { const content = "Link to [[First Page]] and [[Second Page]]."; const links = parseWikiLinks(content); expect(links).toHaveLength(2); expect(links[0].target).toBe("First Page"); expect(links[0].start).toBe(8); expect(links[0].end).toBe(22); expect(links[1].target).toBe("Second Page"); expect(links[1].start).toBe(27); expect(links[1].end).toBe(42); }); it("should handle empty content", () => { const links = parseWikiLinks(""); expect(links).toEqual([]); }); it("should handle content without links", () => { const content = "This is just plain text with no wiki links."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should parse link by slug (kebab-case)", () => { const content = "Reference to [[page-slug-name]]."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("page-slug-name"); expect(links[0].displayText).toBe("page-slug-name"); }); }); describe("Display Text Variation", () => { it("should parse link with custom display text", () => { const content = "See [[Page Name|custom display]] for details."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0]).toEqual({ raw: "[[Page Name|custom display]]", target: "Page Name", displayText: "custom display", start: 4, end: 32, }); }); it("should parse multiple links with display text", () => { const content = "[[First|One]] and [[Second|Two]]"; const links = parseWikiLinks(content); expect(links).toHaveLength(2); expect(links[0].target).toBe("First"); expect(links[0].displayText).toBe("One"); expect(links[1].target).toBe("Second"); expect(links[1].displayText).toBe("Two"); }); it("should handle display text with special characters", () => { const content = "[[Page|Click here! (details)]]"; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].displayText).toBe("Click here! (details)"); }); it("should handle pipe character in target but default display", () => { const content = "[[Page Name]]"; const links = parseWikiLinks(content); expect(links[0].target).toBe("Page Name"); expect(links[0].displayText).toBe("Page Name"); }); }); describe("Edge Cases - Brackets", () => { it("should not parse single brackets", () => { const content = "This [is not] a wiki link."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse three or more opening brackets", () => { const content = "This [[[is not]]] a wiki link."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse unmatched brackets", () => { const content = "This [[is incomplete"; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse reversed brackets", () => { const content = "This ]]not a link[[ text."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should handle nested brackets inside link text", () => { const content = "[[Page [with] brackets]]"; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("Page [with] brackets"); }); it("should handle nested double brackets", () => { // This is tricky - we should parse the outer link const content = "[[Outer [[inner]] link]]"; const links = parseWikiLinks(content); // Should not parse nested double brackets - only the first valid one expect(links).toHaveLength(1); expect(links[0].raw).toBe("[[Outer [[inner]]"); }); }); describe("Edge Cases - Escaped Brackets", () => { it("should not parse escaped opening brackets", () => { const content = "This \\[[is not a link]] text."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should parse link after escaped brackets", () => { const content = "Escaped \\[[not link]] but [[real link]] here."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("real link"); }); it("should handle backslash before brackets in various positions", () => { const content = "Text \\[[ and [[valid link]] more \\]]."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("valid link"); }); }); describe("Edge Cases - Code Blocks", () => { it("should not parse links in inline code", () => { const content = "Use `[[WikiLink]]` syntax for linking."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse links in fenced code blocks", () => { const content = ` Here is some text. \`\`\` [[Link in code block]] \`\`\` End of text. `; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse links in indented code blocks", () => { const content = ` Normal text here. [[Link in indented code]] More code here Normal text again. `; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should parse links outside code blocks but not inside", () => { const content = ` [[Valid Link]] \`\`\` [[Invalid Link]] \`\`\` [[Another Valid Link]] `; const links = parseWikiLinks(content); expect(links).toHaveLength(2); expect(links[0].target).toBe("Valid Link"); expect(links[1].target).toBe("Another Valid Link"); }); it("should not parse links in code blocks with language", () => { const content = ` \`\`\`typescript const link = "[[Not A Link]]"; \`\`\` `; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should handle multiple inline code sections", () => { const content = "Use `[[link1]]` or `[[link2]]` but [[real link]] works."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("real link"); }); it("should handle unclosed code backticks correctly", () => { const content = "Start `code [[link1]] still in code [[link2]]"; const links = parseWikiLinks(content); // If backtick is unclosed, we shouldn't parse any links after it expect(links).toEqual([]); }); it("should handle adjacent code blocks", () => { const content = "`[[code1]]` text [[valid]] `[[code2]]`"; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("valid"); }); }); describe("Edge Cases - Empty and Malformed", () => { it("should not parse empty link brackets", () => { const content = "Empty [[]] brackets."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should not parse whitespace-only links", () => { const content = "Whitespace [[ ]] link."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should trim whitespace from link targets", () => { const content = "Link [[ Page Name ]] here."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("Page Name"); expect(links[0].displayText).toBe("Page Name"); }); it("should trim whitespace from display text", () => { const content = "Link [[Target| display text ]] here."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("Target"); expect(links[0].displayText).toBe("display text"); }); it("should not parse link with empty target but display text", () => { const content = "Link [[|display only]] here."; const links = parseWikiLinks(content); expect(links).toEqual([]); }); it("should handle link with empty display text", () => { const content = "Link [[Target|]] here."; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe("Target"); expect(links[0].displayText).toBe("Target"); }); it("should handle multiple pipes", () => { const content = "Link [[Target|display|extra]] here."; const links = parseWikiLinks(content); // Should use first pipe as separator expect(links).toHaveLength(1); expect(links[0].target).toBe("Target"); expect(links[0].displayText).toBe("display|extra"); }); }); describe("Position Tracking", () => { it("should track correct positions for single link", () => { const content = "Start [[Link]] end"; const links = parseWikiLinks(content); expect(links[0].start).toBe(6); expect(links[0].end).toBe(14); expect(content.substring(links[0].start, links[0].end)).toBe("[[Link]]"); }); it("should track correct positions for multiple links", () => { const content = "[[First]] middle [[Second]] end"; const links = parseWikiLinks(content); expect(links[0].start).toBe(0); expect(links[0].end).toBe(9); expect(links[1].start).toBe(17); expect(links[1].end).toBe(27); expect(content.substring(links[0].start, links[0].end)).toBe("[[First]]"); expect(content.substring(links[1].start, links[1].end)).toBe("[[Second]]"); }); it("should track positions with display text", () => { const content = "Text [[Target|Display]] more"; const links = parseWikiLinks(content); expect(links[0].start).toBe(5); expect(links[0].end).toBe(23); expect(content.substring(links[0].start, links[0].end)).toBe("[[Target|Display]]"); }); it("should track positions in multiline content", () => { const content = `Line 1 Line 2 [[Link]] Line 3`; const links = parseWikiLinks(content); expect(links[0].start).toBe(14); expect(content.substring(links[0].start, links[0].end)).toBe("[[Link]]"); }); }); describe("Complex Scenarios", () => { it("should handle realistic markdown content", () => { const content = ` # Knowledge Base This is a reference to [[Main Page]] and [[Getting Started|start here]]. You can also check [[FAQ]] for common questions. \`\`\`typescript // This [[should not parse]] const link = "[[also not parsed]]"; \`\`\` But [[this works]] after code block. `; const links = parseWikiLinks(content); expect(links).toHaveLength(4); expect(links[0].target).toBe("Main Page"); expect(links[1].target).toBe("Getting Started"); expect(links[1].displayText).toBe("start here"); expect(links[2].target).toBe("FAQ"); expect(links[3].target).toBe("this works"); }); it("should handle links at start and end of content", () => { const content = "[[Start]] middle [[End]]"; const links = parseWikiLinks(content); expect(links).toHaveLength(2); expect(links[0].start).toBe(0); expect(links[1].end).toBe(content.length); }); it("should handle consecutive links", () => { const content = "[[First]][[Second]][[Third]]"; const links = parseWikiLinks(content); expect(links).toHaveLength(3); expect(links[0].target).toBe("First"); expect(links[1].target).toBe("Second"); expect(links[2].target).toBe("Third"); }); it("should handle links with unicode characters", () => { const content = "Link to [[日本語]] and [[Émojis 🚀]]."; const links = parseWikiLinks(content); expect(links).toHaveLength(2); expect(links[0].target).toBe("日本語"); expect(links[1].target).toBe("Émojis 🚀"); }); it("should handle very long link text", () => { const longText = "A".repeat(1000); const content = `Start [[${longText}]] end`; const links = parseWikiLinks(content); expect(links).toHaveLength(1); expect(links[0].target).toBe(longText); }); }); describe("Type Safety", () => { it("should return correctly typed WikiLink objects", () => { const content = "[[Test Link]]"; const links: WikiLink[] = parseWikiLinks(content); expect(links[0]).toHaveProperty("raw"); expect(links[0]).toHaveProperty("target"); expect(links[0]).toHaveProperty("displayText"); expect(links[0]).toHaveProperty("start"); expect(links[0]).toHaveProperty("end"); expect(typeof links[0].raw).toBe("string"); expect(typeof links[0].target).toBe("string"); expect(typeof links[0].displayText).toBe("string"); expect(typeof links[0].start).toBe("number"); expect(typeof links[0].end).toBe("number"); }); }); });