import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { cssDeclarations } from "./css-contract-test-support.ts"; const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); const markdownView = readFileSync(new URL("../src/components/chat-markdown-view.tsx", import.meta.url), "utf8"); const codeBlock = readFileSync(new URL("../src/components/markdown-code-block.tsx", import.meta.url), "utf8"); test("a fenced code block scrolls inside itself instead of spilling out of the message", () => { // There was no rule for fenced blocks at all. A bare
 keeps
  // `white-space: pre` with no overflow, so one long line pushed past the
  // message column; the fix has to keep both properties together.
  const body = cssDeclarations(".markdown-code pre > code", globalStyles);
  assert.match(body, /overflow-x:\s*auto/);
  assert.match(body, /white-space:\s*pre\b/);
  assert.match(cssDeclarations(".markdown-code", globalStyles), /overflow:\s*hidden/);
});

test("the block resets the inline-code chip styling", () => {
  // `.message-markdown code` paints a small chip for inline code. Inside a
  // fence that chip has to go, or every block gets a nested background.
  assert.match(cssDeclarations(".markdown-code pre > code", globalStyles), /background:\s*none/);
  assert.match(cssDeclarations(".markdown-code pre", globalStyles), /background:\s*none/);
});

test("react-markdown routes fences through the block, not a bare pre", () => {
  assert.match(markdownView, /pre: \(\{ children \}\) => /);
});

test("the copy control is labelled and the language is announced as text", () => {
  assert.match(codeBlock, /aria-label=\{copied \? "已复制代码" : "复制代码"\}/);
  assert.match(codeBlock, /language \|\| "代码"/);
  // The copy target is the raw fence source, not rendered markup.
  assert.match(codeBlock, /navigator\.clipboard\.writeText\(source\)/);
  // A rejected clipboard permission must not flip the button to "copied".
  assert.match(codeBlock, /catch \{\s*return;\s*\}/);
});

test("the copy timer is cleared when the block unmounts", () => {
  // Streaming replaces these blocks constantly; a dangling timer would set
  // state on an unmounted node.
  assert.match(codeBlock, /useEffect\(\(\) => \(\) => \{[\s\S]*clearTimeout\(resetTimer\.current\)/);
});