// @vitest-environment jsdom
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { ThemeProvider } from "../context/ThemeContext";
import { ChatMarkdownMessage } from "./ChatMarkdownMessage";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
// Mock clipboard API (not available in jsdom by default)
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText },
writable: true,
configurable: true,
});
describe("ChatMarkdownMessage", () => {
let container: HTMLDivElement;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
writeText.mockClear();
});
afterEach(() => {
container.remove();
});
it("renders markdown headings", () => {
act(() => {
createRoot(container).render(
,
);
});
const heading = container.querySelector("h1");
expect(heading).not.toBeNull();
expect(heading?.textContent).toBe("Hello World");
});
it("renders markdown bold and italic", () => {
act(() => {
createRoot(container).render(
,
);
});
expect(container.querySelector("strong")).not.toBeNull();
expect(container.querySelector("em")).not.toBeNull();
});
it("renders markdown lists", () => {
act(() => {
createRoot(container).render(
,
);
});
expect(container.querySelector("ul")).not.toBeNull();
const items = container.querySelectorAll("li");
expect(items.length).toBe(2);
});
it("renders markdown links", () => {
act(() => {
createRoot(container).render(
,
);
});
const link = container.querySelector("a");
expect(link).not.toBeNull();
expect(link?.getAttribute("href")).toBe("https://example.com");
});
it("renders markdown tables (GFM)", () => {
const tableContent = "| A | B |\n|---|---|\n| 1 | 2 |";
act(() => {
createRoot(container).render(
,
);
});
expect(container.querySelector("table")).not.toBeNull();
expect(container.querySelector("th")).not.toBeNull();
});
it("renders code block with copy button", () => {
act(() => {
createRoot(container).render(
,
);
});
const copyBtn = container.querySelector('[aria-label="Copy code"]');
expect(copyBtn).not.toBeNull();
});
it("renders code block with language label when language is specified", () => {
act(() => {
createRoot(container).render(
,
);
});
expect(container.textContent).toContain("typescript");
});
it("clicking copy button calls clipboard.writeText with code content", async () => {
act(() => {
createRoot(container).render(
,
);
});
const copyBtn = container.querySelector('[aria-label="Copy code"]') as HTMLButtonElement | null;
expect(copyBtn).not.toBeNull();
await act(async () => {
copyBtn?.click();
});
expect(writeText).toHaveBeenCalledTimes(1);
expect(writeText).toHaveBeenCalledWith(expect.stringContaining("const y = 2;"));
});
it("inline code renders without copy button", () => {
act(() => {
createRoot(container).render(
,
);
});
const copyBtn = container.querySelector('[aria-label="Copy code"]');
expect(copyBtn).toBeNull();
expect(container.querySelector("code")).not.toBeNull();
});
it("renders inline images with img tag", () => {
act(() => {
createRoot(container).render(
,
);
});
const img = container.querySelector("img");
expect(img).not.toBeNull();
expect(img?.getAttribute("src")).toBe("https://example.com/img.png");
expect(img?.getAttribute("alt")).toBe("alt text");
});
});