Files
Jyotisha/frontend/tests/chat-bundle-splitting-contract.test.ts
T
Jesse_Chen e635c40224
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(consult): fold the technique audit out of the spoken answer
Keep comparative tables in chat, but hide the long audit behind a collapsed control so the reply stays readable.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 15:29:04 +08:00

97 lines
5.4 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { plainParagraphs } from "../src/components/chat-message-paragraphs.ts";
const readProjectFile = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
const contentSource = readProjectFile("src/components/chat-message-content.tsx");
const markdownViewSource = readProjectFile("src/components/chat-markdown-view.tsx");
const messageRowSource = readProjectFile("src/components/chat-message-row.tsx");
const activitySource = readProjectFile("src/components/agent-activity-status.tsx");
const prefetchSource = readProjectFile("src/components/chat-chunk-prefetch.ts");
test("the markdown pipeline is split out of the initial chat bundle", () => {
// Given: the chat surface statically imports the message content component.
assert.doesNotMatch(contentSource, /^import .*"react-markdown"/m);
assert.doesNotMatch(contentSource, /^import .*"remark-gfm"/m);
// Then: the renderer only arrives through a dynamic import.
assert.match(contentSource, /import\("@\/components\/chat-markdown-view"\)/);
assert.match(contentSource, /markdownRenderer = module\.renderChatMarkdown/);
assert.match(markdownViewSource, /^import ReactMarkdown, \{ type Components \} from "react-markdown";$/m);
assert.match(markdownViewSource, /^import remarkGfm from "remark-gfm";$/m);
assert.match(markdownViewSource, /remarkPlugins=\{\[remarkGfm\]\}/);
assert.match(markdownViewSource, /skipHtml/);
});
test("the pre-markdown fallback renders prose but never raw markdown", () => {
// Given: a reply that carries markdown syntax.
assert.equal(plainParagraphs("**重点**在这里"), null);
assert.equal(plainParagraphs("## 阶段判断"), null);
assert.equal(plainParagraphs("- 第一点"), null);
assert.equal(plainParagraphs("1. 第一点"), null);
assert.equal(plainParagraphs("| 宫位 | 行星 |"), null);
assert.equal(plainParagraphs("`Vimshottari`"), null);
assert.equal(plainParagraphs("参见 https://example.com"), null);
assert.equal(plainParagraphs("> 引用"), null);
assert.equal(plainParagraphs("[链接](/x)"), null);
assert.equal(plainParagraphs("行尾软换行 \n下一行"), null);
// Then: only plain prose takes the fallback path, split exactly like CommonMark.
assert.deepEqual(plainParagraphs("第一段。\n\n第二段。"), ["第一段。", "第二段。"]);
assert.deepEqual(plainParagraphs("一行\n下一行"), ["一行\n下一行"]);
assert.deepEqual(plainParagraphs("只有一段。"), ["只有一段。"]);
// And: one stable wrapper serves both paths, so no styling or caret is lost on upgrade.
assert.equal(contentSource.match(/className="message-markdown"/g)?.length, 1);
assert.match(contentSource, /className="message-answer"/);
assert.match(contentSource, /TechniqueAuditDisclosure/);
assert.match(contentSource, /<p key=\{index\}>\{paragraph\}<\/p>/);
assert.match(contentSource, /plainParagraphs\(spoken\) \?\? \[\]/);
});
test("gsap loads on demand while keeping the reduced-motion gate", () => {
// Given: the animation engine is no longer part of the static import graph.
assert.doesNotMatch(messageRowSource, /^import .*from "gsap"/m);
assert.doesNotMatch(messageRowSource, /@gsap\/react/);
assert.match(messageRowSource, /import\("gsap"\)/);
// Then: reduced-motion users never request it, and the tween contract is unchanged.
assert.match(messageRowSource, /prefers-reduced-motion: reduce/);
assert.match(messageRowSource, /prefers-reduced-motion: no-preference/);
assert.match(messageRowSource, /gsap\.matchMedia\(\)/);
assert.match(messageRowSource, /duration: 0\.18/);
assert.match(messageRowSource, /clearProps: "opacity,transform,visibility"/);
// And: a row that mounts before the chunk lands renders unanimated instead of flashing.
assert.match(messageRowSource, /if \(!gsapCore\) \{/);
assert.match(messageRowSource, /useEntryEffect\(\(\) => \{/);
});
test("the thinking orb loads on demand behind a same-size placeholder", () => {
// Given: only the state type is imported at build time.
assert.doesNotMatch(activitySource, /^import \{ ThinkingOrb/m);
assert.match(activitySource, /^import type \{ OrbState \} from "thinking-orbs";$/m);
assert.match(activitySource, /dynamic\(async \(\) => \(await importThinkingOrb\(\)\)\.ThinkingOrb/);
assert.match(activitySource, /ssr: false/);
// Then: the placeholder reserves the exact 20px orb box so the row cannot shift.
assert.match(activitySource, /loading: \(\) => \(/);
assert.match(activitySource, /height: 20, width: 20/);
assert.match(activitySource, /flex: "0 0 auto"/);
// And: the live region and the decorative marking survive.
assert.match(activitySource, /role="status"/);
assert.match(activitySource, /<ThinkingOrb aria-hidden="true" state=\{state\} size=\{20\}/);
});
test("deferred chat chunks are requested during idle time after first paint", () => {
assert.match(prefetchSource, /if \(typeof window === "undefined"\) return;/);
assert.match(prefetchSource, /window\.requestIdleCallback\(request, \{ timeout: 2_000 \}\)/);
assert.match(prefetchSource, /window\.setTimeout\(request, 300\)/);
assert.match(contentSource, /prefetchOnIdle\(loadMarkdownRenderer\)/);
assert.match(activitySource, /prefetchOnIdle\(importThinkingOrb\)/);
assert.match(messageRowSource, /if \(motionPreferred\(\)\) prefetchOnIdle\(loadGsap\)/);
});