mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
- tests/unit/custom-cli-config.test.ts: fix ERR_MODULE_NOT_FOUND — stale import path cli-tools → cli-code (regression from F8 git mv, missed by F10 audit because it only ran curated test subset).
- tests/unit/ui/CliAgentsPage.test.tsx: update vi.mock path to current cli-code location (was no-op mock pointing to deleted path).
- tests/unit/ui/CliToolCard.test.tsx: update URL strings /dashboard/cli-tools/claude → /dashboard/cli-code/claude (cosmetic alignment with new routes).
- src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx: remove dead case "cliproxyapi" + unused import (no entry in CLI_TOOLS catalog).
- src/app/(dashboard)/dashboard/cli-agents/CliAgentsPageClient.tsx: replace inline div skeleton with shared <CardSkeleton /> for visual consistency with CliCodePageClient.
- src/app/api/cli-tools/{forge,jcode,deepseek-tui,smelt,pi}-settings/route.ts: replace catch (err: any) with catch (err) + (err as NodeJS.ErrnoException).code narrowing (8 instances, eliminates 8 of 11 implicit-any introductions).
Validated: custom-cli-config.test.ts now 3/3 PASS (was 0/1 FAIL with ERR_MODULE_NOT_FOUND); F1/F3 tests 147/147 PASS; UI tests 25/25 PASS; typecheck:core + noimplicit clean.
195 lines
7.2 KiB
TypeScript
195 lines
7.2 KiB
TypeScript
// @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";
|
|
import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog";
|
|
import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus";
|
|
|
|
// ── 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 CliStatusBadge so it doesn't need next-intl internals
|
|
vi.mock("@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge", () => ({
|
|
default: ({
|
|
effectiveConfigStatus,
|
|
}: {
|
|
effectiveConfigStatus: string | null;
|
|
batchStatus: null;
|
|
lastConfiguredAt: string | null;
|
|
}) => <span data-testid="status-badge">{effectiveConfigStatus}</span>,
|
|
}));
|
|
|
|
// ── Import after mocks ────────────────────────────────────────────────────────
|
|
|
|
const { default: CliToolCard } = await import("@/shared/components/cli/CliToolCard");
|
|
|
|
// ── Fixtures ──────────────────────────────────────────────────────────────────
|
|
|
|
function makeTool(overrides: Partial<CliCatalogEntry> = {}): CliCatalogEntry {
|
|
return {
|
|
id: "claude",
|
|
name: "Claude Code",
|
|
icon: "terminal",
|
|
color: "#D97757",
|
|
description: "Anthropic Claude Code CLI",
|
|
docsUrl: "https://example.com",
|
|
configType: "env",
|
|
category: "code",
|
|
vendor: "Anthropic",
|
|
acpSpawnable: false,
|
|
baseUrlSupport: "full",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeBatchStatus(overrides: Partial<ToolBatchStatus> = {}): ToolBatchStatus {
|
|
return {
|
|
detection: { installed: true, runnable: true, version: "1.2.3" },
|
|
config: { status: "configured", endpoint: "http://localhost:20128", lastConfiguredAt: null },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
const containers: HTMLElement[] = [];
|
|
|
|
function renderCard(
|
|
tool: CliCatalogEntry,
|
|
batchStatus: ToolBatchStatus | null,
|
|
detailHref: string,
|
|
hasActiveProviders: boolean
|
|
): HTMLElement {
|
|
const container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
containers.push(container);
|
|
|
|
const root = createRoot(container);
|
|
act(() => {
|
|
root.render(
|
|
<CliToolCard
|
|
tool={tool}
|
|
batchStatus={batchStatus}
|
|
detailHref={detailHref}
|
|
hasActiveProviders={hasActiveProviders}
|
|
/>
|
|
);
|
|
});
|
|
return container;
|
|
}
|
|
|
|
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
|
|
beforeEach(() => {
|
|
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
true;
|
|
});
|
|
|
|
afterEach(() => {
|
|
while (containers.length > 0) {
|
|
containers.pop()?.remove();
|
|
}
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
describe("CliToolCard", () => {
|
|
it("renders tool name", () => {
|
|
const container = renderCard(makeTool(), makeBatchStatus(), "/dashboard/cli-code/claude", true);
|
|
expect(container.textContent).toContain("Claude Code");
|
|
});
|
|
|
|
it("links to detailHref", () => {
|
|
const container = renderCard(makeTool(), makeBatchStatus(), "/dashboard/cli-code/claude", true);
|
|
const link = container.querySelector("a");
|
|
expect(link).not.toBeNull();
|
|
expect(link!.getAttribute("href")).toBe("/dashboard/cli-code/claude");
|
|
});
|
|
|
|
it("shows version when installed", () => {
|
|
const container = renderCard(makeTool(), makeBatchStatus(), "/detail", true);
|
|
expect(container.textContent).toContain("1.2.3");
|
|
});
|
|
|
|
it("shows 'not found' when not installed", () => {
|
|
const status = makeBatchStatus({
|
|
detection: { installed: false, runnable: false, version: undefined },
|
|
});
|
|
const container = renderCard(makeTool(), status, "/detail", true);
|
|
expect(container.textContent).toContain("not found");
|
|
});
|
|
|
|
it("shows 'Configurar →' footer when installed", () => {
|
|
const container = renderCard(makeTool(), makeBatchStatus(), "/detail", true);
|
|
expect(container.textContent).toContain("Configurar →");
|
|
});
|
|
|
|
it("shows 'Como instalar →' footer when not installed", () => {
|
|
const status = makeBatchStatus({
|
|
detection: { installed: false, runnable: false },
|
|
});
|
|
const container = renderCard(makeTool(), status, "/detail", true);
|
|
expect(container.textContent).toContain("Como instalar →");
|
|
});
|
|
|
|
it("shows partial baseUrl amber badge", () => {
|
|
const tool = makeTool({ baseUrlSupport: "partial" });
|
|
const container = renderCard(tool, makeBatchStatus(), "/detail", true);
|
|
expect(container.textContent).toContain("Base URL parcial");
|
|
});
|
|
|
|
it("shows 'também ACP' badge when acpSpawnable is true", () => {
|
|
const tool = makeTool({ acpSpawnable: true });
|
|
const container = renderCard(tool, makeBatchStatus(), "/detail", true);
|
|
expect(container.textContent).toContain("também ACP");
|
|
});
|
|
|
|
it("shows provider tooltip text when hasActiveProviders is false", () => {
|
|
const container = renderCard(makeTool(), makeBatchStatus(), "/detail", false);
|
|
expect(container.textContent).toContain("Conecte um provider em Providers");
|
|
});
|
|
|
|
it("shows install chips when not installed and configType is not guide", () => {
|
|
const status = makeBatchStatus({
|
|
detection: { installed: false, runnable: false },
|
|
});
|
|
const tool = makeTool({ configType: "custom" });
|
|
const container = renderCard(tool, status, "/detail", true);
|
|
expect(container.textContent).toContain("Manual config");
|
|
expect(container.textContent).toContain("Install");
|
|
});
|
|
|
|
it("does NOT show install chips when configType is guide", () => {
|
|
const status = makeBatchStatus({
|
|
detection: { installed: false, runnable: false },
|
|
});
|
|
const tool = makeTool({ configType: "guide" });
|
|
const container = renderCard(tool, status, "/detail", true);
|
|
expect(container.textContent).not.toContain("Manual config");
|
|
});
|
|
|
|
it("renders gracefully with null batchStatus", () => {
|
|
const container = renderCard(makeTool(), null, "/detail", true);
|
|
expect(container.textContent).toContain("Claude Code");
|
|
expect(container.textContent).toContain("not found");
|
|
});
|
|
});
|