@akiojin/gwt 2.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/README.ja.md +323 -0
- package/README.md +347 -0
- package/bin/gwt.js +5 -0
- package/package.json +125 -0
- package/src/claude-history.ts +717 -0
- package/src/claude.ts +292 -0
- package/src/cli/ui/__tests__/SKIPPED_TESTS.md +119 -0
- package/src/cli/ui/__tests__/acceptance/branchList.acceptance.test.tsx.skip +239 -0
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +214 -0
- package/src/cli/ui/__tests__/acceptance/realtimeUpdate.acceptance.test.tsx.skip +219 -0
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +183 -0
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +313 -0
- package/src/cli/ui/__tests__/components/App.test.tsx +270 -0
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +66 -0
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +103 -0
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +92 -0
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +127 -0
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +264 -0
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +246 -0
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +62 -0
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +54 -0
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +68 -0
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +135 -0
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +153 -0
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +215 -0
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +293 -0
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +161 -0
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +215 -0
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +99 -0
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +127 -0
- package/src/cli/ui/__tests__/hooks/useGitData.test.ts.skip +228 -0
- package/src/cli/ui/__tests__/hooks/useScreenState.test.ts +146 -0
- package/src/cli/ui/__tests__/hooks/useTerminalSize.test.ts +98 -0
- package/src/cli/ui/__tests__/integration/branchList.test.tsx.skip +253 -0
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +306 -0
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +405 -0
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +505 -0
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx.skip +216 -0
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +180 -0
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +237 -0
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +775 -0
- package/src/cli/ui/__tests__/utils/statisticsCalculator.test.ts +243 -0
- package/src/cli/ui/components/App.tsx +793 -0
- package/src/cli/ui/components/common/Confirm.tsx +40 -0
- package/src/cli/ui/components/common/ErrorBoundary.tsx +57 -0
- package/src/cli/ui/components/common/Input.tsx +36 -0
- package/src/cli/ui/components/common/LoadingIndicator.tsx +95 -0
- package/src/cli/ui/components/common/Select.tsx +216 -0
- package/src/cli/ui/components/parts/Footer.tsx +41 -0
- package/src/cli/ui/components/parts/Header.test.tsx +85 -0
- package/src/cli/ui/components/parts/Header.tsx +63 -0
- package/src/cli/ui/components/parts/MergeStatusList.tsx +75 -0
- package/src/cli/ui/components/parts/ProgressBar.tsx +73 -0
- package/src/cli/ui/components/parts/ScrollableList.tsx +24 -0
- package/src/cli/ui/components/parts/Stats.tsx +67 -0
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +116 -0
- package/src/cli/ui/components/screens/BatchMergeProgressScreen.tsx +70 -0
- package/src/cli/ui/components/screens/BatchMergeResultScreen.tsx +104 -0
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +213 -0
- package/src/cli/ui/components/screens/BranchListScreen.tsx +299 -0
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +149 -0
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +167 -0
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +100 -0
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +117 -0
- package/src/cli/ui/hooks/useBatchMerge.ts +96 -0
- package/src/cli/ui/hooks/useGitData.ts +157 -0
- package/src/cli/ui/hooks/useScreenState.ts +44 -0
- package/src/cli/ui/hooks/useTerminalSize.ts +33 -0
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +102 -0
- package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +151 -0
- package/src/cli/ui/types.ts +295 -0
- package/src/cli/ui/utils/baseBranch.ts +34 -0
- package/src/cli/ui/utils/branchFormatter.ts +222 -0
- package/src/cli/ui/utils/statisticsCalculator.ts +44 -0
- package/src/codex.ts +139 -0
- package/src/config/builtin-tools.ts +44 -0
- package/src/config/constants.ts +100 -0
- package/src/config/env-history.ts +45 -0
- package/src/config/index.ts +204 -0
- package/src/config/tools.ts +293 -0
- package/src/git.ts +1102 -0
- package/src/github.ts +158 -0
- package/src/index.test.ts +87 -0
- package/src/index.ts +684 -0
- package/src/index.ts.backup +1543 -0
- package/src/launcher.ts +142 -0
- package/src/repositories/git.repository.ts +129 -0
- package/src/repositories/github.repository.ts +83 -0
- package/src/repositories/worktree.repository.ts +69 -0
- package/src/services/BatchMergeService.ts +251 -0
- package/src/services/WorktreeOrchestrator.ts +115 -0
- package/src/services/__tests__/BatchMergeService.test.ts +518 -0
- package/src/services/__tests__/WorktreeOrchestrator.test.ts +258 -0
- package/src/services/dependency-installer.ts +199 -0
- package/src/services/git.service.ts +113 -0
- package/src/services/github.service.ts +61 -0
- package/src/services/worktree.service.ts +66 -0
- package/src/types/api.ts +241 -0
- package/src/types/tools.ts +235 -0
- package/src/utils/spinner.ts +54 -0
- package/src/utils/terminal.ts +272 -0
- package/src/utils.test.ts +43 -0
- package/src/utils.ts +60 -0
- package/src/web/client/index.html +12 -0
- package/src/web/client/src/components/BranchGraph.tsx +231 -0
- package/src/web/client/src/components/EnvEditor.tsx +145 -0
- package/src/web/client/src/components/Terminal.tsx +137 -0
- package/src/web/client/src/hooks/useBranches.ts +41 -0
- package/src/web/client/src/hooks/useConfig.ts +31 -0
- package/src/web/client/src/hooks/useSessions.ts +59 -0
- package/src/web/client/src/hooks/useWorktrees.ts +47 -0
- package/src/web/client/src/index.css +834 -0
- package/src/web/client/src/lib/api.ts +184 -0
- package/src/web/client/src/lib/websocket.ts +174 -0
- package/src/web/client/src/main.tsx +29 -0
- package/src/web/client/src/pages/BranchDetailPage.tsx +847 -0
- package/src/web/client/src/pages/BranchListPage.tsx +264 -0
- package/src/web/client/src/pages/ConfigManagementPage.tsx +203 -0
- package/src/web/client/src/router.tsx +27 -0
- package/src/web/client/vite.config.ts +21 -0
- package/src/web/server/env/importer.ts +54 -0
- package/src/web/server/index.ts +74 -0
- package/src/web/server/pty/manager.ts +189 -0
- package/src/web/server/routes/branches.ts +126 -0
- package/src/web/server/routes/config.ts +220 -0
- package/src/web/server/routes/index.ts +37 -0
- package/src/web/server/routes/sessions.ts +130 -0
- package/src/web/server/routes/worktrees.ts +108 -0
- package/src/web/server/services/branches.ts +368 -0
- package/src/web/server/services/worktrees.ts +85 -0
- package/src/web/server/websocket/handler.ts +180 -0
- package/src/worktree.ts +703 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
5
|
+
import { render, waitFor } from '@testing-library/react';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { App } from '../../components/App.js';
|
|
8
|
+
import { Window } from 'happy-dom';
|
|
9
|
+
import type { BranchInfo } from '../../types.js';
|
|
10
|
+
|
|
11
|
+
// Mock git.js and worktree.js
|
|
12
|
+
vi.mock('../../../git.js', () => ({
|
|
13
|
+
getAllBranches: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('../../../worktree.js', () => ({
|
|
17
|
+
listAdditionalWorktrees: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
import { getAllBranches } from '../../../git.js';
|
|
21
|
+
import { listAdditionalWorktrees } from '../../../worktree.js';
|
|
22
|
+
|
|
23
|
+
describe('Branch List Integration', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// Setup happy-dom
|
|
26
|
+
const window = new Window();
|
|
27
|
+
globalThis.window = window as any;
|
|
28
|
+
globalThis.document = window.document as any;
|
|
29
|
+
|
|
30
|
+
// Reset mocks
|
|
31
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockReset();
|
|
32
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockReset();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should render full application with branch list', async () => {
|
|
40
|
+
const mockBranches: BranchInfo[] = [
|
|
41
|
+
{
|
|
42
|
+
name: 'main',
|
|
43
|
+
type: 'local',
|
|
44
|
+
branchType: 'main',
|
|
45
|
+
isCurrent: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'feature/test',
|
|
49
|
+
type: 'local',
|
|
50
|
+
branchType: 'feature',
|
|
51
|
+
isCurrent: false,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
|
|
56
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
57
|
+
|
|
58
|
+
const onExit = vi.fn();
|
|
59
|
+
const { getByText } = render(<App onExit={onExit} />);
|
|
60
|
+
|
|
61
|
+
// Wait for async data loading
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(getByText(/Claude Worktree/i)).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Verify header
|
|
67
|
+
expect(getByText(/Claude Worktree/i)).toBeDefined();
|
|
68
|
+
|
|
69
|
+
// Verify stats
|
|
70
|
+
expect(getByText(/Local:/)).toBeDefined();
|
|
71
|
+
|
|
72
|
+
// Verify branches are displayed
|
|
73
|
+
expect(getByText(/main/)).toBeDefined();
|
|
74
|
+
expect(getByText(/feature\/test/)).toBeDefined();
|
|
75
|
+
|
|
76
|
+
// Verify footer
|
|
77
|
+
expect(getByText(/Quit/i)).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should display statistics correctly', async () => {
|
|
81
|
+
const mockBranches: BranchInfo[] = [
|
|
82
|
+
{
|
|
83
|
+
name: 'main',
|
|
84
|
+
type: 'local',
|
|
85
|
+
branchType: 'main',
|
|
86
|
+
isCurrent: true,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'feature/a',
|
|
90
|
+
type: 'local',
|
|
91
|
+
branchType: 'feature',
|
|
92
|
+
isCurrent: false,
|
|
93
|
+
worktree: {
|
|
94
|
+
path: '/path/a',
|
|
95
|
+
locked: false,
|
|
96
|
+
prunable: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'origin/main',
|
|
101
|
+
type: 'remote',
|
|
102
|
+
branchType: 'main',
|
|
103
|
+
isCurrent: false,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
|
|
108
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
109
|
+
|
|
110
|
+
const onExit = vi.fn();
|
|
111
|
+
const { getByText, getAllByText } = render(<App onExit={onExit} />);
|
|
112
|
+
|
|
113
|
+
// Wait for async data loading
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
expect(getByText(/Local:/)).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Verify statistics
|
|
119
|
+
expect(getByText(/Local:/)).toBeDefined();
|
|
120
|
+
expect(getAllByText(/2/).length).toBeGreaterThan(0); // 2 local branches
|
|
121
|
+
|
|
122
|
+
expect(getByText(/Remote:/)).toBeDefined();
|
|
123
|
+
expect(getAllByText(/1/).length).toBeGreaterThan(0); // 1 remote branch + 1 worktree
|
|
124
|
+
|
|
125
|
+
expect(getByText(/Worktrees:/)).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle empty branch list', async () => {
|
|
129
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
130
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
131
|
+
|
|
132
|
+
const onExit = vi.fn();
|
|
133
|
+
const { getByText } = render(<App onExit={onExit} />);
|
|
134
|
+
|
|
135
|
+
// Wait for async data loading
|
|
136
|
+
await waitFor(() => {
|
|
137
|
+
expect(getByText(/No branches found/i)).toBeDefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(getByText(/No branches found/i)).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle loading state', async () => {
|
|
144
|
+
// Mock a slow response
|
|
145
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockImplementation(
|
|
146
|
+
() =>
|
|
147
|
+
new Promise((resolve) =>
|
|
148
|
+
setTimeout(() => resolve([]), 100)
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
152
|
+
|
|
153
|
+
const onExit = vi.fn();
|
|
154
|
+
const { getByText, queryByText } = render(<App onExit={onExit} />);
|
|
155
|
+
|
|
156
|
+
// Initially should show loading
|
|
157
|
+
// Note: In happy-dom, useEffect may run synchronously, so we check for either loading or loaded state
|
|
158
|
+
const hasLoading = queryByText(/Loading/i);
|
|
159
|
+
if (hasLoading) {
|
|
160
|
+
expect(hasLoading).toBeDefined();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Wait for data to load
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(queryByText(/Loading/i)).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle error state', async () => {
|
|
170
|
+
const error = new Error('Failed to fetch branches');
|
|
171
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockRejectedValue(error);
|
|
172
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
173
|
+
|
|
174
|
+
const onExit = vi.fn();
|
|
175
|
+
const { getByText } = render(<App onExit={onExit} />);
|
|
176
|
+
|
|
177
|
+
// Wait for error to appear
|
|
178
|
+
await waitFor(() => {
|
|
179
|
+
expect(getByText(/Error:/i)).toBeDefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(getByText(/Error:/i)).toBeDefined();
|
|
183
|
+
expect(getByText(/Failed to fetch branches/i)).toBeDefined();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should display branch icons correctly', async () => {
|
|
187
|
+
const mockBranches: BranchInfo[] = [
|
|
188
|
+
{
|
|
189
|
+
name: 'main',
|
|
190
|
+
type: 'local',
|
|
191
|
+
branchType: 'main',
|
|
192
|
+
isCurrent: true,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'feature/test',
|
|
196
|
+
type: 'local',
|
|
197
|
+
branchType: 'feature',
|
|
198
|
+
isCurrent: false,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'hotfix/urgent',
|
|
202
|
+
type: 'local',
|
|
203
|
+
branchType: 'hotfix',
|
|
204
|
+
isCurrent: false,
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
|
|
209
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
210
|
+
|
|
211
|
+
const onExit = vi.fn();
|
|
212
|
+
const { getByText } = render(<App onExit={onExit} />);
|
|
213
|
+
|
|
214
|
+
// Wait for async data loading
|
|
215
|
+
await waitFor(() => {
|
|
216
|
+
expect(getByText(/⚡/)).toBeDefined();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Check for branch type icons
|
|
220
|
+
expect(getByText(/⚡/)).toBeDefined(); // main icon
|
|
221
|
+
expect(getByText(/⭐/)).toBeDefined(); // current branch icon
|
|
222
|
+
expect(getByText(/✨/)).toBeDefined(); // feature icon
|
|
223
|
+
expect(getByText(/🔥/)).toBeDefined(); // hotfix icon
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should integrate all components correctly', async () => {
|
|
227
|
+
const mockBranches: BranchInfo[] = [
|
|
228
|
+
{
|
|
229
|
+
name: 'main',
|
|
230
|
+
type: 'local',
|
|
231
|
+
branchType: 'main',
|
|
232
|
+
isCurrent: true,
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
(getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
|
|
237
|
+
(listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
238
|
+
|
|
239
|
+
const onExit = vi.fn();
|
|
240
|
+
const { container } = render(<App onExit={onExit} />);
|
|
241
|
+
|
|
242
|
+
// Wait for rendering
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
expect(container.textContent).toContain('Claude Worktree');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Verify all major sections are present
|
|
248
|
+
expect(container.textContent).toContain('Claude Worktree'); // Header
|
|
249
|
+
expect(container.textContent).toContain('Local:'); // Stats
|
|
250
|
+
expect(container.textContent).toContain('main'); // Branch list
|
|
251
|
+
expect(container.textContent).toContain('Quit'); // Footer
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
* Edge case tests for UI components
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
|
|
6
|
+
import { render } from '@testing-library/react';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { App } from '../../components/App.js';
|
|
9
|
+
import { BranchListScreen } from '../../components/screens/BranchListScreen.js';
|
|
10
|
+
import { Window } from 'happy-dom';
|
|
11
|
+
import type { BranchInfo, BranchItem, Statistics } from '../../types.js';
|
|
12
|
+
import * as useGitDataModule from '../../hooks/useGitData.js';
|
|
13
|
+
|
|
14
|
+
const mockRefresh = vi.fn();
|
|
15
|
+
const originalUseGitData = useGitDataModule.useGitData;
|
|
16
|
+
const useGitDataSpy = vi.spyOn(useGitDataModule, 'useGitData');
|
|
17
|
+
|
|
18
|
+
describe('Edge Cases Integration Tests', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Setup happy-dom
|
|
21
|
+
const window = new Window();
|
|
22
|
+
globalThis.window = window as any;
|
|
23
|
+
globalThis.document = window.document as any;
|
|
24
|
+
|
|
25
|
+
// Reset mocks
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
useGitDataSpy.mockReset();
|
|
28
|
+
useGitDataSpy.mockImplementation(originalUseGitData);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* T091: Terminal size極小(10行以下)の動作確認
|
|
33
|
+
*/
|
|
34
|
+
it('[T091] should handle minimal terminal size (10 rows)', () => {
|
|
35
|
+
// Save original rows
|
|
36
|
+
const originalRows = process.stdout.rows;
|
|
37
|
+
|
|
38
|
+
// Set minimal terminal size
|
|
39
|
+
process.stdout.rows = 10;
|
|
40
|
+
|
|
41
|
+
const mockBranches: BranchItem[] = [
|
|
42
|
+
{ name: 'main', label: 'main', value: 'main' },
|
|
43
|
+
{ name: 'feature/a', label: 'feature/a', value: 'feature/a' },
|
|
44
|
+
{ name: 'feature/b', label: 'feature/b', value: 'feature/b' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const mockStats: Statistics = {
|
|
48
|
+
localCount: 3,
|
|
49
|
+
remoteCount: 0,
|
|
50
|
+
worktreeCount: 0,
|
|
51
|
+
changesCount: 0,
|
|
52
|
+
lastUpdated: new Date(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onSelect = vi.fn();
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Should render without crashing
|
|
61
|
+
expect(container).toBeDefined();
|
|
62
|
+
|
|
63
|
+
// Restore original rows
|
|
64
|
+
process.stdout.rows = originalRows;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('[T091] should handle extremely small terminal (5 rows)', () => {
|
|
68
|
+
const originalRows = process.stdout.rows;
|
|
69
|
+
process.stdout.rows = 5;
|
|
70
|
+
|
|
71
|
+
const mockBranches: BranchItem[] = [
|
|
72
|
+
{ name: 'main', label: 'main', value: 'main' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const mockStats: Statistics = {
|
|
76
|
+
localCount: 1,
|
|
77
|
+
remoteCount: 0,
|
|
78
|
+
worktreeCount: 0,
|
|
79
|
+
changesCount: 0,
|
|
80
|
+
lastUpdated: new Date(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onSelect = vi.fn();
|
|
84
|
+
const { getByText } = render(
|
|
85
|
+
<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Header should still be visible
|
|
89
|
+
expect(getByText(/gwt - Branch Selection/i)).toBeDefined();
|
|
90
|
+
|
|
91
|
+
process.stdout.rows = originalRows;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* T092: 非常に長いブランチ名の表示確認
|
|
96
|
+
*/
|
|
97
|
+
it('[T092] should handle very long branch names', () => {
|
|
98
|
+
const longBranchName =
|
|
99
|
+
'feature/very-long-branch-name-that-exceeds-normal-terminal-width-and-should-be-handled-gracefully';
|
|
100
|
+
|
|
101
|
+
const mockBranches: BranchItem[] = [
|
|
102
|
+
{
|
|
103
|
+
name: 'main',
|
|
104
|
+
label: 'main',
|
|
105
|
+
value: 'main',
|
|
106
|
+
latestCommitTimestamp: 1_700_000_000,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: longBranchName,
|
|
110
|
+
label: longBranchName,
|
|
111
|
+
value: longBranchName,
|
|
112
|
+
latestCommitTimestamp: 1_700_000_600,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const mockStats: Statistics = {
|
|
117
|
+
localCount: 2,
|
|
118
|
+
remoteCount: 0,
|
|
119
|
+
worktreeCount: 0,
|
|
120
|
+
changesCount: 0,
|
|
121
|
+
lastUpdated: new Date(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const onSelect = vi.fn();
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Long branch name should be displayed (Ink will handle wrapping/truncation)
|
|
130
|
+
expect(container.textContent).toMatch(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('[T092] should handle branch names with special characters', () => {
|
|
134
|
+
const specialBranchNames = [
|
|
135
|
+
'feature/bug-fix-#123',
|
|
136
|
+
'hotfix/issue@456',
|
|
137
|
+
'release/v1.0.0-beta.1',
|
|
138
|
+
'feature/改善-日本語',
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const mockBranches: BranchItem[] = specialBranchNames.map((name, index) => ({
|
|
142
|
+
name,
|
|
143
|
+
label: name,
|
|
144
|
+
value: name,
|
|
145
|
+
latestCommitTimestamp: 1_700_001_000 + index * 60,
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
const mockStats: Statistics = {
|
|
149
|
+
localCount: mockBranches.length,
|
|
150
|
+
remoteCount: 0,
|
|
151
|
+
worktreeCount: 0,
|
|
152
|
+
changesCount: 0,
|
|
153
|
+
lastUpdated: new Date(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const onSelect = vi.fn();
|
|
157
|
+
const { container } = render(
|
|
158
|
+
<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// All special branch names should be displayed
|
|
162
|
+
specialBranchNames.forEach((name) => {
|
|
163
|
+
expect(container.textContent).toContain(name);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* T093: Error Boundary動作確認
|
|
169
|
+
*/
|
|
170
|
+
it('[T093] should catch errors in App component', async () => {
|
|
171
|
+
// Mock useGitData to throw an error after initial render
|
|
172
|
+
let callCount = 0;
|
|
173
|
+
useGitDataSpy.mockImplementation(() => {
|
|
174
|
+
callCount++;
|
|
175
|
+
if (callCount > 1) {
|
|
176
|
+
throw new Error('Simulated error');
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
branches: [],
|
|
180
|
+
worktrees: [],
|
|
181
|
+
loading: false,
|
|
182
|
+
error: null,
|
|
183
|
+
refresh: mockRefresh,
|
|
184
|
+
lastUpdated: null,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const onExit = vi.fn();
|
|
189
|
+
const { container } = render(<App onExit={onExit} />);
|
|
190
|
+
|
|
191
|
+
// Initial render should work
|
|
192
|
+
expect(container).toBeDefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('[T093] should display error message when data loading fails', () => {
|
|
196
|
+
const testError = new Error('Test error: Failed to load Git data');
|
|
197
|
+
useGitDataSpy.mockReturnValue({
|
|
198
|
+
branches: [],
|
|
199
|
+
worktrees: [],
|
|
200
|
+
loading: false,
|
|
201
|
+
error: testError,
|
|
202
|
+
refresh: mockRefresh,
|
|
203
|
+
lastUpdated: null,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const onExit = vi.fn();
|
|
207
|
+
const { getByText } = render(<App onExit={onExit} />);
|
|
208
|
+
|
|
209
|
+
// Error should be displayed
|
|
210
|
+
expect(getByText(/Error:/i)).toBeDefined();
|
|
211
|
+
expect(getByText(/Failed to load Git data/i)).toBeDefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('[T093] should handle empty branches list gracefully', () => {
|
|
215
|
+
useGitDataSpy.mockReturnValue({
|
|
216
|
+
branches: [],
|
|
217
|
+
worktrees: [],
|
|
218
|
+
loading: false,
|
|
219
|
+
error: null,
|
|
220
|
+
refresh: mockRefresh,
|
|
221
|
+
lastUpdated: null,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const onExit = vi.fn();
|
|
225
|
+
const { container } = render(<App onExit={onExit} />);
|
|
226
|
+
|
|
227
|
+
// Should render without error even with no branches
|
|
228
|
+
expect(container).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Additional edge cases
|
|
233
|
+
*/
|
|
234
|
+
it('should handle large number of worktrees', () => {
|
|
235
|
+
const mockBranches: BranchInfo[] = Array.from({ length: 50 }, (_, i) => ({
|
|
236
|
+
name: `feature/branch-${i}`,
|
|
237
|
+
type: 'local' as const,
|
|
238
|
+
branchType: 'feature' as const,
|
|
239
|
+
isCurrent: false,
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
useGitDataSpy.mockReturnValue({
|
|
243
|
+
branches: mockBranches,
|
|
244
|
+
worktrees: Array.from({ length: 30 }, (_, i) => ({
|
|
245
|
+
branch: `feature/branch-${i}`,
|
|
246
|
+
path: `/path/to/worktree-${i}`,
|
|
247
|
+
head: `commit-${i}`,
|
|
248
|
+
isAccessible: true,
|
|
249
|
+
})),
|
|
250
|
+
loading: false,
|
|
251
|
+
error: null,
|
|
252
|
+
refresh: mockRefresh,
|
|
253
|
+
lastUpdated: new Date(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const onExit = vi.fn();
|
|
257
|
+
const { container } = render(<App onExit={onExit} />);
|
|
258
|
+
|
|
259
|
+
expect(container).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should handle terminal resize gracefully', () => {
|
|
263
|
+
const originalRows = process.stdout.rows;
|
|
264
|
+
|
|
265
|
+
// Start with normal size
|
|
266
|
+
process.stdout.rows = 30;
|
|
267
|
+
|
|
268
|
+
const mockBranches: BranchItem[] = [
|
|
269
|
+
{ name: 'main', label: 'main', value: 'main' },
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
const mockStats: Statistics = {
|
|
273
|
+
localCount: 1,
|
|
274
|
+
remoteCount: 0,
|
|
275
|
+
worktreeCount: 0,
|
|
276
|
+
changesCount: 0,
|
|
277
|
+
lastUpdated: new Date(),
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const onSelect = vi.fn();
|
|
281
|
+
const { container, rerender } = render(
|
|
282
|
+
<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(container).toBeDefined();
|
|
286
|
+
|
|
287
|
+
// Simulate terminal resize
|
|
288
|
+
process.stdout.rows = 15;
|
|
289
|
+
|
|
290
|
+
// Re-render
|
|
291
|
+
rerender(<BranchListScreen branches={mockBranches} stats={mockStats} onSelect={onSelect} />);
|
|
292
|
+
|
|
293
|
+
expect(container).toBeDefined();
|
|
294
|
+
|
|
295
|
+
process.stdout.rows = originalRows;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
afterEach(() => {
|
|
299
|
+
useGitDataSpy.mockReset();
|
|
300
|
+
useGitDataSpy.mockImplementation(originalUseGitData);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
afterAll(() => {
|
|
305
|
+
useGitDataSpy.mockRestore();
|
|
306
|
+
});
|