1import type { FileDiffMetadata } from "@pierre/diffs";
2import { EnvironmentId } from "@t3tools/contracts";
3import { AsyncResult } from "effect/reactivity";
4import { describe, expect, it } from "vite-plus/test";
5
6import { createGitDiffFileContentsLoader } from "./diffFileContents";
7import { getRenderablePatch } from "./diffRendering";
8
9const originalLines = Array.from({ length: 40 }, (_, index) => `line ${index + 1}`);
10const changedLines = originalLines.map((line, index) => (index === 29 ? "line 30 changed" : line));
11
12function toContents(lines: ReadonlyArray<string>) {
13 return `${lines.join("\n")}\n`;
14}
15
16function parsePartialFileDiff(patch: string): FileDiffMetadata {
17 const renderable = getRenderablePatch(patch, "diff-panel:light", {
18 compactPartialHunkOffsets: true,
19 });
20 if (renderable?.kind !== "files") throw new Error("Expected a parsed patch.");
21 return renderable.files[0]!;
22}
23
24function partialFileDiff(): FileDiffMetadata {
25 const patch = [
26 "diff --git a/src/file01.ts b/src/file01.ts",
27 "index 1111111..2222222 100644",
28 "--- a/src/file01.ts",
29 "+++ b/src/file01.ts",
30 "@@ -27,7 +27,7 @@",
31 " line 27",
32 " line 28",
33 " line 29",
34 "-line 30",
35 "+line 30 changed",
36 " line 31",
37 " line 32",
38 " line 33",
39 "",
40 ].join("\n");
41 return parsePartialFileDiff(patch);
42}
43
44function loaderReturning(oldContents: string, newContents: string) {
45 return createGitDiffFileContentsLoader(
46 async () => AsyncResult.success({ oldContents, newContents }),
47 {
48 environmentId: EnvironmentId.make("environment-diff-contents-test"),
49 cwd: "/repo",
50 sourceKind: "working-tree",
51 baseRef: null,
52 headRef: null,
53 cacheKey: "diff-hash",
54 },
55 );
56}
57
58describe("createGitDiffFileContentsLoader", () => {
59 it("loads contents that still reproduce the patch", async () => {
60 const loaded = await loaderReturning(
61 toContents(originalLines),
62 toContents(changedLines),
63 )(partialFileDiff());
64
65 expect(loaded.newFile.contents).toBe(toContents(changedLines));
66 });
67
68 it.each([
69 {
70 name: "end before the patch's hunk",
71 oldLines: originalLines.slice(0, 30),
72 newLines: changedLines.slice(0, 30),
73 },
74 {
75 name: "have unchanged tails that no longer match between sides",
76 oldLines: originalLines,
77 newLines: [...changedLines, "line 41 appended"],
78 },
79 {
80 name: "changed hunk lines without moving them",
81 oldLines: originalLines,
82 newLines: changedLines.map((line, index) => (index === 30 ? "edited since" : line)),
83 },
84 ])("rejects contents that $name", async ({ oldLines, newLines }) => {
85 const loader = loaderReturning(toContents(oldLines), toContents(newLines));
86
87 await expect(loader(partialFileDiff())).rejects.toThrow("changed since the diff was loaded");
88 });
89
90 it("loads a whitespace-ignored patch whose context shows one side's whitespace", async () => {
91 const patch = [
92 "diff --git a/src/file01.ts b/src/file01.ts",
93 "index 1111111..2222222 100644",
94 "--- a/src/file01.ts",
95 "+++ b/src/file01.ts",
96 "@@ -2,4 +2,4 @@ a",
97 " b",
98 " c",
99 " d",
100 "-e",
101 "\\ No newline at end of file",
102 "+E",
103 "\\ No newline at end of file",
104 "",
105 ].join("\n");
106 const loaded = await loaderReturning(
107 "a\nb\n c\nd\ne",
108 "a\nb\nc\nd\nE",
109 )(parsePartialFileDiff(patch));
110
111 expect(loaded.oldFile?.contents).toBe("a\nb\n c\nd\ne");
112 });
113});