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,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
- });