@akiojin/gwt 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +4 -4
- package/README.md +4 -4
- package/dist/cli/ui/components/App.d.ts +4 -4
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +144 -105
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Confirm.js +7 -7
- package/dist/cli/ui/components/common/Confirm.js.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
- package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +2 -2
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Input.js +4 -4
- package/dist/cli/ui/components/common/Input.js.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
- package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +1 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +11 -12
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
- package/dist/client/index.html +1 -1
- package/dist/config/builtin-tools.d.ts +10 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +40 -4
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +4 -3
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts +12 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +154 -0
- package/dist/gemini.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +12 -0
- package/dist/qwen.d.ts.map +1 -0
- package/dist/qwen.js +154 -0
- package/dist/qwen.js.map +1 -0
- package/dist/services/git.service.d.ts.map +1 -1
- package/dist/services/git.service.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +1 -1
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +7 -4
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +10 -4
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/package.json +2 -1
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
- package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +40 -34
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
- package/src/cli/ui/components/App.tsx +247 -150
- package/src/cli/ui/components/common/Confirm.tsx +13 -9
- package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
- package/src/cli/ui/components/common/Input.tsx +12 -4
- package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
- package/src/cli/ui/components/common/Select.tsx +28 -17
- package/src/cli/ui/components/parts/Header.test.tsx +5 -15
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +19 -13
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
- package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
- package/src/cli/ui/types.ts +8 -1
- package/src/config/builtin-tools.ts +42 -4
- package/src/config/index.ts +2 -12
- package/src/config/tools.ts +16 -6
- package/src/gemini.ts +202 -0
- package/src/git.ts +2 -1
- package/src/index.ts +30 -0
- package/src/qwen.ts +208 -0
- package/src/services/git.service.ts +2 -1
- package/src/web/client/src/components/BranchGraph.tsx +3 -2
- package/src/web/client/src/components/EnvEditor.tsx +44 -11
- package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
- package/src/web/client/src/pages/BranchListPage.tsx +37 -13
- package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import {
|
|
5
|
+
describe,
|
|
6
|
+
it,
|
|
7
|
+
expect,
|
|
8
|
+
beforeEach,
|
|
9
|
+
afterEach,
|
|
10
|
+
afterAll,
|
|
11
|
+
vi,
|
|
12
|
+
} from "vitest";
|
|
13
|
+
import { act, render } from "@testing-library/react";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { App } from "../../components/App.js";
|
|
16
|
+
import { Window } from "happy-dom";
|
|
17
|
+
import type { BranchInfo } from "../../types.js";
|
|
18
|
+
import * as useGitDataModule from "../../hooks/useGitData.js";
|
|
11
19
|
|
|
12
20
|
const mockRefresh = vi.fn();
|
|
13
21
|
const originalUseGitData = useGitDataModule.useGitData;
|
|
14
|
-
const useGitDataSpy = vi.spyOn(useGitDataModule,
|
|
22
|
+
const useGitDataSpy = vi.spyOn(useGitDataModule, "useGitData");
|
|
15
23
|
|
|
16
|
-
describe(
|
|
24
|
+
describe("App", () => {
|
|
17
25
|
beforeEach(() => {
|
|
18
26
|
// Setup happy-dom
|
|
19
27
|
const window = new Window();
|
|
@@ -27,20 +35,20 @@ describe('App', () => {
|
|
|
27
35
|
|
|
28
36
|
const mockBranches: BranchInfo[] = [
|
|
29
37
|
{
|
|
30
|
-
name:
|
|
31
|
-
type:
|
|
32
|
-
branchType:
|
|
38
|
+
name: "main",
|
|
39
|
+
type: "local",
|
|
40
|
+
branchType: "main",
|
|
33
41
|
isCurrent: true,
|
|
34
42
|
},
|
|
35
43
|
{
|
|
36
|
-
name:
|
|
37
|
-
type:
|
|
38
|
-
branchType:
|
|
44
|
+
name: "feature/test",
|
|
45
|
+
type: "local",
|
|
46
|
+
branchType: "feature",
|
|
39
47
|
isCurrent: false,
|
|
40
48
|
},
|
|
41
49
|
];
|
|
42
50
|
|
|
43
|
-
it(
|
|
51
|
+
it("should render BranchListScreen when data is loaded", () => {
|
|
44
52
|
useGitDataSpy.mockReturnValue({
|
|
45
53
|
branches: mockBranches,
|
|
46
54
|
loading: false,
|
|
@@ -58,7 +66,7 @@ describe('App', () => {
|
|
|
58
66
|
expect(getByText(/feature\/test/)).toBeDefined();
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
it(
|
|
69
|
+
it("should show loading state initially", async () => {
|
|
62
70
|
useGitDataSpy.mockReturnValue({
|
|
63
71
|
branches: [],
|
|
64
72
|
loading: true,
|
|
@@ -69,7 +77,7 @@ describe('App', () => {
|
|
|
69
77
|
|
|
70
78
|
const onExit = vi.fn();
|
|
71
79
|
const { queryByText, getByText } = render(
|
|
72
|
-
<App onExit={onExit} loadingIndicatorDelay={10}
|
|
80
|
+
<App onExit={onExit} loadingIndicatorDelay={10} />,
|
|
73
81
|
);
|
|
74
82
|
|
|
75
83
|
expect(queryByText(/Loading Git information/i)).toBeNull();
|
|
@@ -81,8 +89,8 @@ describe('App', () => {
|
|
|
81
89
|
expect(getByText(/Loading Git information/i)).toBeDefined();
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
it(
|
|
85
|
-
const error = new Error(
|
|
92
|
+
it("should show error state when Git data fails to load", () => {
|
|
93
|
+
const error = new Error("Failed to fetch branches");
|
|
86
94
|
useGitDataSpy.mockReturnValue({
|
|
87
95
|
branches: [],
|
|
88
96
|
loading: false,
|
|
@@ -98,29 +106,29 @@ describe('App', () => {
|
|
|
98
106
|
expect(getByText(/Failed to fetch branches/i)).toBeDefined();
|
|
99
107
|
});
|
|
100
108
|
|
|
101
|
-
it(
|
|
109
|
+
it("should calculate statistics from branches", () => {
|
|
102
110
|
const branchesWithWorktree: BranchInfo[] = [
|
|
103
111
|
{
|
|
104
|
-
name:
|
|
105
|
-
type:
|
|
106
|
-
branchType:
|
|
112
|
+
name: "main",
|
|
113
|
+
type: "local",
|
|
114
|
+
branchType: "main",
|
|
107
115
|
isCurrent: true,
|
|
108
116
|
},
|
|
109
117
|
{
|
|
110
|
-
name:
|
|
111
|
-
type:
|
|
112
|
-
branchType:
|
|
118
|
+
name: "feature/a",
|
|
119
|
+
type: "local",
|
|
120
|
+
branchType: "feature",
|
|
113
121
|
isCurrent: false,
|
|
114
122
|
worktree: {
|
|
115
|
-
path:
|
|
123
|
+
path: "/path/a",
|
|
116
124
|
locked: false,
|
|
117
125
|
prunable: false,
|
|
118
126
|
},
|
|
119
127
|
},
|
|
120
128
|
{
|
|
121
|
-
name:
|
|
122
|
-
type:
|
|
123
|
-
branchType:
|
|
129
|
+
name: "origin/main",
|
|
130
|
+
type: "remote",
|
|
131
|
+
branchType: "main",
|
|
124
132
|
isCurrent: false,
|
|
125
133
|
},
|
|
126
134
|
];
|
|
@@ -144,7 +152,7 @@ describe('App', () => {
|
|
|
144
152
|
expect(getByText(/Worktrees:/)).toBeDefined();
|
|
145
153
|
});
|
|
146
154
|
|
|
147
|
-
it(
|
|
155
|
+
it("should call onExit when branch is selected", () => {
|
|
148
156
|
useGitDataSpy.mockReturnValue({
|
|
149
157
|
branches: mockBranches,
|
|
150
158
|
loading: false,
|
|
@@ -161,7 +169,7 @@ describe('App', () => {
|
|
|
161
169
|
// which is covered in integration tests
|
|
162
170
|
});
|
|
163
171
|
|
|
164
|
-
it(
|
|
172
|
+
it("should handle empty branch list", () => {
|
|
165
173
|
useGitDataSpy.mockReturnValue({
|
|
166
174
|
branches: [],
|
|
167
175
|
loading: false,
|
|
@@ -176,7 +184,7 @@ describe('App', () => {
|
|
|
176
184
|
expect(getByText(/No branches found/i)).toBeDefined();
|
|
177
185
|
});
|
|
178
186
|
|
|
179
|
-
it(
|
|
187
|
+
it("should wrap with ErrorBoundary", () => {
|
|
180
188
|
// This test verifies ErrorBoundary is present
|
|
181
189
|
// Actual error catching is tested separately
|
|
182
190
|
useGitDataSpy.mockReturnValue({
|
|
@@ -193,7 +201,7 @@ describe('App', () => {
|
|
|
193
201
|
expect(container).toBeDefined();
|
|
194
202
|
});
|
|
195
203
|
|
|
196
|
-
it(
|
|
204
|
+
it("should format branch items with icons", () => {
|
|
197
205
|
useGitDataSpy.mockReturnValue({
|
|
198
206
|
branches: mockBranches,
|
|
199
207
|
loading: false,
|
|
@@ -209,8 +217,8 @@ describe('App', () => {
|
|
|
209
217
|
expect(getByText(/⚡/)).toBeDefined();
|
|
210
218
|
});
|
|
211
219
|
|
|
212
|
-
describe(
|
|
213
|
-
it(
|
|
220
|
+
describe("BranchActionSelectorScreen integration", () => {
|
|
221
|
+
it("should show BranchActionSelectorScreen after branch selection", () => {
|
|
214
222
|
useGitDataSpy.mockReturnValue({
|
|
215
223
|
branches: mockBranches,
|
|
216
224
|
loading: false,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, vi } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { Confirm } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Confirm } from "../../../components/common/Confirm.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
|
-
describe(
|
|
10
|
+
describe("Confirm", () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
// Setup happy-dom
|
|
13
13
|
const window = new Window();
|
|
@@ -15,47 +15,60 @@ describe('Confirm', () => {
|
|
|
15
15
|
globalThis.document = window.document as any;
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it("should render the message", () => {
|
|
19
19
|
const onConfirm = vi.fn();
|
|
20
|
-
const { getByText } = render(
|
|
20
|
+
const { getByText } = render(
|
|
21
|
+
<Confirm message="Are you sure?" onConfirm={onConfirm} />,
|
|
22
|
+
);
|
|
21
23
|
|
|
22
|
-
expect(getByText(
|
|
24
|
+
expect(getByText("Are you sure?")).toBeDefined();
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
it(
|
|
27
|
+
it("should render Yes and No options", () => {
|
|
26
28
|
const onConfirm = vi.fn();
|
|
27
|
-
const { getByText } = render(
|
|
29
|
+
const { getByText } = render(
|
|
30
|
+
<Confirm message="Continue?" onConfirm={onConfirm} />,
|
|
31
|
+
);
|
|
28
32
|
|
|
29
|
-
expect(getByText(
|
|
30
|
-
expect(getByText(
|
|
33
|
+
expect(getByText("Yes")).toBeDefined();
|
|
34
|
+
expect(getByText("No")).toBeDefined();
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
it(
|
|
37
|
+
it("should render custom Yes and No labels", () => {
|
|
34
38
|
const onConfirm = vi.fn();
|
|
35
39
|
const { getByText } = render(
|
|
36
|
-
<Confirm
|
|
40
|
+
<Confirm
|
|
41
|
+
message="Delete?"
|
|
42
|
+
onConfirm={onConfirm}
|
|
43
|
+
yesLabel="Confirm"
|
|
44
|
+
noLabel="Cancel"
|
|
45
|
+
/>,
|
|
37
46
|
);
|
|
38
47
|
|
|
39
|
-
expect(getByText(
|
|
40
|
-
expect(getByText(
|
|
48
|
+
expect(getByText("Confirm")).toBeDefined();
|
|
49
|
+
expect(getByText("Cancel")).toBeDefined();
|
|
41
50
|
});
|
|
42
51
|
|
|
43
|
-
it(
|
|
52
|
+
it("should default to Yes option", () => {
|
|
44
53
|
const onConfirm = vi.fn();
|
|
45
|
-
const { container } = render(
|
|
54
|
+
const { container } = render(
|
|
55
|
+
<Confirm message="Continue?" onConfirm={onConfirm} />,
|
|
56
|
+
);
|
|
46
57
|
|
|
47
58
|
// Verify component renders without error
|
|
48
59
|
expect(container).toBeDefined();
|
|
49
60
|
});
|
|
50
61
|
|
|
51
|
-
it(
|
|
62
|
+
it("should accept defaultNo prop to default to No", () => {
|
|
52
63
|
const onConfirm = vi.fn();
|
|
53
|
-
const { container } = render(
|
|
64
|
+
const { container } = render(
|
|
65
|
+
<Confirm message="Continue?" onConfirm={onConfirm} defaultNo />,
|
|
66
|
+
);
|
|
54
67
|
|
|
55
68
|
expect(container).toBeDefined();
|
|
56
69
|
});
|
|
57
70
|
|
|
58
|
-
it(
|
|
71
|
+
it("should call onConfirm with false by default when rendered", () => {
|
|
59
72
|
const onConfirm = vi.fn();
|
|
60
73
|
render(<Confirm message="Continue?" onConfirm={onConfirm} />);
|
|
61
74
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, vi } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { ErrorBoundary } from
|
|
8
|
-
import { Text, Box } from
|
|
9
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { ErrorBoundary } from "../../../components/common/ErrorBoundary.js";
|
|
8
|
+
import { Text, Box } from "ink";
|
|
9
|
+
import { Window } from "happy-dom";
|
|
10
10
|
|
|
11
11
|
// Component that throws an error
|
|
12
12
|
const ThrowError = ({ shouldThrow }: { shouldThrow: boolean }) => {
|
|
13
13
|
if (shouldThrow) {
|
|
14
|
-
throw new Error(
|
|
14
|
+
throw new Error("Test error message");
|
|
15
15
|
}
|
|
16
16
|
return <Text>No error</Text>;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
describe(
|
|
19
|
+
describe("ErrorBoundary", () => {
|
|
20
20
|
beforeEach(() => {
|
|
21
21
|
// Setup happy-dom
|
|
22
22
|
const window = new Window();
|
|
@@ -24,31 +24,31 @@ describe('ErrorBoundary', () => {
|
|
|
24
24
|
globalThis.document = window.document as any;
|
|
25
25
|
|
|
26
26
|
// Suppress console.error for expected errors in tests
|
|
27
|
-
vi.spyOn(console,
|
|
27
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it(
|
|
30
|
+
it("should render children when no error occurs", () => {
|
|
31
31
|
const { getByText } = render(
|
|
32
32
|
<ErrorBoundary>
|
|
33
33
|
<ThrowError shouldThrow={false} />
|
|
34
|
-
</ErrorBoundary
|
|
34
|
+
</ErrorBoundary>,
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
-
expect(getByText(
|
|
37
|
+
expect(getByText("No error")).toBeDefined();
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it("should catch errors and display error message", () => {
|
|
41
41
|
const { getByText } = render(
|
|
42
42
|
<ErrorBoundary>
|
|
43
43
|
<ThrowError shouldThrow={true} />
|
|
44
|
-
</ErrorBoundary
|
|
44
|
+
</ErrorBoundary>,
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
expect(getByText(/Error:/)).toBeDefined();
|
|
48
48
|
expect(getByText(/Test error message/)).toBeDefined();
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it(
|
|
51
|
+
it("should display custom fallback when provided", () => {
|
|
52
52
|
const CustomFallback = ({ error }: { error: Error }) => (
|
|
53
53
|
<Box>
|
|
54
54
|
<Text color="red">Custom Error: {error.message}</Text>
|
|
@@ -58,18 +58,18 @@ describe('ErrorBoundary', () => {
|
|
|
58
58
|
const { getByText } = render(
|
|
59
59
|
<ErrorBoundary fallback={CustomFallback}>
|
|
60
60
|
<ThrowError shouldThrow={true} />
|
|
61
|
-
</ErrorBoundary
|
|
61
|
+
</ErrorBoundary>,
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
expect(getByText(/Custom Error:/)).toBeDefined();
|
|
65
65
|
expect(getByText(/Test error message/)).toBeDefined();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it(
|
|
68
|
+
it("should reset error state when children change", () => {
|
|
69
69
|
const { rerender, getByText } = render(
|
|
70
70
|
<ErrorBoundary>
|
|
71
71
|
<ThrowError shouldThrow={true} />
|
|
72
|
-
</ErrorBoundary
|
|
72
|
+
</ErrorBoundary>,
|
|
73
73
|
);
|
|
74
74
|
|
|
75
75
|
// Error is shown
|
|
@@ -79,14 +79,14 @@ describe('ErrorBoundary', () => {
|
|
|
79
79
|
rerender(
|
|
80
80
|
<ErrorBoundary>
|
|
81
81
|
<ThrowError shouldThrow={false} />
|
|
82
|
-
</ErrorBoundary
|
|
82
|
+
</ErrorBoundary>,
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
// Original children should be rendered
|
|
86
|
-
expect(getByText(
|
|
86
|
+
expect(getByText("No error")).toBeDefined();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it(
|
|
89
|
+
it("should handle errors with no message", () => {
|
|
90
90
|
const ThrowNoMessage = () => {
|
|
91
91
|
throw new Error();
|
|
92
92
|
};
|
|
@@ -94,7 +94,7 @@ describe('ErrorBoundary', () => {
|
|
|
94
94
|
const { getByText } = render(
|
|
95
95
|
<ErrorBoundary>
|
|
96
96
|
<ThrowNoMessage />
|
|
97
|
-
</ErrorBoundary
|
|
97
|
+
</ErrorBoundary>,
|
|
98
98
|
);
|
|
99
99
|
|
|
100
100
|
expect(getByText(/Error:/)).toBeDefined();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, vi } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { Input } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Input } from "../../../components/common/Input.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
|
-
describe(
|
|
10
|
+
describe("Input", () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
// Setup happy-dom
|
|
13
13
|
const window = new Window();
|
|
@@ -15,35 +15,42 @@ describe('Input', () => {
|
|
|
15
15
|
globalThis.document = window.document as any;
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it("should render with value", () => {
|
|
19
19
|
const onChange = vi.fn();
|
|
20
20
|
const onSubmit = vi.fn();
|
|
21
|
-
const { container } = render(
|
|
21
|
+
const { container } = render(
|
|
22
|
+
<Input value="test" onChange={onChange} onSubmit={onSubmit} />,
|
|
23
|
+
);
|
|
22
24
|
|
|
23
25
|
expect(container).toBeDefined();
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
it(
|
|
28
|
+
it("should render with placeholder", () => {
|
|
27
29
|
const onChange = vi.fn();
|
|
28
30
|
const onSubmit = vi.fn();
|
|
29
31
|
const { getByText } = render(
|
|
30
|
-
<Input
|
|
32
|
+
<Input
|
|
33
|
+
value=""
|
|
34
|
+
onChange={onChange}
|
|
35
|
+
onSubmit={onSubmit}
|
|
36
|
+
placeholder="Enter text..."
|
|
37
|
+
/>,
|
|
31
38
|
);
|
|
32
39
|
|
|
33
|
-
expect(getByText(
|
|
40
|
+
expect(getByText("Enter text...")).toBeDefined();
|
|
34
41
|
});
|
|
35
42
|
|
|
36
|
-
it(
|
|
43
|
+
it("should render with label", () => {
|
|
37
44
|
const onChange = vi.fn();
|
|
38
45
|
const onSubmit = vi.fn();
|
|
39
46
|
const { getByText } = render(
|
|
40
|
-
<Input value="" onChange={onChange} onSubmit={onSubmit} label="Name:"
|
|
47
|
+
<Input value="" onChange={onChange} onSubmit={onSubmit} label="Name:" />,
|
|
41
48
|
);
|
|
42
49
|
|
|
43
|
-
expect(getByText(
|
|
50
|
+
expect(getByText("Name:")).toBeDefined();
|
|
44
51
|
});
|
|
45
52
|
|
|
46
|
-
it(
|
|
53
|
+
it("should render label and placeholder together", () => {
|
|
47
54
|
const onChange = vi.fn();
|
|
48
55
|
const onSubmit = vi.fn();
|
|
49
56
|
const { getByText } = render(
|
|
@@ -53,24 +60,24 @@ describe('Input', () => {
|
|
|
53
60
|
onSubmit={onSubmit}
|
|
54
61
|
label="Branch name:"
|
|
55
62
|
placeholder="feature/..."
|
|
56
|
-
|
|
63
|
+
/>,
|
|
57
64
|
);
|
|
58
65
|
|
|
59
|
-
expect(getByText(
|
|
60
|
-
expect(getByText(
|
|
66
|
+
expect(getByText("Branch name:")).toBeDefined();
|
|
67
|
+
expect(getByText("feature/...")).toBeDefined();
|
|
61
68
|
});
|
|
62
69
|
|
|
63
|
-
it(
|
|
70
|
+
it("should accept mask prop for password input", () => {
|
|
64
71
|
const onChange = vi.fn();
|
|
65
72
|
const onSubmit = vi.fn();
|
|
66
73
|
const { container } = render(
|
|
67
|
-
<Input value="secret" onChange={onChange} onSubmit={onSubmit} mask="*"
|
|
74
|
+
<Input value="secret" onChange={onChange} onSubmit={onSubmit} mask="*" />,
|
|
68
75
|
);
|
|
69
76
|
|
|
70
77
|
expect(container).toBeDefined();
|
|
71
78
|
});
|
|
72
79
|
|
|
73
|
-
it(
|
|
80
|
+
it("should call onChange when value changes", () => {
|
|
74
81
|
const onChange = vi.fn();
|
|
75
82
|
const onSubmit = vi.fn();
|
|
76
83
|
render(<Input value="" onChange={onChange} onSubmit={onSubmit} />);
|
|
@@ -80,7 +87,7 @@ describe('Input', () => {
|
|
|
80
87
|
expect(onChange).not.toHaveBeenCalled();
|
|
81
88
|
});
|
|
82
89
|
|
|
83
|
-
it(
|
|
90
|
+
it("should call onSubmit when submitted", () => {
|
|
84
91
|
const onChange = vi.fn();
|
|
85
92
|
const onSubmit = vi.fn();
|
|
86
93
|
render(<Input value="test" onChange={onChange} onSubmit={onSubmit} />);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import React from
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from
|
|
6
|
-
import { act, render } from
|
|
7
|
-
import { LoadingIndicator } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
import { act, render } from "@testing-library/react";
|
|
7
|
+
import { LoadingIndicator } from "../../../components/common/LoadingIndicator.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
10
|
const advanceTimersBy = async (ms: number) => {
|
|
11
11
|
await act(async () => {
|
|
@@ -25,57 +25,63 @@ afterEach(() => {
|
|
|
25
25
|
vi.useRealTimers();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
describe(
|
|
28
|
+
describe("LoadingIndicator", () => {
|
|
29
29
|
const getSpinnerText = (container: HTMLElement) => {
|
|
30
|
-
return container.querySelector(
|
|
30
|
+
return container.querySelector("ink-text")?.textContent ?? "";
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const getMessageText = (container: HTMLElement) => {
|
|
34
|
-
const texts = container.querySelectorAll(
|
|
35
|
-
return texts.length > 1 ? texts[1]?.textContent ??
|
|
34
|
+
const texts = container.querySelectorAll("ink-text");
|
|
35
|
+
return texts.length > 1 ? (texts[1]?.textContent ?? "") : "";
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
it(
|
|
38
|
+
it("does not render before the delay elapses", async () => {
|
|
39
39
|
const { container } = render(
|
|
40
|
-
<LoadingIndicator isLoading={true} message="Loading data" delay={50}
|
|
40
|
+
<LoadingIndicator isLoading={true} message="Loading data" delay={50} />,
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
-
expect(container.textContent).toBe(
|
|
43
|
+
expect(container.textContent).toBe("");
|
|
44
44
|
|
|
45
45
|
await advanceTimersBy(20);
|
|
46
46
|
|
|
47
|
-
expect(container.textContent).toBe(
|
|
47
|
+
expect(container.textContent).toBe("");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it(
|
|
50
|
+
it("renders after the delay elapses", async () => {
|
|
51
51
|
const { container } = render(
|
|
52
|
-
<LoadingIndicator isLoading={true} message="Loading data" delay={30}
|
|
52
|
+
<LoadingIndicator isLoading={true} message="Loading data" delay={30} />,
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
await advanceTimersBy(30);
|
|
56
56
|
|
|
57
|
-
expect(getMessageText(container)).toContain(
|
|
57
|
+
expect(getMessageText(container)).toContain("Loading data");
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it(
|
|
60
|
+
it("stops rendering when loading becomes false", async () => {
|
|
61
61
|
const { container, rerender } = render(
|
|
62
|
-
<LoadingIndicator isLoading={true} message="Loading data" delay={10}
|
|
62
|
+
<LoadingIndicator isLoading={true} message="Loading data" delay={10} />,
|
|
63
63
|
);
|
|
64
64
|
|
|
65
65
|
await advanceTimersBy(10);
|
|
66
66
|
|
|
67
|
-
expect(getMessageText(container)).toContain(
|
|
67
|
+
expect(getMessageText(container)).toContain("Loading data");
|
|
68
68
|
|
|
69
69
|
await act(async () => {
|
|
70
|
-
rerender(
|
|
70
|
+
rerender(
|
|
71
|
+
<LoadingIndicator
|
|
72
|
+
isLoading={false}
|
|
73
|
+
message="Loading data"
|
|
74
|
+
delay={10}
|
|
75
|
+
/>,
|
|
76
|
+
);
|
|
71
77
|
await vi.advanceTimersByTimeAsync(0);
|
|
72
78
|
});
|
|
73
79
|
|
|
74
|
-
expect(container.textContent).toBe(
|
|
80
|
+
expect(container.textContent).toBe("");
|
|
75
81
|
});
|
|
76
82
|
|
|
77
|
-
it(
|
|
78
|
-
const customFrames = [
|
|
83
|
+
it("cycles through spinner frames over time", async () => {
|
|
84
|
+
const customFrames = [".", "..", "..."];
|
|
79
85
|
const { container } = render(
|
|
80
86
|
<LoadingIndicator
|
|
81
87
|
isLoading={true}
|
|
@@ -83,7 +89,7 @@ describe('LoadingIndicator', () => {
|
|
|
83
89
|
delay={0}
|
|
84
90
|
interval={5}
|
|
85
91
|
frames={customFrames}
|
|
86
|
-
|
|
92
|
+
/>,
|
|
87
93
|
);
|
|
88
94
|
|
|
89
95
|
await advanceTimersBy(0);
|
|
@@ -100,28 +106,28 @@ describe('LoadingIndicator', () => {
|
|
|
100
106
|
|
|
101
107
|
expect(secondFrame).not.toEqual(firstFrame);
|
|
102
108
|
expect(thirdFrame).not.toEqual(secondFrame);
|
|
103
|
-
expect(customFrames).toContain(firstFrame ??
|
|
104
|
-
expect(customFrames).toContain(secondFrame ??
|
|
105
|
-
expect(customFrames).toContain(thirdFrame ??
|
|
106
|
-
expect(getMessageText(container)).toContain(
|
|
109
|
+
expect(customFrames).toContain(firstFrame ?? "");
|
|
110
|
+
expect(customFrames).toContain(secondFrame ?? "");
|
|
111
|
+
expect(customFrames).toContain(thirdFrame ?? "");
|
|
112
|
+
expect(getMessageText(container)).toContain("Loading data");
|
|
107
113
|
});
|
|
108
114
|
|
|
109
|
-
it(
|
|
115
|
+
it("keeps rendering even when only a single frame is provided", async () => {
|
|
110
116
|
const { container } = render(
|
|
111
117
|
<LoadingIndicator
|
|
112
118
|
isLoading={true}
|
|
113
119
|
message="Loading data"
|
|
114
120
|
delay={0}
|
|
115
121
|
interval={10}
|
|
116
|
-
frames={[
|
|
117
|
-
|
|
122
|
+
frames={["*"]}
|
|
123
|
+
/>,
|
|
118
124
|
);
|
|
119
125
|
|
|
120
126
|
await advanceTimersBy(0);
|
|
121
|
-
expect(getSpinnerText(container)).toBe(
|
|
127
|
+
expect(getSpinnerText(container)).toBe("*");
|
|
122
128
|
|
|
123
129
|
await advanceTimersBy(30);
|
|
124
|
-
expect(getSpinnerText(container)).toBe(
|
|
125
|
-
expect(getMessageText(container)).toContain(
|
|
130
|
+
expect(getSpinnerText(container)).toBe("*");
|
|
131
|
+
expect(getMessageText(container)).toContain("Loading data");
|
|
126
132
|
});
|
|
127
133
|
});
|