@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
@@ -2,46 +2,51 @@
2
2
  * @vitest-environment happy-dom
3
3
  * Acceptance tests for User Story 2: Sub-screen Navigation
4
4
  */
5
- import { describe, it, expect, beforeEach, afterAll, vi } from 'vitest';
6
- import type { Mock } from 'vitest';
7
- import { render, waitFor } from '@testing-library/react';
8
- import React from 'react';
9
- import { App } from '../../components/App.js';
10
- import { Window } from 'happy-dom';
11
- import type { BranchInfo } from '../../types.js';
5
+ import { describe, it, expect, beforeEach, afterAll, vi } from "vitest";
6
+ import type { Mock } from "vitest";
7
+ import { render, waitFor } from "@testing-library/react";
8
+ import React from "react";
9
+ import { App } from "../../components/App.js";
10
+ import { Window } from "happy-dom";
11
+ import type { BranchInfo } from "../../types.js";
12
12
 
13
13
  // Mock git.ts and worktree.ts
14
- vi.mock('../../../../git.ts', () => ({
14
+ vi.mock("../../../../git.ts", () => ({
15
15
  __esModule: true,
16
16
  getAllBranches: vi.fn(),
17
- getRepositoryRoot: vi.fn(async () => '/repo'),
17
+ getRepositoryRoot: vi.fn(async () => "/repo"),
18
18
  deleteBranch: vi.fn(async () => undefined),
19
19
  }));
20
20
 
21
- const { acceptanceIsProtectedBranchName, acceptanceSwitchToProtectedBranch } = vi.hoisted(() => ({
22
- acceptanceIsProtectedBranchName: vi.fn(() => false),
23
- acceptanceSwitchToProtectedBranch: vi.fn(async () => 'none' as const),
24
- }));
21
+ const { acceptanceIsProtectedBranchName, acceptanceSwitchToProtectedBranch } =
22
+ vi.hoisted(() => ({
23
+ acceptanceIsProtectedBranchName: vi.fn(() => false),
24
+ acceptanceSwitchToProtectedBranch: vi.fn(async () => "none" as const),
25
+ }));
25
26
 
26
- vi.mock('../../../../worktree.ts', () => ({
27
+ vi.mock("../../../../worktree.ts", () => ({
27
28
  __esModule: true,
28
29
  listAdditionalWorktrees: vi.fn(),
29
30
  createWorktree: vi.fn(async () => undefined),
30
- generateWorktreePath: vi.fn(async () => '/repo/.git/worktree/test'),
31
+ generateWorktreePath: vi.fn(async () => "/repo/.git/worktree/test"),
31
32
  getMergedPRWorktrees: vi.fn(async () => []),
32
33
  removeWorktree: vi.fn(async () => undefined),
33
34
  isProtectedBranchName: acceptanceIsProtectedBranchName,
34
35
  switchToProtectedBranch: acceptanceSwitchToProtectedBranch,
35
36
  }));
36
37
 
37
- import { getAllBranches, getRepositoryRoot, deleteBranch } from '../../../../git.ts';
38
+ import {
39
+ getAllBranches,
40
+ getRepositoryRoot,
41
+ deleteBranch,
42
+ } from "../../../../git.ts";
38
43
  import {
39
44
  listAdditionalWorktrees,
40
45
  createWorktree,
41
46
  generateWorktreePath,
42
47
  getMergedPRWorktrees,
43
48
  removeWorktree,
44
- } from '../../../../worktree.ts';
49
+ } from "../../../../worktree.ts";
45
50
 
46
51
  const mockedGetAllBranches = getAllBranches as Mock;
47
52
  const mockedGetRepositoryRoot = getRepositoryRoot as Mock;
@@ -54,7 +59,7 @@ const mockedRemoveWorktree = removeWorktree as Mock;
54
59
  const mockedIsProtectedBranchName = acceptanceIsProtectedBranchName as Mock;
55
60
  const mockedSwitchToProtectedBranch = acceptanceSwitchToProtectedBranch as Mock;
56
61
 
57
- describe('Acceptance: Navigation (User Story 2)', () => {
62
+ describe("Acceptance: Navigation (User Story 2)", () => {
58
63
  beforeEach(() => {
59
64
  // Setup happy-dom
60
65
  const window = new Window();
@@ -72,21 +77,21 @@ describe('Acceptance: Navigation (User Story 2)', () => {
72
77
  mockedRemoveWorktree.mockReset();
73
78
  mockedIsProtectedBranchName.mockReset();
74
79
  mockedSwitchToProtectedBranch.mockReset();
75
- mockedGetRepositoryRoot.mockResolvedValue('/repo');
76
- mockedSwitchToProtectedBranch.mockResolvedValue('none');
80
+ mockedGetRepositoryRoot.mockResolvedValue("/repo");
81
+ mockedSwitchToProtectedBranch.mockResolvedValue("none");
77
82
  });
78
83
 
79
84
  const mockBranches: BranchInfo[] = [
80
85
  {
81
- name: 'main',
82
- type: 'local',
83
- branchType: 'main',
86
+ name: "main",
87
+ type: "local",
88
+ branchType: "main",
84
89
  isCurrent: true,
85
90
  },
86
91
  {
87
- name: 'feature/test',
88
- type: 'local',
89
- branchType: 'feature',
92
+ name: "feature/test",
93
+ type: "local",
94
+ branchType: "feature",
90
95
  isCurrent: false,
91
96
  },
92
97
  ];
@@ -95,8 +100,10 @@ describe('Acceptance: Navigation (User Story 2)', () => {
95
100
  * T074: Acceptance Scenario 1
96
101
  * nキーで新規ブランチ作成画面に遷移
97
102
  */
98
- it('[AC1] should navigate to branch creator on n key', async () => {
99
- (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
103
+ it("[AC1] should navigate to branch creator on n key", async () => {
104
+ (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(
105
+ mockBranches,
106
+ );
100
107
  (listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
101
108
 
102
109
  const onExit = vi.fn();
@@ -107,23 +114,27 @@ describe('Acceptance: Navigation (User Story 2)', () => {
107
114
  });
108
115
 
109
116
  // Verify n key action is available in footer
110
- const nKeyElements = container.querySelectorAll('*');
117
+ const nKeyElements = container.querySelectorAll("*");
111
118
  let hasNKey = false;
112
119
  nKeyElements.forEach((el) => {
113
- if (el.textContent?.toLowerCase().includes('new branch')) {
120
+ if (el.textContent?.toLowerCase().includes("new branch")) {
114
121
  hasNKey = true;
115
122
  }
116
123
  });
117
124
 
118
- expect(hasNKey || container.textContent?.toLowerCase().includes('n')).toBe(true);
125
+ expect(hasNKey || container.textContent?.toLowerCase().includes("n")).toBe(
126
+ true,
127
+ );
119
128
  });
120
129
 
121
130
  /**
122
131
  * T075: Acceptance Scenario 2
123
132
  * メイン画面にはqキーが存在しない(終了はCtrl+Cのみ)
124
133
  */
125
- it('[AC2] should not have q key on main screen', async () => {
126
- (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
134
+ it("[AC2] should not have q key on main screen", async () => {
135
+ (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(
136
+ mockBranches,
137
+ );
127
138
  (listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
128
139
 
129
140
  const onExit = vi.fn();
@@ -134,23 +145,25 @@ describe('Acceptance: Navigation (User Story 2)', () => {
134
145
  });
135
146
 
136
147
  // Verify q key is NOT in the footer (main screen uses Ctrl+C for exit)
137
- const footerText = container.textContent || '';
148
+ const footerText = container.textContent || "";
138
149
  // Main screen should not have 'q' for quit, but should have other keys
139
150
  expect(footerText.toLowerCase()).not.toMatch(/\[q\]/);
140
- expect(footerText.toLowerCase()).toContain('enter');
151
+ expect(footerText.toLowerCase()).toContain("enter");
141
152
  });
142
153
 
143
154
  /**
144
155
  * T076: Acceptance Scenario 3
145
156
  * Worktree管理でアクション実行後に適切に遷移
146
157
  */
147
- it('[AC3] should handle worktree management navigation', async () => {
148
- (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
158
+ it("[AC3] should handle worktree management navigation", async () => {
159
+ (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(
160
+ mockBranches,
161
+ );
149
162
  (listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([
150
163
  {
151
- branch: 'feature/test',
152
- path: '/path/to/worktree',
153
- head: 'abc123',
164
+ branch: "feature/test",
165
+ path: "/path/to/worktree",
166
+ head: "abc123",
154
167
  isAccessible: true,
155
168
  },
156
169
  ]);
@@ -163,19 +176,23 @@ describe('Acceptance: Navigation (User Story 2)', () => {
163
176
  });
164
177
 
165
178
  // Verify m key action is available for worktree management
166
- const mKeyElements = container.querySelectorAll('*');
179
+ const mKeyElements = container.querySelectorAll("*");
167
180
  let hasMKey = false;
168
181
  mKeyElements.forEach((el) => {
169
- if (el.textContent?.toLowerCase().includes('manage worktrees')) {
182
+ if (el.textContent?.toLowerCase().includes("manage worktrees")) {
170
183
  hasMKey = true;
171
184
  }
172
185
  });
173
186
 
174
- expect(hasMKey || container.textContent?.toLowerCase().includes('m')).toBe(true);
187
+ expect(hasMKey || container.textContent?.toLowerCase().includes("m")).toBe(
188
+ true,
189
+ );
175
190
  });
176
191
 
177
- it('[Integration] should support all navigation keys', async () => {
178
- (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
192
+ it("[Integration] should support all navigation keys", async () => {
193
+ (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(
194
+ mockBranches,
195
+ );
179
196
  (listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
180
197
 
181
198
  const onExit = vi.fn();
@@ -191,8 +208,10 @@ describe('Acceptance: Navigation (User Story 2)', () => {
191
208
  expect(enterKeys.length).toBeGreaterThan(0);
192
209
  });
193
210
 
194
- it('[Integration] should display correct footer actions', async () => {
195
- (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(mockBranches);
211
+ it("[Integration] should display correct footer actions", async () => {
212
+ (getAllBranches as ReturnType<typeof vi.fn>).mockResolvedValue(
213
+ mockBranches,
214
+ );
196
215
  (listAdditionalWorktrees as ReturnType<typeof vi.fn>).mockResolvedValue([]);
197
216
 
198
217
  const onExit = vi.fn();
@@ -203,9 +222,9 @@ describe('Acceptance: Navigation (User Story 2)', () => {
203
222
  });
204
223
 
205
224
  // Verify footer has multiple action keys (main screen doesn't have q key)
206
- const footerText = container.textContent || '';
207
- expect(footerText.toLowerCase()).toContain('enter');
208
- expect(footerText.toLowerCase()).toContain('m'); // Manage worktrees
225
+ const footerText = container.textContent || "";
226
+ expect(footerText.toLowerCase()).toContain("enter");
227
+ expect(footerText.toLowerCase()).toContain("m"); // Manage worktrees
209
228
  });
210
229
  });
211
230
 
@@ -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 { Window } from 'happy-dom';
8
- import { App } from '../../components/App.js';
9
- import type { BranchInfo, BranchItem } from '../../types.js';
10
- import * as useGitDataModule from '../../hooks/useGitData.js';
11
- import * as useScreenStateModule from '../../hooks/useScreenState.js';
12
- import * as BranchListScreenModule from '../../components/screens/BranchListScreen.js';
13
- import * as BranchActionSelectorScreenModule from '../../screens/BranchActionSelectorScreen.js';
14
- import * as worktreeModule from '../../../../worktree.ts';
15
- import * as gitModule from '../../../../git.ts';
16
- import type { ScreenType } from '../../types.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 { Window } from "happy-dom";
16
+ import { App } from "../../components/App.js";
17
+ import type { BranchInfo, BranchItem } from "../../types.js";
18
+ import * as useGitDataModule from "../../hooks/useGitData.js";
19
+ import * as useScreenStateModule from "../../hooks/useScreenState.js";
20
+ import * as BranchListScreenModule from "../../components/screens/BranchListScreen.js";
21
+ import * as BranchActionSelectorScreenModule from "../../screens/BranchActionSelectorScreen.js";
22
+ import * as worktreeModule from "../../../../worktree.ts";
23
+ import * as gitModule from "../../../../git.ts";
24
+ import type { ScreenType } from "../../types.js";
17
25
 
18
26
  const navigateToMock = vi.fn();
19
27
  const goBackMock = vi.fn();
@@ -22,37 +30,47 @@ const resetMock = vi.fn();
22
30
  const originalUseGitData = useGitDataModule.useGitData;
23
31
  const originalUseScreenState = useScreenStateModule.useScreenState;
24
32
  const originalBranchListScreen = BranchListScreenModule.BranchListScreen;
25
- const originalBranchActionSelector = BranchActionSelectorScreenModule.BranchActionSelectorScreen;
33
+ const originalBranchActionSelector =
34
+ BranchActionSelectorScreenModule.BranchActionSelectorScreen;
26
35
  const originalGetRepositoryRoot = gitModule.getRepositoryRoot;
27
36
 
28
- const useGitDataSpy = vi.spyOn(useGitDataModule, 'useGitData');
29
- const useScreenStateSpy = vi.spyOn(useScreenStateModule, 'useScreenState');
30
- const branchListScreenSpy = vi.spyOn(BranchListScreenModule, 'BranchListScreen');
31
- const branchActionSelectorSpy = vi.spyOn(BranchActionSelectorScreenModule, 'BranchActionSelectorScreen');
32
- const switchToProtectedBranchSpy = vi.spyOn(worktreeModule, 'switchToProtectedBranch');
33
- const getRepositoryRootSpy = vi.spyOn(gitModule, 'getRepositoryRoot');
37
+ const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
38
+ const useScreenStateSpy = vi.spyOn(useScreenStateModule, "useScreenState");
39
+ const branchListScreenSpy = vi.spyOn(
40
+ BranchListScreenModule,
41
+ "BranchListScreen",
42
+ );
43
+ const branchActionSelectorSpy = vi.spyOn(
44
+ BranchActionSelectorScreenModule,
45
+ "BranchActionSelectorScreen",
46
+ );
47
+ const switchToProtectedBranchSpy = vi.spyOn(
48
+ worktreeModule,
49
+ "switchToProtectedBranch",
50
+ );
51
+ const getRepositoryRootSpy = vi.spyOn(gitModule, "getRepositoryRoot");
34
52
 
35
53
  const branchListProps: any[] = [];
36
54
  const branchActionProps: any[] = [];
37
55
  const aiToolProps: any[] = [];
38
56
  let currentScreenState: ScreenType;
39
57
 
40
- vi.mock('../../components/screens/AIToolSelectorScreen.js', () => {
58
+ vi.mock("../../components/screens/AIToolSelectorScreen.js", () => {
41
59
  return {
42
60
  AIToolSelectorScreen: (props: unknown) => {
43
61
  aiToolProps.push(props);
44
- return React.createElement('div');
62
+ return React.createElement("div");
45
63
  },
46
64
  };
47
65
  });
48
66
 
49
- describe('App protected branch handling', () => {
67
+ describe("App protected branch handling", () => {
50
68
  beforeEach(() => {
51
69
  const window = new Window();
52
70
  globalThis.window = window as any;
53
71
  globalThis.document = window.document as any;
54
72
 
55
- currentScreenState = 'branch-list';
73
+ currentScreenState = "branch-list";
56
74
  navigateToMock.mockReset();
57
75
  goBackMock.mockReset();
58
76
  resetMock.mockReset();
@@ -73,7 +91,7 @@ describe('App protected branch handling', () => {
73
91
  goBack: goBackMock,
74
92
  reset: () => {
75
93
  resetMock();
76
- currentScreenState = 'branch-list';
94
+ currentScreenState = "branch-list";
77
95
  },
78
96
  }));
79
97
 
@@ -85,8 +103,8 @@ describe('App protected branch handling', () => {
85
103
  branchActionProps.push(props);
86
104
  return React.createElement(originalBranchActionSelector, props);
87
105
  });
88
- switchToProtectedBranchSpy.mockResolvedValue('local');
89
- getRepositoryRootSpy.mockResolvedValue('/repo');
106
+ switchToProtectedBranchSpy.mockResolvedValue("local");
107
+ getRepositoryRootSpy.mockResolvedValue("/repo");
90
108
  });
91
109
 
92
110
  afterEach(() => {
@@ -95,7 +113,9 @@ describe('App protected branch handling', () => {
95
113
  useScreenStateSpy.mockReset();
96
114
  useScreenStateSpy.mockImplementation(originalUseScreenState);
97
115
  branchListScreenSpy.mockImplementation(originalBranchListScreen as any);
98
- branchActionSelectorSpy.mockImplementation(originalBranchActionSelector as any);
116
+ branchActionSelectorSpy.mockImplementation(
117
+ originalBranchActionSelector as any,
118
+ );
99
119
  switchToProtectedBranchSpy.mockReset();
100
120
  getRepositoryRootSpy.mockReset();
101
121
  branchActionProps.length = 0;
@@ -110,18 +130,18 @@ describe('App protected branch handling', () => {
110
130
  getRepositoryRootSpy.mockRestore();
111
131
  });
112
132
 
113
- it('shows protected branch warning and switches root without launching AI tool', async () => {
133
+ it("shows protected branch warning and switches root without launching AI tool", async () => {
114
134
  const branches: BranchInfo[] = [
115
135
  {
116
- name: 'main',
117
- type: 'local',
118
- branchType: 'main',
136
+ name: "main",
137
+ type: "local",
138
+ branchType: "main",
119
139
  isCurrent: false,
120
140
  },
121
141
  {
122
- name: 'feature/example',
123
- type: 'local',
124
- branchType: 'feature',
142
+ name: "feature/example",
143
+ type: "local",
144
+ branchType: "feature",
125
145
  isCurrent: true,
126
146
  },
127
147
  ];
@@ -141,15 +161,15 @@ describe('App protected branch handling', () => {
141
161
  const latestProps = branchListProps.at(-1);
142
162
  expect(latestProps).toBeDefined();
143
163
  if (!latestProps) {
144
- throw new Error('BranchListScreen props missing');
164
+ throw new Error("BranchListScreen props missing");
145
165
  }
146
166
 
147
167
  const protectedBranch = (latestProps.branches as BranchItem[]).find(
148
- (item) => item.name === 'main'
168
+ (item) => item.name === "main",
149
169
  );
150
170
  expect(protectedBranch).toBeDefined();
151
171
  if (!protectedBranch) {
152
- throw new Error('Protected branch item not found');
172
+ throw new Error("Protected branch item not found");
153
173
  }
154
174
 
155
175
  await act(async () => {
@@ -157,13 +177,15 @@ describe('App protected branch handling', () => {
157
177
  await Promise.resolve();
158
178
  });
159
179
 
160
- expect(navigateToMock).toHaveBeenCalledWith('branch-action-selector');
180
+ expect(navigateToMock).toHaveBeenCalledWith("branch-action-selector");
161
181
  expect(branchActionProps).not.toHaveLength(0);
162
182
  const actionProps = branchActionProps.at(-1);
163
- expect(actionProps?.mode).toBe('protected');
164
- expect(actionProps?.infoMessage).toContain('is a root branch');
165
- expect(actionProps?.primaryLabel).toBe('Use root branch (no worktree)');
166
- expect(actionProps?.secondaryLabel).toBe('Create new branch from this branch');
183
+ expect(actionProps?.mode).toBe("protected");
184
+ expect(actionProps?.infoMessage).toContain("is a root branch");
185
+ expect(actionProps?.primaryLabel).toBe("Use root branch (no worktree)");
186
+ expect(actionProps?.secondaryLabel).toBe(
187
+ "Create new branch from this branch",
188
+ );
167
189
 
168
190
  await act(async () => {
169
191
  actionProps?.onUseExisting();
@@ -172,12 +194,12 @@ describe('App protected branch handling', () => {
172
194
  });
173
195
 
174
196
  expect(switchToProtectedBranchSpy).toHaveBeenCalledWith({
175
- branchName: 'main',
197
+ branchName: "main",
176
198
  repoRoot: expect.any(String),
177
199
  remoteRef: null,
178
200
  });
179
201
 
180
- expect(navigateToMock).toHaveBeenCalledWith('ai-tool-selector');
202
+ expect(navigateToMock).toHaveBeenCalledWith("ai-tool-selector");
181
203
  expect(aiToolProps).not.toHaveLength(0);
182
204
  });
183
205
  });