@akiojin/gwt 2.2.0 → 2.4.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 +6 -4
- package/README.md +6 -4
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +6 -3
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +6 -4
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +184 -107
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Confirm.js +7 -7
- package/dist/cli/ui/components/common/Confirm.js.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
- package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +2 -2
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Input.js +4 -4
- package/dist/cli/ui/components/common/Input.js.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
- package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +1 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +11 -12
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +3 -3
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +11 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
- package/dist/cli/ui/utils/modelOptions.js +111 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -0
- package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
- package/dist/client/index.html +1 -1
- package/dist/codex.d.ts +6 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +11 -4
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts +10 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +40 -4
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +4 -3
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts +13 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +157 -0
- package/dist/gemini.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -7
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +13 -0
- package/dist/qwen.d.ts.map +1 -0
- package/dist/qwen.js +157 -0
- package/dist/qwen.js.map +1 -0
- package/dist/services/git.service.d.ts.map +1 -1
- package/dist/services/git.service.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +1 -1
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +7 -4
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +10 -4
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +8 -3
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
- package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +63 -43
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
- package/src/cli/ui/components/App.tsx +317 -150
- package/src/cli/ui/components/common/Confirm.tsx +13 -9
- package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
- package/src/cli/ui/components/common/Input.tsx +12 -4
- package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
- package/src/cli/ui/components/common/Select.tsx +28 -17
- package/src/cli/ui/components/parts/Header.test.tsx +5 -15
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +20 -15
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
- package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
- package/src/cli/ui/types.ts +21 -1
- package/src/cli/ui/utils/modelOptions.test.ts +36 -0
- package/src/cli/ui/utils/modelOptions.ts +122 -0
- package/src/codex.ts +23 -4
- package/src/config/builtin-tools.ts +42 -4
- package/src/config/index.ts +2 -12
- package/src/config/tools.ts +16 -6
- package/src/gemini.ts +207 -0
- package/src/git.ts +2 -1
- package/src/index.ts +86 -6
- package/src/qwen.ts +213 -0
- package/src/services/git.service.ts +2 -1
- package/src/web/client/src/components/BranchGraph.tsx +3 -2
- package/src/web/client/src/components/EnvEditor.tsx +44 -11
- package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
- package/src/web/client/src/pages/BranchListPage.tsx +37 -13
- package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
describe,
|
|
6
|
+
it,
|
|
7
|
+
expect,
|
|
8
|
+
beforeEach,
|
|
9
|
+
afterEach,
|
|
10
|
+
afterAll,
|
|
11
|
+
vi,
|
|
12
|
+
} from "vitest";
|
|
13
|
+
import type { Mock } from "vitest";
|
|
14
|
+
import { render, act, waitFor } from "@testing-library/react";
|
|
15
|
+
import React from "react";
|
|
16
|
+
import type { BranchItem, CleanupTarget } from "../../types.js";
|
|
17
|
+
import { Window } from "happy-dom";
|
|
18
|
+
import * as useGitDataModule from "../../hooks/useGitData.js";
|
|
19
|
+
import * as useScreenStateModule from "../../hooks/useScreenState.js";
|
|
20
|
+
import * as WorktreeManagerScreenModule from "../../components/screens/WorktreeManagerScreen.js";
|
|
21
|
+
import * as BranchCreatorScreenModule from "../../components/screens/BranchCreatorScreen.js";
|
|
22
|
+
import * as BranchListScreenModule from "../../components/screens/BranchListScreen.js";
|
|
23
|
+
import * as worktreeModule from "../../../../worktree.ts";
|
|
24
|
+
import * as gitModule from "../../../../git.ts";
|
|
25
|
+
import { App } from "../../components/App.js";
|
|
18
26
|
|
|
19
27
|
const navigateToMock = vi.fn();
|
|
20
28
|
const goBackMock = vi.fn();
|
|
@@ -26,8 +34,10 @@ const branchListProps: any[] = [];
|
|
|
26
34
|
|
|
27
35
|
const originalUseGitData = useGitDataModule.useGitData;
|
|
28
36
|
const originalUseScreenState = useScreenStateModule.useScreenState;
|
|
29
|
-
const originalWorktreeManagerScreen =
|
|
30
|
-
|
|
37
|
+
const originalWorktreeManagerScreen =
|
|
38
|
+
WorktreeManagerScreenModule.WorktreeManagerScreen;
|
|
39
|
+
const originalBranchCreatorScreen =
|
|
40
|
+
BranchCreatorScreenModule.BranchCreatorScreen;
|
|
31
41
|
const originalBranchListScreen = BranchListScreenModule.BranchListScreen;
|
|
32
42
|
const originalGetMergedPRWorktrees = worktreeModule.getMergedPRWorktrees;
|
|
33
43
|
const originalGenerateWorktreePath = worktreeModule.generateWorktreePath;
|
|
@@ -36,21 +46,36 @@ const originalRemoveWorktree = worktreeModule.removeWorktree;
|
|
|
36
46
|
const originalGetRepositoryRoot = gitModule.getRepositoryRoot;
|
|
37
47
|
const originalDeleteBranch = gitModule.deleteBranch;
|
|
38
48
|
|
|
39
|
-
const useGitDataSpy = vi.spyOn(useGitDataModule,
|
|
40
|
-
const useScreenStateSpy = vi.spyOn(useScreenStateModule,
|
|
41
|
-
const worktreeManagerScreenSpy = vi.spyOn(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
|
|
50
|
+
const useScreenStateSpy = vi.spyOn(useScreenStateModule, "useScreenState");
|
|
51
|
+
const worktreeManagerScreenSpy = vi.spyOn(
|
|
52
|
+
WorktreeManagerScreenModule,
|
|
53
|
+
"WorktreeManagerScreen",
|
|
54
|
+
);
|
|
55
|
+
const branchCreatorScreenSpy = vi.spyOn(
|
|
56
|
+
BranchCreatorScreenModule,
|
|
57
|
+
"BranchCreatorScreen",
|
|
58
|
+
);
|
|
59
|
+
const branchListScreenSpy = vi.spyOn(
|
|
60
|
+
BranchListScreenModule,
|
|
61
|
+
"BranchListScreen",
|
|
62
|
+
);
|
|
63
|
+
const getMergedPRWorktreesSpy = vi.spyOn(
|
|
64
|
+
worktreeModule,
|
|
65
|
+
"getMergedPRWorktrees",
|
|
66
|
+
);
|
|
67
|
+
const generateWorktreePathSpy = vi.spyOn(
|
|
68
|
+
worktreeModule,
|
|
69
|
+
"generateWorktreePath",
|
|
70
|
+
);
|
|
71
|
+
const createWorktreeSpy = vi.spyOn(worktreeModule, "createWorktree");
|
|
72
|
+
const removeWorktreeSpy = vi.spyOn(worktreeModule, "removeWorktree");
|
|
73
|
+
const getRepositoryRootSpy = vi.spyOn(gitModule, "getRepositoryRoot");
|
|
74
|
+
const deleteBranchSpy = vi.spyOn(gitModule, "deleteBranch");
|
|
75
|
+
|
|
76
|
+
describe("App shortcuts integration", () => {
|
|
52
77
|
beforeEach(() => {
|
|
53
|
-
if (typeof globalThis.document ===
|
|
78
|
+
if (typeof globalThis.document === "undefined") {
|
|
54
79
|
const window = new Window();
|
|
55
80
|
globalThis.window = window as any;
|
|
56
81
|
globalThis.document = window.document as any;
|
|
@@ -65,8 +90,8 @@ describe('App shortcuts integration', () => {
|
|
|
65
90
|
branches: [],
|
|
66
91
|
worktrees: [
|
|
67
92
|
{
|
|
68
|
-
branch:
|
|
69
|
-
path:
|
|
93
|
+
branch: "feature/existing",
|
|
94
|
+
path: "/worktrees/feature-existing",
|
|
70
95
|
isAccessible: true,
|
|
71
96
|
},
|
|
72
97
|
],
|
|
@@ -76,7 +101,7 @@ describe('App shortcuts integration', () => {
|
|
|
76
101
|
lastUpdated: null,
|
|
77
102
|
}));
|
|
78
103
|
useScreenStateSpy.mockImplementation(() => ({
|
|
79
|
-
currentScreen:
|
|
104
|
+
currentScreen: "worktree-manager",
|
|
80
105
|
navigateTo: navigateToMock as _Mock,
|
|
81
106
|
goBack: goBackMock as _Mock,
|
|
82
107
|
reset: resetMock as _Mock,
|
|
@@ -95,42 +120,42 @@ describe('App shortcuts integration', () => {
|
|
|
95
120
|
});
|
|
96
121
|
getMergedPRWorktreesSpy.mockResolvedValue([
|
|
97
122
|
{
|
|
98
|
-
branch:
|
|
99
|
-
cleanupType:
|
|
123
|
+
branch: "feature/add-new-feature",
|
|
124
|
+
cleanupType: "worktree-and-branch",
|
|
100
125
|
pullRequest: {
|
|
101
126
|
number: 123,
|
|
102
|
-
title:
|
|
103
|
-
branch:
|
|
104
|
-
mergedAt:
|
|
105
|
-
author:
|
|
127
|
+
title: "Add new feature",
|
|
128
|
+
branch: "feature/add-new-feature",
|
|
129
|
+
mergedAt: "2025-01-20T10:00:00Z",
|
|
130
|
+
author: "user1",
|
|
106
131
|
},
|
|
107
|
-
worktreePath:
|
|
132
|
+
worktreePath: "/worktrees/feature-add-new-feature",
|
|
108
133
|
hasUncommittedChanges: false,
|
|
109
134
|
hasUnpushedCommits: false,
|
|
110
135
|
hasRemoteBranch: true,
|
|
111
136
|
isAccessible: true,
|
|
112
137
|
},
|
|
113
138
|
{
|
|
114
|
-
branch:
|
|
115
|
-
cleanupType:
|
|
139
|
+
branch: "hotfix/urgent-fix",
|
|
140
|
+
cleanupType: "worktree-and-branch",
|
|
116
141
|
pullRequest: {
|
|
117
142
|
number: 456,
|
|
118
|
-
title:
|
|
119
|
-
branch:
|
|
120
|
-
mergedAt:
|
|
121
|
-
author:
|
|
143
|
+
title: "Urgent fix",
|
|
144
|
+
branch: "hotfix/urgent-fix",
|
|
145
|
+
mergedAt: "2025-01-21T09:00:00Z",
|
|
146
|
+
author: "user2",
|
|
122
147
|
},
|
|
123
|
-
worktreePath:
|
|
148
|
+
worktreePath: "/worktrees/hotfix-urgent-fix",
|
|
124
149
|
hasUncommittedChanges: true,
|
|
125
150
|
hasUnpushedCommits: false,
|
|
126
151
|
hasRemoteBranch: true,
|
|
127
152
|
isAccessible: true,
|
|
128
153
|
},
|
|
129
154
|
] as CleanupTarget[]);
|
|
130
|
-
generateWorktreePathSpy.mockResolvedValue(
|
|
155
|
+
generateWorktreePathSpy.mockResolvedValue("/worktrees/new-branch");
|
|
131
156
|
createWorktreeSpy.mockResolvedValue(undefined);
|
|
132
157
|
removeWorktreeSpy.mockResolvedValue(undefined);
|
|
133
|
-
getRepositoryRootSpy.mockResolvedValue(
|
|
158
|
+
getRepositoryRootSpy.mockResolvedValue("/repo");
|
|
134
159
|
deleteBranchSpy.mockResolvedValue(undefined);
|
|
135
160
|
});
|
|
136
161
|
|
|
@@ -148,18 +173,26 @@ describe('App shortcuts integration', () => {
|
|
|
148
173
|
deleteBranchSpy.mockReset();
|
|
149
174
|
useGitDataSpy.mockImplementation(originalUseGitData);
|
|
150
175
|
useScreenStateSpy.mockImplementation(originalUseScreenState);
|
|
151
|
-
worktreeManagerScreenSpy.mockImplementation(
|
|
152
|
-
|
|
176
|
+
worktreeManagerScreenSpy.mockImplementation(
|
|
177
|
+
originalWorktreeManagerScreen as any,
|
|
178
|
+
);
|
|
179
|
+
branchCreatorScreenSpy.mockImplementation(
|
|
180
|
+
originalBranchCreatorScreen as any,
|
|
181
|
+
);
|
|
153
182
|
branchListScreenSpy.mockImplementation(originalBranchListScreen as any);
|
|
154
|
-
getMergedPRWorktreesSpy.mockImplementation(
|
|
155
|
-
|
|
183
|
+
getMergedPRWorktreesSpy.mockImplementation(
|
|
184
|
+
originalGetMergedPRWorktrees as any,
|
|
185
|
+
);
|
|
186
|
+
generateWorktreePathSpy.mockImplementation(
|
|
187
|
+
originalGenerateWorktreePath as any,
|
|
188
|
+
);
|
|
156
189
|
createWorktreeSpy.mockImplementation(originalCreateWorktree as any);
|
|
157
190
|
removeWorktreeSpy.mockImplementation(originalRemoveWorktree as any);
|
|
158
191
|
getRepositoryRootSpy.mockImplementation(originalGetRepositoryRoot as any);
|
|
159
192
|
deleteBranchSpy.mockImplementation(originalDeleteBranch as any);
|
|
160
193
|
});
|
|
161
194
|
|
|
162
|
-
it(
|
|
195
|
+
it("navigates to AI tool selector when worktree is selected", () => {
|
|
163
196
|
const onExit = vi.fn();
|
|
164
197
|
render(<App onExit={onExit} />);
|
|
165
198
|
|
|
@@ -169,15 +202,15 @@ describe('App shortcuts integration', () => {
|
|
|
169
202
|
|
|
170
203
|
onSelect(worktrees[0]);
|
|
171
204
|
|
|
172
|
-
expect(navigateToMock).toHaveBeenCalledWith(
|
|
205
|
+
expect(navigateToMock).toHaveBeenCalledWith("ai-tool-selector");
|
|
173
206
|
});
|
|
174
207
|
|
|
175
|
-
it(
|
|
208
|
+
it("creates new worktree when branch creator submits", async () => {
|
|
176
209
|
const onExit = vi.fn();
|
|
177
210
|
|
|
178
211
|
// Update screen state mock to branch-creator for this test
|
|
179
212
|
useScreenStateSpy.mockReturnValue({
|
|
180
|
-
currentScreen:
|
|
213
|
+
currentScreen: "branch-creator",
|
|
181
214
|
navigateTo: navigateToMock as _Mock,
|
|
182
215
|
goBack: goBackMock as _Mock,
|
|
183
216
|
reset: resetMock as _Mock,
|
|
@@ -189,19 +222,19 @@ describe('App shortcuts integration', () => {
|
|
|
189
222
|
const { onCreate } = branchCreatorProps[0];
|
|
190
223
|
|
|
191
224
|
await act(async () => {
|
|
192
|
-
await onCreate(
|
|
225
|
+
await onCreate("feature/new-branch");
|
|
193
226
|
});
|
|
194
227
|
|
|
195
228
|
expect(createWorktreeSpy).toHaveBeenCalledWith(
|
|
196
229
|
expect.objectContaining({
|
|
197
|
-
branchName:
|
|
230
|
+
branchName: "feature/new-branch",
|
|
198
231
|
isNewBranch: true,
|
|
199
|
-
})
|
|
232
|
+
}),
|
|
200
233
|
);
|
|
201
|
-
expect(navigateToMock).toHaveBeenCalledWith(
|
|
234
|
+
expect(navigateToMock).toHaveBeenCalledWith("ai-tool-selector");
|
|
202
235
|
});
|
|
203
236
|
|
|
204
|
-
it(
|
|
237
|
+
it("displays per-branch cleanup indicators and waits before clearing results", async () => {
|
|
205
238
|
vi.useFakeTimers();
|
|
206
239
|
|
|
207
240
|
try {
|
|
@@ -214,18 +247,18 @@ describe('App shortcuts integration', () => {
|
|
|
214
247
|
() =>
|
|
215
248
|
new Promise<void>((resolve) => {
|
|
216
249
|
resolveRemoveWorktree = resolve;
|
|
217
|
-
})
|
|
250
|
+
}),
|
|
218
251
|
);
|
|
219
252
|
|
|
220
253
|
deleteBranchSpy.mockImplementationOnce(
|
|
221
254
|
() =>
|
|
222
255
|
new Promise<void>((resolve) => {
|
|
223
256
|
resolveDeleteBranch = resolve;
|
|
224
|
-
})
|
|
257
|
+
}),
|
|
225
258
|
);
|
|
226
259
|
|
|
227
260
|
useScreenStateSpy.mockReturnValue({
|
|
228
|
-
currentScreen:
|
|
261
|
+
currentScreen: "branch-list",
|
|
229
262
|
navigateTo: navigateToMock as _Mock,
|
|
230
263
|
goBack: goBackMock as _Mock,
|
|
231
264
|
reset: resetMock as _Mock,
|
|
@@ -237,7 +270,7 @@ describe('App shortcuts integration', () => {
|
|
|
237
270
|
const initialProps = branchListProps.at(-1);
|
|
238
271
|
expect(initialProps).toBeDefined();
|
|
239
272
|
if (!initialProps) {
|
|
240
|
-
throw new Error(
|
|
273
|
+
throw new Error("BranchListScreen props missing");
|
|
241
274
|
}
|
|
242
275
|
|
|
243
276
|
act(() => {
|
|
@@ -252,8 +285,10 @@ describe('App shortcuts integration', () => {
|
|
|
252
285
|
expect(latestProps?.cleanupUI?.inputLocked).toBe(true);
|
|
253
286
|
expect(latestProps?.cleanupUI?.footerMessage?.text).toBeTruthy();
|
|
254
287
|
expect(latestProps?.cleanupUI?.indicators).toMatchObject({
|
|
255
|
-
|
|
256
|
-
|
|
288
|
+
"feature/add-new-feature": expect.objectContaining({
|
|
289
|
+
icon: expect.stringMatching(/⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧/),
|
|
290
|
+
}),
|
|
291
|
+
"hotfix/urgent-fix": expect.objectContaining({ icon: "⏳" }),
|
|
257
292
|
});
|
|
258
293
|
|
|
259
294
|
resolveRemoveWorktree?.();
|
|
@@ -265,10 +300,13 @@ describe('App shortcuts integration', () => {
|
|
|
265
300
|
resolveDeleteBranch?.();
|
|
266
301
|
|
|
267
302
|
expect(removeWorktreeSpy).toHaveBeenCalledWith(
|
|
268
|
-
|
|
269
|
-
true
|
|
303
|
+
"/worktrees/feature-add-new-feature",
|
|
304
|
+
true,
|
|
305
|
+
);
|
|
306
|
+
expect(deleteBranchSpy).toHaveBeenCalledWith(
|
|
307
|
+
"feature/add-new-feature",
|
|
308
|
+
true,
|
|
270
309
|
);
|
|
271
|
-
expect(deleteBranchSpy).toHaveBeenCalledWith('feature/add-new-feature', true);
|
|
272
310
|
|
|
273
311
|
// Flush state updates after processing first target
|
|
274
312
|
await act(async () => {
|
|
@@ -277,8 +315,8 @@ describe('App shortcuts integration', () => {
|
|
|
277
315
|
|
|
278
316
|
latestProps = branchListProps.at(-1);
|
|
279
317
|
expect(latestProps?.cleanupUI?.indicators).toMatchObject({
|
|
280
|
-
|
|
281
|
-
|
|
318
|
+
"feature/add-new-feature": { icon: "✅" },
|
|
319
|
+
"hotfix/urgent-fix": { icon: "⏭️" },
|
|
282
320
|
});
|
|
283
321
|
expect(latestProps?.cleanupUI?.inputLocked).toBe(false);
|
|
284
322
|
|
|
@@ -291,7 +329,11 @@ describe('App shortcuts integration', () => {
|
|
|
291
329
|
latestProps = branchListProps.at(-1);
|
|
292
330
|
expect(latestProps?.cleanupUI?.indicators).toEqual({});
|
|
293
331
|
expect(latestProps?.cleanupUI?.inputLocked).toBe(false);
|
|
294
|
-
expect(
|
|
332
|
+
expect(
|
|
333
|
+
latestProps?.branches?.some(
|
|
334
|
+
(branch: BranchItem) => branch.name === "feature/add-new-feature",
|
|
335
|
+
),
|
|
336
|
+
).toBe(false);
|
|
295
337
|
} finally {
|
|
296
338
|
vi.useRealTimers();
|
|
297
339
|
}
|
|
@@ -303,7 +345,7 @@ afterAll(() => {
|
|
|
303
345
|
useScreenStateSpy.mockRestore();
|
|
304
346
|
worktreeManagerScreenSpy.mockRestore();
|
|
305
347
|
branchCreatorScreenSpy.mockRestore();
|
|
306
|
-
|
|
348
|
+
branchListScreenSpy.mockRestore();
|
|
307
349
|
getMergedPRWorktreesSpy.mockRestore();
|
|
308
350
|
generateWorktreePathSpy.mockRestore();
|
|
309
351
|
createWorktreeSpy.mockRestore();
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import {
|
|
5
|
+
describe,
|
|
6
|
+
it,
|
|
7
|
+
expect,
|
|
8
|
+
beforeEach,
|
|
9
|
+
afterEach,
|
|
10
|
+
afterAll,
|
|
11
|
+
vi,
|
|
12
|
+
} from "vitest";
|
|
13
|
+
import { act, render } from "@testing-library/react";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { App } from "../../components/App.js";
|
|
16
|
+
import { Window } from "happy-dom";
|
|
17
|
+
import type { BranchInfo } from "../../types.js";
|
|
18
|
+
import * as useGitDataModule from "../../hooks/useGitData.js";
|
|
11
19
|
|
|
12
20
|
const mockRefresh = vi.fn();
|
|
13
21
|
const originalUseGitData = useGitDataModule.useGitData;
|
|
14
|
-
const useGitDataSpy = vi.spyOn(useGitDataModule,
|
|
22
|
+
const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
|
|
15
23
|
|
|
16
|
-
describe(
|
|
24
|
+
describe("App", () => {
|
|
17
25
|
beforeEach(() => {
|
|
18
26
|
// Setup happy-dom
|
|
19
27
|
const window = new Window();
|
|
@@ -27,20 +35,20 @@ describe('App', () => {
|
|
|
27
35
|
|
|
28
36
|
const mockBranches: BranchInfo[] = [
|
|
29
37
|
{
|
|
30
|
-
name:
|
|
31
|
-
type:
|
|
32
|
-
branchType:
|
|
38
|
+
name: "main",
|
|
39
|
+
type: "local",
|
|
40
|
+
branchType: "main",
|
|
33
41
|
isCurrent: true,
|
|
34
42
|
},
|
|
35
43
|
{
|
|
36
|
-
name:
|
|
37
|
-
type:
|
|
38
|
-
branchType:
|
|
44
|
+
name: "feature/test",
|
|
45
|
+
type: "local",
|
|
46
|
+
branchType: "feature",
|
|
39
47
|
isCurrent: false,
|
|
40
48
|
},
|
|
41
49
|
];
|
|
42
50
|
|
|
43
|
-
it(
|
|
51
|
+
it("should render BranchListScreen when data is loaded", () => {
|
|
44
52
|
useGitDataSpy.mockReturnValue({
|
|
45
53
|
branches: mockBranches,
|
|
46
54
|
loading: false,
|
|
@@ -58,7 +66,7 @@ describe('App', () => {
|
|
|
58
66
|
expect(getByText(/feature\/test/)).toBeDefined();
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
it(
|
|
69
|
+
it("should show loading state initially", async () => {
|
|
62
70
|
useGitDataSpy.mockReturnValue({
|
|
63
71
|
branches: [],
|
|
64
72
|
loading: true,
|
|
@@ -69,7 +77,7 @@ describe('App', () => {
|
|
|
69
77
|
|
|
70
78
|
const onExit = vi.fn();
|
|
71
79
|
const { queryByText, getByText } = render(
|
|
72
|
-
<App onExit={onExit} loadingIndicatorDelay={10}
|
|
80
|
+
<App onExit={onExit} loadingIndicatorDelay={10} />,
|
|
73
81
|
);
|
|
74
82
|
|
|
75
83
|
expect(queryByText(/Loading Git information/i)).toBeNull();
|
|
@@ -81,8 +89,8 @@ describe('App', () => {
|
|
|
81
89
|
expect(getByText(/Loading Git information/i)).toBeDefined();
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
it(
|
|
85
|
-
const error = new Error(
|
|
92
|
+
it("should show error state when Git data fails to load", () => {
|
|
93
|
+
const error = new Error("Failed to fetch branches");
|
|
86
94
|
useGitDataSpy.mockReturnValue({
|
|
87
95
|
branches: [],
|
|
88
96
|
loading: false,
|
|
@@ -98,29 +106,29 @@ describe('App', () => {
|
|
|
98
106
|
expect(getByText(/Failed to fetch branches/i)).toBeDefined();
|
|
99
107
|
});
|
|
100
108
|
|
|
101
|
-
it(
|
|
109
|
+
it("should calculate statistics from branches", () => {
|
|
102
110
|
const branchesWithWorktree: BranchInfo[] = [
|
|
103
111
|
{
|
|
104
|
-
name:
|
|
105
|
-
type:
|
|
106
|
-
branchType:
|
|
112
|
+
name: "main",
|
|
113
|
+
type: "local",
|
|
114
|
+
branchType: "main",
|
|
107
115
|
isCurrent: true,
|
|
108
116
|
},
|
|
109
117
|
{
|
|
110
|
-
name:
|
|
111
|
-
type:
|
|
112
|
-
branchType:
|
|
118
|
+
name: "feature/a",
|
|
119
|
+
type: "local",
|
|
120
|
+
branchType: "feature",
|
|
113
121
|
isCurrent: false,
|
|
114
122
|
worktree: {
|
|
115
|
-
path:
|
|
123
|
+
path: "/path/a",
|
|
116
124
|
locked: false,
|
|
117
125
|
prunable: false,
|
|
118
126
|
},
|
|
119
127
|
},
|
|
120
128
|
{
|
|
121
|
-
name:
|
|
122
|
-
type:
|
|
123
|
-
branchType:
|
|
129
|
+
name: "origin/main",
|
|
130
|
+
type: "remote",
|
|
131
|
+
branchType: "main",
|
|
124
132
|
isCurrent: false,
|
|
125
133
|
},
|
|
126
134
|
];
|
|
@@ -144,7 +152,7 @@ describe('App', () => {
|
|
|
144
152
|
expect(getByText(/Worktrees:/)).toBeDefined();
|
|
145
153
|
});
|
|
146
154
|
|
|
147
|
-
it(
|
|
155
|
+
it("should call onExit when branch is selected", () => {
|
|
148
156
|
useGitDataSpy.mockReturnValue({
|
|
149
157
|
branches: mockBranches,
|
|
150
158
|
loading: false,
|
|
@@ -161,7 +169,7 @@ describe('App', () => {
|
|
|
161
169
|
// which is covered in integration tests
|
|
162
170
|
});
|
|
163
171
|
|
|
164
|
-
it(
|
|
172
|
+
it("should handle empty branch list", () => {
|
|
165
173
|
useGitDataSpy.mockReturnValue({
|
|
166
174
|
branches: [],
|
|
167
175
|
loading: false,
|
|
@@ -176,7 +184,7 @@ describe('App', () => {
|
|
|
176
184
|
expect(getByText(/No branches found/i)).toBeDefined();
|
|
177
185
|
});
|
|
178
186
|
|
|
179
|
-
it(
|
|
187
|
+
it("should wrap with ErrorBoundary", () => {
|
|
180
188
|
// This test verifies ErrorBoundary is present
|
|
181
189
|
// Actual error catching is tested separately
|
|
182
190
|
useGitDataSpy.mockReturnValue({
|
|
@@ -193,7 +201,7 @@ describe('App', () => {
|
|
|
193
201
|
expect(container).toBeDefined();
|
|
194
202
|
});
|
|
195
203
|
|
|
196
|
-
it(
|
|
204
|
+
it("should format branch items with icons", () => {
|
|
197
205
|
useGitDataSpy.mockReturnValue({
|
|
198
206
|
branches: mockBranches,
|
|
199
207
|
loading: false,
|
|
@@ -209,8 +217,8 @@ describe('App', () => {
|
|
|
209
217
|
expect(getByText(/⚡/)).toBeDefined();
|
|
210
218
|
});
|
|
211
219
|
|
|
212
|
-
describe(
|
|
213
|
-
it(
|
|
220
|
+
describe("BranchActionSelectorScreen integration", () => {
|
|
221
|
+
it("should show BranchActionSelectorScreen after branch selection", () => {
|
|
214
222
|
useGitDataSpy.mockReturnValue({
|
|
215
223
|
branches: mockBranches,
|
|
216
224
|
loading: false,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
6
|
+
import { render, waitFor } from "@testing-library/react";
|
|
7
|
+
import { ModelSelectorScreen } from "../../components/screens/ModelSelectorScreen.js";
|
|
8
|
+
import type { ModelSelectionResult } from "../../components/screens/ModelSelectorScreen.js";
|
|
9
|
+
import { Window } from "happy-dom";
|
|
10
|
+
|
|
11
|
+
const selectMocks: any[] = [];
|
|
12
|
+
|
|
13
|
+
vi.mock("../../components/common/Select.js", () => {
|
|
14
|
+
return {
|
|
15
|
+
Select: (props: any) => {
|
|
16
|
+
selectMocks.push(props);
|
|
17
|
+
return React.createElement("div", {
|
|
18
|
+
"data-testid": "select-mock",
|
|
19
|
+
onClick: () => props.onSelect && props.onSelect(props.items[props.initialIndex ?? 0]),
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("ModelSelectorScreen initial selection", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
selectMocks.length = 0;
|
|
28
|
+
const window = new Window();
|
|
29
|
+
globalThis.window = window as any;
|
|
30
|
+
globalThis.document = window.document as any;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("sets model list initialIndex based on previous selection", async () => {
|
|
34
|
+
const initial: ModelSelectionResult = {
|
|
35
|
+
model: "gpt-5.1-codex-max",
|
|
36
|
+
inferenceLevel: "high",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<ModelSelectorScreen
|
|
41
|
+
tool="codex-cli"
|
|
42
|
+
onBack={() => {}}
|
|
43
|
+
onSelect={() => {}}
|
|
44
|
+
initialSelection={initial}
|
|
45
|
+
/>,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await waitFor(() => expect(selectMocks.length).toBeGreaterThan(0));
|
|
49
|
+
const modelSelect = selectMocks.at(-1);
|
|
50
|
+
const index = modelSelect.initialIndex as number;
|
|
51
|
+
// codex-cli models: [gpt-5.1-codex, gpt-5.1-codex-max, gpt-5.1-codex-mini, gpt-5.1]
|
|
52
|
+
expect(index).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("sets inference list initialIndex based on previous reasoning level", async () => {
|
|
56
|
+
const initial: ModelSelectionResult = {
|
|
57
|
+
model: "gpt-5.1-codex-max",
|
|
58
|
+
inferenceLevel: "high",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
render(
|
|
62
|
+
<ModelSelectorScreen
|
|
63
|
+
tool="codex-cli"
|
|
64
|
+
onBack={() => {}}
|
|
65
|
+
onSelect={() => {}}
|
|
66
|
+
initialSelection={initial}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// trigger onSelect for model to render inference step
|
|
71
|
+
await waitFor(() => expect(selectMocks.length).toBeGreaterThan(0));
|
|
72
|
+
const modelSelect = selectMocks[0];
|
|
73
|
+
modelSelect.onSelect(modelSelect.items[modelSelect.initialIndex]);
|
|
74
|
+
|
|
75
|
+
await waitFor(() => expect(selectMocks.length).toBeGreaterThan(1));
|
|
76
|
+
const inferenceSelect = selectMocks[1];
|
|
77
|
+
const index = inferenceSelect.initialIndex as number;
|
|
78
|
+
// inference order for codex-max: [xhigh, high, medium, low]; "high" should be index 1
|
|
79
|
+
expect(index).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
});
|