import { act, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import { CodeBlock } from "@/components/CodeBlock"; import { ThemeProvider } from "@/hooks/useTheme"; const mockedStyles = vi.hoisted(() => ({ dark: { pre: { background: "#111" } }, light: { pre: { background: "#fff" } }, })); vi.mock("react-syntax-highlighter/dist/esm/prism-async-light", () => ({ default: ({ children, language, style, }: { children: string; language?: string; style: Record; }) => (
      {children}
    
), })); vi.mock("react-syntax-highlighter/dist/esm/styles/prism/one-dark", () => ({ default: mockedStyles.dark, })); vi.mock("react-syntax-highlighter/dist/esm/styles/prism/one-light", () => ({ default: mockedStyles.light, })); describe("CodeBlock", () => { it("renders plain code without mounting the highlighter when highlighting is disabled", () => { render( , ); expect(screen.queryByTestId("highlighted-code")).not.toBeInTheDocument(); expect(screen.getByText("const value = 1;")).toBeInTheDocument(); expect(screen.getByText("ts")).toBeInTheDocument(); expect(screen.getByTestId("plain-code-fallback")).toHaveClass("text-foreground/90"); }); it("can render without chat-style chrome for file previews", () => { render( , ); expect(screen.queryByText("html")).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: /copy/i })).not.toBeInTheDocument(); expect(screen.getByText("1")).toBeInTheDocument(); expect(screen.getByTestId("plain-code-fallback")).toHaveClass("bg-transparent"); }); it("falls back to 'text' language when language is undefined", async () => { render( , ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("highlighted-code")).toBeInTheDocument(); expect(screen.getByTestId("highlighted-code")).toHaveAttribute("data-language", "text"); expect(screen.getByText("const value = 1;")).toBeInTheDocument(); }); it("normalizes file language aliases before loading Prism", async () => { render( , ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("highlighted-code")).toHaveAttribute("data-language", "markup"); }); it("renders ANSI output without mounting the syntax highlighter", () => { render( alert(1)"} /> , ); expect(screen.queryByTestId("highlighted-code")).not.toBeInTheDocument(); expect(screen.getByTestId("ansi-code")).toBeInTheDocument(); expect(screen.getByTestId("ansi-code").closest(".not-prose")).toBeTruthy(); expect(screen.getByText("ansi")).toBeInTheDocument(); expect(screen.getByText("PASS")).toHaveStyle({ color: "#0dbc79" }); expect(screen.getByText("")).toBeInTheDocument(); expect(document.querySelector("script")).toBeNull(); }); it("detects ANSI sequences in regular code blocks", () => { render( , ); expect(screen.queryByTestId("highlighted-code")).not.toBeInTheDocument(); expect(screen.getByText("truecolor")).toHaveStyle({ color: "rgb(35, 209, 139)", }); }); it("copies ANSI output as clean text", async () => { const user = userEvent.setup(); const writeText = vi.fn().mockResolvedValue(undefined); Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText }, }); try { render( , ); await user.click(screen.getByRole("button", { name: /copy/i })); expect(writeText).toHaveBeenCalledWith("PASS"); } finally { Reflect.deleteProperty(navigator, "clipboard"); } }); it("copies with the textarea fallback when Clipboard API is unavailable", async () => { const user = userEvent.setup(); Object.defineProperty(navigator, "clipboard", { configurable: true, value: undefined, }); const execCommand = vi.fn().mockReturnValue(true); Object.defineProperty(document, "execCommand", { configurable: true, value: execCommand, }); try { render( , ); await user.click(screen.getByRole("button", { name: /copy/i })); await waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy")); expect(screen.getByText("Copied")).toBeInTheDocument(); } finally { Reflect.deleteProperty(navigator, "clipboard"); Reflect.deleteProperty(document, "execCommand"); } }); it("reads theme from context without creating per-block observers", async () => { const originalMutationObserver = globalThis.MutationObserver; const observer = vi.fn(); class MockMutationObserver { constructor(callback: MutationCallback) { observer(callback); } observe = vi.fn(); disconnect = vi.fn(); takeRecords() { return []; } } vi.stubGlobal("MutationObserver", MockMutationObserver); try { const { rerender } = render( , ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("highlighted-code")).toHaveAttribute( "data-theme", "dark", ); rerender( , ); await act(async () => { await Promise.resolve(); }); expect(screen.getByTestId("highlighted-code")).toHaveAttribute( "data-theme", "light", ); expect(observer).not.toHaveBeenCalled(); } finally { vi.stubGlobal("MutationObserver", originalMutationObserver); } }); });