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.
- package/LICENSE +21 -0
- package/package.json +24 -4
- package/src/github/auth.ts +3 -3
- package/src/utils/debug.ts +4 -4
- package/.bunignore +0 -7
- package/.github/workflows/ci.yml +0 -73
- package/CLAUDE.md +0 -111
- package/CONTRIBUTING.md +0 -145
- package/bun.lock +0 -267
- package/bunfig.toml +0 -15
- package/cli +0 -0
- package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
- package/docs/ai/VERIFICATION_REPORT.md +0 -87
- package/docs/ai/architecture.md +0 -169
- package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
- package/docs/ai/checks/check-2025-12-02.md +0 -55
- package/docs/ai/checks/test-verification-report.md +0 -85
- package/docs/ai/implementation-guide.md +0 -776
- package/docs/ai/research/gitty-codebase-analysis.md +0 -221
- package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
- package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
- package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
- package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
- package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
- package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
- package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
- package/docs/ai/tickets/TASK-sitrep.md +0 -28
- package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
- package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
- package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
- package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
- package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
- package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
- package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
- package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
- package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
- package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
- package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
- package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
- package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
- package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
- package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
- package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
- package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
- package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
- package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
- package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
- package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
- package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
- package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
- package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
- package/docs/ai/tkt-001-fix-database-error.md +0 -217
- package/docs/ai/ui-enhancement-plan.md +0 -562
- package/test/integration/app.isolated.tsx +0 -240
- package/test/integration/cli-commands.test.ts +0 -287
- package/test/integration/cli-validation.test.ts +0 -264
- package/test/integration/git-operations.test.ts +0 -218
- package/test/integration/scanner.test.ts +0 -228
- package/test/preload.ts +0 -18
- package/test/unit/cli/commands.test.ts +0 -13
- package/test/unit/cli/formatters.test.ts +0 -1116
- package/test/unit/cli/github-commands.test.ts +0 -12
- package/test/unit/components/CloneDialog.test.tsx +0 -240
- package/test/unit/components/ColumnHeader.test.tsx +0 -128
- package/test/unit/components/CommandPalette.test.tsx +0 -355
- package/test/unit/components/ConfirmDialog.test.tsx +0 -111
- package/test/unit/components/ErrorBoundary.test.tsx +0 -139
- package/test/unit/components/FilterBar.test.tsx +0 -43
- package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
- package/test/unit/components/HelpOverlay.test.tsx +0 -90
- package/test/unit/components/Layout.test.tsx +0 -328
- package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
- package/test/unit/components/ProgressBar.test.tsx +0 -138
- package/test/unit/components/ProjectItem.test.tsx +0 -182
- package/test/unit/components/ProjectList.test.tsx +0 -311
- package/test/unit/components/RepoDetailModal.test.tsx +0 -445
- package/test/unit/components/StatusBar.test.tsx +0 -112
- package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
- package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
- package/test/unit/components/test-utils.tsx +0 -63
- package/test/unit/config/loader.test.ts +0 -692
- package/test/unit/db/database.test.ts +0 -978
- package/test/unit/db/index.test.ts +0 -314
- package/test/unit/fixtures/setup.ts +0 -186
- package/test/unit/git/commands-untested.test.ts +0 -205
- package/test/unit/git/commands.test.ts +0 -269
- package/test/unit/git/operations.test.ts +0 -322
- package/test/unit/git/status.test.ts +0 -219
- package/test/unit/github/auth.test.ts +0 -317
- package/test/unit/github/cache.test.ts +0 -1028
- package/test/unit/github/cli.test.ts +0 -135
- package/test/unit/github/unified.test.ts +0 -1201
- package/test/unit/graceful-shutdown.test.ts +0 -83
- package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
- package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
- package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
- package/test/unit/hooks/useProjects.test.tsx +0 -186
- package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
- package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
- package/test/unit/mocks/config.ts +0 -109
- package/test/unit/mocks/git-service.ts +0 -274
- package/test/unit/mocks/github-service.ts +0 -250
- package/test/unit/mocks/index.ts +0 -72
- package/test/unit/mocks/project.ts +0 -148
- package/test/unit/mocks/state-mocks.ts +0 -187
- package/test/unit/mocks/unified.ts +0 -169
- package/test/unit/operations/batch.test.ts +0 -216
- package/test/unit/operations/commands.test.ts +0 -550
- package/test/unit/scanner/errors.test.ts +0 -297
- package/test/unit/scanner/index.test.ts +0 -1011
- package/test/unit/scanner/markers.test.ts +0 -150
- package/test/unit/scanner/submodules.test.ts +0 -99
- package/test/unit/services/git-errors.test.ts +0 -190
- package/test/unit/services/git.test.ts +0 -442
- package/test/unit/services/github-errors.test.ts +0 -293
- package/test/unit/services/github.test.ts +0 -200
- package/test/unit/state/actions.test.ts +0 -217
- package/test/unit/state/reducer.test.ts +0 -745
- package/test/unit/state/store.test.tsx +0 -711
- package/test/unit/types/commands.test.ts +0 -220
- package/test/unit/types/schema.test.ts +0 -179
- package/test/unit/utils/array.test.ts +0 -73
- package/test/unit/utils/debug.test.ts +0 -23
- package/test/unit/utils/errors.test.ts +0 -295
- package/test/unit/utils/markdown.test.ts +0 -163
- package/test/unit/utils/project-utils.test.ts +0 -756
- package/test/unit/utils/rate-limiter.test.ts +0 -256
- package/test/unit/utils/retry.test.ts +0 -165
- package/test/unit/utils/strip-ansi.ts +0 -13
- package/test/unit/utils/timeout.test.ts +0 -93
- 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
|
-
});
|