gitforest 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +24 -4
  3. package/src/components/onboarding/DirectoriesStep.tsx +19 -19
  4. package/src/github/auth.ts +3 -3
  5. package/src/utils/debug.ts +4 -4
  6. package/.bunignore +0 -7
  7. package/.github/workflows/ci.yml +0 -73
  8. package/CLAUDE.md +0 -111
  9. package/CONTRIBUTING.md +0 -145
  10. package/bun.lock +0 -267
  11. package/bunfig.toml +0 -15
  12. package/cli +0 -0
  13. package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
  14. package/docs/ai/VERIFICATION_REPORT.md +0 -87
  15. package/docs/ai/architecture.md +0 -169
  16. package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
  17. package/docs/ai/checks/check-2025-12-02.md +0 -55
  18. package/docs/ai/checks/test-verification-report.md +0 -85
  19. package/docs/ai/implementation-guide.md +0 -776
  20. package/docs/ai/research/gitty-codebase-analysis.md +0 -221
  21. package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
  22. package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
  23. package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
  24. package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
  25. package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
  26. package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
  27. package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
  28. package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
  29. package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
  30. package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
  31. package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
  32. package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
  33. package/docs/ai/tickets/TASK-sitrep.md +0 -28
  34. package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
  35. package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
  36. package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
  37. package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
  38. package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
  39. package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
  40. package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
  41. package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
  42. package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
  43. package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
  44. package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
  45. package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
  46. package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
  47. package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
  48. package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
  49. package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
  50. package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
  51. package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
  52. package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
  53. package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
  54. package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
  55. package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
  56. package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
  57. package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
  58. package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
  59. package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
  60. package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
  61. package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
  62. package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
  63. package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
  64. package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
  65. package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
  66. package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
  67. package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
  68. package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
  69. package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
  70. package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
  71. package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
  72. package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
  73. package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
  74. package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
  75. package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
  76. package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
  77. package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
  78. package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
  79. package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
  80. package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
  81. package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
  82. package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
  83. package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
  84. package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
  85. package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
  86. package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
  87. package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
  88. package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
  89. package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
  90. package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
  91. package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
  92. package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
  93. package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
  94. package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
  95. package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
  96. package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
  97. package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
  98. package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
  99. package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
  100. package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
  101. package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
  102. package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
  103. package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
  104. package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
  105. package/docs/ai/tkt-001-fix-database-error.md +0 -217
  106. package/docs/ai/ui-enhancement-plan.md +0 -562
  107. package/test/integration/app.isolated.tsx +0 -240
  108. package/test/integration/cli-commands.test.ts +0 -287
  109. package/test/integration/cli-validation.test.ts +0 -264
  110. package/test/integration/git-operations.test.ts +0 -218
  111. package/test/integration/scanner.test.ts +0 -228
  112. package/test/preload.ts +0 -18
  113. package/test/unit/cli/commands.test.ts +0 -13
  114. package/test/unit/cli/formatters.test.ts +0 -1116
  115. package/test/unit/cli/github-commands.test.ts +0 -12
  116. package/test/unit/components/CloneDialog.test.tsx +0 -240
  117. package/test/unit/components/ColumnHeader.test.tsx +0 -128
  118. package/test/unit/components/CommandPalette.test.tsx +0 -355
  119. package/test/unit/components/ConfirmDialog.test.tsx +0 -111
  120. package/test/unit/components/ErrorBoundary.test.tsx +0 -139
  121. package/test/unit/components/FilterBar.test.tsx +0 -43
  122. package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
  123. package/test/unit/components/HelpOverlay.test.tsx +0 -90
  124. package/test/unit/components/Layout.test.tsx +0 -328
  125. package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
  126. package/test/unit/components/ProgressBar.test.tsx +0 -138
  127. package/test/unit/components/ProjectItem.test.tsx +0 -182
  128. package/test/unit/components/ProjectList.test.tsx +0 -311
  129. package/test/unit/components/RepoDetailModal.test.tsx +0 -445
  130. package/test/unit/components/StatusBar.test.tsx +0 -112
  131. package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
  132. package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
  133. package/test/unit/components/test-utils.tsx +0 -63
  134. package/test/unit/config/loader.test.ts +0 -692
  135. package/test/unit/db/database.test.ts +0 -978
  136. package/test/unit/db/index.test.ts +0 -314
  137. package/test/unit/fixtures/setup.ts +0 -186
  138. package/test/unit/git/commands-untested.test.ts +0 -205
  139. package/test/unit/git/commands.test.ts +0 -269
  140. package/test/unit/git/operations.test.ts +0 -322
  141. package/test/unit/git/status.test.ts +0 -219
  142. package/test/unit/github/auth.test.ts +0 -317
  143. package/test/unit/github/cache.test.ts +0 -1028
  144. package/test/unit/github/cli.test.ts +0 -135
  145. package/test/unit/github/unified.test.ts +0 -1201
  146. package/test/unit/graceful-shutdown.test.ts +0 -83
  147. package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
  148. package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
  149. package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
  150. package/test/unit/hooks/useProjects.test.tsx +0 -186
  151. package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
  152. package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
  153. package/test/unit/mocks/config.ts +0 -109
  154. package/test/unit/mocks/git-service.ts +0 -274
  155. package/test/unit/mocks/github-service.ts +0 -250
  156. package/test/unit/mocks/index.ts +0 -72
  157. package/test/unit/mocks/project.ts +0 -148
  158. package/test/unit/mocks/state-mocks.ts +0 -187
  159. package/test/unit/mocks/unified.ts +0 -169
  160. package/test/unit/operations/batch.test.ts +0 -216
  161. package/test/unit/operations/commands.test.ts +0 -550
  162. package/test/unit/scanner/errors.test.ts +0 -297
  163. package/test/unit/scanner/index.test.ts +0 -1011
  164. package/test/unit/scanner/markers.test.ts +0 -150
  165. package/test/unit/scanner/submodules.test.ts +0 -99
  166. package/test/unit/services/git-errors.test.ts +0 -190
  167. package/test/unit/services/git.test.ts +0 -442
  168. package/test/unit/services/github-errors.test.ts +0 -293
  169. package/test/unit/services/github.test.ts +0 -200
  170. package/test/unit/state/actions.test.ts +0 -217
  171. package/test/unit/state/reducer.test.ts +0 -745
  172. package/test/unit/state/store.test.tsx +0 -711
  173. package/test/unit/types/commands.test.ts +0 -220
  174. package/test/unit/types/schema.test.ts +0 -179
  175. package/test/unit/utils/array.test.ts +0 -73
  176. package/test/unit/utils/debug.test.ts +0 -23
  177. package/test/unit/utils/errors.test.ts +0 -295
  178. package/test/unit/utils/markdown.test.ts +0 -163
  179. package/test/unit/utils/project-utils.test.ts +0 -756
  180. package/test/unit/utils/rate-limiter.test.ts +0 -256
  181. package/test/unit/utils/retry.test.ts +0 -165
  182. package/test/unit/utils/strip-ansi.ts +0 -13
  183. package/test/unit/utils/timeout.test.ts +0 -93
  184. package/tsconfig.json +0 -29
