This repository has no description
1import { describe, expect, it } from "vitest";
2import { LineOp, type NiceDiff } from "./diff";
3import { renderFormatPatch, renderUnifiedDiff } from "./rawdiff";
4
5const modifiedFile: NiceDiff = {
6 diff: [
7 {
8 name: { old: "foo.go", new: "foo.go" },
9 text_fragments: [
10 {
11 Comment: "func main()",
12 OldPosition: 1,
13 OldLines: 3,
14 NewPosition: 1,
15 NewLines: 3,
16 Lines: [
17 { Op: LineOp.Context, Line: "package main\n" },
18 { Op: LineOp.Delete, Line: "// old comment\n" },
19 { Op: LineOp.Add, Line: "// new comment\n" },
20 { Op: LineOp.Context, Line: "func main() {}\n" }
21 ]
22 }
23 ]
24 }
25 ]
26};
27
28describe("renderUnifiedDiff", () => {
29 it("renders nothing for a missing diff", () => {
30 expect(renderUnifiedDiff(null)).toBe("");
31 expect(renderUnifiedDiff(undefined)).toBe("");
32 });
33
34 it("renders a modified file with the hunk comment", () => {
35 expect(renderUnifiedDiff(modifiedFile)).toBe(
36 "diff --git a/foo.go b/foo.go\n" +
37 "--- a/foo.go\n" +
38 "+++ b/foo.go\n" +
39 "@@ -1,3 +1,3 @@ func main()\n" +
40 " package main\n" +
41 "-// old comment\n" +
42 "+// new comment\n" +
43 " func main() {}\n"
44 );
45 });
46
47 it("renders a new file", () => {
48 const got = renderUnifiedDiff({
49 diff: [
50 {
51 name: { old: "", new: "new.go" },
52 is_new: true,
53 text_fragments: [
54 {
55 OldPosition: 0,
56 OldLines: 0,
57 NewPosition: 1,
58 NewLines: 2,
59 Lines: [
60 { Op: LineOp.Add, Line: "package main\n" },
61 { Op: LineOp.Add, Line: "func main() {}\n" }
62 ]
63 }
64 ]
65 }
66 ]
67 });
68 expect(got).toBe(
69 "diff --git a/new.go b/new.go\n" +
70 "new file mode 100644\n" +
71 "--- /dev/null\n" +
72 "+++ b/new.go\n" +
73 "@@ -0,0 +1,2 @@\n" +
74 "+package main\n" +
75 "+func main() {}\n"
76 );
77 });
78
79 it("renders a deleted file", () => {
80 const got = renderUnifiedDiff({
81 diff: [
82 {
83 name: { old: "old.go", new: "" },
84 is_delete: true,
85 text_fragments: [
86 {
87 OldPosition: 1,
88 OldLines: 2,
89 NewPosition: 0,
90 NewLines: 0,
91 Lines: [
92 { Op: LineOp.Delete, Line: "package main\n" },
93 { Op: LineOp.Delete, Line: "func main() {}\n" }
94 ]
95 }
96 ]
97 }
98 ]
99 });
100 expect(got).toBe(
101 "diff --git a/old.go b/old.go\n" +
102 "deleted file mode 100644\n" +
103 "--- a/old.go\n" +
104 "+++ /dev/null\n" +
105 "@@ -1,2 +0,0 @@\n" +
106 "-package main\n" +
107 "-func main() {}\n"
108 );
109 });
110
111 it("renders a renamed file with multiple fragments", () => {
112 const got = renderUnifiedDiff({
113 diff: [
114 {
115 name: { old: "old.go", new: "renamed.go" },
116 is_rename: true,
117 text_fragments: [
118 {
119 OldPosition: 1,
120 OldLines: 2,
121 NewPosition: 1,
122 NewLines: 2,
123 Lines: [
124 { Op: LineOp.Context, Line: "package main\n" },
125 { Op: LineOp.Delete, Line: "func old() {}\n" },
126 { Op: LineOp.Add, Line: "func renamed() {}\n" }
127 ]
128 },
129 {
130 Comment: "func init()",
131 OldPosition: 10,
132 OldLines: 1,
133 NewPosition: 10,
134 NewLines: 1,
135 Lines: [{ Op: LineOp.Context, Line: "var x = 1\n" }]
136 }
137 ]
138 }
139 ]
140 });
141 expect(got).toBe(
142 "diff --git a/old.go b/renamed.go\n" +
143 "rename from old.go\n" +
144 "rename to renamed.go\n" +
145 "--- a/old.go\n" +
146 "+++ b/renamed.go\n" +
147 "@@ -1,2 +1,2 @@\n" +
148 " package main\n" +
149 "-func old() {}\n" +
150 "+func renamed() {}\n" +
151 "@@ -10,1 +10,1 @@ func init()\n" +
152 " var x = 1\n"
153 );
154 });
155
156 it("renders multiple files", () => {
157 const file = (name: string, oldLine: string, newLine: string) => ({
158 name: { old: name, new: name },
159 text_fragments: [
160 {
161 OldPosition: 1,
162 OldLines: 1,
163 NewPosition: 1,
164 NewLines: 1,
165 Lines: [
166 { Op: LineOp.Delete, Line: `${oldLine}\n` },
167 { Op: LineOp.Add, Line: `${newLine}\n` }
168 ]
169 }
170 ]
171 });
172 const got = renderUnifiedDiff({
173 diff: [file("a.go", "old a", "new a"), file("b.go", "old b", "new b")]
174 });
175 expect(got).toBe(
176 "diff --git a/a.go b/a.go\n" +
177 "--- a/a.go\n" +
178 "+++ b/a.go\n" +
179 "@@ -1,1 +1,1 @@\n" +
180 "-old a\n" +
181 "+new a\n" +
182 "diff --git a/b.go b/b.go\n" +
183 "--- a/b.go\n" +
184 "+++ b/b.go\n" +
185 "@@ -1,1 +1,1 @@\n" +
186 "-old b\n" +
187 "+new b\n"
188 );
189 });
190
191 it("marks a missing trailing newline on delete and context lines", () => {
192 const got = renderUnifiedDiff({
193 diff: [
194 {
195 name: { old: "foo.go", new: "foo.go" },
196 text_fragments: [
197 {
198 OldPosition: 1,
199 OldLines: 2,
200 NewPosition: 1,
201 NewLines: 2,
202 Lines: [
203 { Op: LineOp.Delete, Line: "old" },
204 { Op: LineOp.Add, Line: "new\n" },
205 { Op: LineOp.Context, Line: "tail" }
206 ]
207 }
208 ]
209 }
210 ]
211 });
212 expect(got).toBe(
213 "diff --git a/foo.go b/foo.go\n" +
214 "--- a/foo.go\n" +
215 "+++ b/foo.go\n" +
216 "@@ -1,2 +1,2 @@\n" +
217 "-old\n" +
218 "\\ No newline at end of file\n" +
219 "+new\n" +
220 " tail\n" +
221 "\\ No newline at end of file\n"
222 );
223 });
224});
225
226describe("renderFormatPatch", () => {
227 it("renders nothing for a missing diff", () => {
228 expect(renderFormatPatch(null)).toBe("");
229 expect(renderFormatPatch(undefined)).toBe("");
230 });
231
232 it("renders the full patch for a rename and a delete", () => {
233 const got = renderFormatPatch({
234 commit: {
235 hash: [0xab, 0xc1, 0x23, 0x45, 0x67, 0x89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
236 message: "Fix the bug\n\nThis patch resolves the long-standing issue.\n\n\n",
237 author: { Name: "Alice Dev", Email: "alice@example.com", When: "2024-03-15T10:30:00Z" }
238 },
239 stat: { files_changed: 2, insertions: 1, deletions: 3 },
240 diff: [
241 {
242 name: { old: "old.go", new: "renamed.go" },
243 is_rename: true,
244 text_fragments: [
245 {
246 OldPosition: 1,
247 OldLines: 2,
248 NewPosition: 1,
249 NewLines: 2,
250 LinesAdded: 1,
251 LinesDeleted: 1,
252 Lines: [
253 { Op: LineOp.Context, Line: "package main\n" },
254 { Op: LineOp.Delete, Line: "func old() {}\n" },
255 { Op: LineOp.Add, Line: "func renamed() {}\n" }
256 ]
257 }
258 ]
259 },
260 {
261 name: { old: "gone.go", new: "" },
262 is_delete: true,
263 text_fragments: [
264 {
265 OldPosition: 1,
266 OldLines: 2,
267 NewPosition: 0,
268 NewLines: 0,
269 LinesAdded: 0,
270 LinesDeleted: 2,
271 Lines: [
272 { Op: LineOp.Delete, Line: "package main\n" },
273 { Op: LineOp.Delete, Line: "func main() {}\n" }
274 ]
275 }
276 ]
277 }
278 ]
279 });
280 expect(got).toBe(
281 "From abc1234567890000000000000000000000000000 Mon Sep 17 00:00:00 2001\n" +
282 "From: Alice Dev <alice@example.com>\n" +
283 "Date: Fri, 15 Mar 2024 10:30:00 +0000\n" +
284 "Subject: [PATCH] Fix the bug\n" +
285 "\n" +
286 "This patch resolves the long-standing issue.\n" +
287 "---\n" +
288 " renamed.go | 2 +-\n" +
289 " gone.go | 2 --\n" +
290 " 2 file(s) changed, 1 insertion(s)(+), 3 deletion(s)(-)\n" +
291 "\n" +
292 "diff --git a/old.go b/renamed.go\n" +
293 "rename from old.go\n" +
294 "rename to renamed.go\n" +
295 "--- a/old.go\n" +
296 "+++ b/renamed.go\n" +
297 "@@ -1,2 +1,2 @@\n" +
298 " package main\n" +
299 "-func old() {}\n" +
300 "+func renamed() {}\n" +
301 "diff --git a/gone.go b/gone.go\n" +
302 "deleted file mode 100644\n" +
303 "--- a/gone.go\n" +
304 "+++ /dev/null\n" +
305 "@@ -1,2 +0,0 @@\n" +
306 "-package main\n" +
307 "-func main() {}\n" +
308 "\n--\ntangled.sh\n"
309 );
310 });
311
312 it("omits the body for a single-line message and uses the zero time for a missing When", () => {
313 const got = renderFormatPatch({
314 commit: {
315 message: "Single line commit",
316 author: { Name: "", Email: "", When: "" }
317 }
318 });
319 expect(got).toBe(
320 "From Mon Sep 17 00:00:00 2001\n" +
321 "From: <>\n" +
322 "Date: Mon, 01 Jan 0001 00:00:00 +0000\n" +
323 "Subject: [PATCH] Single line commit\n" +
324 "\n" +
325 "---\n" +
326 " 0 file(s) changed, 0 insertion(s)(+), 0 deletion(s)(-)\n" +
327 "\n" +
328 "\n--\ntangled.sh\n"
329 );
330 });
331
332 it("uses the zero time for an unparseable When", () => {
333 const got = renderFormatPatch({
334 commit: {
335 this: "abc123",
336 message: "x",
337 author: { Name: "A", Email: "a@b.c", When: "not a date" }
338 }
339 });
340 expect(got).toContain("Date: Mon, 01 Jan 0001 00:00:00 +0000\n");
341 });
342
343 it("pads the year to four digits", () => {
344 const got = renderFormatPatch({
345 commit: {
346 this: "abc123",
347 message: "x",
348 author: { Name: "A", Email: "a@b.c", When: "0999-06-15T10:30:00Z" }
349 }
350 });
351 expect(got).toContain("Date: Sat, 15 Jun 0999 10:30:00 +0000\n");
352 });
353});