@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.
Files changed (172) hide show
  1. package/README.ja.md +6 -4
  2. package/README.md +6 -4
  3. package/dist/claude.d.ts +1 -0
  4. package/dist/claude.d.ts.map +1 -1
  5. package/dist/claude.js +6 -3
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli/ui/components/App.d.ts +6 -4
  8. package/dist/cli/ui/components/App.d.ts.map +1 -1
  9. package/dist/cli/ui/components/App.js +184 -107
  10. package/dist/cli/ui/components/App.js.map +1 -1
  11. package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
  12. package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
  13. package/dist/cli/ui/components/common/Confirm.js +7 -7
  14. package/dist/cli/ui/components/common/Confirm.js.map +1 -1
  15. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
  16. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
  17. package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
  18. package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
  19. package/dist/cli/ui/components/common/Input.d.ts +2 -2
  20. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  21. package/dist/cli/ui/components/common/Input.js +4 -4
  22. package/dist/cli/ui/components/common/Input.js.map +1 -1
  23. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
  24. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
  25. package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
  26. package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
  27. package/dist/cli/ui/components/common/Select.d.ts +1 -1
  28. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  29. package/dist/cli/ui/components/common/Select.js +11 -12
  30. package/dist/cli/ui/components/common/Select.js.map +1 -1
  31. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +3 -3
  32. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
  34. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
  36. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
  38. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
  40. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
  42. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
  44. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  45. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
  46. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  47. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
  48. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
  49. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
  50. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
  51. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
  52. package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
  53. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
  54. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
  55. package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
  56. package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
  57. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  58. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
  59. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  60. package/dist/cli/ui/types.d.ts +11 -1
  61. package/dist/cli/ui/types.d.ts.map +1 -1
  62. package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
  63. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
  64. package/dist/cli/ui/utils/modelOptions.js +111 -0
  65. package/dist/cli/ui/utils/modelOptions.js.map +1 -0
  66. package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
  67. package/dist/client/index.html +1 -1
  68. package/dist/codex.d.ts +6 -0
  69. package/dist/codex.d.ts.map +1 -1
  70. package/dist/codex.js +11 -4
  71. package/dist/codex.js.map +1 -1
  72. package/dist/config/builtin-tools.d.ts +10 -2
  73. package/dist/config/builtin-tools.d.ts.map +1 -1
  74. package/dist/config/builtin-tools.js +40 -4
  75. package/dist/config/builtin-tools.js.map +1 -1
  76. package/dist/config/index.d.ts.map +1 -1
  77. package/dist/config/index.js.map +1 -1
  78. package/dist/config/tools.d.ts.map +1 -1
  79. package/dist/config/tools.js +4 -3
  80. package/dist/config/tools.js.map +1 -1
  81. package/dist/gemini.d.ts +13 -0
  82. package/dist/gemini.d.ts.map +1 -0
  83. package/dist/gemini.js +157 -0
  84. package/dist/gemini.js.map +1 -0
  85. package/dist/git.d.ts.map +1 -1
  86. package/dist/git.js.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +59 -7
  89. package/dist/index.js.map +1 -1
  90. package/dist/qwen.d.ts +13 -0
  91. package/dist/qwen.d.ts.map +1 -0
  92. package/dist/qwen.js +157 -0
  93. package/dist/qwen.js.map +1 -0
  94. package/dist/services/git.service.d.ts.map +1 -1
  95. package/dist/services/git.service.js.map +1 -1
  96. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  97. package/dist/web/client/src/components/BranchGraph.js +1 -1
  98. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  99. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  100. package/dist/web/client/src/components/EnvEditor.js +7 -4
  101. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  102. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  103. package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
  104. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  105. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  106. package/dist/web/client/src/pages/BranchListPage.js +10 -4
  107. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  108. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  109. package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
  110. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  111. package/package.json +2 -1
  112. package/src/claude.ts +8 -3
  113. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
  114. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
  115. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
  116. package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
  117. package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
  118. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
  119. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
  120. package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
  121. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +63 -43
  122. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
  123. package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
  124. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
  125. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
  126. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
  127. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
  128. package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
  129. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
  130. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
  131. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
  132. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
  133. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
  134. package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
  135. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
  136. package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
  137. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
  138. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
  139. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
  140. package/src/cli/ui/components/App.tsx +317 -150
  141. package/src/cli/ui/components/common/Confirm.tsx +13 -9
  142. package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
  143. package/src/cli/ui/components/common/Input.tsx +12 -4
  144. package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
  145. package/src/cli/ui/components/common/Select.tsx +28 -17
  146. package/src/cli/ui/components/parts/Header.test.tsx +5 -15
  147. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +20 -15
  148. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
  149. package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
  150. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
  151. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
  152. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
  153. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
  154. package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
  155. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
  156. package/src/cli/ui/types.ts +21 -1
  157. package/src/cli/ui/utils/modelOptions.test.ts +36 -0
  158. package/src/cli/ui/utils/modelOptions.ts +122 -0
  159. package/src/codex.ts +23 -4
  160. package/src/config/builtin-tools.ts +42 -4
  161. package/src/config/index.ts +2 -12
  162. package/src/config/tools.ts +16 -6
  163. package/src/gemini.ts +207 -0
  164. package/src/git.ts +2 -1
  165. package/src/index.ts +86 -6
  166. package/src/qwen.ts +213 -0
  167. package/src/services/git.service.ts +2 -1
  168. package/src/web/client/src/components/BranchGraph.tsx +3 -2
  169. package/src/web/client/src/components/EnvEditor.tsx +44 -11
  170. package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
  171. package/src/web/client/src/pages/BranchListPage.tsx +37 -13
  172. 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 { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
5
- import type { Mock } from 'vitest';
6
- import { render, act, waitFor } from '@testing-library/react';
7
- import React from 'react';
8
- import type { BranchItem, CleanupTarget } from '../../types.js';
9
- import { Window } from 'happy-dom';
10
- import * as useGitDataModule from '../../hooks/useGitData.js';
11
- import * as useScreenStateModule from '../../hooks/useScreenState.js';
12
- import * as WorktreeManagerScreenModule from '../../components/screens/WorktreeManagerScreen.js';
13
- import * as BranchCreatorScreenModule from '../../components/screens/BranchCreatorScreen.js';
14
- import * as BranchListScreenModule from '../../components/screens/BranchListScreen.js';
15
- import * as worktreeModule from '../../../../worktree.ts';
16
- import * as gitModule from '../../../../git.ts';
17
- import { App } from '../../components/App.js';
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 = WorktreeManagerScreenModule.WorktreeManagerScreen;
30
- const originalBranchCreatorScreen = BranchCreatorScreenModule.BranchCreatorScreen;
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, 'useGitData');
40
- const useScreenStateSpy = vi.spyOn(useScreenStateModule, 'useScreenState');
41
- const worktreeManagerScreenSpy = vi.spyOn(WorktreeManagerScreenModule, 'WorktreeManagerScreen');
42
- const branchCreatorScreenSpy = vi.spyOn(BranchCreatorScreenModule, 'BranchCreatorScreen');
43
- const branchListScreenSpy = vi.spyOn(BranchListScreenModule, 'BranchListScreen');
44
- const getMergedPRWorktreesSpy = vi.spyOn(worktreeModule, 'getMergedPRWorktrees');
45
- const generateWorktreePathSpy = vi.spyOn(worktreeModule, 'generateWorktreePath');
46
- const createWorktreeSpy = vi.spyOn(worktreeModule, 'createWorktree');
47
- const removeWorktreeSpy = vi.spyOn(worktreeModule, 'removeWorktree');
48
- const getRepositoryRootSpy = vi.spyOn(gitModule, 'getRepositoryRoot');
49
- const deleteBranchSpy = vi.spyOn(gitModule, 'deleteBranch');
50
-
51
- describe('App shortcuts integration', () => {
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 === 'undefined') {
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: 'feature/existing',
69
- path: '/worktrees/feature-existing',
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: 'worktree-manager',
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: 'feature/add-new-feature',
99
- cleanupType: 'worktree-and-branch',
123
+ branch: "feature/add-new-feature",
124
+ cleanupType: "worktree-and-branch",
100
125
  pullRequest: {
101
126
  number: 123,
102
- title: 'Add new feature',
103
- branch: 'feature/add-new-feature',
104
- mergedAt: '2025-01-20T10:00:00Z',
105
- author: 'user1',
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: '/worktrees/feature-add-new-feature',
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: 'hotfix/urgent-fix',
115
- cleanupType: 'worktree-and-branch',
139
+ branch: "hotfix/urgent-fix",
140
+ cleanupType: "worktree-and-branch",
116
141
  pullRequest: {
117
142
  number: 456,
118
- title: 'Urgent fix',
119
- branch: 'hotfix/urgent-fix',
120
- mergedAt: '2025-01-21T09:00:00Z',
121
- author: 'user2',
143
+ title: "Urgent fix",
144
+ branch: "hotfix/urgent-fix",
145
+ mergedAt: "2025-01-21T09:00:00Z",
146
+ author: "user2",
122
147
  },
123
- worktreePath: '/worktrees/hotfix-urgent-fix',
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('/worktrees/new-branch');
155
+ generateWorktreePathSpy.mockResolvedValue("/worktrees/new-branch");
131
156
  createWorktreeSpy.mockResolvedValue(undefined);
132
157
  removeWorktreeSpy.mockResolvedValue(undefined);
133
- getRepositoryRootSpy.mockResolvedValue('/repo');
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(originalWorktreeManagerScreen as any);
152
- branchCreatorScreenSpy.mockImplementation(originalBranchCreatorScreen as any);
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(originalGetMergedPRWorktrees as any);
155
- generateWorktreePathSpy.mockImplementation(originalGenerateWorktreePath as any);
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('navigates to AI tool selector when worktree is selected', () => {
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('ai-tool-selector');
205
+ expect(navigateToMock).toHaveBeenCalledWith("ai-tool-selector");
173
206
  });
174
207
 
175
- it('creates new worktree when branch creator submits', async () => {
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: 'branch-creator',
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('feature/new-branch');
225
+ await onCreate("feature/new-branch");
193
226
  });
194
227
 
195
228
  expect(createWorktreeSpy).toHaveBeenCalledWith(
196
229
  expect.objectContaining({
197
- branchName: 'feature/new-branch',
230
+ branchName: "feature/new-branch",
198
231
  isNewBranch: true,
199
- })
232
+ }),
200
233
  );
201
- expect(navigateToMock).toHaveBeenCalledWith('ai-tool-selector');
234
+ expect(navigateToMock).toHaveBeenCalledWith("ai-tool-selector");
202
235
  });
