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);
}
}
});