@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,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { Footer } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Footer } from "../../../components/parts/Footer.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
|
-
describe(
|
|
10
|
+
describe("Footer", () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
// Setup happy-dom
|
|
13
13
|
const window = new Window();
|
|
@@ -16,12 +16,12 @@ describe('Footer', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
const mockActions = [
|
|
19
|
-
{ key:
|
|
20
|
-
{ key:
|
|
21
|
-
{ key:
|
|
19
|
+
{ key: "enter", description: "Select" },
|
|
20
|
+
{ key: "esc", description: "Back" },
|
|
21
|
+
{ key: "h", description: "Help" },
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
it(
|
|
24
|
+
it("should render all actions", () => {
|
|
25
25
|
const { getByText } = render(<Footer actions={mockActions} />);
|
|
26
26
|
|
|
27
27
|
expect(getByText(/enter/)).toBeDefined();
|
|
@@ -32,29 +32,31 @@ describe('Footer', () => {
|
|
|
32
32
|
expect(getByText(/Help/)).toBeDefined();
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it(
|
|
35
|
+
it("should render with empty actions array", () => {
|
|
36
36
|
const { container } = render(<Footer actions={[]} />);
|
|
37
37
|
|
|
38
38
|
expect(container).toBeDefined();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it(
|
|
42
|
-
const singleAction = [{ key:
|
|
41
|
+
it("should render single action", () => {
|
|
42
|
+
const singleAction = [{ key: "esc", description: "Exit" }];
|
|
43
43
|
const { getByText } = render(<Footer actions={singleAction} />);
|
|
44
44
|
|
|
45
45
|
expect(getByText(/esc/)).toBeDefined();
|
|
46
46
|
expect(getByText(/Exit/)).toBeDefined();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it(
|
|
49
|
+
it("should render actions in a horizontal layout", () => {
|
|
50
50
|
const { container } = render(<Footer actions={mockActions} />);
|
|
51
51
|
|
|
52
52
|
// Verify component renders without error
|
|
53
53
|
expect(container).toBeDefined();
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it(
|
|
57
|
-
const { getAllByText } = render(
|
|
56
|
+
it("should accept custom separator", () => {
|
|
57
|
+
const { getAllByText } = render(
|
|
58
|
+
<Footer actions={mockActions} separator=" | " />,
|
|
59
|
+
);
|
|
58
60
|
|
|
59
61
|
const separators = getAllByText(/\|/);
|
|
60
62
|
expect(separators.length).toBeGreaterThan(0);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { Header } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Header } from "../../../components/parts/Header.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
|
-
describe(
|
|
10
|
+
describe("Header", () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
// Setup happy-dom
|
|
13
13
|
const window = new Window();
|
|
@@ -15,38 +15,38 @@ describe('Header', () => {
|
|
|
15
15
|
globalThis.document = window.document as any;
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it("should render title", () => {
|
|
19
19
|
const { getByText } = render(<Header title="gwt" />);
|
|
20
20
|
|
|
21
|
-
expect(getByText(
|
|
21
|
+
expect(getByText("gwt")).toBeDefined();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it(
|
|
24
|
+
it("should render divider", () => {
|
|
25
25
|
const { getByText } = render(<Header title="Test" />);
|
|
26
26
|
|
|
27
27
|
// Check for a line of dashes (divider)
|
|
28
28
|
expect(getByText(/─+/)).toBeDefined();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it(
|
|
31
|
+
it("should render title in bold and cyan by default", () => {
|
|
32
32
|
const { container } = render(<Header title="Test Title" />);
|
|
33
33
|
|
|
34
34
|
expect(container).toBeDefined();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it(
|
|
37
|
+
it("should accept custom title color", () => {
|
|
38
38
|
const { container } = render(<Header title="Test" titleColor="green" />);
|
|
39
39
|
|
|
40
40
|
expect(container).toBeDefined();
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it(
|
|
43
|
+
it("should accept custom divider character", () => {
|
|
44
44
|
const { getByText } = render(<Header title="Test" dividerChar="=" />);
|
|
45
45
|
|
|
46
46
|
expect(getByText(/=+/)).toBeDefined();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it(
|
|
49
|
+
it("should render without divider when showDivider is false", () => {
|
|
50
50
|
const { queryByText } = render(<Header title="Test" showDivider={false} />);
|
|
51
51
|
|
|
52
52
|
expect(queryByText(/─+/)).toBeNull();
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { ScrollableList } from
|
|
8
|
-
import { Text } from
|
|
9
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { ScrollableList } from "../../../components/parts/ScrollableList.js";
|
|
8
|
+
import { Text } from "ink";
|
|
9
|
+
import { Window } from "happy-dom";
|
|
10
10
|
|
|
11
|
-
describe(
|
|
11
|
+
describe("ScrollableList", () => {
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
// Setup happy-dom
|
|
14
14
|
const window = new Window();
|
|
@@ -16,53 +16,53 @@ describe('ScrollableList', () => {
|
|
|
16
16
|
globalThis.document = window.document as any;
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it(
|
|
19
|
+
it("should render children", () => {
|
|
20
20
|
const { getByText } = render(
|
|
21
21
|
<ScrollableList>
|
|
22
22
|
<Text>Item 1</Text>
|
|
23
23
|
<Text>Item 2</Text>
|
|
24
24
|
<Text>Item 3</Text>
|
|
25
|
-
</ScrollableList
|
|
25
|
+
</ScrollableList>,
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
-
expect(getByText(
|
|
29
|
-
expect(getByText(
|
|
30
|
-
expect(getByText(
|
|
28
|
+
expect(getByText("Item 1")).toBeDefined();
|
|
29
|
+
expect(getByText("Item 2")).toBeDefined();
|
|
30
|
+
expect(getByText("Item 3")).toBeDefined();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it(
|
|
33
|
+
it("should render with no children", () => {
|
|
34
34
|
const { container } = render(<ScrollableList>{null}</ScrollableList>);
|
|
35
35
|
|
|
36
36
|
expect(container).toBeDefined();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it(
|
|
39
|
+
it("should accept maxHeight prop", () => {
|
|
40
40
|
const { container } = render(
|
|
41
41
|
<ScrollableList maxHeight={10}>
|
|
42
42
|
<Text>Content</Text>
|
|
43
|
-
</ScrollableList
|
|
43
|
+
</ScrollableList>,
|
|
44
44
|
);
|
|
45
45
|
|
|
46
46
|
expect(container).toBeDefined();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it(
|
|
49
|
+
it("should render in a vertical layout", () => {
|
|
50
50
|
const { container } = render(
|
|
51
51
|
<ScrollableList>
|
|
52
52
|
<Text>Content</Text>
|
|
53
|
-
</ScrollableList
|
|
53
|
+
</ScrollableList>,
|
|
54
54
|
);
|
|
55
55
|
|
|
56
56
|
expect(container).toBeDefined();
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it(
|
|
59
|
+
it("should handle single child", () => {
|
|
60
60
|
const { getByText } = render(
|
|
61
61
|
<ScrollableList>
|
|
62
62
|
<Text>Single Item</Text>
|
|
63
|
-
</ScrollableList
|
|
63
|
+
</ScrollableList>,
|
|
64
64
|
);
|
|
65
65
|
|
|
66
|
-
expect(getByText(
|
|
66
|
+
expect(getByText("Single Item")).toBeDefined();
|
|
67
67
|
});
|
|
68
68
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach } from
|
|
5
|
-
import { render } from
|
|
6
|
-
import React from
|
|
7
|
-
import { Stats } from
|
|
8
|
-
import type { Statistics } from
|
|
9
|
-
import { Window } from
|
|
10
|
-
|
|
11
|
-
describe(
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
import { render } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Stats } from "../../../components/parts/Stats.js";
|
|
8
|
+
import type { Statistics } from "../../../types.js";
|
|
9
|
+
import { Window } from "happy-dom";
|
|
10
|
+
|
|
11
|
+
describe("Stats", () => {
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
// Setup happy-dom
|
|
14
14
|
const window = new Window();
|
|
@@ -21,10 +21,10 @@ describe('Stats', () => {
|
|
|
21
21
|
remoteCount: 8,
|
|
22
22
|
worktreeCount: 3,
|
|
23
23
|
changesCount: 2,
|
|
24
|
-
lastUpdated: new Date(
|
|
24
|
+
lastUpdated: new Date("2025-01-25T12:00:00Z"),
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
it(
|
|
27
|
+
it("should render all statistics", () => {
|
|
28
28
|
const { getByText } = render(<Stats stats={mockStats} />);
|
|
29
29
|
|
|
30
30
|
expect(getByText(/Local:/)).toBeDefined();
|
|
@@ -37,7 +37,7 @@ describe('Stats', () => {
|
|
|
37
37
|
expect(getByText(/2/)).toBeDefined();
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it("should render with zero counts", () => {
|
|
41
41
|
const zeroStats: Statistics = {
|
|
42
42
|
localCount: 0,
|
|
43
43
|
remoteCount: 0,
|
|
@@ -53,21 +53,23 @@ describe('Stats', () => {
|
|
|
53
53
|
expect(zeros.length).toBe(4); // All 4 counts are 0
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it(
|
|
56
|
+
it("should render in a horizontal layout", () => {
|
|
57
57
|
const { container } = render(<Stats stats={mockStats} />);
|
|
58
58
|
|
|
59
59
|
// Verify component renders without error
|
|
60
60
|
expect(container).toBeDefined();
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it(
|
|
64
|
-
const { getAllByText } = render(
|
|
63
|
+
it("should accept custom separator", () => {
|
|
64
|
+
const { getAllByText } = render(
|
|
65
|
+
<Stats stats={mockStats} separator=" | " />,
|
|
66
|
+
);
|
|
65
67
|
|
|
66
68
|
const separators = getAllByText(/\|/);
|
|
67
69
|
expect(separators.length).toBeGreaterThan(0);
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
it(
|
|
72
|
+
it("should handle large numbers", () => {
|
|
71
73
|
const largeStats: Statistics = {
|
|
72
74
|
localCount: 999,
|
|
73
75
|
remoteCount: 888,
|
|
@@ -84,51 +86,61 @@ describe('Stats', () => {
|
|
|
84
86
|
expect(getByText(/666/)).toBeDefined();
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
it(
|
|
89
|
+
it("should display lastUpdated when provided", () => {
|
|
88
90
|
const now = new Date();
|
|
89
91
|
const lastUpdated = new Date(now.getTime() - 5000); // 5 seconds ago
|
|
90
92
|
|
|
91
|
-
const { getByText } = render(
|
|
93
|
+
const { getByText } = render(
|
|
94
|
+
<Stats stats={mockStats} lastUpdated={lastUpdated} />,
|
|
95
|
+
);
|
|
92
96
|
|
|
93
97
|
expect(getByText(/Updated:/)).toBeDefined();
|
|
94
98
|
expect(getByText(/ago/)).toBeDefined();
|
|
95
99
|
});
|
|
96
100
|
|
|
97
|
-
it(
|
|
98
|
-
const { queryByText } = render(
|
|
101
|
+
it("should not display lastUpdated when null", () => {
|
|
102
|
+
const { queryByText } = render(
|
|
103
|
+
<Stats stats={mockStats} lastUpdated={null} />,
|
|
104
|
+
);
|
|
99
105
|
|
|
100
106
|
expect(queryByText(/Updated:/)).toBeNull();
|
|
101
107
|
});
|
|
102
108
|
|
|
103
|
-
it(
|
|
109
|
+
it("should not display lastUpdated when not provided", () => {
|
|
104
110
|
const { queryByText } = render(<Stats stats={mockStats} />);
|
|
105
111
|
|
|
106
112
|
expect(queryByText(/Updated:/)).toBeNull();
|
|
107
113
|
});
|
|
108
114
|
|
|
109
|
-
it(
|
|
115
|
+
it("should format relative time correctly (seconds)", () => {
|
|
110
116
|
const now = new Date();
|
|
111
117
|
const lastUpdated = new Date(now.getTime() - 30000); // 30 seconds ago
|
|
112
118
|
|
|
113
|
-
const { getByText } = render(
|
|
119
|
+
const { getByText } = render(
|
|
120
|
+
<Stats stats={mockStats} lastUpdated={lastUpdated} />,
|
|
121
|
+
);
|
|
114
122
|
|
|
115
123
|
expect(getByText(/30s ago/)).toBeDefined();
|
|
116
124
|
});
|
|
117
125
|
|
|
118
|
-
it(
|
|
126
|
+
it("should format relative time correctly (minutes)", () => {
|
|
119
127
|
const now = new Date();
|
|
120
128
|
const lastUpdated = new Date(now.getTime() - 120000); // 2 minutes ago
|
|
121
129
|
|
|
122
|
-
const { getByText } = render(
|
|
130
|
+
const { getByText } = render(
|
|
131
|
+
<Stats stats={mockStats} lastUpdated={lastUpdated} />,
|
|
132
|
+
);
|
|
123
133
|
|
|
124
134
|
expect(getByText(/2m ago/)).toBeDefined();
|
|
125
135
|
});
|
|
126
136
|
|
|
127
|
-
it(
|
|
137
|
+
it("should format relative time correctly (hours)", () => {
|
|
128
138
|
const now = new Date();
|
|
129
139
|
const lastUpdated = new Date(now.getTime() - 7200000); // 2 hours ago
|
|
130
140
|
|
|
131
|
-
const { getByText } = render(
|
|
141
|
+
const { getByText } = render(
|
|
142
|
+
<Stats stats={mockStats} lastUpdated={lastUpdated} />,
|
|
143
|
+
);
|
|
132
144
|
|
|
133
145
|
expect(getByText(/2h ago/)).toBeDefined();
|
|
134
146
|
});
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, vi } from
|
|
5
|
-
import { render, waitFor } from
|
|
6
|
-
import React from
|
|
7
|
-
import { AIToolSelectorScreen } from
|
|
8
|
-
import { Window } from
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
import { render, waitFor } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { AIToolSelectorScreen } from "../../../components/screens/AIToolSelectorScreen.js";
|
|
8
|
+
import { Window } from "happy-dom";
|
|
9
9
|
|
|
10
10
|
// Mock getAllTools
|
|
11
|
-
vi.mock(
|
|
11
|
+
vi.mock("../../../config/tools.js", () => ({
|
|
12
12
|
getAllTools: vi.fn().mockResolvedValue([
|
|
13
13
|
{
|
|
14
|
-
id:
|
|
15
|
-
displayName:
|
|
14
|
+
id: "claude-code",
|
|
15
|
+
displayName: "Claude Code",
|
|
16
16
|
isBuiltin: true,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
id:
|
|
20
|
-
displayName:
|
|
19
|
+
id: "codex-cli",
|
|
20
|
+
displayName: "Codex",
|
|
21
21
|
isBuiltin: true,
|
|
22
22
|
},
|
|
23
23
|
]),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
describe(
|
|
26
|
+
describe("AIToolSelectorScreen", () => {
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
// Setup happy-dom
|
|
29
29
|
const window = new Window();
|
|
@@ -31,49 +31,49 @@ describe('AIToolSelectorScreen', () => {
|
|
|
31
31
|
globalThis.document = window.document as any;
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it(
|
|
34
|
+
it("should render header with title", () => {
|
|
35
35
|
const onBack = vi.fn();
|
|
36
36
|
const onSelect = vi.fn();
|
|
37
37
|
const { getByText } = render(
|
|
38
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
38
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
expect(getByText(/AI Tool Selection/i)).toBeDefined();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
it(
|
|
44
|
+
it("should render AI tool options", async () => {
|
|
45
45
|
const onBack = vi.fn();
|
|
46
46
|
const onSelect = vi.fn();
|
|
47
47
|
const { getByText } = render(
|
|
48
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
48
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
// Wait for tools to load
|
|
52
52
|
await waitFor(() => {
|
|
53
53
|
expect(getByText(/Claude Code/i)).toBeDefined();
|
|
54
|
-
expect(getByText(/Codex
|
|
54
|
+
expect(getByText(/Codex/i)).toBeDefined();
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it(
|
|
58
|
+
it("should render footer with actions", () => {
|
|
59
59
|
const onBack = vi.fn();
|
|
60
60
|
const onSelect = vi.fn();
|
|
61
61
|
const { getAllByText } = render(
|
|
62
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
62
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
63
63
|
);
|
|
64
64
|
|
|
65
65
|
expect(getAllByText(/enter/i).length).toBeGreaterThan(0);
|
|
66
66
|
expect(getAllByText(/esc/i).length).toBeGreaterThan(0);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
it(
|
|
69
|
+
it("should use terminal height for layout calculation", () => {
|
|
70
70
|
const originalRows = process.stdout.rows;
|
|
71
71
|
process.stdout.rows = 30;
|
|
72
72
|
|
|
73
73
|
const onBack = vi.fn();
|
|
74
74
|
const onSelect = vi.fn();
|
|
75
75
|
const { container } = render(
|
|
76
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
76
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
77
77
|
);
|
|
78
78
|
|
|
79
79
|
expect(container).toBeDefined();
|
|
@@ -81,22 +81,22 @@ describe('AIToolSelectorScreen', () => {
|
|
|
81
81
|
process.stdout.rows = originalRows;
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it(
|
|
84
|
+
it("should handle back navigation with ESC key", () => {
|
|
85
85
|
const onBack = vi.fn();
|
|
86
86
|
const onSelect = vi.fn();
|
|
87
87
|
const { container } = render(
|
|
88
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
88
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
// Test will verify onBack is called when ESC is pressed
|
|
92
92
|
expect(container).toBeDefined();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
it(
|
|
95
|
+
it("should handle tool selection", () => {
|
|
96
96
|
const onBack = vi.fn();
|
|
97
97
|
const onSelect = vi.fn();
|
|
98
98
|
const { container } = render(
|
|
99
|
-
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect}
|
|
99
|
+
<AIToolSelectorScreen onBack={onBack} onSelect={onSelect} />,
|
|
100
100
|
);
|
|
101
101
|
|
|
102
102
|
// Test will verify onSelect is called with correct tool
|
|
@@ -106,15 +106,15 @@ describe('AIToolSelectorScreen', () => {
|
|
|
106
106
|
/**
|
|
107
107
|
* T210: カスタムツール表示のテスト
|
|
108
108
|
*/
|
|
109
|
-
describe(
|
|
110
|
-
it(
|
|
109
|
+
describe("Custom tool display", () => {
|
|
110
|
+
it("should load tools from getAllTools() dynamically", async () => {
|
|
111
111
|
// TODO: 実装後にテストを記述
|
|
112
112
|
// getAllTools()がモックされ、呼び出されることを確認
|
|
113
113
|
// モックの戻り値がツールアイテムとして表示されることを確認
|
|
114
114
|
expect(true).toBe(true);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
it(
|
|
117
|
+
it("should display both builtin and custom tools", async () => {
|
|
118
118
|
// TODO: 実装後にテストを記述
|
|
119
119
|
// getAllTools()がビルトインツール(claude-code, codex-cli)と
|
|
120
120
|
// カスタムツール(例: aider)を返す場合、
|
|
@@ -122,28 +122,28 @@ describe('AIToolSelectorScreen', () => {
|
|
|
122
122
|
expect(true).toBe(true);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
it(
|
|
125
|
+
it("should display custom tool with icon if defined", async () => {
|
|
126
126
|
// TODO: 実装後にテストを記述
|
|
127
127
|
// カスタムツールにiconフィールドがある場合、
|
|
128
128
|
// それが表示されることを確認
|
|
129
129
|
expect(true).toBe(true);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it(
|
|
132
|
+
it("should display custom tool without icon if not defined", async () => {
|
|
133
133
|
// TODO: 実装後にテストを記述
|
|
134
134
|
// カスタムツールにiconフィールドがない場合、
|
|
135
135
|
// ツール名のみが表示されることを確認
|
|
136
136
|
expect(true).toBe(true);
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
it(
|
|
139
|
+
it("should handle custom tool selection", async () => {
|
|
140
140
|
// TODO: 実装後にテストを記述
|
|
141
141
|
// カスタムツールを選択した場合、
|
|
142
142
|
// onSelect()がカスタムツールのIDで呼び出されることを確認
|
|
143
143
|
expect(true).toBe(true);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
it(
|
|
146
|
+
it("should display only builtin tools if no custom tools exist", async () => {
|
|
147
147
|
// TODO: 実装後にテストを記述
|
|
148
148
|
// getAllTools()がビルトインツールのみを返す場合、
|
|
149
149
|
// ビルトインツールのみが表示されることを確認
|