203
236
 
204
- it('displays per-branch cleanup indicators and waits before clearing results', async () => {
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: 'branch-list',
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('BranchListScreen props missing');
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
- 'feature/add-new-feature': expect.objectContaining({ icon: expect.stringMatching(/⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧/) }),
256
- 'hotfix/urgent-fix': expect.objectContaining({ icon: '⏳' }),
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
- '/worktrees/feature-add-new-feature',
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
- 'feature/add-new-feature': { icon: '' },
281
- 'hotfix/urgent-fix': { icon: '⏭️' },
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(latestProps?.branches?.some((branch: BranchItem) => branch.name === 'feature/add-new-feature')).toBe(false);
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
- branchListScreenSpy.mockRestore();
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 { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
5
- import { act, render } 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
- import * as useGitDataModule from '../../hooks/useGitData.js';
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, 'useGitData');
22
+ const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
15
23
 
16
- describe('App', () => {
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: 'main',
31
- type: 'local',
32
- branchType: 'main',
38
+ name: "main",
39
+ type: "local",
40
+ branchType: "main",
33
41
  isCurrent: true,
34
42
  },
35
43
  {
36
- name: 'feature/test',
37
- type: 'local',
38
- branchType: 'feature',
44
+ name: "feature/test",
45
+ type: "local",
46
+ branchType: "feature",
39
47
  isCurrent: false,
40
48
  },
41
49
  ];
42
50
 
43
- it('should render BranchListScreen when data is loaded', () => {
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('should show loading state initially', async () => {
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('should show error state when Git data fails to load', () => {
85
- const error = new Error('Failed to fetch branches');
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('should calculate statistics from branches', () => {
109
+ it("should calculate statistics from branches", () => {
102
110
  const branchesWithWorktree: BranchInfo[] = [
103
111
  {
104
- name: 'main',
105
- type: 'local',
106
- branchType: 'main',
112
+ name: "main",
113
+ type: "local",
114
+ branchType: "main",
107
115
  isCurrent: true,
108
116
  },
109
117
  {
110
- name: 'feature/a',
111
- type: 'local',
112
- branchType: 'feature',
118
+ name: "feature/a",
119
+ type: "local",
120
+ branchType: "feature",
113
121
  isCurrent: false,
114
122
  worktree: {
115
- path: '/path/a',
123
+ path: "/path/a",
116
124
  locked: false,
117
125
  prunable: false,
118
126
  },
119
127
  },
120
128
  {
121
- name: 'origin/main',
122
- type: 'remote',
123
- branchType: 'main',
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('should call onExit when branch is selected', () => {
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('should handle empty branch list', () => {
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('should wrap with ErrorBoundary', () => {
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('should format branch items with icons', () => {
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('BranchActionSelectorScreen integration', () => {
213
- it('should show BranchActionSelectorScreen after branch selection', () => {
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
+ });