// @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";
// ── i18n stub ─────────────────────────────────────────────────────────────────
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
// ── next/navigation stub ──────────────────────────────────────────────────────
const mockReplace = vi.fn();
const mockGet = vi.fn((key: string) => {
if (key === "tab") return "translate";
if (key === "mode") return "send";
if (key === "advanced") return null;
return null;
});
vi.mock("next/navigation", () => ({
useRouter: () => ({ replace: mockReplace }),
useSearchParams: () => ({
get: mockGet,
toString: () => "tab=translate&mode=send",
}),
}));
// ── Shared component stubs ────────────────────────────────────────────────────
vi.mock("@/shared/components", () => ({
Card: ({ children, className }: { children: React.ReactNode; className?: string }) => (
onAdvancedSlugChange?.("rawjson")}
/>
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/MonitorTab",
() => ({
default: ({ onGoToTranslate }: { onGoToTranslate?: () => void }) => (
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/AdvancedSection",
() => ({
default: ({
children,
forceOpenSlug,
}: {
children?: React.ReactNode;
forceOpenSlug?: string | null;
}) => (
{children}
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/RawJsonPanel",
() => ({
default: ({ forceOpen }: { forceOpen?: boolean }) => (
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/PipelineView",
() => ({
default: ({ forceOpen }: { forceOpen?: boolean; pipelineSteps?: unknown[] }) => (
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion",
() => ({
default: ({ forceOpen }: { forceOpen?: boolean }) => (
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/TestBenchAccordion",
() => ({
default: ({ forceOpen }: { forceOpen?: boolean }) => (
),
}),
);
vi.mock(
"@/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion",
() => ({
default: ({ forceOpen }: { forceOpen?: boolean }) => (
),
}),
);
// ── DOM lifecycle helpers ─────────────────────────────────────────────────────
const cleanupCallbacks: Array<() => void> = [];
function makeContainer(): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
cleanupCallbacks.push(() => container.remove());
return container;
}
async function mount(component: React.ReactElement, container: HTMLElement) {
const root = createRoot(container);
await act(async () => {
root.render(component);
});
return root;
}
// ── Import component AFTER mocks ──────────────────────────────────────────────
import TranslatorPageClient from "@/app/(dashboard)/dashboard/translator/TranslatorPageClient";
describe("TranslatorPageClient", () => {
beforeEach(() => {
vi.clearAllMocks();
// Reset mockGet to default (translate tab, no advanced)
mockGet.mockImplementation((key: string) => {
if (key === "tab") return "translate";
if (key === "mode") return "send";
if (key === "advanced") return null;
return null;
});
});
afterEach(() => {
cleanupCallbacks.forEach((fn) => fn());
cleanupCallbacks.length = 0;
});
it("renders smoke — default tab=translate shows TranslateTab and AdvancedSection", async () => {
const container = makeContainer();
await mount(
, container);
expect(container.querySelector('[data-testid="translator-concept-card"]')).toBeTruthy();
expect(container.querySelector('[data-testid="translate-tab"]')).toBeTruthy();
expect(container.querySelector('[data-testid="advanced-section"]')).toBeTruthy();
expect(container.querySelector('[data-testid="monitor-tab"]')).toBeNull();
});
it("SegmentedControl has role=tablist and aria-label", async () => {
const container = makeContainer();
await mount(
, container);
const ctrl = container.querySelector('[role="tablist"]');
expect(ctrl).toBeTruthy();
expect(ctrl?.getAttribute("aria-label")).toBeTruthy();
});
it("ConceptCard and AutoFeaturesCard (Card) render", async () => {
const container = makeContainer();
await mount(
, container);
expect(container.querySelector('[data-testid="translator-concept-card"]')).toBeTruthy();
// AutoFeaturesCard renders as a Card with a button
const cards = container.querySelectorAll('[data-testid="card"]');
expect(cards.length).toBeGreaterThan(0);
});
it("clicking Monitor tab calls router.replace with tab=monitor", async () => {
const container = makeContainer();
await mount(
, container);
const monitorTabBtn = container.querySelector('[data-testid="tab-monitor"]');
expect(monitorTabBtn).toBeTruthy();
await act(async () => {
(monitorTabBtn as HTMLElement).click();
});
expect(mockReplace).toHaveBeenCalledWith(
expect.stringContaining("tab=monitor"),
expect.any(Object),
);
});
it("8 FeatureChips render inside AutoFeaturesCard when expanded", async () => {
const container = makeContainer();
await mount(
, container);
// Find the toggle button inside the AutoFeaturesCard (Card)
const toggleBtn = Array.from(container.querySelectorAll("button")).find(
(btn) => btn.querySelector(".material-symbols-outlined")?.textContent === "auto_fix_high",
);
expect(toggleBtn).toBeTruthy();
await act(async () => {
toggleBtn!.click();
});
const chips = container.querySelectorAll('[data-testid="feature-chip"]');
expect(chips.length).toBe(8);
});
it("AdvancedSection receives 5 accordion children", async () => {
const container = makeContainer();
await mount(
, container);
const advSection = container.querySelector('[data-testid="advanced-section"]');
expect(advSection).toBeTruthy();
expect(advSection?.querySelector('[data-testid="raw-json-panel"]')).toBeTruthy();
expect(advSection?.querySelector('[data-testid="pipeline-view"]')).toBeTruthy();
expect(advSection?.querySelector('[data-testid="stream-transformer-accordion"]')).toBeTruthy();
expect(advSection?.querySelector('[data-testid="test-bench-accordion"]')).toBeTruthy();
expect(advSection?.querySelector('[data-testid="compression-preview-accordion"]')).toBeTruthy();
});
});