diff --git a/open-sse/services/__tests__/tierResolver.test.ts b/open-sse/services/__tests__/tierResolver.test.ts index 7f2ce7a9f8..62ad4f1867 100644 --- a/open-sse/services/__tests__/tierResolver.test.ts +++ b/open-sse/services/__tests__/tierResolver.test.ts @@ -197,7 +197,7 @@ describe("TierResolver", () => { { provider: "openai", model: "gpt-4o" }, { provider: "openai", model: "gpt-4o" }, ]); - // Observable effect of the cache: the duplicate resolves to the same tier and only +// Observable effect of the cache: the duplicate resolves to the same tier and only // ONE entry is memoized (getTierStats counts cache entries, not classify calls). expect(results).toHaveLength(2); expect(results[0].tier).toBe(results[1].tier); diff --git a/tests/unit/AutoComboCatalog.test.tsx b/tests/unit/AutoComboCatalog.test.tsx index 64aabf8971..f4162130ae 100644 --- a/tests/unit/AutoComboCatalog.test.tsx +++ b/tests/unit/AutoComboCatalog.test.tsx @@ -26,7 +26,7 @@ function makeContainer(): HTMLElement { return container; } -describe("AutoComboCatalog", () => { +describe("AutoComboCatalog", { timeout: 15_000 }, () => { beforeEach(() => { ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } diff --git a/tests/unit/agent-skills-page.test.tsx b/tests/unit/agent-skills-page.test.tsx index bae548fe9c..b315e71806 100644 --- a/tests/unit/agent-skills-page.test.tsx +++ b/tests/unit/agent-skills-page.test.tsx @@ -8,7 +8,12 @@ import type { AgentSkill, SkillCoverage } from "../../src/lib/agentSkills/types" // ── i18n stub ──────────────────────────────────────────────────────────────── vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, + useLocale: () => "en", + useTranslations: () => { + const t = (key: string) => key; + t.has = () => false; + return t; + }, })); // ── next/link stub ─────────────────────────────────────────────────────────── @@ -30,7 +35,10 @@ vi.mock("next/link", () => ({ // ── next/dynamic stub — renders placeholder immediately ─────────────────────── vi.mock("next/dynamic", () => ({ - default: (loader: () => Promise<{ default: React.ComponentType<{ children: string }> }>, _opts?: unknown) => { + default: ( + loader: () => Promise<{ default: React.ComponentType<{ children: string }> }>, + _opts?: unknown + ) => { // Return a synchronous stub that renders children as plain text. return function DynamicStub({ children }: { children: string }) { return
{children}
; @@ -49,7 +57,8 @@ function makeSkill(overrides: Partial = {}): AgentSkill { area: "providers", icon: "hub", endpoints: ["POST /api/providers", "GET /api/providers"], - rawUrl: "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md", + rawUrl: + "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md", githubUrl: "https://github.com/diegosouzapw/OmniRoute/blob/main/skills/omni-providers/SKILL.md", ...overrides, }; @@ -63,7 +72,7 @@ function make42Skills(): AgentSkill[] { id: `omni-skill-${i}`, name: `API Skill ${i}`, category: "api", - }), + }) ); } for (let i = 0; i < 20; i++) { @@ -74,7 +83,7 @@ function make42Skills(): AgentSkill[] { category: "cli", endpoints: undefined, cliCommands: [`skill${i} run`, `skill${i} status`], - }), + }) ); } return skills; @@ -96,7 +105,11 @@ const PARTIAL_COVERAGE: SkillCoverage = { // ── Fetch mock factory ─────────────────────────────────────────────────────── -function mockFetch(skills: AgentSkill[], coverage: SkillCoverage, rawMarkdown = "# Test Skill\nContent here.") { +function mockFetch( + skills: AgentSkill[], + coverage: SkillCoverage, + rawMarkdown = "# Test Skill\nContent here." +) { return vi.fn(async (url: string | Request) => { const urlStr = typeof url === "string" ? url : url.toString(); if (urlStr === "/api/agent-skills") { @@ -128,7 +141,9 @@ function makeContainer(): HTMLElement { } beforeEach(() => { - (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; // Mock clipboard Object.defineProperty(navigator, "clipboard", { value: { writeText: vi.fn().mockResolvedValue(undefined) }, @@ -165,9 +180,8 @@ describe("AgentSkillsPageClient", () => { const skills = make42Skills(); vi.stubGlobal("fetch", mockFetch(skills, FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -181,9 +195,8 @@ describe("AgentSkillsPageClient", () => { it("renders SkillsConceptCard variant=agent at the top", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -198,16 +211,17 @@ describe("AgentSkillsPageClient", () => { const skills = make42Skills(); vi.stubGlobal("fetch", mockFetch(skills, FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { root?.render(); }); - const filterApiBtn = container.querySelector("[data-testid='filter-api']") as HTMLButtonElement | null; + const filterApiBtn = container.querySelector( + "[data-testid='filter-api']" + ) as HTMLButtonElement | null; expect(filterApiBtn).not.toBeNull(); await act(async () => { @@ -222,16 +236,17 @@ describe("AgentSkillsPageClient", () => { const skills = make42Skills(); vi.stubGlobal("fetch", mockFetch(skills, FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { root?.render(); }); - const filterCliBtn = container.querySelector("[data-testid='filter-cli']") as HTMLButtonElement | null; + const filterCliBtn = container.querySelector( + "[data-testid='filter-cli']" + ) as HTMLButtonElement | null; await act(async () => { filterCliBtn?.click(); }); @@ -246,16 +261,17 @@ describe("AgentSkillsPageClient", () => { const fetchMock = mockFetch(skills, FULL_COVERAGE, "# omni-skill-0 doc"); vi.stubGlobal("fetch", fetchMock); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { root?.render(); }); - const firstCard = container.querySelector("[data-testid='skill-card-omni-skill-0']") as HTMLElement | null; + const firstCard = container.querySelector( + "[data-testid='skill-card-omni-skill-0']" + ) as HTMLElement | null; expect(firstCard).not.toBeNull(); await act(async () => { @@ -264,7 +280,7 @@ describe("AgentSkillsPageClient", () => { // Before debounce fires — raw fetch should NOT have been made yet const rawFetchCallsBefore = (fetchMock as ReturnType).mock.calls.filter( - ([url]: [string]) => typeof url === "string" && url.includes("/raw"), + ([url]: [string]) => typeof url === "string" && url.includes("/raw") ); expect(rawFetchCallsBefore.length).toBe(0); @@ -275,7 +291,7 @@ describe("AgentSkillsPageClient", () => { // Now the raw fetch should have been triggered const rawFetchCallsAfter = (fetchMock as ReturnType).mock.calls.filter( - ([url]: [string]) => typeof url === "string" && url.includes("/raw"), + ([url]: [string]) => typeof url === "string" && url.includes("/raw") ); expect(rawFetchCallsAfter.length).toBeGreaterThan(0); @@ -285,9 +301,8 @@ describe("AgentSkillsPageClient", () => { it("preview pane shows empty state when no card is selected", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -301,9 +316,8 @@ describe("AgentSkillsPageClient", () => { it("CoverageBar is rendered with 100% = green bars when coverage is full", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -328,9 +342,8 @@ describe("AgentSkillsPageClient", () => { it("generate button is hidden when coverage is 100%", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -344,9 +357,8 @@ describe("AgentSkillsPageClient", () => { it("generate button is visible when coverage is partial", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), PARTIAL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -361,16 +373,17 @@ describe("AgentSkillsPageClient", () => { const skills = make42Skills(); vi.stubGlobal("fetch", mockFetch(skills, FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { root?.render(); }); - const searchInput = container.querySelector("[data-testid='search-input']") as HTMLInputElement | null; + const searchInput = container.querySelector( + "[data-testid='search-input']" + ) as HTMLInputElement | null; expect(searchInput).not.toBeNull(); await act(async () => { @@ -380,7 +393,7 @@ describe("AgentSkillsPageClient", () => { // React uses onChange const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, - "value", + "value" )?.set; nativeInputValueSetter?.call(searchInput, "API Skill 0"); searchInput.dispatchEvent(new Event("change", { bubbles: true })); @@ -396,9 +409,8 @@ describe("AgentSkillsPageClient", () => { it("MCP and A2A links bar is present", async () => { vi.stubGlobal("fetch", mockFetch(make42Skills(), FULL_COVERAGE)); - const { AgentSkillsPageClient } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient" - ); + const { AgentSkillsPageClient } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient"); const container = makeContainer(); root = createRoot(container); await act(async () => { @@ -414,9 +426,8 @@ describe("AgentSkillsPageClient", () => { describe("CoverageBar", () => { it("renders two progressbars with correct aria attributes", async () => { - const { CoverageBar } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar" - ); + const { CoverageBar } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { @@ -438,9 +449,8 @@ describe("CoverageBar", () => { }); it("applies red color class when coverage is below 75%", async () => { - const { CoverageBar } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar" - ); + const { CoverageBar } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar"); const lowCoverage: SkillCoverage = { api: { have: 5, total: 22 }, cli: { have: 0, total: 20 }, @@ -462,9 +472,8 @@ describe("CoverageBar", () => { }); it("applies amber color class when coverage is between 75% and 100%", async () => { - const { CoverageBar } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar" - ); + const { CoverageBar } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/CoverageBar"); const partialCoverage: SkillCoverage = { api: { have: 18, total: 22 }, // ~81.8% = amber cli: { have: 15, total: 20 }, // 75% = amber @@ -490,9 +499,8 @@ describe("CoverageBar", () => { describe("SkillCard", () => { it("renders skill name and description", async () => { - const { SkillCard } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard" - ); + const { SkillCard } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard"); const skill = makeSkill({ name: "Providers", description: "Manage connections" }); const container = makeContainer(); const localRoot = createRoot(container); @@ -507,9 +515,8 @@ describe("SkillCard", () => { }); it("has role=button and aria-pressed=false when not selected", async () => { - const { SkillCard } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard" - ); + const { SkillCard } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { @@ -524,9 +531,8 @@ describe("SkillCard", () => { }); it("has aria-pressed=true when selected", async () => { - const { SkillCard } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard" - ); + const { SkillCard } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { @@ -540,9 +546,8 @@ describe("SkillCard", () => { }); it("calls onClick when clicked", async () => { - const { SkillCard } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard" - ); + const { SkillCard } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard"); const handleClick = vi.fn(); const container = makeContainer(); const localRoot = createRoot(container); @@ -558,9 +563,8 @@ describe("SkillCard", () => { }); it("shows first 2 endpoints as chips for API skill", async () => { - const { SkillCard } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard" - ); + const { SkillCard } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillCard"); const skill = makeSkill({ endpoints: ["POST /api/providers", "GET /api/providers", "DELETE /api/providers/:id"], }); @@ -583,15 +587,12 @@ describe("SkillCard", () => { describe("SkillPreviewPane", () => { it("renders empty state when skillId is null", async () => { - const { SkillPreviewPane } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane" - ); + const { SkillPreviewPane } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { - localRoot.render( - , - ); + localRoot.render(); }); const empty = container.querySelector("[data-testid='skill-preview-empty']"); @@ -602,9 +603,8 @@ describe("SkillPreviewPane", () => { }); it("renders markdown when skillId and markdown are provided", async () => { - const { SkillPreviewPane } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane" - ); + const { SkillPreviewPane } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { @@ -613,7 +613,7 @@ describe("SkillPreviewPane", () => { skillId="omni-providers" markdown="# Providers\nContent here." loading={false} - />, + /> ); }); @@ -625,15 +625,12 @@ describe("SkillPreviewPane", () => { }); it("shows error state when skillId provided but markdown is empty string", async () => { - const { SkillPreviewPane } = await import( - "../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane" - ); + const { SkillPreviewPane } = + await import("../../src/app/(dashboard)/dashboard/agent-skills/components/SkillPreviewPane"); const container = makeContainer(); const localRoot = createRoot(container); await act(async () => { - localRoot.render( - , - ); + localRoot.render(); }); // markdown is "" (falsy) — should show error state diff --git a/tests/unit/dashboard/batch/concept-cards.test.tsx b/tests/unit/dashboard/batch/concept-cards.test.tsx index e543b75dc5..5bc2a716d5 100644 --- a/tests/unit/dashboard/batch/concept-cards.test.tsx +++ b/tests/unit/dashboard/batch/concept-cards.test.tsx @@ -20,14 +20,23 @@ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, })); +const storage = new Map(); +Object.defineProperty(globalThis, "localStorage", { + configurable: true, + value: { + clear: () => storage.clear(), + getItem: (key: string) => storage.get(key) ?? null, + removeItem: (key: string) => storage.delete(key), + setItem: (key: string, value: string) => storage.set(key, value), + }, +}); + // ── Import components after mocks ───────────────────────────────────────────── -const { default: BatchConceptCard } = await import( - "../../../../src/app/(dashboard)/dashboard/batch/components/BatchConceptCard" -); -const { default: FilesConceptCard } = await import( - "../../../../src/app/(dashboard)/dashboard/batch/components/FilesConceptCard" -); +const { default: BatchConceptCard } = + await import("../../../../src/app/(dashboard)/dashboard/batch/components/BatchConceptCard"); +const { default: FilesConceptCard } = + await import("../../../../src/app/(dashboard)/dashboard/batch/components/FilesConceptCard"); // ── Helpers ─────────────────────────────────────────────────────────────────── diff --git a/tests/unit/dashboard/batch/list-regression.test.tsx b/tests/unit/dashboard/batch/list-regression.test.tsx index 333a715e77..7fcdd6a118 100644 --- a/tests/unit/dashboard/batch/list-regression.test.tsx +++ b/tests/unit/dashboard/batch/list-regression.test.tsx @@ -22,7 +22,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; // ── Mocks ───────────────────────────────────────────────────────────────────── vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, + useLocale: () => "en", + useTranslations: () => { + const t = (key: string) => key; + t.has = () => false; + return t; + }, })); vi.mock("next/link", () => ({ @@ -33,17 +38,20 @@ vi.mock("next/link", () => ({ // Mock retryFailed (used by BatchRowActions → useBatchActions) vi.mock("@/lib/batches/retryFailed", () => ({ - buildRetryPlan: vi.fn(() => ({ retriableLines: 0, newJsonl: "", failedCustomIds: [], skippedLines: 0 })), + buildRetryPlan: vi.fn(() => ({ + retriableLines: 0, + newJsonl: "", + failedCustomIds: [], + skippedLines: 0, + })), })); // ── Import components after mocks ───────────────────────────────────────────── -const { default: BatchListTab } = await import( - "../../../../src/app/(dashboard)/dashboard/batch/BatchListTab" -); -const { default: FilesListTab } = await import( - "../../../../src/app/(dashboard)/dashboard/batch/FilesListTab" -); +const { default: BatchListTab } = + await import("../../../../src/app/(dashboard)/dashboard/batch/BatchListTab"); +const { default: FilesListTab } = + await import("../../../../src/app/(dashboard)/dashboard/batch/FilesListTab"); // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -56,21 +64,23 @@ function makeDiv() { } // Batch record factory -function makeBatch(overrides: Partial<{ - id: string; - status: string; - endpoint: string; - model: string; - requestCountsTotal: number; - requestCountsCompleted: number; - requestCountsFailed: number; - outputFileId: string | null; - errorFileId: string | null; - expiresAt: number | null; - inputFileId: string; - completionWindow: string; - createdAt: number; -}> = {}) { +function makeBatch( + overrides: Partial<{ + id: string; + status: string; + endpoint: string; + model: string; + requestCountsTotal: number; + requestCountsCompleted: number; + requestCountsFailed: number; + outputFileId: string | null; + errorFileId: string | null; + expiresAt: number | null; + inputFileId: string; + completionWindow: string; + createdAt: number; + }> = {} +) { return { id: overrides.id ?? "batch-001", endpoint: overrides.endpoint ?? "/v1/chat/completions", @@ -99,14 +109,16 @@ function makeBatch(overrides: Partial<{ } // File record factory -function makeFile(overrides: Partial<{ - id: string; - filename: string; - bytes: number; - purpose: string; - createdAt: number; - expiresAt: number | null; -}> = {}) { +function makeFile( + overrides: Partial<{ + id: string; + filename: string; + bytes: number; + purpose: string; + createdAt: number; + expiresAt: number | null; + }> = {} +) { return { id: overrides.id ?? "file-001", filename: overrides.filename ?? "batch-input.jsonl", @@ -131,7 +143,10 @@ function render(jsx: React.ReactElement) { // ── Lifecycle ───────────────────────────────────────────────────────────────── beforeEach(() => { - vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}), text: async () => "" })); + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ ok: true, json: async () => ({}), text: async () => "" }) + ); vi.stubGlobal("confirm", vi.fn().mockReturnValue(false)); // don't confirm dialogs }); @@ -163,9 +178,7 @@ describe("BatchListTab — rendering", () => { it("2. 'Remove completed' button appears when there are completed batches", () => { const batches = [makeBatch({ id: "batch-completed", status: "completed" })]; - const el = render( - - ); + const el = render(); // button text contains "Remove completed" const btn = Array.from(el.querySelectorAll("button")).find((b) => b.textContent?.includes("batchListRemoveCompleted") @@ -205,9 +218,7 @@ describe("BatchListTab — rendering", () => { makeBatch({ id: "batch-completed", status: "completed" }), makeBatch({ id: "batch-in-progress", status: "in_progress" }), ]; - const el = render( - - ); + const el = render(); // Both visible initially expect(el.textContent).toContain("batch-completed"); @@ -231,9 +242,7 @@ describe("BatchListTab — rendering", () => { makeBatch({ id: "batch-unique-aaa", status: "completed" }), makeBatch({ id: "batch-unique-bbb", status: "completed" }), ]; - const el = render( - - ); + const el = render(); // Search input exists and accepts text const input = el.querySelector("input[type='text']") as HTMLInputElement; @@ -245,27 +254,21 @@ describe("BatchListTab — rendering", () => { }); it("6. shows loading spinner when loading=true and no batches", () => { - const el = render( - - ); + const el = render(); // Loading state renders a spinner (animate-spin class) const spinner = el.querySelector(".animate-spin"); expect(spinner).not.toBeNull(); }); it("7. shows empty state when no batches and not loading", () => { - const el = render( - - ); + const el = render(); // Should show some empty-state indicator (no spinner) expect(el.querySelector(".animate-spin")).toBeNull(); }); it("8. sanitization: rendered content contains no stack traces or file paths", () => { const batches = [makeBatch({ id: "batch-safe", status: "completed" })]; - const el = render( - - ); + const el = render(); const text = el.textContent ?? ""; expect(text).not.toMatch(/\/home\//); expect(text).not.toMatch(/at \//); @@ -283,9 +286,7 @@ describe("BatchListTab — rendering", () => { requestCountsFailed: 10, }), ]; - const el = render( - - ); + const el = render(); // The i18n key is rendered literally in tests (mock useTranslations returns key) expect(el.textContent).toContain("batchListProgressPartial"); }); @@ -300,21 +301,21 @@ describe("BatchListTab — rendering", () => { requestCountsFailed: 0, }), ]; - const el = render( - - ); + const el = render(); expect(el.textContent).toContain("-50%"); }); it("18. Provider column derives provider from model id (A-1)", () => { const batches = [ makeBatch({ id: "batch-openai", model: "gpt-4o", status: "completed" }), - makeBatch({ id: "batch-anthropic", model: "claude-3-5-sonnet-20241022", status: "completed" }), + makeBatch({ + id: "batch-anthropic", + model: "claude-3-5-sonnet-20241022", + status: "completed", + }), makeBatch({ id: "batch-gemini", model: "gemini-1.5-flash", status: "completed" }), ]; - const el = render( - - ); + const el = render(); const text = el.textContent ?? ""; expect(text).toContain("OpenAI"); expect(text).toContain("Anthropic"); @@ -327,9 +328,7 @@ describe("BatchListTab — rendering", () => { makeBatch({ id: "b-o1", model: "o1-preview", status: "completed" }), makeBatch({ id: "b-o3", model: "o3-mini", status: "completed" }), ]; - const el = render( - - ); + const el = render(); const text = el.textContent ?? ""; // All three are OpenAI families — the column should print "OpenAI" three times. const occurrences = (text.match(/OpenAI/g) ?? []).length; @@ -338,13 +337,15 @@ describe("BatchListTab — rendering", () => { it("20. Provider derivation routes unknown / null model through i18n keys (R2)", () => { const batches = [ - makeBatch({ id: "b-unknown-model", model: "weird-model-name-not-recognized", status: "completed" }), + makeBatch({ + id: "b-unknown-model", + model: "weird-model-name-not-recognized", + status: "completed", + }), // makeBatch's default model is gpt-4o; explicitly null-ish models below makeBatch({ id: "b-null-model", model: "", status: "completed" }), ]; - const el = render( - - ); + const el = render(); const text = el.textContent ?? ""; // i18n mock returns the key literal — proves we route through t() and // didn't leave hardcoded "Other" / "—" English strings in the cell. @@ -362,9 +363,7 @@ describe("FilesListTab — rendering", () => { makeFile({ id: "file-bbb", filename: "output-bbb.jsonl", purpose: "batch-output" }), makeFile({ id: "file-ccc", filename: "fine-tune-ccc.jsonl", purpose: "fine-tune" }), ]; - const el = render( - - ); + const el = render(); expect(el.textContent).toContain("input-aaa.jsonl"); expect(el.textContent).toContain("output-bbb.jsonl"); expect(el.textContent).toContain("fine-tune-ccc.jsonl"); @@ -375,9 +374,7 @@ describe("FilesListTab — rendering", () => { makeFile({ id: "file-batch", filename: "batch-input.jsonl", purpose: "batch" }), makeFile({ id: "file-fine", filename: "fine-tune.jsonl", purpose: "fine-tune" }), ]; - const el = render( - - ); + const el = render(); expect(el.textContent).toContain("batch-input.jsonl"); expect(el.textContent).toContain("fine-tune.jsonl"); @@ -399,9 +396,7 @@ describe("FilesListTab — rendering", () => { makeFile({ id: "file-alpha", filename: "alpha-batch.jsonl", purpose: "batch" }), makeFile({ id: "file-beta", filename: "beta-batch.jsonl", purpose: "batch" }), ]; - const el = render( - - ); + const el = render(); // Search input exists const input = el.querySelector("input[type='text']") as HTMLInputElement; @@ -413,9 +408,7 @@ describe("FilesListTab — rendering", () => { }); it("12. 'used by' column header is visible (i18n key)", () => { - const el = render( - - ); + const el = render(); // filesListUsedByColumn key is rendered in the header expect(el.textContent).toContain("filesListUsedByColumn"); }); @@ -433,9 +426,7 @@ describe("FilesListTab — rendering", () => { model: "gpt-4o", }, ]; - const el = render( - - ); + const el = render(); // Truncated id prefix (12 chars) appears inline expect(el.textContent).toContain("batch-using-"); // Role label is rendered next to the id (G-AUD1 — plan §4 "b1 (input)") @@ -447,9 +438,7 @@ describe("FilesListTab — rendering", () => { }); it("14. loading state shows spinner", () => { - const el = render( - - ); + const el = render(); const spinner = el.querySelector(".animate-spin"); expect(spinner).not.toBeNull(); }); diff --git a/tests/unit/omni-skills-page.test.tsx b/tests/unit/omni-skills-page.test.tsx index 0b584985df..ffc2d53eeb 100644 --- a/tests/unit/omni-skills-page.test.tsx +++ b/tests/unit/omni-skills-page.test.tsx @@ -4,11 +4,10 @@ * split, file structure, source patterns, and exported symbols without DOM rendering. * * Run: - * node --import tsx/esm --test tests/unit/omni-skills-page.test.tsx + * vitest run tests/unit/omni-skills-page.test.tsx */ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; +import { describe, expect, it } from "vitest"; import { existsSync, readFileSync } from "node:fs"; import { resolve, join } from "node:path"; @@ -20,11 +19,13 @@ const base = resolve(join(cwd, "src/app/(dashboard)/dashboard/omni-skills")); describe("File structure — omni-skills directory", () => { it("old /dashboard/skills directory does not exist", () => { const oldPath = resolve(join(cwd, "src/app/(dashboard)/dashboard/skills")); - assert.ok(!existsSync(oldPath), `Old skills/ directory must be absent (found at ${oldPath})`); + expect(existsSync(oldPath), `Old skills/ directory must be absent (found at ${oldPath})`).toBe( + false + ); }); it("new /dashboard/omni-skills directory exists", () => { - assert.ok(existsSync(base), `omni-skills/ directory must exist at ${base}`); + expect(existsSync(base), `omni-skills/ directory must exist at ${base}`).toBe(true); }); const expectedFiles = [ @@ -40,9 +41,8 @@ describe("File structure — omni-skills directory", () => { for (const file of expectedFiles) { it(`file exists: omni-skills/${file}`, () => { - assert.ok( - existsSync(resolve(join(base, file))), - `Expected omni-skills/${file} to exist` + expect(existsSync(resolve(join(base, file))), `Expected omni-skills/${file} to exist`).toBe( + true ); }); } @@ -54,21 +54,24 @@ describe("page.tsx — server component", () => { const src = readFileSync(resolve(join(base, "page.tsx")), "utf-8"); it("is a server component (no 'use client' directive)", () => { - assert.ok( + expect( !src.includes('"use client"') && !src.includes("'use client'"), "page.tsx must not have 'use client'" - ); + ).toBe(true); }); it("imports and renders OmniSkillsPageClient", () => { - assert.ok(src.includes("OmniSkillsPageClient"), "page.tsx must reference OmniSkillsPageClient"); + expect( + src.includes("OmniSkillsPageClient"), + "page.tsx must reference OmniSkillsPageClient" + ).toBe(true); }); it("has a default export named Page", () => { - assert.ok( + expect( src.includes("export default function Page"), "page.tsx must have 'export default function Page'" - ); + ).toBe(true); }); }); @@ -78,48 +81,50 @@ describe("OmniSkillsPageClient.tsx", () => { const src = readFileSync(resolve(join(base, "OmniSkillsPageClient.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "OmniSkillsPageClient must start with 'use client'"); + expect( + src.startsWith('"use client"'), + "OmniSkillsPageClient must start with 'use client'" + ).toBe(true); }); it("has all 4 tab IDs", () => { for (const tabId of ["skills", "executions", "sandbox", "marketplace"]) { - assert.ok( + expect( src.includes(`id: "${tabId}"`), `OmniSkillsPageClient must have tab id="${tabId}"` - ); + ).toBe(true); } }); it("renders SkillsConceptCard with variant='omni'", () => { - assert.ok( + expect( src.includes('variant="omni"'), - "OmniSkillsPageClient must render " - ); + 'OmniSkillsPageClient must render ' + ).toBe(true); }); it("imports SkillsConceptCard from shared components", () => { - assert.ok( + expect( src.includes("SkillsConceptCard"), "OmniSkillsPageClient must import SkillsConceptCard" - ); + ).toBe(true); }); it("has selectedSkillId state", () => { - assert.ok( + expect( src.includes("selectedSkillId"), "OmniSkillsPageClient must maintain selectedSkillId state" - ); + ).toBe(true); }); it("wires OmniSkillsList with inspector props", () => { - assert.ok( - src.includes("OmniSkillsList"), - "OmniSkillsPageClient must render OmniSkillsList" + expect(src.includes("OmniSkillsList"), "OmniSkillsPageClient must render OmniSkillsList").toBe( + true ); - assert.ok( + expect( src.includes("onSelectSkill"), "OmniSkillsPageClient must pass onSelectSkill to OmniSkillsList" - ); + ).toBe(true); }); it("renders all 4 tab components", () => { @@ -129,12 +134,12 @@ describe("OmniSkillsPageClient.tsx", () => { "OmniSandboxTab", "OmniMarketplaceTab", ]) { - assert.ok(src.includes(component), `OmniSkillsPageClient must render <${component}>`); + expect(src.includes(component), `OmniSkillsPageClient must render <${component}>`).toBe(true); } }); it("has install modal with hardcoded 'X' close button (preserved behavior)", () => { - assert.ok(src.includes("showInstallModal"), "must preserve showInstallModal state"); + expect(src.includes("showInstallModal"), "must preserve showInstallModal state").toBe(true); }); }); @@ -144,31 +149,31 @@ describe("OmniSkillCard.tsx", () => { const src = readFileSync(resolve(join(base, "components/OmniSkillCard.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "must be a client component"); + expect(src.startsWith('"use client"'), "must be a client component").toBe(true); }); it("accepts skill, selected, onClick props", () => { - assert.ok(src.includes("OmniSkillCardProps"), "must define OmniSkillCardProps"); - assert.ok(src.includes("selected:"), "must have selected prop"); - assert.ok(src.includes("onClick:"), "must have onClick prop"); + expect(src.includes("OmniSkillCardProps"), "must define OmniSkillCardProps").toBe(true); + expect(src.includes("selected:"), "must have selected prop").toBe(true); + expect(src.includes("onClick:"), "must have onClick prop").toBe(true); }); it("has role='button' for accessibility", () => { - assert.ok(src.includes('role="button"'), "must have role='button' for accessibility"); + expect(src.includes('role="button"'), "must have role='button' for accessibility").toBe(true); }); it("exports OmniSkillCard", () => { - assert.ok( + expect( src.includes("export function OmniSkillCard") || src.includes("export { OmniSkillCard }"), "must export OmniSkillCard" - ); + ).toBe(true); }); it("exports OmniSkill interface", () => { - assert.ok( + expect( src.includes("export interface OmniSkill"), "must export OmniSkill interface for other components" - ); + ).toBe(true); }); }); @@ -178,44 +183,42 @@ describe("SkillInspectorPane.tsx", () => { const src = readFileSync(resolve(join(base, "components/SkillInspectorPane.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "must be a client component"); + expect(src.startsWith('"use client"'), "must be a client component").toBe(true); }); it("has all 4 sub-tab IDs", () => { for (const tabId of ["schema", "handler", "executions", "sandbox"]) { - assert.ok( - src.includes(`"${tabId}"`), - `SkillInspectorPane must include sub-tab "${tabId}"` + expect(src.includes(`"${tabId}"`), `SkillInspectorPane must include sub-tab "${tabId}"`).toBe( + true ); } }); it("has empty state text when no skill selected", () => { - assert.ok( - src.includes("Selecione uma skill"), - "must have empty state message" - ); + expect( + src.includes("selectSkillToInspect"), + "must have empty state message (i18n key selectSkillToInspect)" + ).toBe(true); }); it("fetches /api/skills/[id] for skill detail", () => { - assert.ok( + expect( src.includes("/api/skills/${selectedSkillId}") || src.includes("`/api/skills/${selectedSkillId}`"), "must fetch /api/skills/${selectedSkillId} for detail" - ); + ).toBe(true); }); it("fetches /api/skills/executions for the executions tab", () => { - assert.ok( - src.includes("api/skills/executions?skillId=") || - src.includes("api/skills/executions"), + expect( + src.includes("api/skills/executions?skillId=") || src.includes("api/skills/executions"), "must fetch executions for the selected skill" - ); + ).toBe(true); }); it("has ON / AUTO / OFF / Uninstall buttons", () => { - assert.ok(src.includes("onSetMode"), "must call onSetMode for mode buttons"); - assert.ok(src.includes("onUninstall"), "must call onUninstall"); + expect(src.includes("onSetMode"), "must call onSetMode for mode buttons").toBe(true); + expect(src.includes("onUninstall"), "must call onUninstall").toBe(true); }); }); @@ -225,19 +228,21 @@ describe("OmniSkillsList.tsx", () => { const src = readFileSync(resolve(join(base, "components/OmniSkillsList.tsx")), "utf-8"); it("uses grid-cols-12 split layout", () => { - assert.ok(src.includes("grid-cols-12"), "must use 12-column grid for split layout"); + expect(src.includes("grid-cols-12"), "must use 12-column grid for split layout").toBe(true); }); it("renders OmniSkillCard for each skill", () => { - assert.ok(src.includes("OmniSkillCard"), "must render OmniSkillCard per skill"); + expect(src.includes("OmniSkillCard"), "must render OmniSkillCard per skill").toBe(true); }); it("renders SkillInspectorPane on the right", () => { - assert.ok(src.includes("SkillInspectorPane"), "must include SkillInspectorPane in right col"); + expect(src.includes("SkillInspectorPane"), "must include SkillInspectorPane in right col").toBe( + true + ); }); it("has onSelectSkill prop to control inspector state", () => { - assert.ok(src.includes("onSelectSkill"), "must accept onSelectSkill prop"); + expect(src.includes("onSelectSkill"), "must accept onSelectSkill prop").toBe(true); }); }); @@ -247,18 +252,18 @@ describe("OmniExecutionsTab.tsx", () => { const src = readFileSync(resolve(join(base, "components/OmniExecutionsTab.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "must be a client component"); + expect(src.startsWith('"use client"'), "must be a client component").toBe(true); }); it("renders a table with skill/status/duration/time columns", () => { - assert.ok(src.includes("{t(\"skill\")}"), "must have skill column"); - assert.ok(src.includes("{t(\"status\")}"), "must have status column"); - assert.ok(src.includes("{t(\"duration\")}"), "must have duration column"); + expect(src.includes('{t("skill")}'), "must have skill column").toBe(true); + expect(src.includes('{t("status")}'), "must have status column").toBe(true); + expect(src.includes('{t("duration")}'), "must have duration column").toBe(true); }); it("has pagination buttons", () => { - assert.ok(src.includes("onPagePrev"), "must accept onPagePrev handler"); - assert.ok(src.includes("onPageNext"), "must accept onPageNext handler"); + expect(src.includes("onPagePrev"), "must accept onPagePrev handler").toBe(true); + expect(src.includes("onPageNext"), "must accept onPageNext handler").toBe(true); }); }); @@ -268,13 +273,13 @@ describe("OmniSandboxTab.tsx", () => { const src = readFileSync(resolve(join(base, "components/OmniSandboxTab.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "must be a client component"); + expect(src.startsWith('"use client"'), "must be a client component").toBe(true); }); it("shows sandbox config values", () => { - assert.ok(src.includes("100ms"), "must show 100ms CPU limit"); - assert.ok(src.includes("256MB"), "must show 256MB memory limit"); - assert.ok(src.includes("30s"), "must show 30s timeout"); + expect(src.includes("100ms"), "must show 100ms CPU limit").toBe(true); + expect(src.includes("256MB"), "must show 256MB memory limit").toBe(true); + expect(src.includes("30s"), "must show 30s timeout").toBe(true); }); }); @@ -284,42 +289,41 @@ describe("OmniMarketplaceTab.tsx", () => { const src = readFileSync(resolve(join(base, "components/OmniMarketplaceTab.tsx")), "utf-8"); it("starts with 'use client'", () => { - assert.ok(src.startsWith('"use client"'), "must be a client component"); + expect(src.startsWith('"use client"'), "must be a client component").toBe(true); }); it("has marketplace search logic", () => { - assert.ok( + expect( src.includes("/api/skills/marketplace"), "must call /api/skills/marketplace endpoint" - ); + ).toBe(true); }); it("has skills.sh search logic", () => { - assert.ok(src.includes("/api/skills/skillssh"), "must call /api/skills/skillssh endpoint"); + expect(src.includes("/api/skills/skillssh"), "must call /api/skills/skillssh endpoint").toBe( + true + ); }); it("accepts skillsProvider and onRefreshSkills props", () => { - assert.ok(src.includes("skillsProvider"), "must accept skillsProvider prop"); - assert.ok(src.includes("onRefreshSkills"), "must accept onRefreshSkills prop"); + expect(src.includes("skillsProvider"), "must accept skillsProvider prop").toBe(true); + expect(src.includes("onRefreshSkills"), "must accept onRefreshSkills prop").toBe(true); }); }); // ─── E2E test update ────────────────────────────────────────────────────────── describe("E2E spec path", () => { - const src = readFileSync( - resolve(join(cwd, "tests/e2e/skills-marketplace.spec.ts")), - "utf-8" - ); + const src = readFileSync(resolve(join(cwd, "tests/e2e/skills-marketplace.spec.ts")), "utf-8"); it("uses /dashboard/omni-skills (not /dashboard/skills)", () => { - assert.ok( + expect( src.includes("/dashboard/omni-skills"), "E2E spec must navigate to /dashboard/omni-skills" - ); - assert.ok( + ).toBe(true); + expect( !src.includes('"/dashboard/skills"') && !src.includes("'/dashboard/skills'"), "E2E spec must not have the old /dashboard/skills path in gotoDashboardRoute call" - ); + ).toBe(true); }); }); diff --git a/tests/unit/shared/components/AutoRoutingBanner.test.tsx b/tests/unit/shared/components/AutoRoutingBanner.test.tsx deleted file mode 100644 index 4d0d32d04f..0000000000 --- a/tests/unit/shared/components/AutoRoutingBanner.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// @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"; - -const cleanupCallbacks: Array<() => void> = []; - -function createTestStorage(): Storage { - const entries = new Map(); - return { - get length() { - return entries.size; - }, - clear: () => entries.clear(), - getItem: (key) => entries.get(key) ?? null, - key: (index) => Array.from(entries.keys())[index] ?? null, - removeItem: (key) => { - entries.delete(key); - }, - setItem: (key, value) => { - entries.set(key, value); - }, - }; -} - -function makeContainer(): HTMLElement { - const container = document.createElement("div"); - document.body.appendChild(container); - cleanupCallbacks.push(() => { - container.remove(); - }); - return container; -} - -describe("AutoRoutingBanner", () => { - beforeEach(() => { - ( - globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } - ).IS_REACT_ACT_ENVIRONMENT = true; - vi.stubGlobal("localStorage", createTestStorage()); - localStorage.clear(); - }); - - afterEach(() => { - while (cleanupCallbacks.length > 0) { - cleanupCallbacks.pop()?.(); - } - document.body.innerHTML = ""; - localStorage.clear(); - vi.unstubAllGlobals(); - }); - - it("renders banner on first mount", async () => { - const { default: AutoRoutingBanner } = await import("@/shared/components/AutoRoutingBanner"); - const container = makeContainer(); - const root = createRoot(container); - await act(async () => { - root.render(); - }); - expect(container.querySelector('[role="banner"]')).toBeTruthy(); - expect(container.textContent).toContain("Auto-Routing Active"); - }); - - it("includes link to Combos page", async () => { - const { default: AutoRoutingBanner } = await import("@/shared/components/AutoRoutingBanner"); - const container = makeContainer(); - const root = createRoot(container); - await act(async () => { - root.render(); - }); - const link = container.querySelector('a[href="/dashboard/combos"]'); - expect(link).toBeTruthy(); - }); - - it("can be dismissed by clicking close button", async () => { - const { default: AutoRoutingBanner } = await import("@/shared/components/AutoRoutingBanner"); - const container = makeContainer(); - const root = createRoot(container); - await act(async () => { - root.render(); - }); - expect(container.querySelector('[role="banner"]')).toBeTruthy(); - const closeButton = container.querySelector('button[aria-label="Dismiss auto-routing banner"]'); - expect(closeButton).toBeTruthy(); - await act(async () => { - closeButton?.click(); - }); - expect(container.querySelector('[role="banner"]')).toBeFalsy(); - }); - - it("persists dismissal to localStorage", async () => { - const { default: AutoRoutingBanner } = await import("@/shared/components/AutoRoutingBanner"); - const container = makeContainer(); - const root = createRoot(container); - await act(async () => { - root.render(); - }); - const closeButton = container.querySelector('button[aria-label="Dismiss auto-routing banner"]'); - await act(async () => { - closeButton?.click(); - }); - expect(localStorage.getItem("auto-routing-banner-dismissed")).toBe("true"); - }); - - it("remains hidden after dismissal on remount", async () => { - localStorage.setItem("auto-routing-banner-dismissed", "true"); - const { default: AutoRoutingBanner } = await import("@/shared/components/AutoRoutingBanner"); - const container = makeContainer(); - const root = createRoot(container); - await act(async () => { - root.render(); - }); - expect(container.querySelector('[role="banner"]')).toBeFalsy(); - }); -}); diff --git a/tests/unit/translator-friendly-compression.test.tsx b/tests/unit/translator-friendly-compression.test.tsx index 517a7e79f2..502448c3af 100644 --- a/tests/unit/translator-friendly-compression.test.tsx +++ b/tests/unit/translator-friendly-compression.test.tsx @@ -21,7 +21,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // --------------------------------------------------------------------------- vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, + useTranslations: () => { + const t = (key: string) => key; + t.has = () => true; + return t; + }, + useLocale: () => "en", })); // Stub shared components @@ -91,11 +96,10 @@ async function renderComponent( forceOpen?: boolean; inputContent?: string; onOpenChange?: (open: boolean) => void; - } = {}, + } = {} ) { - const { default: CompressionPreviewAccordion } = await import( - "@/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion" - ); + const { default: CompressionPreviewAccordion } = + await import("@/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -106,9 +110,7 @@ async function renderComponent( /** Click the accordion toggle button to open/close it. */ async function clickToggle(container: HTMLElement) { - const btn = container.querySelector( - "button[aria-expanded]", - ) as HTMLButtonElement | null; + const btn = container.querySelector("button[aria-expanded]") as HTMLButtonElement | null; expect(btn).toBeTruthy(); await act(async () => { btn?.click(); @@ -137,9 +139,8 @@ afterEach(() => { describe("CompressionPreviewAccordion — export", () => { it("exports a default function component", async () => { - const mod = await import( - "@/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion" - ); + const mod = + await import("@/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion"); expect(typeof mod.default).toBe("function"); }); }); @@ -345,7 +346,7 @@ describe("CompressionPreviewAccordion — Preview fetch", () => { expect.objectContaining({ method: "POST", headers: expect.objectContaining({ "Content-Type": "application/json" }), - }), + }) ); // Verify body has correct shape @@ -534,7 +535,7 @@ describe("CompressionPreviewAccordion — error path (Hard Rule #12)", () => { it("error message does NOT contain stack-trace lines (at /path/...)", async () => { const stackError = new Error( - "Something went wrong\n at /home/user/app/src/file.ts:42:13\n at Object. /home/user/app/tests/test.ts:10:5", + "Something went wrong\n at /home/user/app/src/file.ts:42:13\n at Object. /home/user/app/tests/test.ts:10:5" ); const fetchMock = vi.fn().mockRejectedValue(stackError); vi.stubGlobal("fetch", fetchMock); @@ -578,7 +579,7 @@ describe("CompressionPreviewAccordion — error path (Hard Rule #12)", () => { const errorEl = container.querySelector("[role='alert']"); expect(errorEl).toBeTruthy(); - expect(errorEl?.textContent).toContain("Preview failed"); + expect(errorEl?.textContent).toContain("compressionPreviewFailed"); vi.unstubAllGlobals(); }); diff --git a/tests/unit/translator-friendly-concept-card.test.tsx b/tests/unit/translator-friendly-concept-card.test.tsx index fea085dffa..ae1d3d8d7f 100644 --- a/tests/unit/translator-friendly-concept-card.test.tsx +++ b/tests/unit/translator-friendly-concept-card.test.tsx @@ -6,18 +6,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Minimal i18n stub — returns the key so tests can assert on fallback rendering vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, + useLocale: () => "en", + useTranslations: () => { + const t = (key: string) => key; + t.has = () => false; + return t; + }, })); // Minimal shared component stubs — Card wraps children, Tooltip passes through vi.mock("@/shared/components", () => ({ - Card: ({ - children, - className, - }: { - children: React.ReactNode; - className?: string; - }) =>
{children}
, + Card: ({ children, className }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), })); vi.mock("@/shared/components/Tooltip", () => ({ @@ -50,16 +53,14 @@ describe("TranslatorConceptCard", () => { }); it("exports a default function component", { timeout: 30000 }, async () => { - const mod = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const mod = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); expect(typeof mod.default).toBe("function"); }); it("renders the card with info icon and headline", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -74,9 +75,8 @@ describe("TranslatorConceptCard", () => { }); it("renders the flow diagram with 4 FlowNode elements (app, source, hub, target)", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -92,9 +92,8 @@ describe("TranslatorConceptCard", () => { }); it("renders the diagram with arrow_forward separators", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -107,16 +106,15 @@ describe("TranslatorConceptCard", () => { }); it("toggle button starts collapsed (aria-expanded=false)", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { root.render(); }); const toggleBtn = container.querySelector( - "button[aria-controls='translator-concept-how-it-works']", + "button[aria-controls='translator-concept-how-it-works']" ); expect(toggleBtn).toBeTruthy(); expect(toggleBtn?.getAttribute("aria-expanded")).toBe("false"); @@ -125,16 +123,15 @@ describe("TranslatorConceptCard", () => { }); it("toggle expands 'Como funciona' section and sets aria-expanded=true", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { root.render(); }); const toggleBtn = container.querySelector( - "button[aria-controls='translator-concept-how-it-works']", + "button[aria-controls='translator-concept-how-it-works']" ) as HTMLButtonElement | null; expect(toggleBtn).toBeTruthy(); @@ -149,16 +146,15 @@ describe("TranslatorConceptCard", () => { }); it("toggle collapses section on second click and restores aria-expanded=false", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { root.render(); }); const toggleBtn = container.querySelector( - "button[aria-controls='translator-concept-how-it-works']", + "button[aria-controls='translator-concept-how-it-works']" ) as HTMLButtonElement | null; // Expand @@ -176,9 +172,8 @@ describe("TranslatorConceptCard", () => { }); it("toggle button icon changes between expand_more and expand_less", async () => { - const { default: TranslatorConceptCard } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard" - ); + const { default: TranslatorConceptCard } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -186,7 +181,7 @@ describe("TranslatorConceptCard", () => { }); const toggleBtn = container.querySelector( - "button[aria-controls='translator-concept-how-it-works']", + "button[aria-controls='translator-concept-how-it-works']" ) as HTMLButtonElement | null; // Initially collapsed: should show expand_more @@ -221,16 +216,14 @@ describe("TranslateFlowDiagram", () => { }); it("exports a default function component", async () => { - const mod = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram" - ); + const mod = + await import("@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"); expect(typeof mod.default).toBe("function"); }); it("renders all 4 flow node icons (app, source, hub, target)", async () => { - const { default: TranslateFlowDiagram } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram" - ); + const { default: TranslateFlowDiagram } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -246,9 +239,8 @@ describe("TranslateFlowDiagram", () => { }); it("renders exactly 3 arrow_forward separators between 4 nodes", async () => { - const { default: TranslateFlowDiagram } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram" - ); + const { default: TranslateFlowDiagram } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -260,9 +252,8 @@ describe("TranslateFlowDiagram", () => { }); it("renders a responsive grid container", async () => { - const { default: TranslateFlowDiagram } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram" - ); + const { default: TranslateFlowDiagram } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -276,9 +267,8 @@ describe("TranslateFlowDiagram", () => { }); it("i18n fallback: renders labels using fallback strings when translations return keys", async () => { - const { default: TranslateFlowDiagram } = await import( - "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram" - ); + const { default: TranslateFlowDiagram } = + await import("@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"); const container = makeContainer(); const root = createRoot(container); await act(async () => { @@ -287,13 +277,13 @@ describe("TranslateFlowDiagram", () => { // When mock returns key, tr() detects key === translation and uses fallback // The fallback text should appear in the DOM const text = container.textContent ?? ""; - expect(text).toContain("Sua app"); + expect(text).toContain("Your app"); expect(text).toContain("ex: SDK Anthropic"); - expect(text).toContain("Formato origem"); + expect(text).toContain("Source format"); expect(text).toContain("claude"); // 4th node: OpenAI hub expect(text).toContain("OpenAI (hub)"); - expect(text).toContain("Provider destino"); + expect(text).toContain("Target provider"); expect(text).toContain("Gemini"); }); }); diff --git a/tests/unit/translator-friendly-monitor-tab.test.tsx b/tests/unit/translator-friendly-monitor-tab.test.tsx index 1eb1d30285..7b023e5476 100644 --- a/tests/unit/translator-friendly-monitor-tab.test.tsx +++ b/tests/unit/translator-friendly-monitor-tab.test.tsx @@ -6,7 +6,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // ── i18n stub — returns fallback key so we can assert on translateOrFallback ── vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, + useLocale: () => "en", + useTranslations: () => { + const t = (key: string) => key; + t.has = () => false; + return t; + }, })); // ── Shared component stubs ──────────────────────────────────────────────────── @@ -58,16 +63,13 @@ vi.mock("@/shared/components", () => ({ })); // ── FORMAT_META stub ────────────────────────────────────────────────────────── -vi.mock( - "@/app/(dashboard)/dashboard/translator/exampleTemplates", - () => ({ - FORMAT_META: { - openai: { label: "OpenAI", color: "green" }, - claude: { label: "Claude", color: "orange" }, - gemini: { label: "Gemini", color: "blue" }, - }, - }), -); +vi.mock("@/app/(dashboard)/dashboard/translator/exampleTemplates", () => ({ + FORMAT_META: { + openai: { label: "OpenAI", color: "green" }, + claude: { label: "Claude", color: "orange" }, + gemini: { label: "Gemini", color: "blue" }, + }, +})); // ── fetch mock helpers ──────────────────────────────────────────────────────── function mockFetchEmpty() { @@ -76,7 +78,7 @@ function mockFetchEmpty() { vi.fn().mockResolvedValue({ ok: true, json: async () => ({ success: true, events: [] }), - }), + }) ); } @@ -90,14 +92,14 @@ function mockFetchWithEvents( targetFormat?: string; status?: string; latency?: number; - }>, + }> ) { vi.stubGlobal( "fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({ success: true, events }), - }), + }) ); } @@ -120,7 +122,7 @@ function makeContainer(): HTMLElement { */ async function mountAndFlushInitialFetch( component: React.ReactElement, - container: HTMLElement, + container: HTMLElement ): Promise> { const root = createRoot(container); await act(async () => { @@ -137,8 +139,9 @@ async function mountAndFlushInitialFetch( describe("MonitorTab", () => { beforeEach(() => { vi.useFakeTimers(); - (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = - true; + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; }); afterEach(() => { @@ -152,17 +155,14 @@ describe("MonitorTab", () => { // ── 1. Smoke render ────────────────────────────────────────────────────────── it("exports a default function component", async () => { - const mod = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const mod = await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); expect(typeof mod.default).toBe("function"); }); it("renders the origin hint header (monitorOriginHint) always visible", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -176,9 +176,8 @@ describe("MonitorTab", () => { it("renders 6 StatCards with correct icons", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -196,9 +195,8 @@ describe("MonitorTab", () => { // ── 2. Empty state ─────────────────────────────────────────────────────────── it("shows empty state when events array is empty", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -210,30 +208,30 @@ describe("MonitorTab", () => { it("empty state shows CTA description text from monitorEmptyCta", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); // When t() mock returns key, translateOrFallback detects key === translation and uses hardcoded fallback const emptyDescription = container.querySelector("[data-testid='empty-description']"); - expect(emptyDescription?.textContent).toContain("Volte para a aba Translate"); + expect(emptyDescription?.textContent).toContain("Go back to the Translate tab"); }); - it("empty state 'Ir para Translate' button calls onGoToTranslate callback", async () => { + it("empty state 'Go to Translate' button calls onGoToTranslate callback", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); const onGoToTranslate = vi.fn(); await mountAndFlushInitialFetch(, container); - const actionBtn = container.querySelector("[data-testid='empty-action']") as HTMLButtonElement | null; + const actionBtn = container.querySelector( + "[data-testid='empty-action']" + ) as HTMLButtonElement | null; expect(actionBtn).toBeTruthy(); // Label comes from monitorOpenTranslateButton fallback - expect(actionBtn?.textContent).toContain("Ir para Translate"); + expect(actionBtn?.textContent).toContain("Go to Translate"); await act(async () => { actionBtn?.click(); @@ -243,9 +241,8 @@ describe("MonitorTab", () => { it("empty state action button is not rendered when onGoToTranslate is not provided", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -280,9 +277,8 @@ describe("MonitorTab", () => { ]; mockFetchWithEvents(sampleEvents); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -309,9 +305,8 @@ describe("MonitorTab", () => { ]; mockFetchWithEvents(sampleEvents); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -324,9 +319,8 @@ describe("MonitorTab", () => { const sampleEvents = [{ id: "x", status: "success" }]; mockFetchWithEvents(sampleEvents); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -343,14 +337,13 @@ describe("MonitorTab", () => { // ── 4. Auto-refresh toggle ─────────────────────────────────────────────────── it("toggle auto-refresh button is present with aria-label and shows live state text", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); const toggleBtn = container.querySelector( - "[data-testid='auto-refresh-toggle']", + "[data-testid='auto-refresh-toggle']" ) as HTMLButtonElement | null; expect(toggleBtn).toBeTruthy(); @@ -362,14 +355,13 @@ describe("MonitorTab", () => { it("clicking toggle changes button text from live to paused", async () => { mockFetchEmpty(); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); const toggleBtn = container.querySelector( - "[data-testid='auto-refresh-toggle']", + "[data-testid='auto-refresh-toggle']" ) as HTMLButtonElement | null; expect(toggleBtn).toBeTruthy(); @@ -389,9 +381,8 @@ describe("MonitorTab", () => { }); vi.stubGlobal("fetch", fetchMock); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); @@ -412,15 +403,14 @@ describe("MonitorTab", () => { }); vi.stubGlobal("fetch", fetchMock); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); await mountAndFlushInitialFetch(, container); // Pause auto-refresh const toggleBtn = container.querySelector( - "[data-testid='auto-refresh-toggle']", + "[data-testid='auto-refresh-toggle']" ) as HTMLButtonElement | null; await act(async () => { toggleBtn?.click(); @@ -452,14 +442,13 @@ describe("MonitorTab", () => { it("fetch error does not leak stack traces into the DOM", async () => { vi.stubGlobal( "fetch", - vi.fn().mockRejectedValue( - new Error("Network Error\n at fetch (/some/internal/path.ts:42:10)"), - ), + vi + .fn() + .mockRejectedValue(new Error("Network Error\n at fetch (/some/internal/path.ts:42:10)")) ); - const { default: MonitorTab } = await import( - "@/app/(dashboard)/dashboard/translator/components/MonitorTab" - ); + const { default: MonitorTab } = + await import("@/app/(dashboard)/dashboard/translator/components/MonitorTab"); const container = makeContainer(); // Don't use mountAndFlushInitialFetch here — we want to let the rejection settle const root = createRoot(container); diff --git a/vitest.config.ts b/vitest.config.ts index f6ecedc017..d602eecb71 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -105,6 +105,7 @@ export default defineConfig({ "tests/unit/ui/combos-page-smoke.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed "tests/unit/ui/evals-tab-smoke.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed ], + coverage: { reportsDirectory: "coverage", },