Skip to content
Source
Sign inSign up

reviewGuideTour.test.ts

188 lines5.8 KB

1import { parsePatchFiles } from "@pierre/diffs/utils/parsePatchFiles";
2import { describe, expect, it } from "vite-plus/test";
3​
4import {
5 excerptExpansionOldContents,
6 excerptRows,
7 fileDiffRows,
8 rowsToPatch,
9} from "./reviewGuideTour.js";
10​
11const PATCH = `diff --git a/src/tray.ts b/src/tray.ts
12--- a/src/tray.ts
13+++ b/src/tray.ts
14@@ -10,5 +10,6 @@ function tray()
15 const a = 1;
16-const size = pick();
17+const size = "xs";
18+const variant = "tray";
19 const b = 2;
20 const c = 3;
21 const d = 4;
22@@ -40,3 +41,2 @@ function footer()
23 keep();
24-dropped();
25 tail();
26`;
27​
28function parse(patch: string) {
29 const file = parsePatchFiles(patch, { cacheKeyPrefix: `test:${patch.length}` })[0]?.files[0];
30 if (file === undefined) throw new Error("patch did not parse");
31 return file;
33​
34const rows = fileDiffRows(parse(PATCH));
35​
36const LINE_ENDING_CASES = [
37 {
38 name: "adding the final newline",
39 body: "@@ -1 +1 @@\n-same\n\\ No newline at end of file\n+same\n",
40 oldContents: "same",
41 newContents: "same\n",
42 },
43 {
44 name: "removing the final newline",
45 body: "@@ -1 +1 @@\n-same\n+same\n\\ No newline at end of file\n",
46 oldContents: "same\n",
47 newContents: "same",
48 },
49 {
50 name: "neither side having a final newline",
51 body: "@@ -1 +1 @@\n-old\n\\ No newline at end of file\n+new\n\\ No newline at end of file\n",
52 oldContents: "old",
53 newContents: "new",
54 },
55 {
56 name: "CRLF lines",
57 body: "@@ -1 +1 @@\n-old\r\n+new\r\n",
58 oldContents: "old\r\n",
59 newContents: "new\r\n",
60 },
61 {
62 name: "changing CRLF to LF",
63 body: "@@ -1 +1 @@\n-same\r\n+same\n",
64 oldContents: "same\r\n",
65 newContents: "same\n",
66 },
67 {
68 name: "context without a final newline",
69 body: "@@ -1,2 +1,2 @@\n-old\n+new\n tail\n\\ No newline at end of file\n",
70 oldContents: "old\ntail",
71 newContents: "new\ntail",
72 },
73 {
74 name: "CRLF context",
75 body: "@@ -1,2 +1,2 @@\n-old\r\n+new\r\n tail\r\n",
76 oldContents: "old\r\ntail\r\n",
77 newContents: "new\r\ntail\r\n",
78 },
79].map(({ body, ...testCase }) => ({
80 ...testCase,
81 patch: `diff --git a/src/tray.ts b/src/tray.ts\n--- a/src/tray.ts\n+++ b/src/tray.ts\n${body}`,
82 start: 1,
83 end: 2,
84}));
85​
86describe("fileDiffRows", () => {
87 it("numbers both sides through context, deletions and additions", () => {
88 expect(rows.map((row) => [row.kind, row.oldLine, row.newLine])).toEqual([
89 ["context", 10, 10],
90 ["deletion", 11, null],
91 ["addition", null, 11],
92 ["addition", null, 12],
93 ["context", 12, 13],
94 ["context", 13, 14],
95 ["context", 14, 15],
96 ["context", 40, 41],
97 ["deletion", 41, null],
98 ["context", 42, 42],
99 ]);
100 });
101});
102​
103describe("excerptRows", () => {
104 it("brings the replaced lines along with an excerpt that starts on an addition", () => {
105 const shown = excerptRows(rows, { side: "new", start: 11, end: 12 });
106 expect(shown.map((row) => row.kind)).toEqual(["deletion", "addition", "addition"]);
107 });
108​
109 it("selects old-side lines for a removal and stops at the hunk boundary", () => {
110 const shown = excerptRows(rows, { side: "old", start: 41, end: 41 });
111 expect(shown.map((row) => [row.kind, row.oldLine])).toEqual([["deletion", 41]]);
112 });
113});
114​
115describe("rowsToPatch", () => {
116 it.each([{ name: "LF slice", patch: PATCH, start: 10, end: 13 }, ...LINE_ENDING_CASES])(
117 "cuts a slice that parses back to the same lines and numbers: $name",
118 ({ patch, start, end }) => {
119 const original = parse(patch);
120 const shown = excerptRows(fileDiffRows(original), { side: "new", start, end });
121 const slice = parse(rowsToPatch("src/tray.ts", "src/tray.ts", shown));
122 expect(fileDiffRows(slice)).toEqual(shown);
123 expect(slice.hunks.map((hunk) => [hunk.noEOFCRDeletions, hunk.noEOFCRAdditions])).toEqual(
124 original.hunks
125 .filter((_, index) => shown.some((row) => row.hunk === index))
126 .map((hunk) => [hunk.noEOFCRDeletions, hunk.noEOFCRAdditions]),
127 );
128 },
129 );
130​
131 it("starts a hunk per source hunk so separate hunks stay separate", () => {
132 const shown = excerptRows(rows, { side: "old", start: 14, end: 42 });
133 const patch = rowsToPatch("src/tray.ts", "src/tray.ts", shown);
134 expect(patch.match(/^@@ /gm)).toHaveLength(2);
135 expect(patch).toContain("@@ -14,1 +15,1 @@");
136 expect(patch).toContain("@@ -40,3 +41,2 @@");
137 });
138});
139​
140describe("excerptExpansionOldContents", () => {
141 const newFile = Array.from({ length: 50 }, (_, index) => `line ${index + 1}\n`);
142 newFile.splice(
143 9,
144 6,
145 "const a = 1;\n",
146 'const size = "xs";\n',
147 'const variant = "tray";\n',
148 "const b = 2;\n",
149 "const c = 3;\n",
150 "const d = 4;\n",
151 );
152 newFile.splice(40, 2, "keep();\n", "tail();\n");
153 const newContents = newFile.join("");
154​
155 it.each([
156 {
157 name: "LF slice",
158 patch: PATCH,
159 start: 11,
160 end: 12,
161 newContents,
162 oldContents: [...newFile.slice(0, 10), "const size = pick();\n", ...newFile.slice(12)].join(
163 "",
164 ),
165 },
166 ...LINE_ENDING_CASES,
167 {
168 name: "partial replacement of an unterminated old EOF",
169 patch:
170 "diff --git a/src/tray.ts b/src/tray.ts\n--- a/src/tray.ts\n+++ b/src/tray.ts\n@@ -1 +1,3 @@\n-a\n\\ No newline at end of file\n+a\n+b\n+c\n",
171 start: 1,
172 end: 1,
173 newContents: "a\nb\nc\n",
174 oldContents: "a\nb\nc\n",
175 },
176 ])(
177 "puts back only the excerpt's lines and keeps both sides numbered as in the file: $name",
178 ({ patch, start, end, newContents, oldContents }) => {
179 const shown = excerptRows(fileDiffRows(parse(patch)), { side: "new", start, end });
180 expect(excerptExpansionOldContents(newContents, shown)).toBe(oldContents);
181 },
182 );
183​
184 it("does not try to expand an excerpt that spans hunks", () => {
185 const shown = excerptRows(rows, { side: "old", start: 14, end: 42 });
186 expect(excerptExpansionOldContents(newContents, shown)).toBeNull();
187 });
188});