@akiojin/gwt 2.9.1 → 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.
@@ -15,14 +15,7 @@ import { render, act, waitFor } from "@testing-library/react";
15
15
  import React from "react";
16
16
  import type { BranchItem, CleanupTarget } from "../../types.js";
17
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
+ let App: typeof import("../../components/App.js").App;
26
19
 
27
20
  const navigateToMock = vi.fn();
28
21
  const goBackMock = vi.fn();
@@ -32,49 +25,77 @@ const worktreeScreenProps: any[] = [];
32
25
  const branchCreatorProps: any[] = [];
33
26
  const branchListProps: any[] = [];
34
27
 
35
- const originalUseGitData = useGitDataModule.useGitData;
36
- const originalUseScreenState = useScreenStateModule.useScreenState;
37
- const originalWorktreeManagerScreen =
38
- WorktreeManagerScreenModule.WorktreeManagerScreen;
39
- const originalBranchCreatorScreen =
40
- BranchCreatorScreenModule.BranchCreatorScreen;
41
- const originalBranchListScreen = BranchListScreenModule.BranchListScreen;
42
- const originalGetMergedPRWorktrees = worktreeModule.getMergedPRWorktrees;
43
- const originalGenerateWorktreePath = worktreeModule.generateWorktreePath;
44
- const originalCreateWorktree = worktreeModule.createWorktree;
45
- const originalRemoveWorktree = worktreeModule.removeWorktree;
46
- const originalGetRepositoryRoot = gitModule.getRepositoryRoot;
47
- const originalDeleteBranch = gitModule.deleteBranch;
48
-
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");
28
+ const useGitDataMock = vi.fn();
29
+ const useScreenStateMock = vi.fn();
30
+ const getMergedPRWorktreesMock = vi.fn();
31
+ const generateWorktreePathMock = vi.fn();
32
+ const createWorktreeMock = vi.fn();
33
+ const removeWorktreeMock = vi.fn();
34
+ const getRepositoryRootMock = vi.fn();
35
+ const deleteBranchMock = vi.fn();
36
+
37
+ vi.mock("../../hooks/useGitData.js", () => ({
38
+ useGitData: (...args: any[]) => useGitDataMock(...args),
39
+ }));
40
+
41
+ vi.mock("../../hooks/useScreenState.js", () => ({
42
+ useScreenState: (...args: any[]) => useScreenStateMock(...args),
43
+ }));
44
+
45
+ vi.mock("../../../../worktree.js", async () => {
46
+ const actual = await vi.importActual<
47
+ typeof import("../../../../worktree.js")
48
+ >("../../../../worktree.js");
49
+ return {
50
+ ...actual,
51
+ getMergedPRWorktrees: getMergedPRWorktreesMock,
52
+ generateWorktreePath: generateWorktreePathMock,
53
+ createWorktree: createWorktreeMock,
54
+ removeWorktree: removeWorktreeMock,
55
+ };
56
+ });
57
+
58
+ vi.mock("../../../../git.js", async () => {
59
+ const actual =
60
+ await vi.importActual<typeof import("../../../../git.js")>(
61
+ "../../../../git.js",
62
+ );
63
+ return {
64
+ ...actual,
65
+ getRepositoryRoot: getRepositoryRootMock,
66
+ deleteBranch: deleteBranchMock,
67
+ };
68
+ });
69
+
70
+ vi.mock("../../components/screens/WorktreeManagerScreen.js", () => {
71
+ return {
72
+ WorktreeManagerScreen: (props: any) => {
73
+ worktreeScreenProps.push(props);
74
+ return React.createElement("div", null, "WorktreeManagerScreenMock");
75
+ },
76
+ };
77
+ });
78
+
79
+ vi.mock("../../components/screens/BranchCreatorScreen.js", () => {
80
+ return {
81
+ BranchCreatorScreen: (props: any) => {
82
+ branchCreatorProps.push(props);
83
+ return React.createElement("div", null, "BranchCreatorScreenMock");
84
+ },
85
+ };
86
+ });
87
+
88
+ vi.mock("../../components/screens/BranchListScreen.js", () => {
89
+ return {
90
+ BranchListScreen: (props: any) => {
91
+ branchListProps.push(props);
92
+ return React.createElement("div", null, "BranchListScreenMock");
93
+ },
94
+ };
95
+ });
75
96
 
