import React, { act } from "react"; (globalThis as { React?: typeof React }).React = React; import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { renderToStaticMarkup } from "react-dom/server"; import { createRoot } from "react-dom/client"; import { parseHTML } from "linkedom"; import { MessageResponse, ToolContentResponse } from "../components/ai-elements/message.tsx"; import { Reasoning, ReasoningContent } from "../components/ai-elements/reasoning.tsx"; import { transformMathDelimiters } from "../src/core/math-delims.ts"; const surfaces = { message: (source: string) => {source}, tool: (source: string) => {source}, reasoning: (source: string) => {source}, }; const documentFor = (element: React.ReactElement) => parseHTML(`${renderToStaticMarkup(element)}`).document; const annotations = (root: ParentNode) => [...root.querySelectorAll('annotation[encoding="application/x-tex"]')].map(node => node.textContent); // PR266 identified the less-than + letter trigger. Test the actual streaming // render seam, not a TeX-space workaround: valid TeX must remain byte-identical. for (const [name, surface] of Object.entries(surfaces)) describe(`streaming math — ${name}`, () => { for (const [source, tex] of [ ["$a { const document = documentFor(surface(`${source}\n\nTAIL`)); assert.deepEqual(annotations(document), [tex]); assert.ok(document.body.textContent.includes("TAIL")); assert.ok(!document.body.textContent.includes("[blocked]")); assert.equal(document.querySelectorAll(".katex-error").length, 0); }); it("retains probability brackets, both formulas, and the following question", () => { const source = String.raw`$F_X(2)=0.60$이라고 하자. $P[-1 { for (const source of ["$a { for (const source of ["`$a { const partial = documentFor(surface("before bold TAIL")); assert.equal(complete.querySelector("b")?.textContent, "bold"); assert.equal(complete.body.textContent, "before bold TAIL"); }); it("keeps the other incomplete-Markdown repairs", () => { const bold = documentFor(surface("**bold TAIL")); assert.equal(bold.querySelector("strong")?.textContent, "bold TAIL"); const link = documentFor(surface("[partial TAIL")); assert.ok(link.body.textContent.includes("partial TAIL")); assert.ok(link.body.textContent.includes("[blocked]"), "the incomplete-link placeholder is still hardened"); }); it("still removes script/event attributes and blocks javascript links", () => { const document = documentFor(surface('before bold [unsafe](javascript:alert) TAIL')); assert.equal(document.querySelectorAll("script, [onclick]").length, 0); assert.ok(![...document.querySelectorAll("[href]")].some(node => node.getAttribute("href")?.startsWith("javascript:"))); assert.ok(!document.body.textContent.includes("alert(1)")); assert.ok(document.body.textContent.includes("TAIL")); assert.ok(document.body.textContent.includes("[blocked]")); }); }); it("preserves native TeX source in both streaming and static modes", () => { const source = String.raw`$a{source}); const staticDocument = documentFor({source}); assert.deepEqual(annotations(streaming), ["a node.innerHTML), [...staticDocument.querySelectorAll(".katex-html")].map(node => node.innerHTML)); }); // Exercise successive updates on the SAME mounted components, rather than // mistaking independent server renders for incremental streaming coverage. it("retains less-than prefixes through mounted streaming updates on every entry point", async () => { const dom = parseHTML(""); // linkedom has no layout/animation engine; Radix's open reasoning panel // only needs these inert browser hooks. This test checks text, not layout. const globals = { window: dom.window, document: dom.document, Element: dom.window.Element, HTMLElement: dom.window.HTMLElement, IS_REACT_ACT_ENVIRONMENT: true, getComputedStyle: () => ({ display: "block", animationName: "none" }), requestAnimationFrame: (callback: FrameRequestCallback) => setTimeout(() => callback(0), 0), cancelAnimationFrame: (handle: ReturnType) => clearTimeout(handle) }; const previous = Object.getOwnPropertyDescriptors(globalThis); Object.assign(globalThis, globals); try { for (const [name, surface] of Object.entries(surfaces)) { const host = document.createElement("div"); document.body.append(host); const root = createRoot(host); try { for (const source of ["$a", "$a<", "$a root.render(surface(source))); if (source.includes(" root.unmount()); host.remove(); } } } finally { for (const key of Object.keys(globals)) { if (previous[key]) Object.defineProperty(globalThis, key, previous[key]); else Reflect.deleteProperty(globalThis, key); } } });