@@ -1,355 +0,0 @@
1
- import { describe, test, expect, vi } from "bun:test";
2
- import { render } from "ink-testing-library";
3
- import { CommandPalette } from "../../../src/components/CommandPalette.tsx";
4
- import type { CommandPaletteProps } from "../../../src/components/CommandPalette.tsx";
5
- import type { CommandConfig, UnifiedRepo } from "../../../src/types/index.ts";
6
-
7
- describe("CommandPalette", () => {
8
- const mockRepos: UnifiedRepo[] = [
9
- {
10
- id: "repo1",
11
- name: "test-repo-1",
12
- source: "local",
13
- localPath: "/path/to/repo1",
14
- isCloned: true,
15
- isOnGitHub: false,
16
- local: {
17
- id: "local-repo1",
18
- name: "test-repo-1",
19
- path: "/path/to/repo1",
20
- type: "git",
21
- projectMarker: "package.json",
22
- status: null,
23
- submodule: null,
24
- lastScanned: new Date(),
25
- lastModified: null,
26
- },
27
- github: null,
28
- },
29
- {
30
- id: "repo2",
31
- name: "test-repo-2",
32
- source: "github",
33
- localPath: null,
34
- isCloned: false,
35
- isOnGitHub: true,
36
- local: null,
37
- github: {
38
- name: "test-repo-2",
39
- fullName: "user/test-repo-2",
40
- owner: "user",
41
- description: "Test repo 2",
42
- htmlUrl: "https://github.com/user/test-repo-2",
43
- sshUrl: "git@github.com:user/test-repo-2.git",
44
- cloneUrl: "https://github.com/user/test-repo-2.git",
45
- isPrivate: false,
46
- isArchived: false,
47
- isFork: false,
48
- pushedAt: new Date(),
49
- updatedAt: new Date(),
50
- defaultBranch: "main",
51
- language: "TypeScript",
52
- size: 512000,
53
- },
54
- },
55
- ];
56
-
57
- const defaultProps: CommandPaletteProps = {
58
- commands: [],
59
- selectedRepos: mockRepos,
60
- onClose: vi.fn(),
61
- };
62
-
63
- test("renders with empty commands", () => {
64
- const { lastFrame } = render(<CommandPalette {...defaultProps} />);
65
- const output = lastFrame();
66
-
67
- expect(output).toContain("Command Palette");
68
- expect(output).toContain("No custom commands configured.");
69
- expect(output).toContain("Add commands to your config file:");
70
- expect(output).toContain("commands:");
71
- expect(output).toContain("- name: \"Open in Editor\"");
72
- expect(output).toContain("key: \"e\"");
73
- expect(output).toContain("command: \"code .\"");
74
- expect(output).toContain("[Esc] Close");
75
- });
76
-
77
- test("renders command list", () => {
78
- const commands: CommandConfig[] = [
79
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
80
- { key: "t", name: "Run Tests", command: "npm test", confirm: true, background: false },
81
- { key: "b", name: "Build", command: "npm run build", background: true, confirm: false },
82
- ];
83
-
84
- const { lastFrame } = render(
85
- <CommandPalette {...defaultProps} commands={commands} />
86
- );
87
- const output = lastFrame();
88
-
89
- expect(output).toContain("Command Palette");
90
- expect(output).toContain("Available Commands");
91
- expect(output).toContain("[e]");
92
- expect(output).toContain("Open in Editor");
93
- expect(output).toContain("code .");
94
- expect(output).toContain("[t]");
95
- expect(output).toContain("Run Tests");
96
- expect(output).toContain("npm test");
97
- expect(output).toContain("(confirm)");
98
- expect(output).toContain("[b]");
99
- expect(output).toContain("Build");
100
- expect(output).toContain("npm run build");
101
- expect(output).toContain("(bg)");
102
- });
103
-
104
- test("shows target repositories", () => {
105
- const commands: CommandConfig[] = [
106
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
107
- ];
108
-
109
- const { lastFrame } = render(
110
- <CommandPalette {...defaultProps} commands={commands} />
111
- );
112
- const output = lastFrame();
113
-
114
- expect(output).toContain("Target:");
115
- expect(output).toContain("test-repo-1, test-repo-2");
116
- });
117
-
118
- test("shows path for local repositories", () => {
119
- const commands: CommandConfig[] = [
120
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
121
- ];
122
-
123
- const { lastFrame } = render(
124
- <CommandPalette {...defaultProps} commands={commands} />
125
- );
126
- const output = lastFrame();
127
-
128
- expect(output).toContain("Path:");
129
- expect(output).toContain("/path/to/repo1");
130
- });
131
-
132
- test("limits displayed repo names to 3", () => {
133
- const manyRepos: UnifiedRepo[] = [
134
- ...mockRepos,
135
- {
136
- id: "repo3",
137
- name: "test-repo-3",
138
- source: "local" as const,
139
- localPath: "/path/to/repo3",
140
- isCloned: true,
141
- isOnGitHub: false,
142
- local: {
143
- id: "local-repo3",
144
- name: "test-repo-3",
145
- path: "/path/to/repo3",
146
- type: "git" as const,
147
- projectMarker: null,
148
- status: null,
149
- submodule: null,
150
- lastScanned: new Date(),
151
- lastModified: null,
152
- },
153
- github: null,
154
- },
155
- {
156
- id: "repo4",
157
- name: "test-repo-4",
158
- source: "local" as const,
159
- localPath: "/path/to/repo4",
160
- isCloned: true,
161
- isOnGitHub: false,
162
- local: {
163
- id: "local-repo4",
164
- name: "test-repo-4",
165
- path: "/path/to/repo4",
166
- type: "git" as const,
167
- projectMarker: null,
168
- status: null,
169
- submodule: null,
170
- lastScanned: new Date(),
171
- lastModified: null,
172
- },
173
- github: null,
174
- },
175
- ];
176
-
177
- const commands: CommandConfig[] = [
178
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
179
- ];
180
-
181
- const { lastFrame } = render(
182
- <CommandPalette {...defaultProps} selectedRepos={manyRepos} commands={commands} />
183
- );
184
- const output = lastFrame();
185
-
186
- expect(output).toContain("test-repo-1, test-repo-2, test-repo-3");
187
- expect(output).toContain("+1 more");
188
- });
189
-
190
- test("shows single repository", () => {
191
- const commands: CommandConfig[] = [
192
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
193
- ];
194
-
195
- const { lastFrame } = render(
196
- <CommandPalette {...defaultProps} selectedRepos={[mockRepos[0]!]} commands={commands} />
197
- );
198
- const output = lastFrame();
199
-
200
- expect(output).toContain("Target:");
201
- expect(output).toContain("test-repo-1");
202
- expect(output).not.toContain("more");
203
- });
204
-
205
- test("shows footer instructions", () => {
206
- const commands: CommandConfig[] = [
207
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
208
- ];
209
-
210
- const { lastFrame } = render(
211
- <CommandPalette {...defaultProps} commands={commands} />
212
- );
213
- const output = lastFrame();
214
-
215
- expect(output).toContain("[key] Execute");
216
- expect(output).toContain("[Esc] Close");
217
- });
218
-
219
- test("handles close callback", () => {
220
- const onClose = vi.fn();
221
- // Note: onClose is handled by parent component through keybindings
222
- // This test just ensures the prop is passed correctly
223
- const { lastFrame } = render(
224
- <CommandPalette {...defaultProps} onClose={onClose} />
225
- );
226
-
227
- const output = lastFrame();
228
- expect(output).toContain("Command Palette");
229
- });
230
-
231
- test("renders with GitHub-only repository", () => {
232
- const githubOnlyRepo: UnifiedRepo = {
233
- id: "github-only-repo",
234
- name: "github-only-repo",
235
- source: "github",
236
- localPath: null,
237
- isCloned: false,
238
- isOnGitHub: true,
239
- local: null,
240
- github: {
241
- name: "github-only-repo",
242
- fullName: "user/github-only-repo",
243
- owner: "user",
244
- description: "GitHub only repo",
245
- htmlUrl: "https://github.com/user/github-only-repo",
246
- sshUrl: "git@github.com:user/github-only-repo.git",
247
- cloneUrl: "https://github.com/user/github-only-repo.git",
248
- isPrivate: false,
249
- isArchived: false,
250
- isFork: false,
251
- pushedAt: new Date(),
252
- updatedAt: new Date(),
253
- defaultBranch: "main",
254
- language: "TypeScript",
255
- size: 512000,
256
- },
257
- };
258
-
259
- const commands: CommandConfig[] = [
260
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
261
- ];
262
-
263
- const { lastFrame } = render(
264
- <CommandPalette {...defaultProps} selectedRepos={[githubOnlyRepo]} commands={commands} />
265
- );
266
- const output = lastFrame();
267
-
268
- expect(output).toContain("Target:");
269
- expect(output).toContain("github-only-repo");
270
- expect(output).not.toContain("Path:"); // No path for GitHub-only repos
271
- });
272
-
273
- test("renders with many commands", () => {
274
- const manyCommands: CommandConfig[] = Array.from({ length: 10 }, (_, i) => ({
275
- key: `${i}`,
276
- name: `Command ${i}`,
277
- command: `cmd${i}`,
278
- confirm: false,
279
- background: false,
280
- }));
281
-
282
- const { lastFrame } = render(
283
- <CommandPalette {...defaultProps} commands={manyCommands} />
284
- );
285
- const output = lastFrame();
286
-
287
- expect(output).toContain("Available Commands");
288
- // All commands should be rendered
289
- for (let i = 0; i < 10; i++) {
290
- expect(output).toContain(`[${i}]`);
291
- expect(output).toContain(`Command ${i}`);
292
- expect(output).toContain(`cmd${i}`);
293
- }
294
- });
295
-
296
- test("renders with command containing special characters", () => {
297
- const commands: CommandConfig[] = [
298
- {
299
- key: "r",
300
- name: "Run with args",
301
- command: 'npm run test -- --coverage --watch',
302
- confirm: false,
303
- background: false
304
- },
305
- ];
306
-
307
- const { lastFrame } = render(
308
- <CommandPalette {...defaultProps} commands={commands} />
309
- );
310
- const output = lastFrame();
311
-
312
- expect(output).toContain("[r]");
313
- expect(output).toContain("Run with args");
314
- expect(output).toContain("npm run test -- --coverage --watch");
315
- });
316
-
317
- test("renders with empty repository list", () => {
318
- const commands: CommandConfig[] = [
319
- { key: "e", name: "Open in Editor", command: "code .", confirm: false, background: false },
320
- ];
321
-
322
- const { lastFrame } = render(
323
- <CommandPalette {...defaultProps} selectedRepos={[]} commands={commands} />
324
- );
325
- const output = lastFrame();
326
-
327
- expect(output).toContain("Command Palette");
328
- expect(output).toContain("Target:");
329
- expect(output).toContain(""); // Empty target
330
- });
331
-
332
- test("maintains command order", () => {
333
- const commands: CommandConfig[] = [
334
- { key: "z", name: "Z Command", command: "z", confirm: false, background: false },
335
- { key: "a", name: "A Command", command: "a", confirm: false, background: false },
336
- { key: "m", name: "M Command", command: "m", confirm: false, background: false },
337
- ];
338
-
339
- const { lastFrame } = render(
340
- <CommandPalette {...defaultProps} commands={commands} />
341
- );
342
- const output = lastFrame();
343
-
344
- // Commands should appear in the order they were provided
345
- const zIndex = output?.indexOf("Z Command") ?? -1;
346
- const aIndex = output?.indexOf("A Command") ?? -1;
347
- const mIndex = output?.indexOf("M Command") ?? -1;
348
-
349
- expect(zIndex).toBeGreaterThan(-1);
350
- expect(aIndex).toBeGreaterThan(-1);
351
- expect(mIndex).toBeGreaterThan(-1);
352
- expect(zIndex).toBeLessThan(aIndex);
353
- expect(aIndex).toBeLessThan(mIndex);
354
- });
355
- });
@@ -1,111 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { render } from "ink-testing-library";
3
- import { ConfirmDialog } from "../../../src/components/ConfirmDialog.tsx";
4
-
5
- describe("ConfirmDialog", () => {
6
- const defaultProps = {
7
- title: "Test Dialog",
8
- message: "Are you sure?",
9
- items: ["item1", "item2", "item3"],
10
- onConfirm: () => {},
11
- onCancel: () => {},
12
- };
13
-
14
- test("renders with basic props", () => {
15
- const { lastFrame } = render(<ConfirmDialog {...defaultProps} />);
16
- const output = lastFrame();
17
-
18
- expect(output).toContain("Test Dialog");
19
- expect(output).toContain("Are you sure?");
20
- expect(output).toContain("item1");
21
- expect(output).toContain("item2");
22
- expect(output).toContain("item3");
23
- expect(output).toContain("y to confirm");
24
- expect(output).toContain("n to cancel");
25
- });
26
-
27
- test("renders with different operations", () => {
28
- const operations = ["setup", "create", "archive"] as const;
29
-
30
- operations.forEach((operation) => {
31
- const { lastFrame } = render(
32
- <ConfirmDialog
33
- {...defaultProps}
34
- title={`${operation} operation`}
35
- message={`This will ${operation} repositories`}
36
- />
37
- );
38
-
39
- const output = lastFrame();
40
- expect(output).toContain(`${operation} operation`);
41
- expect(output).toContain(`This will ${operation} repositories`);
42
- });
43
- });
44
-
45
- test("shows visibility toggle when enabled", () => {
46
- const { lastFrame } = render(
47
- <ConfirmDialog {...defaultProps} showVisibilityToggle={true} />
48
- );
49
- const output = lastFrame();
50
-
51
- expect(output).toContain("Visibility:");
52
- expect(output).toContain("[●] Privat"); // Truncated due to width
53
- expect(output).toContain("[ ] Publi"); // Truncated due to width
54
- expect(output).toContain("(v to"); // Truncated due to width
55
- });
56
-
57
- test("shows default visibility as public", () => {
58
- const { lastFrame } = render(
59
- <ConfirmDialog
60
- {...defaultProps}
61
- showVisibilityToggle={true}
62
- defaultVisibility="public"
63
- />
64
- );
65
- const output = lastFrame();
66
-
67
- expect(output).toContain("[ ] Privat"); // Truncated due to width
68
- expect(output).toContain("[●] Publi"); // Truncated due to width
69
- });
70
-
71
- test("limits item list to 5 items", () => {
72
- const manyItems = Array.from({ length: 10 }, (_, i) => `item${i + 1}`);
73
- const { lastFrame } = render(
74
- <ConfirmDialog {...defaultProps} items={manyItems} />
75
- );
76
- const output = lastFrame();
77
-
78
- expect(output).toContain("item1");
79
- expect(output).toContain("item5");
80
- expect(output).not.toContain("item6");
81
- expect(output).toContain("...and 5 more");
82
- });
83
-
84
- test("renders with empty items array", () => {
85
- const { lastFrame } = render(
86
- <ConfirmDialog {...defaultProps} items={[]} />
87
- );
88
- const output = lastFrame();
89
-
90
- expect(output).toContain("Test Dialog");
91
- expect(output).toContain("Are you sure?");
92
- expect(output).not.toContain("•");
93
- });
94
-
95
- test("renders with custom props", () => {
96
- const customProps = {
97
- ...defaultProps,
98
- title: "Custom Title",
99
- message: "Custom message",
100
- items: ["custom1", "custom2"],
101
- };
102
-
103
- const { lastFrame } = render(<ConfirmDialog {...customProps} />);
104
- const output = lastFrame();
105
-
106
- expect(output).toContain("Custom Title");
107
- expect(output).toContain("Custom message");
108
- expect(output).toContain("custom1");
109
- expect(output).toContain("custom2");
110
- });
111
- });
@@ -1,139 +0,0 @@
1
- /**
2
- * Tests for ErrorBoundary component
3
- */
4
-
5
- import { describe, test, expect } from "bun:test";
6
- import { render } from "ink-testing-library";
7
- import { Text } from "ink";
8
- import { ErrorBoundary } from "../../../src/components/ErrorBoundary.tsx";
9
-
10
- describe("ErrorBoundary", () => {
11
- test("renders children when no error occurs", () => {
12
- const { lastFrame } = render(
13
- <ErrorBoundary>
14
- <Text>Test content</Text>
15
- </ErrorBoundary>
16
- );
17
-
18
- expect(lastFrame()).toContain("Test content");
19
- });
20
-
21
- test("displays error UI when child throws", () => {
22
- const ThrowingComponent = () => {
23
- throw new Error("Test error");
24
- };
25
-
26
- const { lastFrame } = render(
27
- <ErrorBoundary>
28
- <ThrowingComponent />
29
- </ErrorBoundary>
30
- );
31
-
32
- // Error boundaries render the error UI immediately
33
- const frame = lastFrame();
34
- expect(frame).toContain("⚠️ An Error Occurred");
35
- expect(frame).toContain("Test error");
36
- expect(frame).toContain("Press any key to retry");
37
- });
38
-
39
- test("shows error message in yellow", () => {
40
- const ThrowingComponent = () => {
41
- throw new Error("Custom error message");
42
- };
43
-
44
- const { lastFrame } = render(
45
- <ErrorBoundary>
46
- <ThrowingComponent />
47
- </ErrorBoundary>
48
- );
49
-
50
- const frame = lastFrame();
51
- expect(frame).toContain("Error:");
52
- expect(frame).toContain("Custom error message");
53
- });
54
-
55
- test("shows stack trace when available", () => {
56
- const ThrowingComponent = () => {
57
- const error = new Error("Stack trace error");
58
- error.stack = `Error: Stack trace error
59
- at ThrowingComponent (test.tsx:10:5)
60
- at renderWithHooks (react-dom.js:12345:67)
61
- at mountIndeterminateComponent (react-dom.js:12346:78)
62
- at beginWork (react-dom.js:12347:89)`;
63
- throw error;
64
- };
65
-
66
- const { lastFrame } = render(
67
- <ErrorBoundary>
68
- <ThrowingComponent />
69
- </ErrorBoundary>
70
- );
71
-
72
- const frame = lastFrame();
73
- expect(frame).toContain("Stack trace (first 5 lines)");
74
- expect(frame).toContain("ThrowingComponent");
75
- expect(frame).toContain("test.tsx:10:5");
76
- });
77
-
78
- test("uses custom fallback when provided", () => {
79
- const ThrowingComponent = () => {
80
- throw new Error("Test error");
81
- };
82
-
83
- const CustomFallback = () => <Text>Custom error UI</Text>;
84
-
85
- const { lastFrame } = render(
86
- <ErrorBoundary fallback={<CustomFallback />}>
87
- <ThrowingComponent />
88
- </ErrorBoundary>
89
- );
90
-
91
- const frame = lastFrame();
92
- expect(frame).toContain("Custom error UI");
93
- expect(frame).not.toContain("An Error Occurred");
94
- });
95
-
96
- test("calls onRetry callback when retry is triggered", () => {
97
- let retryCalled = false;
98
- const onRetry = () => {
99
- retryCalled = true;
100
- };
101
-
102
- const ThrowingComponent = () => {
103
- throw new Error("Retry test error");
104
- };
105
-
106
- const { stdin, lastFrame } = render(
107
- <ErrorBoundary onRetry={onRetry}>
108
- <ThrowingComponent />
109
- </ErrorBoundary>
110
- );
111
-
112
- const frame = lastFrame();
113
- expect(frame).toContain("Press any key to retry");
114
-
115
- // Simulate pressing a key
116
- stdin.write(" ");
117
-
118
- // The retry should have been called
119
- expect(retryCalled).toBe(true);
120
- });
121
-
122
- test("has red border and warning icon", () => {
123
- const ThrowingComponent = () => {
124
- throw new Error("Border test");
125
- };
126
-
127
- const { lastFrame } = render(
128
- <ErrorBoundary>
129
- <ThrowingComponent />
130
- </ErrorBoundary>
131
- );
132
-
133
- const frame = lastFrame();
134
- // The border style and color are applied to the Box
135
- // We can verify the content is wrapped in the error UI
136
- expect(frame).toContain("⚠️");
137
- expect(frame).toContain("An Error Occurred");
138
- });
139
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * Tests for FilterBar component
3
- */
4
-
5
- import { describe, test, expect } from "bun:test";
6
- import React from "react";
7
- import { render } from "ink-testing-library";
8
- import { FilterBar } from "../../../src/components/FilterBar.tsx";
9
- import { StoreProvider } from "../../../src/state/store.tsx";
10
-
11
- /**
12
- * Helper to render FilterBar with store context
13
- */
14
- function renderFilterBar() {
15
- return render(
16
- <StoreProvider>
17
- <FilterBar />
18
- </StoreProvider>
19
- );
20
- }
21
-
22
- describe("FilterBar", () => {
23
- describe("normal mode", () => {
24
- test("shows filter label", () => {
25
- const { lastFrame } = renderFilterBar();
26
-
27
- expect(lastFrame()).toContain("Filter:");
28
- });
29
-
30
- test("shows filter hint when empty", () => {
31
- const { lastFrame } = renderFilterBar();
32
-
33
- expect(lastFrame()).toContain("(press / to filter)");
34
- });
35
-
36
- test("shows sort indicator", () => {
37
- const { lastFrame } = renderFilterBar();
38
-
39
- expect(lastFrame()).toContain("Sort:");
40
- expect(lastFrame()).toContain("(press s to change)");
41
- });
42
- });
43
- });