1import { describe, expect, it } from "vite-plus/test";
2
3import { buildTurnDiffTree, summarizeTurnDiffStats } from "./turnDiffTree";
4
5describe("summarizeTurnDiffStats", () => {
6 it("sums only files with numeric additions/deletions", () => {
7 const stat = summarizeTurnDiffStats([
8 { path: "README.md", kind: "modified", additions: 3, deletions: 1 },
9 { path: "docs/notes.md", kind: "modified", additions: 0, deletions: 0 },
10 { path: "src/index.ts", kind: "modified", additions: 5, deletions: 2 },
11 ]);
12
13 expect(stat).toEqual({ additions: 8, deletions: 3 });
14 });
15});
16
17describe("buildTurnDiffTree", () => {
18 it("builds nested directory nodes with aggregated stats", () => {
19 const tree = buildTurnDiffTree([
20 { path: "src/index.ts", kind: "modified", additions: 2, deletions: 1 },
21 { path: "src/components/Button.tsx", kind: "modified", additions: 4, deletions: 2 },
22 { path: "README.md", kind: "modified", additions: 1, deletions: 0 },
23 ]);
24
25 expect(tree).toEqual([
26 {
27 kind: "directory",
28 name: "src",
29 path: "src",
30 stat: { additions: 6, deletions: 3 },
31 children: [
32 {
33 kind: "directory",
34 name: "components",
35 path: "src/components",
36 stat: { additions: 4, deletions: 2 },
37 children: [
38 {
39 kind: "file",
40 name: "Button.tsx",
41 path: "src/components/Button.tsx",
42 stat: { additions: 4, deletions: 2 },
43 },
44 ],
45 },
46 {
47 kind: "file",
48 name: "index.ts",
49 path: "src/index.ts",
50 stat: { additions: 2, deletions: 1 },
51 },
52 ],
53 },
54 {
55 kind: "file",
56 name: "README.md",
57 path: "README.md",
58 stat: { additions: 1, deletions: 0 },
59 },
60 ]);
61 });
62
63 it("keeps zero-valued file stats and includes only their numeric contribution", () => {
64 const tree = buildTurnDiffTree([
65 { path: "docs/notes.md", kind: "modified", additions: 0, deletions: 0 },
66 { path: "docs/todo.md", kind: "modified", additions: 1, deletions: 1 },
67 ]);
68
69 expect(tree).toEqual([
70 {
71 kind: "directory",
72 name: "docs",
73 path: "docs",
74 stat: { additions: 1, deletions: 1 },
75 children: [
76 {
77 kind: "file",
78 name: "notes.md",
79 path: "docs/notes.md",
80 stat: { additions: 0, deletions: 0 },
81 },
82 {
83 kind: "file",
84 name: "todo.md",
85 path: "docs/todo.md",
86 stat: { additions: 1, deletions: 1 },
87 },
88 ],
89 },
90 ]);
91 });
92
93 it("normalizes file paths with windows separators", () => {
94 const tree = buildTurnDiffTree([
95 { path: "apps\\web\\src\\index.ts", kind: "modified", additions: 2, deletions: 1 },
96 ]);
97
98 expect(tree).toEqual([
99 {
100 kind: "directory",
101 name: "apps/web/src",
102 path: "apps/web/src",
103 stat: { additions: 2, deletions: 1 },
104 children: [
105 {
106 kind: "file",
107 name: "index.ts",
108 path: "apps/web/src/index.ts",
109 stat: { additions: 2, deletions: 1 },
110 },
111 ],
112 },
113 ]);
114 });
115
116 it("compacts only single-directory chains and stops at branch points", () => {
117 const tree = buildTurnDiffTree([
118 { path: "apps/server/src/index.ts", kind: "modified", additions: 2, deletions: 1 },
119 { path: "apps/server/main.ts", kind: "modified", additions: 4, deletions: 0 },
120 ]);
121
122 expect(tree).toEqual([
123 {
124 kind: "directory",
125 name: "apps/server",
126 path: "apps/server",
127 stat: { additions: 6, deletions: 1 },
128 children: [
129 {
130 kind: "directory",
131 name: "src",
132 path: "apps/server/src",
133 stat: { additions: 2, deletions: 1 },
134 children: [
135 {
136 kind: "file",
137 name: "index.ts",
138 path: "apps/server/src/index.ts",
139 stat: { additions: 2, deletions: 1 },
140 },
141 ],
142 },
143 {
144 kind: "file",
145 name: "main.ts",
146 path: "apps/server/main.ts",
147 stat: { additions: 4, deletions: 0 },
148 },
149 ],
150 },
151 ]);
152 });
153
154 it("preserves leading/trailing whitespace in path segments", () => {
155 const tree = buildTurnDiffTree([
156 { path: "a/file.ts", kind: "modified", additions: 1, deletions: 0 },
157 { path: " a/file.ts", kind: "modified", additions: 2, deletions: 0 },
158 ]);
159
160 expect(tree).toHaveLength(2);
161 const directoryNodes = tree.filter(
162 (node): node is Extract<(typeof tree)[number], { kind: "directory" }> =>
163 node.kind === "directory",
164 );
165 expect(directoryNodes.map((node) => node.name).toSorted()).toEqual([" a", "a"]);
166 expect(directoryNodes.map((node) => node.path).toSorted()).toEqual([" a", "a"]);
167 });
168});