@akiojin/gwt 2.9.0 → 2.10.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.md +6 -0
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +10 -1
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -11
- package/dist/index.js.map +1 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +43 -23
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -67
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +107 -130
- package/src/cli/ui/__tests__/components/App.test.tsx +102 -71
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +57 -30
- package/src/cli/ui/components/App.tsx +16 -3
- package/src/cli/ui/types.ts +1 -1
- package/src/index.test.ts +1 -1
- package/src/index.ts +13 -12
- package/src/worktree.ts +54 -37
|
@@ -12,25 +12,59 @@ import {
|
|
|
12
12
|
} from "vitest";
|
|
13
13
|
import { act, render } from "@testing-library/react";
|
|
14
14
|
import React from "react";
|
|
15
|
-
import { App } from "../../components/App.js";
|
|
16
15
|
import { Window } from "happy-dom";
|
|
17
16
|
import type { BranchInfo } from "../../types.js";
|
|
18
|
-
import
|
|
17
|
+
import type { BranchListScreenProps } from "../../components/screens/BranchListScreen.js";
|
|
19
18
|
|
|
20
19
|
const mockRefresh = vi.fn();
|
|
21
|
-
|
|
22
|
-
const
|
|
20
|
+
let App: typeof import("../../components/App.js").App;
|
|
21
|
+
const branchListProps: BranchListScreenProps[] = [];
|
|
22
|
+
const useGitDataMock = vi.fn();
|
|
23
|
+
|
|
24
|
+
vi.mock("../../hooks/useGitData.js", () => ({
|
|
25
|
+
useGitData: (...args: any[]) => useGitDataMock(...args),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("../../components/screens/BranchListScreen.js", () => {
|
|
29
|
+
return {
|
|
30
|
+
BranchListScreen: (props: BranchListScreenProps) => {
|
|
31
|
+
branchListProps.push(props);
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<div>gwt - Branch Selection</div>
|
|
35
|
+
<div>Local: {props.stats?.localCount ?? 0}</div>
|
|
36
|
+
<div>Remote: {props.stats?.remoteCount ?? 0}</div>
|
|
37
|
+
<div>Worktrees: {props.stats?.worktreeCount ?? 0}</div>
|
|
38
|
+
<div>Changes: {props.stats?.changesCount ?? 0}</div>
|
|
39
|
+
{props.loading && <div>Loading Git information</div>}
|
|
40
|
+
{props.error && <div>Error: {props.error.message}</div>}
|
|
41
|
+
{!props.loading && !props.error && props.branches.length === 0 && (
|
|
42
|
+
<div>No branches found</div>
|
|
43
|
+
)}
|
|
44
|
+
<ul>
|
|
45
|
+
{props.branches.map((branch) => (
|
|
46
|
+
<li
|
|
47
|
+
key={branch.name}
|
|
48
|
+
>{`${branch.icons?.join("") ?? ""} ${branch.name}`}</li>
|
|
49
|
+
))}
|
|
50
|
+
</ul>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
23
56
|
|
|
24
57
|
describe("App", () => {
|
|
25
|
-
beforeEach(() => {
|
|
58
|
+
beforeEach(async () => {
|
|
26
59
|
// Setup happy-dom
|
|
27
60
|
const window = new Window();
|
|
28
61
|
globalThis.window = window as any;
|
|
29
62
|
globalThis.document = window.document as any;
|
|
30
63
|
|
|
31
64
|
vi.clearAllMocks();
|
|
32
|
-
|
|
33
|
-
|
|
65
|
+
useGitDataMock.mockReset();
|
|
66
|
+
branchListProps.length = 0;
|
|
67
|
+
App = (await import("../../components/App.js")).App;
|
|
34
68
|
});
|
|
35
69
|
|
|
36
70
|
const mockBranches: BranchInfo[] = [
|
|
@@ -49,61 +83,61 @@ describe("App", () => {
|
|
|
49
83
|
];
|
|
50
84
|
|
|
51
85
|
it("should render BranchListScreen when data is loaded", () => {
|
|
52
|
-
|
|
86
|
+
useGitDataMock.mockImplementation(() => ({
|
|
53
87
|
branches: mockBranches,
|
|
54
88
|
loading: false,
|
|
55
89
|
error: null,
|
|
56
90
|
worktrees: [],
|
|
57
91
|
refresh: mockRefresh,
|
|
58
|
-
});
|
|
92
|
+
}));
|
|
59
93
|
|
|
60
94
|
const onExit = vi.fn();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
expect(
|
|
95
|
+
render(<App onExit={onExit} />);
|
|
96
|
+
|
|
97
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
98
|
+
const props = branchListProps.at(-1);
|
|
99
|
+
expect(props?.loading).toBe(false);
|
|
100
|
+
expect(props?.error).toBeNull();
|
|
101
|
+
const branchNames = props?.branches.map((b) => b.name);
|
|
102
|
+
expect(branchNames).toContain("main");
|
|
103
|
+
expect(branchNames).toContain("feature/test");
|
|
67
104
|
});
|
|
68
105
|
|
|
69
106
|
it("should show loading state initially", async () => {
|
|
70
|
-
|
|
107
|
+
useGitDataMock.mockImplementation(() => ({
|
|
71
108
|
branches: [],
|
|
72
109
|
loading: true,
|
|
73
110
|
error: null,
|
|
74
111
|
worktrees: [],
|
|
75
112
|
refresh: mockRefresh,
|
|
76
|
-
});
|
|
113
|
+
}));
|
|
77
114
|
|
|
78
115
|
const onExit = vi.fn();
|
|
79
|
-
|
|
80
|
-
<App onExit={onExit} loadingIndicatorDelay={10} />,
|
|
81
|
-
);
|
|
116
|
+
render(<App onExit={onExit} loadingIndicatorDelay={10} />);
|
|
82
117
|
|
|
83
|
-
expect(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
expect(getByText(/Loading Git information/i)).toBeDefined();
|
|
118
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
119
|
+
const props = branchListProps.at(-1);
|
|
120
|
+
expect(props?.loading).toBe(true);
|
|
121
|
+
expect(props?.loadingIndicatorDelay).toBe(10);
|
|
90
122
|
});
|
|
91
123
|
|
|
92
124
|
it("should show error state when Git data fails to load", () => {
|
|
93
125
|
const error = new Error("Failed to fetch branches");
|
|
94
|
-
|
|
126
|
+
useGitDataMock.mockImplementation(() => ({
|
|
95
127
|
branches: [],
|
|
96
128
|
loading: false,
|
|
97
129
|
error,
|
|
98
130
|
worktrees: [],
|
|
99
131
|
refresh: mockRefresh,
|
|
100
|
-
});
|
|
132
|
+
}));
|
|
101
133
|
|
|
102
134
|
const onExit = vi.fn();
|
|
103
|
-
|
|
135
|
+
render(<App onExit={onExit} />);
|
|
104
136
|
|
|
105
|
-
expect(
|
|
106
|
-
|
|
137
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
138
|
+
const props = branchListProps.at(-1);
|
|
139
|
+
expect(props?.error?.message).toBe("Failed to fetch branches");
|
|
140
|
+
expect(props?.loading).toBe(false);
|
|
107
141
|
});
|
|
108
142
|
|
|
109
143
|
it("should calculate statistics from branches", () => {
|
|
@@ -133,67 +167,71 @@ describe("App", () => {
|
|
|
133
167
|
},
|
|
134
168
|
];
|
|
135
169
|
|
|
136
|
-
|
|
170
|
+
useGitDataMock.mockImplementation(() => ({
|
|
137
171
|
branches: branchesWithWorktree,
|
|
138
172
|
loading: false,
|
|
139
173
|
error: null,
|
|
140
174
|
worktrees: [],
|
|
141
175
|
refresh: mockRefresh,
|
|
142
|
-
});
|
|
176
|
+
}));
|
|
143
177
|
|
|
144
178
|
const onExit = vi.fn();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
expect(
|
|
150
|
-
expect(
|
|
151
|
-
expect(
|
|
152
|
-
expect(getByText(/Worktrees:/)).toBeDefined();
|
|
179
|
+
render(<App onExit={onExit} />);
|
|
180
|
+
|
|
181
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
182
|
+
const props = branchListProps.at(-1);
|
|
183
|
+
expect(props?.stats.localCount).toBe(2);
|
|
184
|
+
expect(props?.stats.remoteCount).toBe(1);
|
|
185
|
+
expect(props?.stats.worktreeCount).toBe(1);
|
|
153
186
|
});
|
|
154
187
|
|
|
155
|
-
it("should
|
|
156
|
-
|
|
188
|
+
it("should render branch selection without triggering exit", () => {
|
|
189
|
+
useGitDataMock.mockImplementation(() => ({
|
|
157
190
|
branches: mockBranches,
|
|
158
191
|
loading: false,
|
|
159
192
|
error: null,
|
|
160
193
|
worktrees: [],
|
|
161
194
|
refresh: mockRefresh,
|
|
162
|
-
});
|
|
195
|
+
}));
|
|
163
196
|
|
|
164
197
|
const onExit = vi.fn();
|
|
165
198
|
const { container } = render(<App onExit={onExit} />);
|
|
166
199
|
|
|
167
200
|
expect(container).toBeDefined();
|
|
201
|
+
expect(onExit).not.toHaveBeenCalled();
|
|
168
202
|
// Note: Testing actual selection requires simulating user input,
|
|
169
203
|
// which is covered in integration tests
|
|
170
204
|
});
|
|
171
205
|
|
|
172
206
|
it("should handle empty branch list", () => {
|
|
173
|
-
|
|
207
|
+
useGitDataMock.mockImplementation(() => ({
|
|
174
208
|
branches: [],
|
|
175
209
|
loading: false,
|
|
176
210
|
error: null,
|
|
177
211
|
worktrees: [],
|
|
178
212
|
refresh: mockRefresh,
|
|
179
|
-
});
|
|
213
|
+
}));
|
|
180
214
|
|
|
181
215
|
const onExit = vi.fn();
|
|
182
|
-
|
|
216
|
+
render(<App onExit={onExit} />);
|
|
183
217
|
|
|
184
|
-
expect(
|
|
218
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
219
|
+
const props = branchListProps.at(-1);
|
|
220
|
+
expect(props?.branches).toHaveLength(0);
|
|
221
|
+
expect(props?.loading).toBe(false);
|
|
222
|
+
expect(props?.error).toBeNull();
|
|
185
223
|
});
|
|
186
224
|
|
|
187
225
|
it("should wrap with ErrorBoundary", () => {
|
|
188
226
|
// This test verifies ErrorBoundary is present
|
|
189
227
|
// Actual error catching is tested separately
|
|
190
|
-
|
|
228
|
+
useGitDataMock.mockImplementation(() => ({
|
|
191
229
|
branches: mockBranches,
|
|
192
230
|
loading: false,
|
|
193
231
|
error: null,
|
|
194
232
|
worktrees: [],
|
|
195
233
|
refresh: mockRefresh,
|
|
196
|
-
});
|
|
234
|
+
}));
|
|
197
235
|
|
|
198
236
|
const onExit = vi.fn();
|
|
199
237
|
const { container } = render(<App onExit={onExit} />);
|
|
@@ -202,30 +240,32 @@ describe("App", () => {
|
|
|
202
240
|
});
|
|
203
241
|
|
|
204
242
|
it("should format branch items with icons", () => {
|
|
205
|
-
|
|
243
|
+
useGitDataMock.mockImplementation(() => ({
|
|
206
244
|
branches: mockBranches,
|
|
207
245
|
loading: false,
|
|
208
246
|
error: null,
|
|
209
247
|
worktrees: [],
|
|
210
248
|
refresh: mockRefresh,
|
|
211
|
-
});
|
|
249
|
+
}));
|
|
212
250
|
|
|
213
251
|
const onExit = vi.fn();
|
|
214
|
-
|
|
252
|
+
render(<App onExit={onExit} />);
|
|
215
253
|
|
|
216
|
-
|
|
217
|
-
|
|
254
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
255
|
+
const props = branchListProps.at(-1);
|
|
256
|
+
const main = props?.branches.find((b: any) => b.name === "main");
|
|
257
|
+
expect(main?.icons).toContain("⚡");
|
|
218
258
|
});
|
|
219
259
|
|
|
220
260
|
describe("BranchActionSelectorScreen integration", () => {
|
|
221
261
|
it("should show BranchActionSelectorScreen after branch selection", () => {
|
|
222
|
-
|
|
262
|
+
useGitDataMock.mockImplementation(() => ({
|
|
223
263
|
branches: mockBranches,
|
|
224
264
|
loading: false,
|
|
225
265
|
error: null,
|
|
226
266
|
worktrees: [],
|
|
227
267
|
refresh: mockRefresh,
|
|
228
|
-
});
|
|
268
|
+
}));
|
|
229
269
|
|
|
230
270
|
const onExit = vi.fn();
|
|
231
271
|
const { container } = render(<App onExit={onExit} />);
|
|
@@ -235,13 +275,13 @@ describe("App", () => {
|
|
|
235
275
|
});
|
|
236
276
|
|
|
237
277
|
it('should navigate to AI tool selector when "use existing" is selected', () => {
|
|
238
|
-
|
|
278
|
+
useGitDataMock.mockImplementation(() => ({
|
|
239
279
|
branches: mockBranches,
|
|
240
280
|
loading: false,
|
|
241
281
|
error: null,
|
|
242
282
|
worktrees: [],
|
|
243
283
|
refresh: mockRefresh,
|
|
244
|
-
});
|
|
284
|
+
}));
|
|
245
285
|
|
|
246
286
|
const onExit = vi.fn();
|
|
247
287
|
const { container } = render(<App onExit={onExit} />);
|
|
@@ -251,13 +291,13 @@ describe("App", () => {
|
|
|
251
291
|
});
|
|
252
292
|
|
|
253
293
|
it('should navigate to branch creator when "create new" is selected', () => {
|
|
254
|
-
|
|
294
|
+
useGitDataMock.mockImplementation(() => ({
|
|
255
295
|
branches: mockBranches,
|
|
256
296
|
loading: false,
|
|
257
297
|
error: null,
|
|
258
298
|
worktrees: [],
|
|
259
299
|
refresh: mockRefresh,
|
|
260
|
-
});
|
|
300
|
+
}));
|
|
261
301
|
|
|
262
302
|
const onExit = vi.fn();
|
|
263
303
|
const { container } = render(<App onExit={onExit} />);
|
|
@@ -266,13 +306,4 @@ describe("App", () => {
|
|
|
266
306
|
expect(container).toBeDefined();
|
|
267
307
|
});
|
|
268
308
|
});
|
|
269
|
-
|
|
270
|
-
afterEach(() => {
|
|
271
|
-
useGitDataSpy.mockReset();
|
|
272
|
-
useGitDataSpy.mockImplementation(originalUseGitData);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
afterAll(() => {
|
|
277
|
-
useGitDataSpy.mockRestore();
|
|
278
309
|
});
|
|
@@ -13,18 +13,44 @@ import {
|
|
|
13
13
|
} from "vitest";
|
|
14
14
|
import { render } from "@testing-library/react";
|
|
15
15
|
import React from "react";
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
16
|
+
import * as BranchListScreenModule from "../../components/screens/BranchListScreen.js";
|
|
17
|
+
import type { BranchListScreenProps } from "../../components/screens/BranchListScreen.js";
|
|
18
18
|
import { Window } from "happy-dom";
|
|
19
19
|
import type { BranchInfo, BranchItem, Statistics } from "../../types.js";
|
|
20
|
-
import * as useGitDataModule from "../../hooks/useGitData.js";
|
|
21
20
|
|
|
22
21
|
const mockRefresh = vi.fn();
|
|
23
|
-
const
|
|
24
|
-
const
|
|
22
|
+
const branchListProps: BranchListScreenProps[] = [];
|
|
23
|
+
const useGitDataMock = vi.fn();
|
|
24
|
+
let App: typeof import("../../components/App.js").App;
|
|
25
|
+
|
|
26
|
+
vi.mock("../../hooks/useGitData.js", () => ({
|
|
27
|
+
useGitData: (...args: any[]) => useGitDataMock(...args),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("../../components/screens/BranchListScreen.js", () => ({
|
|
31
|
+
BranchListScreen: (props: BranchListScreenProps) => {
|
|
32
|
+
branchListProps.push(props);
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
<div>BranchList</div>
|
|
36
|
+
{props.error && <div>Error: {props.error.message}</div>}
|
|
37
|
+
<div>
|
|
38
|
+
Local:{props.stats?.localCount ?? 0} Remote:
|
|
39
|
+
{props.stats?.remoteCount ?? 0} Worktrees:
|
|
40
|
+
{props.stats?.worktreeCount ?? 0}
|
|
41
|
+
</div>
|
|
42
|
+
<ul>
|
|
43
|
+
{props.branches.map((b) => (
|
|
44
|
+
<li key={b.name}>{b.name}</li>
|
|
45
|
+
))}
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
25
51
|
|
|
26
52
|
describe("Edge Cases Integration Tests", () => {
|
|
27
|
-
beforeEach(() => {
|
|
53
|
+
beforeEach(async () => {
|
|
28
54
|
// Setup happy-dom
|
|
29
55
|
const window = new Window();
|
|
30
56
|
globalThis.window = window as any;
|
|
@@ -32,8 +58,9 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
32
58
|
|
|
33
59
|
// Reset mocks
|
|
34
60
|
vi.clearAllMocks();
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
useGitDataMock.mockReset();
|
|
62
|
+
branchListProps.length = 0;
|
|
63
|
+
App = (await import("../../components/App.js")).App;
|
|
37
64
|
});
|
|
38
65
|
|
|
39
66
|
/**
|
|
@@ -62,7 +89,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
62
89
|
|
|
63
90
|
const onSelect = vi.fn();
|
|
64
91
|
const { container } = render(
|
|
65
|
-
<BranchListScreen
|
|
92
|
+
<BranchListScreenModule.BranchListScreen
|
|
66
93
|
branches={mockBranches}
|
|
67
94
|
stats={mockStats}
|
|
68
95
|
onSelect={onSelect}
|
|
@@ -93,16 +120,16 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
93
120
|
};
|
|
94
121
|
|
|
95
122
|
const onSelect = vi.fn();
|
|
96
|
-
const {
|
|
97
|
-
<BranchListScreen
|
|
123
|
+
const { container } = render(
|
|
124
|
+
<BranchListScreenModule.BranchListScreen
|
|
98
125
|
branches={mockBranches}
|
|
99
126
|
stats={mockStats}
|
|
100
127
|
onSelect={onSelect}
|
|
101
128
|
/>,
|
|
102
129
|
);
|
|
103
130
|
|
|
104
|
-
|
|
105
|
-
expect(
|
|
131
|
+
expect(container).toBeDefined();
|
|
132
|
+
expect(branchListProps.at(-1)?.branches).toHaveLength(1);
|
|
106
133
|
|
|
107
134
|
process.stdout.rows = originalRows;
|
|
108
135
|
});
|
|
@@ -139,15 +166,17 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
139
166
|
|
|
140
167
|
const onSelect = vi.fn();
|
|
141
168
|
const { container } = render(
|
|
142
|
-
<BranchListScreen
|
|
169
|
+
<BranchListScreenModule.BranchListScreen
|
|
143
170
|
branches={mockBranches}
|
|
144
171
|
stats={mockStats}
|
|
145
172
|
onSelect={onSelect}
|
|
146
173
|
/>,
|
|
147
174
|
);
|
|
148
175
|
|
|
149
|
-
|
|
150
|
-
expect(
|
|
176
|
+
expect(container.textContent).toContain(longBranchName);
|
|
177
|
+
expect(
|
|
178
|
+
branchListProps.at(-1)?.branches?.some((b) => b.name === longBranchName),
|
|
179
|
+
).toBe(true);
|
|
151
180
|
});
|
|
152
181
|
|
|
153
182
|
it("[T092] should handle branch names with special characters", () => {
|
|
@@ -177,7 +206,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
177
206
|
|
|
178
207
|
const onSelect = vi.fn();
|
|
179
208
|
const { container } = render(
|
|
180
|
-
<BranchListScreen
|
|
209
|
+
<BranchListScreenModule.BranchListScreen
|
|
181
210
|
branches={mockBranches}
|
|
182
211
|
stats={mockStats}
|
|
183
212
|
onSelect={onSelect}
|
|
@@ -196,7 +225,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
196
225
|
it("[T093] should catch errors in App component", async () => {
|
|
197
226
|
// Mock useGitData to throw an error after initial render
|
|
198
227
|
let callCount = 0;
|
|
199
|
-
|
|
228
|
+
useGitDataMock.mockImplementation(() => {
|
|
200
229
|
callCount++;
|
|
201
230
|
if (callCount > 1) {
|
|
202
231
|
throw new Error("Simulated error");
|
|
@@ -220,7 +249,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
220
249
|
|
|
221
250
|
it("[T093] should display error message when data loading fails", () => {
|
|
222
251
|
const testError = new Error("Test error: Failed to load Git data");
|
|
223
|
-
|
|
252
|
+
useGitDataMock.mockReturnValue({
|
|
224
253
|
branches: [],
|
|
225
254
|
worktrees: [],
|
|
226
255
|
loading: false,
|
|
@@ -232,13 +261,15 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
232
261
|
const onExit = vi.fn();
|
|
233
262
|
const { getByText } = render(<App onExit={onExit} />);
|
|
234
263
|
|
|
235
|
-
|
|
264
|
+
expect(branchListProps).not.toHaveLength(0);
|
|
265
|
+
expect(branchListProps.at(-1)?.error).toBe(testError);
|
|
266
|
+
// Error should be displayed via stubbed screen
|
|
236
267
|
expect(getByText(/Error:/i)).toBeDefined();
|
|
237
268
|
expect(getByText(/Failed to load Git data/i)).toBeDefined();
|
|
238
269
|
});
|
|
239
270
|
|
|
240
271
|
it("[T093] should handle empty branches list gracefully", () => {
|
|
241
|
-
|
|
272
|
+
useGitDataMock.mockReturnValue({
|
|
242
273
|
branches: [],
|
|
243
274
|
worktrees: [],
|
|
244
275
|
loading: false,
|
|
@@ -265,7 +296,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
265
296
|
isCurrent: false,
|
|
266
297
|
}));
|
|
267
298
|
|
|
268
|
-
|
|
299
|
+
useGitDataMock.mockReturnValue({
|
|
269
300
|
branches: mockBranches,
|
|
270
301
|
worktrees: Array.from({ length: 30 }, (_, i) => ({
|
|
271
302
|
branch: `feature/branch-${i}`,
|
|
@@ -305,7 +336,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
305
336
|
|
|
306
337
|
const onSelect = vi.fn();
|
|
307
338
|
const { container, rerender } = render(
|
|
308
|
-
<BranchListScreen
|
|
339
|
+
<BranchListScreenModule.BranchListScreen
|
|
309
340
|
branches={mockBranches}
|
|
310
341
|
stats={mockStats}
|
|
311
342
|
onSelect={onSelect}
|
|
@@ -319,7 +350,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
319
350
|
|
|
320
351
|
// Re-render
|
|
321
352
|
rerender(
|
|
322
|
-
<BranchListScreen
|
|
353
|
+
<BranchListScreenModule.BranchListScreen
|
|
323
354
|
branches={mockBranches}
|
|
324
355
|
stats={mockStats}
|
|
325
356
|
onSelect={onSelect}
|
|
@@ -332,11 +363,7 @@ describe("Edge Cases Integration Tests", () => {
|
|
|
332
363
|
});
|
|
333
364
|
|
|
334
365
|
afterEach(() => {
|
|
335
|
-
|
|
336
|
-
|
|
366
|
+
useGitDataMock.mockReset();
|
|
367
|
+
branchListProps.length = 0;
|
|
337
368
|
});
|
|
338
369
|
});
|
|
339
|
-
|
|
340
|
-
afterAll(() => {
|
|
341
|
-
useGitDataSpy.mockRestore();
|
|
342
|
-
});
|
|
@@ -31,7 +31,11 @@ import type {
|
|
|
31
31
|
InferenceLevel,
|
|
32
32
|
SelectedBranchState,
|
|
33
33
|
} from "../types.js";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
getRepositoryRoot,
|
|
36
|
+
deleteBranch,
|
|
37
|
+
deleteRemoteBranch,
|
|
38
|
+
} from "../../../git.js";
|
|
35
39
|
import {
|
|
36
40
|
createWorktree,
|
|
37
41
|
generateWorktreePath,
|
|
@@ -728,6 +732,16 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
728
732
|
}
|
|
729
733
|
|
|
730
734
|
await deleteBranch(target.branch, true);
|
|
735
|
+
|
|
736
|
+
// マージ済みの場合のみリモートブランチも削除
|
|
737
|
+
if (target.hasRemoteBranch && target.reasons?.includes("merged-pr")) {
|
|
738
|
+
try {
|
|
739
|
+
await deleteRemoteBranch(target.branch);
|
|
740
|
+
} catch {
|
|
741
|
+
// リモート削除失敗はログのみ、処理は続行
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
731
745
|
succeededBranches.push(target.branch);
|
|
732
746
|
setCleanupIndicators((prev) => ({
|
|
733
747
|
...prev,
|
|
@@ -803,8 +817,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
803
817
|
// All selections complete - exit with result
|
|
804
818
|
if (selectedBranch && selectedTool) {
|
|
805
819
|
const defaultModel = getDefaultModelOption(selectedTool);
|
|
806
|
-
const resolvedModel =
|
|
807
|
-
selectedModel?.model ?? defaultModel?.id ?? null;
|
|
820
|
+
const resolvedModel = selectedModel?.model ?? defaultModel?.id ?? null;
|
|
808
821
|
const resolvedInference =
|
|
809
822
|
selectedModel?.inferenceLevel ??
|
|
810
823
|
getDefaultInferenceForModel(defaultModel ?? undefined);
|
package/src/cli/ui/types.ts
CHANGED
|
@@ -132,7 +132,7 @@ export interface WorktreeWithPR {
|
|
|
132
132
|
pullRequest: PullRequest | null;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
export type CleanupReason = "merged-pr" | "no-diff-with-base";
|
|
135
|
+
export type CleanupReason = "merged-pr" | "no-diff-with-base" | "remote-synced";
|
|
136
136
|
|
|
137
137
|
export interface CleanupTarget {
|
|
138
138
|
worktreePath: string | null; // null for local branch only cleanup
|
package/src/index.test.ts
CHANGED
|
@@ -38,7 +38,7 @@ describe("showVersion via CLI args", () => {
|
|
|
38
38
|
process.argv = ["node", "index.js", "--version"];
|
|
39
39
|
|
|
40
40
|
// getPackageVersion()をモック
|
|
41
|
-
const mockVersion = "
|
|
41
|
+
const mockVersion = "2.6.1";
|
|
42
42
|
vi.spyOn(utils, "getPackageVersion").mockResolvedValue(mockVersion);
|
|
43
43
|
|
|
44
44
|
// Act: main()を呼び出す
|
package/src/index.ts
CHANGED
|
@@ -562,6 +562,19 @@ export async function handleAIToolWorkflow(
|
|
|
562
562
|
throw new Error(`Tool not found: ${tool}`);
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
+
// Save selection immediately so "last tool" is reflected even if the tool
|
|
566
|
+
// is interrupted or killed mid-run (e.g., Ctrl+C).
|
|
567
|
+
await saveSession({
|
|
568
|
+
lastWorktreePath: worktreePath,
|
|
569
|
+
lastBranch: branch,
|
|
570
|
+
lastUsedTool: tool,
|
|
571
|
+
toolLabel: toolConfig.displayName ?? tool,
|
|
572
|
+
mode,
|
|
573
|
+
model: model ?? null,
|
|
574
|
+
timestamp: Date.now(),
|
|
575
|
+
repositoryRoot: repoRoot,
|
|
576
|
+
});
|
|
577
|
+
|
|
565
578
|
// Launch selected AI tool
|
|
566
579
|
// Builtin tools use their dedicated launch functions
|
|
567
580
|
// Custom tools use the generic launchCustomAITool function
|
|
@@ -665,18 +678,6 @@ export async function handleAIToolWorkflow(
|
|
|
665
678
|
});
|
|
666
679
|
}
|
|
667
680
|
|
|
668
|
-
// Save session with lastUsedTool (tool selection is confirmed at this point)
|
|
669
|
-
await saveSession({
|
|
670
|
-
lastWorktreePath: worktreePath,
|
|
671
|
-
lastBranch: branch,
|
|
672
|
-
lastUsedTool: tool,
|
|
673
|
-
toolLabel: toolConfig.displayName ?? tool,
|
|
674
|
-
mode,
|
|
675
|
-
model: model ?? null,
|
|
676
|
-
timestamp: Date.now(),
|
|
677
|
-
repositoryRoot: repoRoot,
|
|
678
|
-
});
|
|
679
|
-
|
|
680
681
|
printInfo("Session completed successfully. Returning to main menu...");
|
|
681
682
|
return;
|
|
682
683
|
} catch (error) {
|