1import { describe, expect, it } from "vite-plus/test";
2
3import { splitPullRequestBody } from "./pullRequestMarkdown.ts";
4
5describe("pull request body segmentation", () => {
6 it("keeps a plain body as a single markdown run", () => {
7 expect(splitPullRequestBody("## What changed\n\nSome prose.")).toEqual([
8 { id: "markdown:0", kind: "markdown", text: "## What changed\n\nSome prose." },
9 ]);
10 });
11
12 // GitHub writes a dropped image into the body as an `<img>` tag, so a bare attachment link on
13 // its own line is the shape it uses for a video — every one sampled in the wild was one.
14 it("lifts a dropped video out and keeps the prose around it", () => {
15 expect(
16 splitPullRequestBody(
17 "Before\n\nhttps://github.com/user-attachments/assets/2f8c1a90-1b2c-4d5e-8f90-abcdef123456\n\nAfter",
18 ),
19 ).toEqual([
20 { id: "markdown:0", kind: "markdown", text: "Before" },
21 {
22 id: "attachment:1",
23 kind: "attachment",
24 url: "https://github.com/user-attachments/assets/2f8c1a90-1b2c-4d5e-8f90-abcdef123456",
25 media: "video",
26 },
27 { id: "markdown:2", kind: "markdown", text: "After" },
28 ]);
29 });
30
31 it("names a bare link to a video file a video", () => {
32 expect(splitPullRequestBody("https://example.com/demo.mp4?raw=1")).toEqual([
33 {
34 id: "attachment:0",
35 kind: "attachment",
36 url: "https://example.com/demo.mp4?raw=1",
37 media: "video",
38 },
39 ]);
40 });
41
42 it("reads the source out of a video tag, including a multi-line one", () => {
43 expect(
44 splitPullRequestBody(
45 '<video controls>\n <source src="https://example.com/a.webm">\n</video>',
46 ),
47 ).toEqual([
48 {
49 id: "attachment:0",
50 kind: "attachment",
51 url: "https://example.com/a.webm",
52 media: "video",
53 },
54 ]);
55 });
56
57 it("leaves a dropped image as markdown so it still renders as an image", () => {
58 const body = "![shot](https://github.com/user-attachments/assets/2f8c1a90-1b2c-4d5e-8f90-ab)";
59 expect(splitPullRequestBody(body)).toEqual([
60 { id: "markdown:0", kind: "markdown", text: body },
61 ]);
62 });
63
64 it("leaves the img tag GitHub writes for a dropped image as markdown", () => {
65 const body =
66 '<img width="1414" alt="image" src="https://github.com/user-attachments/assets/7195f963-51a9-4331-be74-3e06be760422" />';
67 expect(splitPullRequestBody(body)).toEqual([
68 { id: "markdown:0", kind: "markdown", text: body },
69 ]);
70 });
71
72 it("leaves an ordinary link alone, whether it is bare or written as markdown", () => {
73 const body = "https://example.com/page\n\n[the docs](https://example.com/docs)";
74 expect(splitPullRequestBody(body)).toEqual([
75 { id: "markdown:0", kind: "markdown", text: body },
76 ]);
77 });
78
79 it("never lifts a link inside fenced code out of it", () => {
80 const body = "```\nhttps://example.com/demo.mp4\n```";
81 expect(splitPullRequestBody(body)).toEqual([
82 { id: "markdown:0", kind: "markdown", text: body },
83 ]);
84 });
85
86 it("does not let a different fence marker close an open fence", () => {
87 const body = "```\n~~~\nhttps://example.com/demo.mp4\n```";
88 expect(splitPullRequestBody(body)).toEqual([
89 { id: "markdown:0", kind: "markdown", text: body },
90 ]);
91 });
92
93 it("keeps a video tag that shares its line with prose as markdown", () => {
94 const body = 'Before <video src="https://example.com/a.mp4"></video> after';
95 expect(splitPullRequestBody(body)).toEqual([
96 { id: "markdown:0", kind: "markdown", text: body },
97 ]);
98 });
99
100 it("keeps the indentation that opens a code block", () => {
101 const body = " const answer = 42;";
102 expect(splitPullRequestBody(body)).toEqual([
103 { id: "markdown:0", kind: "markdown", text: body },
104 ]);
105 });
106
107 it("treats an info-string fence as a nested opener, not a close", () => {
108 const body = "```\n```javascript\nhttps://example.com/demo.mp4\n```";
109 expect(splitPullRequestBody(body)).toEqual([
110 { id: "markdown:0", kind: "markdown", text: body },
111 ]);
112 });
113
114 it("leaves an indented code line alone even when it is only a video link", () => {
115 const body = " https://example.com/demo.mp4";
116 expect(splitPullRequestBody(body)).toEqual([
117 { id: "markdown:0", kind: "markdown", text: body },
118 ]);
119 });
120
121 it("leaves an unclosed video tag as prose without eating the rest of the body", () => {
122 const body = ['<video src="https://example.com/a.mp4">', "still prose", "more prose"].join(
123 "\n",
124 );
125 expect(splitPullRequestBody(body)).toEqual([
126 { id: "markdown:0", kind: "markdown", text: body },
127 ]);
128 });
129
130 it("stays linear when the body is full of unclosed video tags", () => {
131 const body = Array.from(
132 { length: 4_000 },
133 () => '<video src="https://example.com/a.mp4">',
134 ).join("\n");
135 const startedAt = performance.now();
136 expect(splitPullRequestBody(body)).toHaveLength(1);
137 expect(performance.now() - startedAt).toBeLessThan(1_000);
138 });
139
140 it("refuses a non-http source rather than making a link out of it", () => {
141 const body = '<video src="javascript:alert(1)"></video>';
142 expect(splitPullRequestBody(body)).toEqual([
143 { id: "markdown:0", kind: "markdown", text: body },
144 ]);
145 });
146});
147
148describe("media on custom hosts", () => {
149 it.each([
150 ["https://drop.example.com/capture.PNG?token=abc#preview", "image"],
151 ["[Recording](https://drop.example.com/recording.mov)", "video"],
152 ["<https://drop.example.com/shot.webp>", "image"],
153 ])("recognizes %s without a request", (body, media) => {
154 expect(splitPullRequestBody(body)).toEqual([
155 expect.objectContaining({ kind: "attachment", media }),
156 ]);
157 });
158
159 it("keeps inline links and media examples in code as prose", () => {
160 const body =
161 "See [recording](https://drop.example.com/a.mp4) for context.\n\n```md\nhttps://drop.example.com/a.png\n```";
162 expect(splitPullRequestBody(body)).toEqual([
163 { id: "markdown:0", kind: "markdown", text: body },
164 ]);
165 });
166});