1import { describe, expect, it } from "vite-plus/test";
2
3import { resolveMarkdownLinkIcon, resolveMarkdownLinkPresentation } from "./markdownLinks";
4
5describe("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
16describe("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});