76
97
  describe("App shortcuts integration", () => {
77
- beforeEach(() => {
98
+ beforeEach(async () => {
78
99
  if (typeof globalThis.document === "undefined") {
79
100
  const window = new Window();
80
101
  globalThis.window = window as any;
@@ -86,7 +107,7 @@ describe("App shortcuts integration", () => {
86
107
  navigateToMock.mockClear();
87
108
  goBackMock.mockClear();
88
109
  resetMock.mockClear();
89
- useGitDataSpy.mockImplementation(() => ({
110
+ useGitDataMock.mockReturnValue({
90
111
  branches: [],
91
112
  worktrees: [
92
113
  {
@@ -99,26 +120,14 @@ describe("App shortcuts integration", () => {
99
120
  error: null,
100
121
  refresh: vi.fn(),
101
122
  lastUpdated: null,
102
- }));
103
- useScreenStateSpy.mockImplementation(() => ({
104
- currentScreen: "worktree-manager",
105
- navigateTo: navigateToMock as _Mock,
106
- goBack: goBackMock as _Mock,
107
- reset: resetMock as _Mock,
108
- }));
109
- worktreeManagerScreenSpy.mockImplementation((props: any) => {
110
- worktreeScreenProps.push(props);
111
- return React.createElement(originalWorktreeManagerScreen, props);
112
- });
113
- branchCreatorScreenSpy.mockImplementation((props: any) => {
114
- branchCreatorProps.push(props);
115
- return React.createElement(originalBranchCreatorScreen, props);
116
123
  });
117
- branchListScreenSpy.mockImplementation((props: any) => {
118
- branchListProps.push(props);
119
- return React.createElement(originalBranchListScreen, props);
124
+ useScreenStateMock.mockReturnValue({
125
+ currentScreen: "worktree-manager",
126
+ navigateTo: navigateToMock as Mock,
127
+ goBack: goBackMock as Mock,
128
+ reset: resetMock as Mock,
120
129
  });
121
- getMergedPRWorktreesSpy.mockResolvedValue([
130
+ getMergedPRWorktreesMock.mockResolvedValue([
122
131
  {
123
132
  branch: "feature/add-new-feature",
124
133
  cleanupType: "worktree-and-branch",
@@ -152,44 +161,26 @@ describe("App shortcuts integration", () => {
152
161
  isAccessible: true,
153
162
  },
154
163
  ] as CleanupTarget[]);
155
- generateWorktreePathSpy.mockResolvedValue("/worktrees/new-branch");
156
- createWorktreeSpy.mockResolvedValue(undefined);
157
- removeWorktreeSpy.mockResolvedValue(undefined);
158
- getRepositoryRootSpy.mockResolvedValue("/repo");
159
- deleteBranchSpy.mockResolvedValue(undefined);
164
+ generateWorktreePathMock.mockResolvedValue("/worktrees/new-branch");
165
+ createWorktreeMock.mockResolvedValue(undefined);
166
+ removeWorktreeMock.mockResolvedValue(undefined);
167
+ getRepositoryRootMock.mockResolvedValue("/repo");
168
+ deleteBranchMock.mockResolvedValue(undefined);
169
+ App = (await import("../../components/App.js")).App;
160
170
  });
161
171
 
162
172
  afterEach(() => {
163
- useGitDataSpy.mockReset();
164
- useScreenStateSpy.mockReset();
165
- worktreeManagerScreenSpy.mockReset();
166
- branchCreatorScreenSpy.mockReset();
167
- branchListScreenSpy.mockReset();
168
- getMergedPRWorktreesSpy.mockReset();
169
- generateWorktreePathSpy.mockReset();
170
- createWorktreeSpy.mockReset();
171
- removeWorktreeSpy.mockReset();
172
- getRepositoryRootSpy.mockReset();
173
- deleteBranchSpy.mockReset();
174
- useGitDataSpy.mockImplementation(originalUseGitData);
175
- useScreenStateSpy.mockImplementation(originalUseScreenState);
176
- worktreeManagerScreenSpy.mockImplementation(
177
- originalWorktreeManagerScreen as any,
178
- );
179
- branchCreatorScreenSpy.mockImplementation(
180
- originalBranchCreatorScreen as any,
181
- );
182
- branchListScreenSpy.mockImplementation(originalBranchListScreen as any);
183
- getMergedPRWorktreesSpy.mockImplementation(
184
- originalGetMergedPRWorktrees as any,
185
- );
186
- generateWorktreePathSpy.mockImplementation(
187
- originalGenerateWorktreePath as any,
188
- );
189
- createWorktreeSpy.mockImplementation(originalCreateWorktree as any);
190
- removeWorktreeSpy.mockImplementation(originalRemoveWorktree as any);
191
- getRepositoryRootSpy.mockImplementation(originalGetRepositoryRoot as any);
192
- deleteBranchSpy.mockImplementation(originalDeleteBranch as any);
173
+ useGitDataMock.mockReset();
174
+ useScreenStateMock.mockReset();
175
+ getMergedPRWorktreesMock.mockReset();
176
+ generateWorktreePathMock.mockReset();
177
+ createWorktreeMock.mockReset();
178
+ removeWorktreeMock.mockReset();
179
+ getRepositoryRootMock.mockReset();
180
+ deleteBranchMock.mockReset();
181
+ worktreeScreenProps.length = 0;
182
+ branchCreatorProps.length = 0;
183
+ branchListProps.length = 0;
193
184
  });
194
185
 
195
186
  it("navigates to AI tool selector when worktree is selected", () => {
@@ -209,11 +200,11 @@ describe("App shortcuts integration", () => {
209
200
  const onExit = vi.fn();
210
201
 
211
202
  // Update screen state mock to branch-creator for this test
212
- useScreenStateSpy.mockReturnValue({
203
+ useScreenStateMock.mockReturnValue({
213
204
  currentScreen: "branch-creator",
214
- navigateTo: navigateToMock as _Mock,
215
- goBack: goBackMock as _Mock,
216
- reset: resetMock as _Mock,
205
+ navigateTo: navigateToMock as Mock,
206
+ goBack: goBackMock as Mock,
207
+ reset: resetMock as Mock,
217
208
  });
218
209
 
219
210
  render(<App onExit={onExit} />);
@@ -225,7 +216,7 @@ describe("App shortcuts integration", () => {
225
216
  await onCreate("feature/new-branch");
226
217
  });
227
218
 
228
- expect(createWorktreeSpy).toHaveBeenCalledWith(
219
+ expect(createWorktreeMock).toHaveBeenCalledWith(
229
220
  expect.objectContaining({
230
221
  branchName: "feature/new-branch",
231
222
  isNewBranch: true,
@@ -243,25 +234,25 @@ describe("App shortcuts integration", () => {
243
234
  let resolveRemoveWorktree: (() => void) | undefined;
244
235
  let resolveDeleteBranch: (() => void) | undefined;
245
236
 
246
- removeWorktreeSpy.mockImplementationOnce(
237
+ removeWorktreeMock.mockImplementationOnce(
247
238
  () =>
248
239
  new Promise<void>((resolve) => {
249
240
  resolveRemoveWorktree = resolve;
250
241
  }),
251
242
  );
252
243
 
253
- deleteBranchSpy.mockImplementationOnce(
244
+ deleteBranchMock.mockImplementationOnce(
254
245
  () =>
255
246
  new Promise<void>((resolve) => {
256
247
  resolveDeleteBranch = resolve;
257
248
  }),
258
249
  );
259
250
 
260
- useScreenStateSpy.mockReturnValue({
251
+ useScreenStateMock.mockReturnValue({
261
252
  currentScreen: "branch-list",
262
- navigateTo: navigateToMock as _Mock,
263
- goBack: goBackMock as _Mock,
264
- reset: resetMock as _Mock,
253
+ navigateTo: navigateToMock as Mock,
254
+ goBack: goBackMock as Mock,
255
+ reset: resetMock as Mock,
265
256
  });
266
257
 
267
258
  render(<App onExit={onExit} />);
@@ -299,11 +290,11 @@ describe("App shortcuts integration", () => {
299
290
 
300
291
  resolveDeleteBranch?.();
301
292
 
302
- expect(removeWorktreeSpy).toHaveBeenCalledWith(
293
+ expect(removeWorktreeMock).toHaveBeenCalledWith(
303
294
  "/worktrees/feature-add-new-feature",
304
295
  true,
305
296
  );
306
- expect(deleteBranchSpy).toHaveBeenCalledWith(
297
+ expect(deleteBranchMock).toHaveBeenCalledWith(
307
298
  "feature/add-new-feature",
308
299
  true,
309
300
  );
@@ -339,17 +330,3 @@ describe("App shortcuts integration", () => {
339
330
  }
340
331
  });
341
332
  });
342
-
343
- afterAll(() => {
344
- useGitDataSpy.mockRestore();
345
- useScreenStateSpy.mockRestore();
346
- worktreeManagerScreenSpy.mockRestore();
347
- branchCreatorScreenSpy.mockRestore();
348
- branchListScreenSpy.mockRestore();
349
- getMergedPRWorktreesSpy.mockRestore();
350
- generateWorktreePathSpy.mockRestore();
351
- createWorktreeSpy.mockRestore();
352
- removeWorktreeSpy.mockRestore();
353
- getRepositoryRootSpy.mockRestore();
354
- deleteBranchSpy.mockRestore();
355
- });
@@ -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 * as useGitDataModule from "../../hooks/useGitData.js";
17
+ import type { BranchListScreenProps } from "../../components/screens/BranchListScreen.js";
19
18
 
20
19
  const mockRefresh = vi.fn();
21
- const originalUseGitData = useGitDataModule.useGitData;
22
- const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
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
- useGitDataSpy.mockReset();
33
- useGitDataSpy.mockImplementation(originalUseGitData);
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
- useGitDataSpy.mockReturnValue({
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
- const { getByText } = render(<App onExit={onExit} />);
62
-
63
- // Check for BranchListScreen elements
64
- expect(getByText(/gwt - Branch Selection/i)).toBeDefined();
65
- expect(getByText(/main/)).toBeDefined();
66
- expect(getByText(/feature\/test/)).toBeDefined();
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
- useGitDataSpy.mockReturnValue({
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
- const { queryByText, getByText } = render(
80
- <App onExit={onExit} loadingIndicatorDelay={10} />,
81
- );
116
+ render(<App onExit={onExit} loadingIndicatorDelay={10} />);
82
117
 
83
- expect(queryByText(/Loading Git information/i)).toBeNull();
84
-
85
- await act(async () => {
86
- await new Promise((resolve) => setTimeout(resolve, 15));
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
- useGitDataSpy.mockReturnValue({
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
- const { getByText } = render(<App onExit={onExit} />);
135
+ render(<App onExit={onExit} />);
104
136
 
105
- expect(getByText(/Error:/i)).toBeDefined();
106
- expect(getByText(/Failed to fetch branches/i)).toBeDefined();
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
- useGitDataSpy.mockReturnValue({
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
- const { getByText, getAllByText } = render(<App onExit={onExit} />);
146
-
147
- // Check for statistics
148
- expect(getByText(/Local:/)).toBeDefined();
149
- expect(getAllByText(/2/).length).toBeGreaterThan(0); // 2 local branches
150
- expect(getByText(/Remote:/)).toBeDefined();
151
- expect(getAllByText(/1/).length).toBeGreaterThan(0); // 1 remote branch + 1 worktree
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 call onExit when branch is selected", () => {
156
- useGitDataSpy.mockReturnValue({
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
- useGitDataSpy.mockReturnValue({
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
- const { getByText } = render(<App onExit={onExit} />);
216
+ render(<App onExit={onExit} />);
183
217
 
184
- expect(getByText(/No branches found/i)).toBeDefined();
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
- useGitDataSpy.mockReturnValue({
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
- useGitDataSpy.mockReturnValue({
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
- const { getByText } = render(<App onExit={onExit} />);
252
+ render(<App onExit={onExit} />);
215
253
 
216
- // Check for branch type icon (main = ⚡)
217
- expect(getByText(/⚡/)).toBeDefined();
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
- useGitDataSpy.mockReturnValue({
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
- useGitDataSpy.mockReturnValue({
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
- useGitDataSpy.mockReturnValue({
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
  });