| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { resolveMarkdownLinkIcon, resolveMarkdownLinkPresentation } from "./markdownLinks"; |
| 4 | |
| 5 | describe("resolveMarkdownLinkIcon", () => { |
| 6 | it("gives GitHub hosts the brand mark and everything else the generic glyph", () => { |
| 7 | expect(resolveMarkdownLinkIcon("github.com")).toBe("github"); |
| 8 | expect(resolveMarkdownLinkIcon("GitHub.com")).toBe("github"); |
| 9 | expect(resolveMarkdownLinkIcon("gist.github.com")).toBe("github"); |
| 10 | expect(resolveMarkdownLinkIcon("github.community")).toBeNull(); |
| 11 | expect(resolveMarkdownLinkIcon("notgithub.com")).toBeNull(); |
| 12 | expect(resolveMarkdownLinkIcon("example.com")).toBeNull(); |
| 13 | }); |
| 14 | }); |
| 15 | |
| 16 | describe("resolveMarkdownLinkPresentation", () => { |
| 17 | it("keeps thread-link hrefs for in-app navigation", () => { |
| 18 | const href = "t3-thread://v1/thread-1"; |
| 19 | expect(resolveMarkdownLinkPresentation(href)).toEqual({ kind: "link", href }); |
| 20 | }); |
| 21 | |
| 22 | it("treats protocol-relative media as an external URL, not a filesystem path", () => { |
| 23 | expect(resolveMarkdownLinkPresentation("//cdn.example.com/clip.mp4?sig=a%2fb#t=2")).toEqual({ |
| 24 | kind: "external", |
| 25 | href: "https://cdn.example.com/clip.mp4?sig=a%2fb#t=2", |
| 26 | host: "cdn.example.com", |
| 27 | }); |
| 28 | }); |
| 29 | |
| 30 | it("separates encoded filename characters from a video playback fragment", () => { |
| 31 | expect(resolveMarkdownLinkPresentation("/tmp/clip%23one.mp4#t=2")).toMatchObject({ |
| 32 | path: "/tmp/clip#one.mp4", |
| 33 | label: "clip#one.mp4", |
| 34 | icon: "video", |
| 35 | }); |
| 36 | }); |
| 37 | |
| 38 | it("extracts external link hosts", () => { |
| 39 | expect(resolveMarkdownLinkPresentation("https://example.com/docs?q=1")).toEqual({ |
| 40 | kind: "external", |
| 41 | href: "https://example.com/docs?q=1", |
| 42 | host: "example.com", |
| 43 | }); |
| 44 | }); |
| 45 | |
| 46 | it.each([ |
| 47 | ["file:///Users/julius/project/src/main.ts#L42C7", "/Users/julius/project/src/main.ts"], |
| 48 | ["file://server/share/src/main.ts#L42C7", "\\\\server\\share\\src\\main.ts"], |
| 49 | ])("preserves the file URL path and position for %s", (href, path) => { |
| 50 | expect(resolveMarkdownLinkPresentation(href)).toEqual({ |
| 51 | kind: "file", |
| 52 | href, |
| 53 | icon: "typescript", |
| 54 | label: "main.ts:42:7", |
| 55 | path, |
| 56 | line: 42, |
| 57 | column: 7, |
| 58 | }); |
| 59 | }); |
| 60 | |
| 61 | it("recognizes relative source paths and bare filenames", () => { |
| 62 | expect(resolveMarkdownLinkPresentation("apps/mobile/src/index.ts:10")).toEqual({ |
| 63 | kind: "file", |
| 64 | href: "apps/mobile/src/index.ts:10", |
| 65 | icon: "typescript", |
| 66 | label: "index.ts:10", |
| 67 | path: "apps/mobile/src/index.ts", |
| 68 | line: 10, |
| 69 | }); |
| 70 | expect(resolveMarkdownLinkPresentation("AGENTS.md")).toEqual({ |
| 71 | kind: "file", |
| 72 | href: "AGENTS.md", |
| 73 | icon: "agents", |
| 74 | label: "AGENTS.md", |
| 75 | path: "AGENTS.md", |
| 76 | }); |
| 77 | expect(resolveMarkdownLinkPresentation("package.json")).toEqual({ |
| 78 | kind: "file", |
| 79 | href: "package.json", |
| 80 | icon: "npm", |
| 81 | label: "package.json", |
| 82 | path: "package.json", |
| 83 | }); |
| 84 | }); |
| 85 | |
| 86 | it.each(["md", "html", "xml"])("recognizes a bare spaced .%s filename", (extension) => { |
| 87 | expect( |
| 88 | resolveMarkdownLinkPresentation(`Updated%20cutover%20checklist.${extension}`), |
| 89 | ).toMatchObject({ |
| 90 | kind: "file", |
| 91 | path: `Updated cutover checklist.${extension}`, |
| 92 | label: `Updated cutover checklist.${extension}`, |
| 93 | }); |
| 94 | }); |
| 95 | |
| 96 | it("recognizes spaced relative paths", () => { |
| 97 | expect(resolveMarkdownLinkPresentation("docs/My%20Folder/checklist.xml")).toMatchObject({ |
| 98 | kind: "file", |
| 99 | path: "docs/My Folder/checklist.xml", |
| 100 | label: "checklist.xml", |
| 101 | }); |
| 102 | }); |
| 103 | |
| 104 | it("extracts line fragments from relative file links", () => { |
| 105 | expect(resolveMarkdownLinkPresentation("src/main.ts#L18C2")).toMatchObject({ |
| 106 | kind: "file", |
| 107 | path: "src/main.ts", |
| 108 | line: 18, |
| 109 | column: 2, |
| 110 | label: "main.ts:18:2", |
| 111 | }); |
| 112 | }); |
| 113 | |
| 114 | it("uses the Pierre complete icon mappings", () => { |
| 115 | expect(resolveMarkdownLinkPresentation("src/Button.tsx")).toMatchObject({ |
| 116 | kind: "file", |
| 117 | icon: "react", |
| 118 | }); |
| 119 | expect(resolveMarkdownLinkPresentation("vite.config.ts")).toMatchObject({ |
| 120 | kind: "file", |
| 121 | icon: "vite", |
| 122 | }); |
| 123 | expect(resolveMarkdownLinkPresentation("Dockerfile")).toMatchObject({ |
| 124 | kind: "file", |
| 125 | icon: "docker", |
| 126 | }); |
| 127 | expect(resolveMarkdownLinkPresentation("pnpm-lock.yaml")).toMatchObject({ |
| 128 | kind: "file", |
| 129 | icon: "pnpm", |
| 130 | }); |
| 131 | }); |
| 132 | |
| 133 | it("does not style app routes as file links", () => { |
| 134 | expect(resolveMarkdownLinkPresentation("/chat/settings")).toEqual({ |
| 135 | kind: "link", |
| 136 | href: null, |
| 137 | }); |
| 138 | }); |
| 139 | }); |