Spaces:
Runtime error
Runtime error
| // @vitest-environment jsdom | |
| import React from "react"; | |
| import { act } from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| // ββ Mocks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| vi.mock("next/link", () => ({ | |
| default: ({ | |
| href, | |
| children, | |
| ...props | |
| }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) => ( | |
| <a href={href} {...props}> | |
| {children} | |
| </a> | |
| ), | |
| })); | |
| vi.mock("next-intl", () => ({ | |
| useTranslations: () => (key: string) => key, | |
| useLocale: () => "en", | |
| })); | |
| // Stub fetch globally | |
| const mockFetch = vi.fn().mockResolvedValue({ | |
| ok: true, | |
| json: async () => ({ connections: [], keys: [], data: [], cloudEnabled: false }), | |
| }); | |
| vi.stubGlobal("fetch", mockFetch); | |
| // Stub next/navigation | |
| vi.mock("next/navigation", () => ({ | |
| notFound: () => { | |
| throw new Error("NOT_FOUND"); | |
| }, | |
| })); | |
| // Stub CLI_TOOLS catalog | |
| vi.mock("@/shared/constants/cliTools", () => ({ | |
| CLI_TOOLS: { | |
| claude: { | |
| id: "claude", | |
| name: "Claude Code", | |
| icon: "terminal", | |
| color: "#D97757", | |
| category: "code", | |
| configType: "env", | |
| vendor: "Anthropic", | |
| baseUrlSupport: "full", | |
| defaultModels: [], | |
| }, | |
| codex: { | |
| id: "codex", | |
| name: "Codex", | |
| icon: "terminal", | |
| color: "#000", | |
| category: "code", | |
| configType: "custom", | |
| vendor: "OpenAI", | |
| baseUrlSupport: "full", | |
| defaultModels: [], | |
| }, | |
| custom: { | |
| id: "custom", | |
| name: "Custom CLI", | |
| icon: "terminal", | |
| color: "#888", | |
| category: "code", | |
| configType: "custom-builder", | |
| vendor: undefined, | |
| baseUrlSupport: "full", | |
| defaultModels: [], | |
| }, | |
| "hermes-agent": { | |
| id: "hermes-agent", | |
| name: "Hermes Agent", | |
| icon: "terminal", | |
| color: "#5865f2", | |
| category: "agent", | |
| configType: "custom", | |
| vendor: "HermesAI", | |
| baseUrlSupport: "full", | |
| defaultModels: [], | |
| }, | |
| forge: { | |
| id: "forge", | |
| name: "Forge", | |
| icon: "terminal", | |
| color: "#888", | |
| category: "code", | |
| configType: "custom", | |
| vendor: undefined, | |
| baseUrlSupport: "partial", | |
| defaultModels: [], | |
| }, | |
| }, | |
| })); | |
| // Stub model constants | |
| vi.mock("@/shared/constants/models", () => ({ | |
| PROVIDER_ID_TO_ALIAS: {}, | |
| getModelsByProviderId: () => [], | |
| })); | |
| // Stub specialized cards β render a testid so we can identify which was rendered | |
| vi.mock("../../../src/app/(dashboard)/dashboard/cli-code/components/index", () => ({ | |
| ClaudeToolCard: () => <div data-testid="ClaudeToolCard" />, | |
| CodexToolCard: () => <div data-testid="CodexToolCard" />, | |
| DroidToolCard: () => <div data-testid="DroidToolCard" />, | |
| OpenClawToolCard: () => <div data-testid="OpenClawToolCard" />, | |
| ClineToolCard: () => <div data-testid="ClineToolCard" />, | |
| KiloToolCard: () => <div data-testid="KiloToolCard" />, | |
| DefaultToolCard: ({ toolId }: { toolId: string }) => ( | |
| <div data-testid="DefaultToolCard" data-toolid={toolId} /> | |
| ), | |
| AntigravityToolCard: () => <div data-testid="AntigravityToolCard" />, | |
| CopilotToolCard: () => <div data-testid="CopilotToolCard" />, | |
| CustomCliCard: () => <div data-testid="CustomCliCard" />, | |
| HermesAgentToolCard: () => <div data-testid="HermesAgentToolCard" />, | |
| })); | |
| vi.mock("../../../src/app/(dashboard)/dashboard/cli-code/components/CliproxyapiToolCard", () => ({ | |
| default: () => <div data-testid="CliproxyapiToolCard" />, | |
| })); | |
| // ββ Import after mocks ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const { default: ToolDetailClient } = await import( | |
| "@/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient" | |
| ); | |
| // ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const containers: HTMLElement[] = []; | |
| function renderDetail(toolId: string, category: "code" | "agent"): HTMLElement { | |
| const container = document.createElement("div"); | |
| document.body.appendChild(container); | |
| containers.push(container); | |
| const root = createRoot(container); | |
| act(() => { | |
| root.render(<ToolDetailClient toolId={toolId} category={category} />); | |
| }); | |
| return container; | |
| } | |
| // ββ Lifecycle βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| beforeEach(() => { | |
| ( | |
| globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } | |
| ).IS_REACT_ACT_ENVIRONMENT = true; | |
| mockFetch.mockClear(); | |
| }); | |
| afterEach(() => { | |
| while (containers.length > 0) { | |
| containers.pop()?.remove(); | |
| } | |
| document.body.innerHTML = ""; | |
| }); | |
| // ββ Tests βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe("ToolDetailClient", () => { | |
| it("renders ClaudeToolCard for toolId=claude", async () => { | |
| const container = renderDetail("claude", "code"); | |
| // Wait for async state resolution | |
| await act(async () => {}); | |
| expect(container.querySelector("[data-testid='ClaudeToolCard']")).not.toBeNull(); | |
| }); | |
| it("renders CodexToolCard for toolId=codex", async () => { | |
| const container = renderDetail("codex", "code"); | |
| await act(async () => {}); | |
| expect(container.querySelector("[data-testid='CodexToolCard']")).not.toBeNull(); | |
| }); | |
| it("renders CustomCliCard for toolId=custom", async () => { | |
| const container = renderDetail("custom", "code"); | |
| await act(async () => {}); | |
| expect(container.querySelector("[data-testid='CustomCliCard']")).not.toBeNull(); | |
| }); | |
| it("renders DefaultToolCard for unknown tool (forge, configType:custom)", async () => { | |
| const container = renderDetail("forge", "code"); | |
| await act(async () => {}); | |
| const card = container.querySelector("[data-testid='DefaultToolCard']"); | |
| expect(card).not.toBeNull(); | |
| expect(card!.getAttribute("data-toolid")).toBe("forge"); | |
| }); | |
| it("renders nothing (null) for completely unknown toolId", async () => { | |
| const container = renderDetail("totally-unknown-xyz", "code"); | |
| await act(async () => {}); | |
| // CLI_TOOLS["totally-unknown-xyz"] is undefined β returns null β empty container | |
| expect(container.textContent).toBe(""); | |
| }); | |
| }); | |