@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,180 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ink-testing-library';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { BranchListScreen } from '../../components/screens/BranchListScreen.js';
|
|
5
|
+
import type { BranchItem, Statistics } from '../../types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate mock branch items for performance testing
|
|
9
|
+
*/
|
|
10
|
+
function generateMockBranches(count: number): BranchItem[] {
|
|
11
|
+
const branches: BranchItem[] = [];
|
|
12
|
+
const types = ['feature', 'hotfix', 'release', 'other'] as const;
|
|
13
|
+
const branchTypes = ['main', 'develop', 'feature', 'hotfix', 'release', 'other'] as const;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < count; i++) {
|
|
16
|
+
const type = types[i % types.length];
|
|
17
|
+
const branchType = branchTypes[i % branchTypes.length];
|
|
18
|
+
const hasWorktree = i % 3 === 0;
|
|
19
|
+
|
|
20
|
+
branches.push({
|
|
21
|
+
name: `${type}/test-branch-${i.toString().padStart(4, '0')}`,
|
|
22
|
+
branchType,
|
|
23
|
+
type: i % 10 === 0 ? 'remote' : 'local',
|
|
24
|
+
isCurrent: i === 0,
|
|
25
|
+
worktree: hasWorktree
|
|
26
|
+
? {
|
|
27
|
+
path: `/mock/worktree/${type}-${i}`,
|
|
28
|
+
branch: `${type}/test-branch-${i.toString().padStart(4, '0')}`,
|
|
29
|
+
isAccessible: i % 5 !== 0, // Some inaccessible
|
|
30
|
+
}
|
|
31
|
+
: undefined,
|
|
32
|
+
worktreeStatus: hasWorktree ? (i % 5 !== 0 ? 'active' : 'inaccessible') : undefined,
|
|
33
|
+
hasChanges: i % 4 === 0,
|
|
34
|
+
icons: [],
|
|
35
|
+
label: `${type}/test-branch-${i.toString().padStart(4, '0')}`,
|
|
36
|
+
value: `${type}/test-branch-${i.toString().padStart(4, '0')}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return branches;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Unused for now - keeping for potential future use
|
|
44
|
+
// const mockStats: Statistics = {
|
|
45
|
+
// total: 0,
|
|
46
|
+
// local: 0,
|
|
47
|
+
// remote: 0,
|
|
48
|
+
// current: 0,
|
|
49
|
+
// feature: 0,
|
|
50
|
+
// hotfix: 0,
|
|
51
|
+
// release: 0,
|
|
52
|
+
// worktree: 0,
|
|
53
|
+
// };
|
|
54
|
+
|
|
55
|
+
describe('BranchListScreen Performance', () => {
|
|
56
|
+
it('should render 100+ branches within acceptable time', () => {
|
|
57
|
+
const branches = generateMockBranches(150);
|
|
58
|
+
const stats: Statistics = {
|
|
59
|
+
total: branches.length,
|
|
60
|
+
local: branches.filter((b) => b.type === 'local').length,
|
|
61
|
+
remote: branches.filter((b) => b.type === 'remote').length,
|
|
62
|
+
current: 1,
|
|
63
|
+
feature: branches.filter((b) => b.branchType === 'feature').length,
|
|
64
|
+
hotfix: branches.filter((b) => b.branchType === 'hotfix').length,
|
|
65
|
+
release: branches.filter((b) => b.branchType === 'release').length,
|
|
66
|
+
worktree: branches.filter((b) => b.worktree).length,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const startTime = performance.now();
|
|
70
|
+
|
|
71
|
+
const { unmount } = render(
|
|
72
|
+
<BranchListScreen
|
|
73
|
+
branches={branches}
|
|
74
|
+
stats={stats}
|
|
75
|
+
onSelect={() => {}}
|
|
76
|
+
onNavigate={() => {}}
|
|
77
|
+
onQuit={() => {}}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const renderTime = performance.now() - startTime;
|
|
82
|
+
|
|
83
|
+
unmount();
|
|
84
|
+
|
|
85
|
+
// Rendering should complete within 500ms (generous threshold)
|
|
86
|
+
expect(renderTime).toBeLessThan(500);
|
|
87
|
+
|
|
88
|
+
// Log performance metrics
|
|
89
|
+
console.log(`\n📊 Performance Test Results:`);
|
|
90
|
+
console.log(` Branches: ${branches.length}`);
|
|
91
|
+
console.log(` Render time: ${renderTime.toFixed(2)}ms`);
|
|
92
|
+
console.log(` Average per branch: ${(renderTime / branches.length).toFixed(3)}ms`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle re-render efficiently when stats update', () => {
|
|
96
|
+
const branches = generateMockBranches(100);
|
|
97
|
+
const stats: Statistics = {
|
|
98
|
+
total: branches.length,
|
|
99
|
+
local: branches.filter((b) => b.type === 'local').length,
|
|
100
|
+
remote: branches.filter((b) => b.type === 'remote').length,
|
|
101
|
+
current: 1,
|
|
102
|
+
feature: branches.filter((b) => b.branchType === 'feature').length,
|
|
103
|
+
hotfix: branches.filter((b) => b.branchType === 'hotfix').length,
|
|
104
|
+
release: branches.filter((b) => b.branchType === 'release').length,
|
|
105
|
+
worktree: branches.filter((b) => b.worktree).length,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const { rerender, unmount } = render(
|
|
109
|
+
<BranchListScreen
|
|
110
|
+
branches={branches}
|
|
111
|
+
stats={stats}
|
|
112
|
+
onSelect={() => {}}
|
|
113
|
+
onNavigate={() => {}}
|
|
114
|
+
onQuit={() => {}}
|
|
115
|
+
lastUpdated={new Date()}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Simulate stats update (real-time refresh)
|
|
120
|
+
const startTime = performance.now();
|
|
121
|
+
|
|
122
|
+
rerender(
|
|
123
|
+
<BranchListScreen
|
|
124
|
+
branches={branches}
|
|
125
|
+
stats={{ ...stats, total: stats.total + 1 }}
|
|
126
|
+
onSelect={() => {}}
|
|
127
|
+
onNavigate={() => {}}
|
|
128
|
+
onQuit={() => {}}
|
|
129
|
+
lastUpdated={new Date()}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const rerenderTime = performance.now() - startTime;
|
|
134
|
+
|
|
135
|
+
unmount();
|
|
136
|
+
|
|
137
|
+
// Re-render should be very fast (< 100ms)
|
|
138
|
+
expect(rerenderTime).toBeLessThan(100);
|
|
139
|
+
|
|
140
|
+
console.log(`\n🔄 Re-render Performance:`);
|
|
141
|
+
console.log(` Re-render time: ${rerenderTime.toFixed(2)}ms`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should handle large branch list (200+ branches)', () => {
|
|
145
|
+
const branches = generateMockBranches(250);
|
|
146
|
+
const stats: Statistics = {
|
|
147
|
+
total: branches.length,
|
|
148
|
+
local: branches.filter((b) => b.type === 'local').length,
|
|
149
|
+
remote: branches.filter((b) => b.type === 'remote').length,
|
|
150
|
+
current: 1,
|
|
151
|
+
feature: branches.filter((b) => b.branchType === 'feature').length,
|
|
152
|
+
hotfix: branches.filter((b) => b.branchType === 'hotfix').length,
|
|
153
|
+
release: branches.filter((b) => b.branchType === 'release').length,
|
|
154
|
+
worktree: branches.filter((b) => b.worktree).length,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const startTime = performance.now();
|
|
158
|
+
|
|
159
|
+
const { unmount } = render(
|
|
160
|
+
<BranchListScreen
|
|
161
|
+
branches={branches}
|
|
162
|
+
stats={stats}
|
|
163
|
+
onSelect={() => {}}
|
|
164
|
+
onNavigate={() => {}}
|
|
165
|
+
onQuit={() => {}}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const renderTime = performance.now() - startTime;
|
|
170
|
+
|
|
171
|
+
unmount();
|
|
172
|
+
|
|
173
|
+
// Even with 250+ branches, should render within 1 second
|
|
174
|
+
expect(renderTime).toBeLessThan(1000);
|
|
175
|
+
|
|
176
|
+
console.log(`\n🚀 Large Branch List Performance:`);
|
|
177
|
+
console.log(` Branches: ${branches.length}`);
|
|
178
|
+
console.log(` Render time: ${renderTime.toFixed(2)}ms`);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
5
|
+
import { render } from '@testing-library/react';
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { Window } from 'happy-dom';
|
|
8
|
+
import type { BranchInfo, WorktreeInfo } from '../../types.js';
|
|
9
|
+
import { formatBranchItems } from '../../utils/branchFormatter.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* T082-1: useMemo optimization tests
|
|
13
|
+
* Tests that branchItems are not unnecessarily regenerated when data content is the same
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Mock useGitData hook
|
|
17
|
+
vi.mock('../../hooks/useGitData.js', () => ({
|
|
18
|
+
useGitData: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { useGitData } from '../../hooks/useGitData.js';
|
|
22
|
+
// const mockUseGitData = useGitData as ReturnType<typeof vi.fn>;
|
|
23
|
+
|
|
24
|
+
// Helper function to create a stable hash of branch data
|
|
25
|
+
function createBranchHash(branches: BranchInfo[]): string {
|
|
26
|
+
return branches
|
|
27
|
+
.map((b) => `${b.name}-${b.type}-${b.isCurrent}`)
|
|
28
|
+
.join(',');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Helper function to create a stable hash of worktree data
|
|
32
|
+
function createWorktreeHash(worktrees: WorktreeInfo[]): string {
|
|
33
|
+
return worktrees
|
|
34
|
+
.map((w) => `${w.branch}-${w.path}`)
|
|
35
|
+
.join(',');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Test component that uses optimized useMemo
|
|
39
|
+
function TestComponent({ branches, worktrees }: { branches: BranchInfo[]; worktrees: WorktreeInfo[] }) {
|
|
40
|
+
// Count how many times formatBranchItems is called
|
|
41
|
+
const formatCallCount = React.useRef(0);
|
|
42
|
+
|
|
43
|
+
// Optimized useMemo with content-based dependencies
|
|
44
|
+
const branchItems = useMemo(() => {
|
|
45
|
+
formatCallCount.current++;
|
|
46
|
+
const worktreeMap = new Map();
|
|
47
|
+
for (const wt of worktrees) {
|
|
48
|
+
worktreeMap.set(wt.branch, {
|
|
49
|
+
path: wt.path,
|
|
50
|
+
locked: false,
|
|
51
|
+
prunable: wt.isAccessible === false,
|
|
52
|
+
isAccessible: wt.isAccessible ?? true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return formatBranchItems(branches, worktreeMap);
|
|
56
|
+
}, [
|
|
57
|
+
createBranchHash(branches),
|
|
58
|
+
createWorktreeHash(worktrees),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div data-testid="branch-count">{branchItems.length}</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe('useMemo Optimization (T082-1)', () => {
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
const window = new Window();
|
|
69
|
+
globalThis.window = window as any;
|
|
70
|
+
globalThis.document = window.document as any;
|
|
71
|
+
vi.clearAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should not regenerate branchItems when data content is the same', () => {
|
|
75
|
+
const mockBranches: BranchInfo[] = [
|
|
76
|
+
{
|
|
77
|
+
name: 'main',
|
|
78
|
+
branchType: 'main',
|
|
79
|
+
type: 'local',
|
|
80
|
+
isCurrent: true,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'feature/test-1',
|
|
84
|
+
branchType: 'feature',
|
|
85
|
+
type: 'local',
|
|
86
|
+
isCurrent: false,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const mockWorktrees: WorktreeInfo[] = [
|
|
91
|
+
{
|
|
92
|
+
path: '/mock/worktree/feature-test-1',
|
|
93
|
+
branch: 'feature/test-1',
|
|
94
|
+
isAccessible: true,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const { rerender } = render(
|
|
99
|
+
<TestComponent branches={mockBranches} worktrees={mockWorktrees} />
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Create new arrays with the same content
|
|
103
|
+
const sameBranches: BranchInfo[] = [
|
|
104
|
+
{
|
|
105
|
+
name: 'main',
|
|
106
|
+
branchType: 'main',
|
|
107
|
+
type: 'local',
|
|
108
|
+
isCurrent: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'feature/test-1',
|
|
112
|
+
branchType: 'feature',
|
|
113
|
+
type: 'local',
|
|
114
|
+
isCurrent: false,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const sameWorktrees: WorktreeInfo[] = [
|
|
119
|
+
{
|
|
120
|
+
path: '/mock/worktree/feature-test-1',
|
|
121
|
+
branch: 'feature/test-1',
|
|
122
|
+
isAccessible: true,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// Verify that arrays are different references
|
|
127
|
+
expect(sameBranches).not.toBe(mockBranches);
|
|
128
|
+
expect(sameWorktrees).not.toBe(mockWorktrees);
|
|
129
|
+
|
|
130
|
+
// Verify that content is the same
|
|
131
|
+
expect(createBranchHash(sameBranches)).toBe(createBranchHash(mockBranches));
|
|
132
|
+
expect(createWorktreeHash(sameWorktrees)).toBe(createWorktreeHash(mockWorktrees));
|
|
133
|
+
|
|
134
|
+
// Re-render with same content but different references
|
|
135
|
+
rerender(
|
|
136
|
+
<TestComponent branches={sameBranches} worktrees={sameWorktrees} />
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// formatBranchItems should only be called once (not twice)
|
|
140
|
+
// This test would fail with the current implementation because useMemo
|
|
141
|
+
// depends on array references, not content
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should regenerate branchItems when data content changes', () => {
|
|
145
|
+
const initialBranches: BranchInfo[] = [
|
|
146
|
+
{
|
|
147
|
+
name: 'main',
|
|
148
|
+
branchType: 'main',
|
|
149
|
+
type: 'local',
|
|
150
|
+
isCurrent: true,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const { rerender } = render(
|
|
155
|
+
<TestComponent branches={initialBranches} worktrees={[]} />
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Add a new branch
|
|
159
|
+
const updatedBranches: BranchInfo[] = [
|
|
160
|
+
...initialBranches,
|
|
161
|
+
{
|
|
162
|
+
name: 'feature/new',
|
|
163
|
+
branchType: 'feature',
|
|
164
|
+
type: 'local',
|
|
165
|
+
isCurrent: false,
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
// Verify content changed
|
|
170
|
+
expect(createBranchHash(updatedBranches)).not.toBe(createBranchHash(initialBranches));
|
|
171
|
+
|
|
172
|
+
// Re-render with new content
|
|
173
|
+
rerender(
|
|
174
|
+
<TestComponent branches={updatedBranches} worktrees={[]} />
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// formatBranchItems should be called twice (once for initial, once for update)
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle branch order changes correctly', () => {
|
|
181
|
+
const branches1: BranchInfo[] = [
|
|
182
|
+
{
|
|
183
|
+
name: 'main',
|
|
184
|
+
branchType: 'main',
|
|
185
|
+
type: 'local',
|
|
186
|
+
isCurrent: true,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'feature/test',
|
|
190
|
+
branchType: 'feature',
|
|
191
|
+
type: 'local',
|
|
192
|
+
isCurrent: false,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const branches2: BranchInfo[] = [
|
|
197
|
+
{
|
|
198
|
+
name: 'feature/test',
|
|
199
|
+
branchType: 'feature',
|
|
200
|
+
type: 'local',
|
|
201
|
+
isCurrent: false,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'main',
|
|
205
|
+
branchType: 'main',
|
|
206
|
+
type: 'local',
|
|
207
|
+
isCurrent: true,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// Different order should produce different hashes
|
|
212
|
+
expect(createBranchHash(branches1)).not.toBe(createBranchHash(branches2));
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should detect subtle branch property changes', () => {
|
|
216
|
+
const branches1: BranchInfo[] = [
|
|
217
|
+
{
|
|
218
|
+
name: 'main',
|
|
219
|
+
branchType: 'main',
|
|
220
|
+
type: 'local',
|
|
221
|
+
isCurrent: false,
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
const branches2: BranchInfo[] = [
|
|
226
|
+
{
|
|
227
|
+
name: 'main',
|
|
228
|
+
branchType: 'main',
|
|
229
|
+
type: 'local',
|
|
230
|
+
isCurrent: true, // Changed from false to true
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
// isCurrent change should be detected
|
|
235
|
+
expect(createBranchHash(branches1)).not.toBe(createBranchHash(branches2));
|
|
236
|
+
});
|
|
237
|
+
});
|