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,197 +0,0 @@
1
- /**
2
- * Tests for FilterOptionsOverlay 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 { FilterOptionsOverlay } from "../../../src/components/FilterOptionsOverlay.tsx";
9
- import { StoreProvider } from "../../../src/state/store.tsx";
10
-
11
- // Helper to render with store provider
12
- function renderWithStore(ui: React.ReactNode) {
13
- return render(<StoreProvider>{ui}</StoreProvider>);
14
- }
15
-
16
- describe("FilterOptionsOverlay", () => {
17
- test("renders title", () => {
18
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
19
-
20
- expect(lastFrame()).toContain("Filter Options");
21
- });
22
-
23
- describe("Quick Filters section", () => {
24
- test("shows all filter options with keyboard shortcuts", () => {
25
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
26
-
27
- expect(lastFrame()).toContain("Quick Filters:");
28
-
29
- // Check all filter options are present
30
- expect(lastFrame()).toContain("0");
31
- expect(lastFrame()).toContain("All repos");
32
-
33
- expect(lastFrame()).toContain("1");
34
- expect(lastFrame()).toContain("Dirty (uncommitted)");
35
-
36
- expect(lastFrame()).toContain("2");
37
- expect(lastFrame()).toContain("Unpushed commits");
38
-
39
- expect(lastFrame()).toContain("3");
40
- expect(lastFrame()).toContain("No remote configured");
41
-
42
- expect(lastFrame()).toContain("4");
43
- expect(lastFrame()).toContain("GitHub-only");
44
-
45
- expect(lastFrame()).toContain("5");
46
- expect(lastFrame()).toContain("Local-only");
47
-
48
- expect(lastFrame()).toContain("6");
49
- expect(lastFrame()).toContain("Private repos");
50
-
51
- expect(lastFrame()).toContain("7");
52
- expect(lastFrame()).toContain("Public repos");
53
-
54
- expect(lastFrame()).toContain("8");
55
- expect(lastFrame()).toContain("Archived");
56
-
57
- expect(lastFrame()).toContain("9");
58
- expect(lastFrame()).toContain("Forks");
59
- });
60
-
61
- test("shows active filter with checkmark", () => {
62
- // Mock a custom state where "dirty" filter is active
63
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
64
-
65
- // Default state has "all" filter active
66
- expect(lastFrame()).toContain("All repos");
67
- // Should contain checkmark for active filter
68
- const frame = lastFrame();
69
- expect(frame && (frame.includes("✓") || frame.includes("✓"))).toBe(true);
70
- });
71
- });
72
-
73
- describe("View Modes section", () => {
74
- test("shows view mode options with Tab instruction", () => {
75
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
76
-
77
- expect(lastFrame()).toContain("View Modes:");
78
- expect(lastFrame()).toContain("(Tab to cycle)");
79
-
80
- // Check all view modes are present
81
- expect(lastFrame()).toContain("local");
82
- expect(lastFrame()).toContain("github");
83
- expect(lastFrame()).toContain("combined");
84
- });
85
-
86
- test("highlights current view mode", () => {
87
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
88
-
89
- // Default view mode should be highlighted
90
- // Since we can't easily test colors, we verify the text is present
91
- expect(lastFrame()).toContain("local");
92
- });
93
- });
94
-
95
- describe("Sort section", () => {
96
- test("shows sort options with 's' instruction", () => {
97
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
98
-
99
- expect(lastFrame()).toContain("Sort:");
100
- expect(lastFrame()).toContain("(s to cycle)");
101
-
102
- // Check all sort options are present
103
- expect(lastFrame()).toContain("status");
104
- expect(lastFrame()).toContain("name");
105
- expect(lastFrame()).toContain("lastActivity");
106
- expect(lastFrame()).toContain("stars");
107
- expect(lastFrame()).toContain("size");
108
- });
109
-
110
- test("highlights current sort field", () => {
111
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
112
-
113
- // Default sort field should be highlighted
114
- expect(lastFrame()).toContain("status");
115
- });
116
- });
117
-
118
- describe("Search section", () => {
119
- test("shows search functionality", () => {
120
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
121
-
122
- expect(lastFrame()).toContain("Search:");
123
- expect(lastFrame()).toContain("/");
124
- expect(lastFrame()).toContain("Start text search (by name, description, path)");
125
- });
126
- });
127
-
128
- describe("Language Filter section", () => {
129
- test("shows language filter instructions", () => {
130
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
131
-
132
- expect(lastFrame()).toContain("Language Filter:");
133
- expect(lastFrame()).toContain("Type language name to filter (e.g., typescript, rust)");
134
- });
135
-
136
- test("shows current language filter when set", () => {
137
- // We can't easily mock the state with the current setup
138
- // But we can verify the structure is there
139
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
140
-
141
- expect(lastFrame()).toContain("Current:");
142
- expect(lastFrame()).toContain("[none]");
143
- });
144
- });
145
-
146
- describe("Footer", () => {
147
- test("shows close instruction", () => {
148
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
149
-
150
- expect(lastFrame()).toContain("Press any key to close");
151
- });
152
- });
153
-
154
- describe("Visual layout", () => {
155
- test("has proper border styling", () => {
156
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
157
-
158
- // Check for border characters (this is a simplified check)
159
- const frame = lastFrame();
160
- // The component should render with border styling
161
- expect(frame).toBeTruthy();
162
- expect(frame && frame.length).toBeGreaterThan(0);
163
- });
164
-
165
- test("has proper section spacing", () => {
166
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
167
-
168
- const frame = lastFrame();
169
-
170
- // Verify all sections are present
171
- expect(frame).toContain("Filter Options");
172
- expect(frame).toContain("Quick Filters:");
173
- expect(frame).toContain("View Modes:");
174
- expect(frame).toContain("Sort:");
175
- expect(frame).toContain("Search:");
176
- expect(frame).toContain("Language Filter:");
177
- });
178
- });
179
-
180
- describe("Keyboard shortcuts", () => {
181
- test("shows all keyboard shortcuts", () => {
182
- const { lastFrame } = renderWithStore(<FilterOptionsOverlay />);
183
-
184
- const frame = lastFrame();
185
-
186
- // Check numbers 0-9 are shown for quick filters
187
- for (let i = 0; i <= 9; i++) {
188
- expect(frame).toContain(i.toString());
189
- }
190
-
191
- // Check other shortcuts
192
- expect(frame).toContain("/"); // Search
193
- expect(frame).toContain("s"); // Sort
194
- expect(frame).toContain("Tab"); // View mode cycle
195
- });
196
- });
197
- });
@@ -1,90 +0,0 @@
1
- /**
2
- * Tests for HelpOverlay 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 { HelpOverlay } from "../../../src/components/HelpOverlay.tsx";
9
-
10
- describe("HelpOverlay", () => {
11
- test("renders keyboard shortcuts header", () => {
12
- const { lastFrame } = render(<HelpOverlay />);
13
-
14
- expect(lastFrame()).toContain("Keyboard Shortcuts");
15
- });
16
-
17
- describe("navigation section", () => {
18
- test("shows navigation keys", () => {
19
- const { lastFrame } = render(<HelpOverlay />);
20
-
21
- expect(lastFrame()).toContain("Navigation");
22
- expect(lastFrame()).toContain("Move down");
23
- expect(lastFrame()).toContain("Move up");
24
- expect(lastFrame()).toContain("Go to top");
25
- expect(lastFrame()).toContain("Go to bottom");
26
- });
27
- });
28
-
29
- describe("selection section", () => {
30
- test("shows selection keys", () => {
31
- const { lastFrame } = render(<HelpOverlay />);
32
-
33
- expect(lastFrame()).toContain("Selection");
34
- expect(lastFrame()).toContain("Toggle selection");
35
- expect(lastFrame()).toContain("Select all");
36
- });
37
- });
38
-
39
- describe("git operations section", () => {
40
- test("shows git operation keys", () => {
41
- const { lastFrame } = render(<HelpOverlay />);
42
-
43
- expect(lastFrame()).toContain("Git Operations");
44
- expect(lastFrame()).toContain("Push selected");
45
- expect(lastFrame()).toContain("Pull all repos");
46
- // Text may wrap across lines - check for key parts
47
- expect(lastFrame()).toContain("Fetch all");
48
- expect(lastFrame()).toContain("Init git");
49
- });
50
- });
51
-
52
- describe("github section", () => {
53
- test("shows github keys", () => {
54
- const { lastFrame } = render(<HelpOverlay />);
55
-
56
- expect(lastFrame()).toContain("GitHub");
57
- // Text may wrap - check for key parts
58
- expect(lastFrame()).toContain("Create GitHub");
59
- expect(lastFrame()).toContain("Archive GitHub");
60
- });
61
- });
62
-
63
- describe("view section", () => {
64
- test("shows view keys", () => {
65
- const { lastFrame } = render(<HelpOverlay />);
66
-
67
- expect(lastFrame()).toContain("View");
68
- expect(lastFrame()).toContain("Filter projects");
69
- expect(lastFrame()).toContain("Cycle sort field");
70
- expect(lastFrame()).toContain("Refresh");
71
- });
72
- });
73
-
74
- describe("general section", () => {
75
- test("shows general keys", () => {
76
- const { lastFrame } = render(<HelpOverlay />);
77
-
78
- expect(lastFrame()).toContain("General");
79
- expect(lastFrame()).toContain("Toggle help");
80
- expect(lastFrame()).toContain("Cancel");
81
- expect(lastFrame()).toContain("Quit");
82
- });
83
- });
84
-
85
- test("shows close instruction", () => {
86
- const { lastFrame } = render(<HelpOverlay />);
87
-
88
- expect(lastFrame()).toContain("Press any key to close");
89
- });
90
- });
@@ -1,328 +0,0 @@
1
- /**
2
- * Tests for Layout component
3
- *
4
- * Uses TestStoreProvider for dependency injection instead of mock.module()
5
- */
6
-
7
- import { describe, test, expect } from "bun:test";
8
- import React from "react";
9
- import { render } from "ink-testing-library";
10
- import { Layout } from "../../../src/components/Layout.tsx";
11
- import { StoreProvider, TestStoreProvider } from "../../../src/state/store.tsx";
12
- import type { GitforestConfig, UnifiedAppState, AppMode, UnifiedRepo } from "../../../src/types/index.ts";
13
- import { createMockUnifiedAppState, createMockUnifiedRepo, createLocalOnlyUnifiedRepo, createGitHubOnlyUnifiedRepo, createSyncedUnifiedRepo, createMockGitHubRepoInfo } from "../mocks/index.ts";
14
-
15
- /**
16
- * Mock config for testing
17
- */
18
- const mockConfig: GitforestConfig = {
19
- directories: [{ path: "/test", maxDepth: 3 }],
20
- scan: { ignore: [], includeHidden: false, concurrency: 4 },
21
- github: { defaultVisibility: "private" },
22
- display: { showSubmodules: true, showNonGitProjects: true, sortBy: "status", sortDirection: "desc" },
23
- cache: {
24
- ttlSeconds: 300,
25
- githubTtlSeconds: 600,
26
- enableBackgroundRefresh: true,
27
- backgroundRefreshIntervalSeconds: 300,
28
- },
29
- commands: [
30
- { name: "Test Command", key: "t", command: "echo test", confirm: false, background: false }
31
- ],
32
- };
33
-
34
- /**
35
- * Mock refresh function
36
- */
37
- const mockRefresh = async () => {};
38
-
39
- /**
40
- * Mock clone function
41
- */
42
- const mockClone = async (_repos: any[], _targetDir: string, _useSSH: boolean) => {};
43
-
44
- /**
45
- * Track dispatch calls
46
- */
47
- let dispatchCalls: any[] = [];
48
- const mockDispatch = (action: any) => {
49
- dispatchCalls.push(action);
50
- };
51
-
52
- /**
53
- * Helper to create a mock state with specific mode and repos
54
- */
55
- function createStateForMode(mode: AppMode, repos: UnifiedRepo[] = []): UnifiedAppState {
56
- return createMockUnifiedAppState({
57
- mode,
58
- isLoading: false,
59
- unifiedRepos: repos,
60
- // Add specific dialog states based on mode
61
- confirmDialog: mode === "confirm" ? {
62
- operation: "setup" as const,
63
- title: "Test Confirm",
64
- message: "Are you sure?",
65
- items: ["item1", "item2"],
66
- projectPaths: ["/test/path1", "/test/path2"],
67
- showVisibilityToggle: false,
68
- } : null,
69
- cloneDialog: mode === "clone" ? {
70
- repos: [createMockUnifiedRepo({ name: "test-repo" })],
71
- directories: [{ path: "/test/dir", maxDepth: 3 }],
72
- selectedDirIndex: 0,
73
- useSSH: false,
74
- } : null,
75
- detailModal: mode === "detail" ? {
76
- repo: createMockUnifiedRepo({ name: "detail-repo" }),
77
- readmeContent: "# Test README",
78
- readmeLoading: false,
79
- readmeError: null,
80
- readmeScrollOffset: 0,
81
- } : null,
82
- });
83
- }
84
-
85
- /**
86
- * Helper to render Layout with TestStoreProvider (no mock.module needed)
87
- */
88
- function renderLayoutWithState(state: UnifiedAppState) {
89
- // Clear previous dispatch calls
90
- dispatchCalls = [];
91
-
92
- return render(
93
- <TestStoreProvider initialState={state} dispatch={mockDispatch}>
94
- <Layout config={mockConfig} onRefresh={mockRefresh} onClone={mockClone} />
95
- </TestStoreProvider>
96
- );
97
- }
98
-
99
- /**
100
- * Helper to render Layout with store provider (for normal mode tests)
101
- */
102
- function renderLayout() {
103
- return render(
104
- <StoreProvider>
105
- <Layout config={mockConfig} onRefresh={mockRefresh} />
106
- </StoreProvider>
107
- );
108
- }
109
-
110
- describe("Layout", () => {
111
-
112
- describe("header", () => {
113
- test("shows gitforest title", () => {
114
- const { lastFrame } = renderLayout();
115
-
116
- expect(lastFrame()).toContain("gitforest");
117
- });
118
-
119
- test("shows subtitle", () => {
120
- const { lastFrame } = renderLayout();
121
-
122
- expect(lastFrame()).toContain("Git Repository Manager");
123
- });
124
- });
125
-
126
- describe("filter bar", () => {
127
- test("shows filter bar", () => {
128
- const { lastFrame } = renderLayout();
129
-
130
- expect(lastFrame()).toContain("Filter:");
131
- });
132
- });
133
-
134
- describe("initial state", () => {
135
- test("shows loading spinner initially", () => {
136
- const { lastFrame } = renderLayout();
137
-
138
- // Initial state has isLoading: true
139
- expect(lastFrame()).toContain("Scanning projects");
140
- });
141
- });
142
-
143
- describe("mode rendering", () => {
144
- test("renders HelpOverlay when mode is 'help'", () => {
145
- const state = createStateForMode("help");
146
- const { lastFrame } = renderLayoutWithState(state);
147
-
148
- expect(lastFrame()).toContain("Keyboard Shortcuts");
149
- expect(lastFrame()).toContain("Navigation");
150
- expect(lastFrame()).toContain("Selection");
151
- });
152
-
153
- test("renders CommandPalette when mode is 'command-palette'", () => {
154
- const state = createStateForMode("command-palette");
155
- const { lastFrame } = renderLayoutWithState(state);
156
-
157
- expect(lastFrame()).toContain("Command Palette");
158
- expect(lastFrame()).toContain("Available Commands");
159
- expect(lastFrame()).toContain("[t] Test Command");
160
- });
161
-
162
- test("renders ConfirmDialog when mode is 'confirm'", () => {
163
- const state = createStateForMode("confirm");
164
- const { lastFrame } = renderLayoutWithState(state);
165
-
166
- expect(lastFrame()).toContain("Test Confirm");
167
- expect(lastFrame()).toContain("Are you sure?");
168
- expect(lastFrame()).toContain("item1");
169
- expect(lastFrame()).toContain("item2");
170
- });
171
-
172
- test("renders CloneDialog when mode is 'clone'", () => {
173
- const state = createStateForMode("clone");
174
- const { lastFrame } = renderLayoutWithState(state);
175
-
176
- expect(lastFrame()).toContain("Clone GitHub Repository");
177
- expect(lastFrame()).toContain("test-repo");
178
- expect(lastFrame()).toContain("Target directory");
179
- });
180
-
181
- test("renders RepoDetailModal when mode is 'detail'", () => {
182
- const state = createStateForMode("detail");
183
- const { lastFrame } = renderLayoutWithState(state);
184
-
185
- expect(lastFrame()).toContain("detail-repo");
186
- expect(lastFrame()).toContain("Test README");
187
- });
188
-
189
- test("renders filter-options placeholder when mode is 'filter-options'", () => {
190
- const state = createStateForMode("filter-options");
191
- const { lastFrame } = renderLayoutWithState(state);
192
-
193
- expect(lastFrame()).toContain("Filter Options Overlay - To be implemented");
194
- });
195
-
196
- test("renders main layout when mode is 'normal'", () => {
197
- const state = createStateForMode("normal");
198
- const { lastFrame } = renderLayoutWithState(state);
199
-
200
- expect(lastFrame()).toContain("gitforest");
201
- expect(lastFrame()).toContain("Git Repository Manager");
202
- expect(lastFrame()).toContain("Filter:");
203
- expect(lastFrame()).toContain("No repositories found");
204
- });
205
- });
206
-
207
- describe("StatusBar props", () => {
208
- test("passes correct counts to StatusBar", () => {
209
- const repos = [
210
- createMockUnifiedRepo({
211
- name: "dirty-repo",
212
- local: { status: { isDirty: true } } as any
213
- }),
214
- createMockUnifiedRepo({
215
- name: "ahead-repo",
216
- local: { status: { isAhead: true } } as any
217
- }),
218
- createLocalOnlyUnifiedRepo("local-only"),
219
- createGitHubOnlyUnifiedRepo("github-only"),
220
- createSyncedUnifiedRepo("synced"),
221
- ];
222
-
223
- const state = createStateForMode("normal", repos);
224
- const { lastFrame } = renderLayoutWithState(state);
225
-
226
- // Check that StatusBar receives the correct counts
227
- expect(lastFrame()).toContain("5 repos");
228
- expect(lastFrame()).toContain("1 synced");
229
- expect(lastFrame()).toContain("3 local");
230
- expect(lastFrame()).toContain("1 remote");
231
- expect(lastFrame()).toContain("1 dirty");
232
- expect(lastFrame()).toContain("1 unpushed");
233
- });
234
- });
235
-
236
- describe("dialog handlers", () => {
237
- test("handleCloneConfirm calls onClone with correct params", async () => {
238
- const state = createStateForMode("clone");
239
- const { lastFrame } = renderLayoutWithState(state);
240
-
241
- // The dialog should be rendered
242
- expect(lastFrame()).toContain("Clone GitHub Repository");
243
- expect(lastFrame()).toContain("test-repo");
244
- expect(lastFrame()).toContain("Esc/n to cancel");
245
- });
246
-
247
- test("handleCloneSelectDir dispatches UPDATE_CLONE_DIALOG", () => {
248
- const state = createStateForMode("clone");
249
- const { lastFrame } = renderLayoutWithState(state);
250
-
251
- expect(lastFrame()).toContain("Target directory");
252
- // Directory selection would be tested in CloneDialog component tests
253
- });
254
-
255
- test("handleCloneToggleSSH toggles SSH setting", () => {
256
- const state = createStateForMode("clone");
257
- const { lastFrame } = renderLayoutWithState(state);
258
-
259
- expect(lastFrame()).toContain("Protocol:");
260
- expect(lastFrame()).toContain("SSH");
261
- expect(lastFrame()).toContain("HTTPS");
262
- // SSH toggle would be tested in CloneDialog component tests
263
- });
264
- });
265
-
266
- describe("detail modal handlers", () => {
267
- test("handleDetailClose would dispatch HIDE_DETAIL_MODAL", () => {
268
- const state = createStateForMode("detail");
269
- const { lastFrame } = renderLayoutWithState(state);
270
-
271
- expect(lastFrame()).toContain("detail-repo");
272
- // Close handler would be tested in RepoDetailModal component tests
273
- });
274
-
275
- test("handleDetailAction handles browser action", () => {
276
- const repo = createMockUnifiedRepo({
277
- name: "browser-repo",
278
- github: createMockGitHubRepoInfo({
279
- name: "browser-repo",
280
- htmlUrl: "https://github.com/test/browser-repo"
281
- })
282
- });
283
- const state = createStateForMode("detail");
284
- state.detailModal!.repo = repo;
285
-
286
- const { lastFrame } = renderLayoutWithState(state);
287
-
288
- expect(lastFrame()).toContain("browser-repo");
289
- // Browser action would open the URL (tested in integration tests)
290
- });
291
-
292
- test("handleDetailAction handles editor action", () => {
293
- const repo = createMockUnifiedRepo({
294
- name: "editor-repo",
295
- localPath: "/test/editor-repo"
296
- });
297
- const state = createStateForMode("detail");
298
- state.detailModal!.repo = repo;
299
-
300
- const { lastFrame } = renderLayoutWithState(state);
301
-
302
- expect(lastFrame()).toContain("editor-repo");
303
- // Editor action would open the editor (tested in integration tests)
304
- });
305
-
306
- test("handleDetailScroll dispatches UPDATE_DETAIL_MODAL", () => {
307
- const state = createStateForMode("detail");
308
- const { lastFrame } = renderLayoutWithState(state);
309
-
310
- expect(lastFrame()).toContain("Test README");
311
- // Scroll handler would be tested in RepoDetailModal component tests
312
- });
313
-
314
- test("handleDetailCommand executes command", () => {
315
- const repo = createMockUnifiedRepo({
316
- name: "command-repo",
317
- localPath: "/test/command-repo"
318
- });
319
- const state = createStateForMode("detail");
320
- state.detailModal!.repo = repo;
321
-
322
- const { lastFrame } = renderLayoutWithState(state);
323
-
324
- expect(lastFrame()).toContain("command-repo");
325
- // Command execution would be tested in RepoDetailModal component tests
326
- });
327
- });
328
- });
@@ -1,45 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import React from "react";
3
- import { render } from "ink-testing-library";
4
- import { MarkdownRenderer } from "../../../src/components/MarkdownRenderer";
5
-
6
- test("MarkdownRenderer renders basic markdown", () => {
7
- const markdown = `# Hello World
8
-
9
- This is **bold** and *italic* text.`;
10
-
11
- const { lastFrame } = render(
12
- React.createElement(MarkdownRenderer, { content: markdown })
13
- );
14
-
15
- const output = lastFrame();
16
- expect(output).toContain("Hello World");
17
- expect(output).toContain("bold");
18
- expect(output).toContain("italic");
19
- });
20
-
21
- test("MarkdownRenderer handles code blocks", () => {
22
- const markdown = "\`\`\`javascript\nconst x = 1;\n\`\`\`";
23
-
24
- const { lastFrame } = render(
25
- React.createElement(MarkdownRenderer, { content: markdown })
26
- );
27
-
28
- const output = lastFrame();
29
- expect(output).toContain("const x = 1;");
30
- });
31
-
32
- test("MarkdownRenderer handles lists", () => {
33
- const markdown = `- Item 1
34
- - Item 2
35
- - Item 3`;
36
-
37
- const { lastFrame } = render(
38
- React.createElement(MarkdownRenderer, { content: markdown })
39
- );
40
-
41
- const output = lastFrame();
42
- expect(output).toContain("Item 1");
43
- expect(output).toContain("Item 2");
44
- expect(output).toContain("Item 3");
45
- });