| 1 | import { RunId } from "@t3tools/contracts"; |
| 2 | import { renderToStaticMarkup } from "react-dom/server"; |
| 3 | import { describe, expect, it } from "vite-plus/test"; |
| 4 | |
| 5 | import { ChangedFilesCard, ChangedFilesTree } from "./ChangedFilesTree"; |
| 6 | |
| 7 | describe("ChangedFilesCard", () => { |
| 8 | it("keeps its compact header sticky while preserving singular labels", () => { |
| 9 | const markup = renderToStaticMarkup( |
| 10 | <ChangedFilesCard |
| 11 | runId={RunId.make("run-1")} |
| 12 | files={[{ path: "README.md", kind: "modified", additions: 2, deletions: 1 }]} |
| 13 | allDirectoriesExpanded |
| 14 | resolvedTheme="light" |
| 15 | onToggleAllDirectories={() => {}} |
| 16 | onOpenTurnDiff={() => {}} |
| 17 | />, |
| 18 | ); |
| 19 | |
| 20 | expect(markup).toContain('data-changed-files-state="tree"'); |
| 21 | expect(markup).toContain('aria-label="Open diff"'); |
| 22 | expect(markup).toContain('role="group" aria-label="2 additions, 1 deletions"'); |
| 23 | expect(markup).toContain("1 changed file"); |
| 24 | expect(markup).not.toContain("1 changed files"); |
| 25 | }); |
| 26 | |
| 27 | it("shows collapsed folders and root files together", () => { |
| 28 | const markup = renderToStaticMarkup( |
| 29 | <ChangedFilesCard |
| 30 | runId={RunId.make("run-1")} |
| 31 | files={[ |
| 32 | { path: "apps/web/src/App.tsx", kind: "modified", additions: 120, deletions: 20 }, |
| 33 | { path: "apps/web/src/App.test.tsx", kind: "modified", additions: 30, deletions: 2 }, |
| 34 | { |
| 35 | path: "packages/shared/src/git.ts", |
| 36 | kind: "modified", |
| 37 | additions: 15, |
| 38 | deletions: 4, |
| 39 | }, |
| 40 | { path: "README.md", kind: "modified", additions: 3, deletions: 0 }, |
| 41 | ]} |
| 42 | allDirectoriesExpanded={false} |
| 43 | resolvedTheme="light" |
| 44 | onToggleAllDirectories={() => {}} |
| 45 | onOpenTurnDiff={() => {}} |
| 46 | />, |
| 47 | ); |
| 48 | |
| 49 | expect(markup).toContain('data-changed-files-state="tree"'); |
| 50 | expect(markup).toContain('aria-expanded="false"'); |
| 51 | expect(markup).toContain("apps/web/src"); |
| 52 | expect(markup).not.toContain("App.tsx"); |
| 53 | expect(markup).toContain("packages/shared/src"); |
| 54 | expect(markup).not.toContain("git.ts"); |
| 55 | expect(markup).toContain("README.md"); |
| 56 | expect(markup).not.toContain("Show all"); |
| 57 | expect(markup).not.toContain("App.test.tsx"); |
| 58 | }); |
| 59 | |
| 60 | it("keeps the folder tree visible when folders are collapsed", () => { |
| 61 | const markup = renderToStaticMarkup( |
| 62 | <ChangedFilesCard |
| 63 | runId={RunId.make("run-1")} |
| 64 | files={[{ path: "apps/web/src/App.tsx", kind: "modified", additions: 120, deletions: 20 }]} |
| 65 | allDirectoriesExpanded={false} |
| 66 | resolvedTheme="light" |
| 67 | onToggleAllDirectories={() => {}} |
| 68 | onOpenTurnDiff={() => {}} |
| 69 | />, |
| 70 | ); |
| 71 | |
| 72 | expect(markup).toContain('data-changed-files-state="tree"'); |
| 73 | expect(markup).toContain("1 changed file"); |
| 74 | expect(markup).toContain("apps/web/src"); |
| 75 | expect(markup).not.toContain("Show all"); |
| 76 | expect(markup).not.toContain("App.tsx"); |
| 77 | }); |
| 78 | }); |
| 79 | |
| 80 | describe("ChangedFilesTree", () => { |
| 81 | it.each([ |
| 82 | { |
| 83 | name: "a compacted single-chain directory", |
| 84 | files: [ |
| 85 | { path: "apps/web/src/index.ts", kind: "modified", additions: 2, deletions: 1 }, |
| 86 | { path: "apps/web/src/main.ts", kind: "modified", additions: 3, deletions: 0 }, |
| 87 | ], |
| 88 | visibleLabels: ["apps/web/src"], |
| 89 | hiddenLabels: ["index.ts", "main.ts"], |
| 90 | }, |
| 91 | { |
| 92 | name: "a branch point after a compacted prefix", |
| 93 | files: [ |
| 94 | { |
| 95 | path: "apps/server/src/git/Layers/GitCore.ts", |
| 96 | kind: "modified", |
| 97 | additions: 4, |
| 98 | deletions: 3, |
| 99 | }, |
| 100 | { |
| 101 | path: "apps/server/src/provider/Layers/CodexAdapter.ts", |
| 102 | kind: "modified", |
| 103 | additions: 7, |
| 104 | deletions: 2, |
| 105 | }, |
| 106 | ], |
| 107 | visibleLabels: ["apps/server/src"], |
| 108 | hiddenLabels: ["git", "provider", "GitCore.ts", "CodexAdapter.ts"], |
| 109 | }, |
| 110 | { |
| 111 | name: "mixed root files and nested compacted directories", |
| 112 | files: [ |
| 113 | { path: "README.md", kind: "modified", additions: 1, deletions: 0 }, |
| 114 | { path: "packages/shared/src/git.ts", kind: "modified", additions: 8, deletions: 2 }, |
| 115 | { |
| 116 | path: "packages/contracts/src/orchestration.ts", |
| 117 | kind: "modified", |
| 118 | additions: 13, |
| 119 | deletions: 3, |
| 120 | }, |
| 121 | ], |
| 122 | visibleLabels: ["README.md", "packages"], |
| 123 | hiddenLabels: ["shared/src", "contracts/src", "git.ts", "orchestration.ts"], |
| 124 | }, |
| 125 | ])( |
| 126 | "renders $name collapsed on the first render when collapse-all is active", |
| 127 | ({ files, visibleLabels, hiddenLabels }) => { |
| 128 | const markup = renderToStaticMarkup( |
| 129 | <ChangedFilesTree |
| 130 | runId={RunId.make("turn-1")} |
| 131 | files={files} |
| 132 | allDirectoriesExpanded={false} |
| 133 | resolvedTheme="light" |
| 134 | onOpenTurnDiff={() => {}} |
| 135 | />, |
| 136 | ); |
| 137 | |
| 138 | for (const label of visibleLabels) { |
| 139 | expect(markup).toContain(label); |
| 140 | } |
| 141 | for (const label of hiddenLabels) { |
| 142 | expect(markup).not.toContain(label); |
| 143 | } |
| 144 | }, |
| 145 | ); |
| 146 | |
| 147 | it.each([ |
| 148 | { |
| 149 | name: "a compacted single-chain directory", |
| 150 | files: [ |
| 151 | { path: "apps/web/src/index.ts", kind: "modified", additions: 2, deletions: 1 }, |
| 152 | { path: "apps/web/src/main.ts", kind: "modified", additions: 3, deletions: 0 }, |
| 153 | ], |
| 154 | visibleLabels: ["apps/web/src", "index.ts", "main.ts"], |
| 155 | }, |
| 156 | { |
| 157 | name: "a branch point after a compacted prefix", |
| 158 | files: [ |
| 159 | { |
| 160 | path: "apps/server/src/git/Layers/GitCore.ts", |
| 161 | kind: "modified", |
| 162 | additions: 4, |
| 163 | deletions: 3, |
| 164 | }, |
| 165 | { |
| 166 | path: "apps/server/src/provider/Layers/CodexAdapter.ts", |
| 167 | kind: "modified", |
| 168 | additions: 7, |
| 169 | deletions: 2, |
| 170 | }, |
| 171 | ], |
| 172 | visibleLabels: [ |
| 173 | "apps/server/src", |
| 174 | "git/Layers", |
| 175 | "provider/Layers", |
| 176 | "GitCore.ts", |
| 177 | "CodexAdapter.ts", |
| 178 | ], |
| 179 | }, |
| 180 | { |
| 181 | name: "mixed root files and nested compacted directories", |
| 182 | files: [ |
| 183 | { path: "README.md", kind: "modified", additions: 1, deletions: 0 }, |
| 184 | { path: "packages/shared/src/git.ts", kind: "modified", additions: 8, deletions: 2 }, |
| 185 | { |
| 186 | path: "packages/contracts/src/orchestration.ts", |
| 187 | kind: "modified", |
| 188 | additions: 13, |
| 189 | deletions: 3, |
| 190 | }, |
| 191 | ], |
| 192 | visibleLabels: [ |
| 193 | "README.md", |
| 194 | "packages", |
| 195 | "shared/src", |
| 196 | "contracts/src", |
| 197 | "git.ts", |
| 198 | "orchestration.ts", |
| 199 | ], |
| 200 | }, |
| 201 | ])( |
| 202 | "renders $name expanded on the first render when expand-all is active", |
| 203 | ({ files, visibleLabels }) => { |
| 204 | const markup = renderToStaticMarkup( |
| 205 | <ChangedFilesTree |
| 206 | runId={RunId.make("turn-1")} |
| 207 | files={files} |
| 208 | allDirectoriesExpanded |
| 209 | resolvedTheme="light" |
| 210 | onOpenTurnDiff={() => {}} |
| 211 | />, |
| 212 | ); |
| 213 | |
| 214 | for (const label of visibleLabels) { |
| 215 | expect(markup).toContain(label); |
| 216 | } |
| 217 | }, |
| 218 | ); |
| 219 | }); |