gitforest 0.1.0 → 1.0.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +24 -4
  3. package/src/github/auth.ts +3 -3
  4. package/src/utils/debug.ts +4 -4
  5. package/.bunignore +0 -7
  6. package/.github/workflows/ci.yml +0 -73
  7. package/CLAUDE.md +0 -111
  8. package/CONTRIBUTING.md +0 -145
  9. package/bun.lock +0 -267
  10. package/bunfig.toml +0 -15
  11. package/cli +0 -0
  12. package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
  13. package/docs/ai/VERIFICATION_REPORT.md +0 -87
  14. package/docs/ai/architecture.md +0 -169
  15. package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
  16. package/docs/ai/checks/check-2025-12-02.md +0 -55
  17. package/docs/ai/checks/test-verification-report.md +0 -85
  18. package/docs/ai/implementation-guide.md +0 -776
  19. package/docs/ai/research/gitty-codebase-analysis.md +0 -221
  20. package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
  21. package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
  22. package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
  23. package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
  24. package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
  25. package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
  26. package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
  27. package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
  28. package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
  29. package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
  30. package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
  31. package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
  32. package/docs/ai/tickets/TASK-sitrep.md +0 -28
  33. package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
  34. package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
  35. package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
  36. package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
  37. package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
  38. package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
  39. package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
  40. package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
  41. package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
  42. package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
  43. package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
  44. package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
  45. package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
  46. package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
  47. package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
  48. package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
  49. package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
  50. package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
  51. package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
  52. package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
  53. package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
  54. package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
  55. package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
  56. package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
  57. package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
  58. package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
  59. package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
  60. package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
  61. package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
  62. package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
  63. package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
  64. package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
  65. package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
  66. package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
  67. package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
  68. package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
  69. package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
  70. package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
  71. package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
  72. package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
  73. package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
  74. package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
  75. package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
  76. package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
  77. package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
  78. package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
  79. package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
  80. package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
  81. package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
  82. package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
  83. package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
  84. package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
  85. package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
  86. package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
  87. package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
  88. package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
  89. package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
  90. package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
  91. package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
  92. package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
  93. package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
  94. package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
  95. package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
  96. package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
  97. package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
  98. package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
  99. package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
  100. package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
  101. package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
  102. package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
  103. package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
  104. package/docs/ai/tkt-001-fix-database-error.md +0 -217
  105. package/docs/ai/ui-enhancement-plan.md +0 -562
  106. package/test/integration/app.isolated.tsx +0 -240
  107. package/test/integration/cli-commands.test.ts +0 -287
  108. package/test/integration/cli-validation.test.ts +0 -264
  109. package/test/integration/git-operations.test.ts +0 -218
  110. package/test/integration/scanner.test.ts +0 -228
  111. package/test/preload.ts +0 -18
  112. package/test/unit/cli/commands.test.ts +0 -13
  113. package/test/unit/cli/formatters.test.ts +0 -1116
  114. package/test/unit/cli/github-commands.test.ts +0 -12
  115. package/test/unit/components/CloneDialog.test.tsx +0 -240
  116. package/test/unit/components/ColumnHeader.test.tsx +0 -128
  117. package/test/unit/components/CommandPalette.test.tsx +0 -355
  118. package/test/unit/components/ConfirmDialog.test.tsx +0 -111
  119. package/test/unit/components/ErrorBoundary.test.tsx +0 -139
  120. package/test/unit/components/FilterBar.test.tsx +0 -43
  121. package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
  122. package/test/unit/components/HelpOverlay.test.tsx +0 -90
  123. package/test/unit/components/Layout.test.tsx +0 -328
  124. package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
  125. package/test/unit/components/ProgressBar.test.tsx +0 -138
  126. package/test/unit/components/ProjectItem.test.tsx +0 -182
  127. package/test/unit/components/ProjectList.test.tsx +0 -311
  128. package/test/unit/components/RepoDetailModal.test.tsx +0 -445
  129. package/test/unit/components/StatusBar.test.tsx +0 -112
  130. package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
  131. package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
  132. package/test/unit/components/test-utils.tsx +0 -63
  133. package/test/unit/config/loader.test.ts +0 -692
  134. package/test/unit/db/database.test.ts +0 -978
  135. package/test/unit/db/index.test.ts +0 -314
  136. package/test/unit/fixtures/setup.ts +0 -186
  137. package/test/unit/git/commands-untested.test.ts +0 -205
  138. package/test/unit/git/commands.test.ts +0 -269
  139. package/test/unit/git/operations.test.ts +0 -322
  140. package/test/unit/git/status.test.ts +0 -219
  141. package/test/unit/github/auth.test.ts +0 -317
  142. package/test/unit/github/cache.test.ts +0 -1028
  143. package/test/unit/github/cli.test.ts +0 -135
  144. package/test/unit/github/unified.test.ts +0 -1201
  145. package/test/unit/graceful-shutdown.test.ts +0 -83
  146. package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
  147. package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
  148. package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
  149. package/test/unit/hooks/useProjects.test.tsx +0 -186
  150. package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
  151. package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
  152. package/test/unit/mocks/config.ts +0 -109
  153. package/test/unit/mocks/git-service.ts +0 -274
  154. package/test/unit/mocks/github-service.ts +0 -250
  155. package/test/unit/mocks/index.ts +0 -72
  156. package/test/unit/mocks/project.ts +0 -148
  157. package/test/unit/mocks/state-mocks.ts +0 -187
  158. package/test/unit/mocks/unified.ts +0 -169
  159. package/test/unit/operations/batch.test.ts +0 -216
  160. package/test/unit/operations/commands.test.ts +0 -550
  161. package/test/unit/scanner/errors.test.ts +0 -297
  162. package/test/unit/scanner/index.test.ts +0 -1011
  163. package/test/unit/scanner/markers.test.ts +0 -150
  164. package/test/unit/scanner/submodules.test.ts +0 -99
  165. package/test/unit/services/git-errors.test.ts +0 -190
  166. package/test/unit/services/git.test.ts +0 -442
  167. package/test/unit/services/github-errors.test.ts +0 -293
  168. package/test/unit/services/github.test.ts +0 -200
  169. package/test/unit/state/actions.test.ts +0 -217
  170. package/test/unit/state/reducer.test.ts +0 -745
  171. package/test/unit/state/store.test.tsx +0 -711
  172. package/test/unit/types/commands.test.ts +0 -220
  173. package/test/unit/types/schema.test.ts +0 -179
  174. package/test/unit/utils/array.test.ts +0 -73
  175. package/test/unit/utils/debug.test.ts +0 -23
  176. package/test/unit/utils/errors.test.ts +0 -295
  177. package/test/unit/utils/markdown.test.ts +0 -163
  178. package/test/unit/utils/project-utils.test.ts +0 -756
  179. package/test/unit/utils/rate-limiter.test.ts +0 -256
  180. package/test/unit/utils/retry.test.ts +0 -165
  181. package/test/unit/utils/strip-ansi.ts +0 -13
  182. package/test/unit/utils/timeout.test.ts +0 -93
  183. 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
- });