import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; const contentSource = readFileSync(new URL("../src/components/chat-message-content.tsx", import.meta.url), "utf8"); const disclosureSource = readFileSync(new URL("../src/components/chat-answer-detail.tsx", import.meta.url), "utf8"); const transcriptSource = readFileSync(new URL("../src/components/chat-transcript.tsx", import.meta.url), "utf8"); const onboardingSource = readFileSync(new URL("../src/components/onboarding-chat-message.tsx", import.meta.url), "utf8"); const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); test("the answer fold is a closed details element with a chapter caption", () => { assert.match(disclosureSource, /
/); assert.doesNotMatch(disclosureSource, /]*\sopen/); assert.match(disclosureSource, /完整分析/); assert.match(disclosureSource, /answerDetailCaption\(headings\)/); assert.doesNotMatch(disclosureSource, />展开 { assert.match(disclosureSource, /streaming \? "正在写…" : answerDetailCaption\(headings\)/); assert.match(disclosureSource, /\{streaming \? null :
\{children\}<\/div>\}/); assert.doesNotMatch(disclosureSource, /spinner|骨架|正在加载|InlineSpinner|aria-busy/i); assert.match(contentSource, /reportBody = !streaming && report/); }); test("ChatMessageContent splits spoken from report and only folds when a report exists", () => { assert.match(contentSource, /splitSpokenAndReport\(split\.spoken\)/); assert.match(contentSource, /report\s*\n\s*\? \(\s*\n\s* 0 && split\.rows\.length === 0/); }); test("the folded technique audit moves inside the report fold when a report exists", () => { assert.match(contentSource, /
\{renderProse\(report, renderMarkdown\)\}<\/div>/); assert.match(contentSource, /\{foldedAudit\}/); assert.match(contentSource, /const reportBody = !streaming && report/); assert.match(contentSource, /AnswerDetailDisclosure headings=\{layers\.headings\} streaming=\{streaming\}/); assert.match(contentSource, /\{reportBody\}/); }); test("spoken streaming still uses StreamingMarkdown so the memoised prefix stays intact", () => { assert.match(contentSource, /streaming\s*\n\s*\? /); }); test("copy still sends the original message text, not the spoken layer", () => { assert.match(transcriptSource, /onCopy=\{\(\) => actionsRef\.current\.onCopy\(feedbackKey, message\.text\)\}/); }); test("onboarding typewriter messages still go through ChatMessageContent", () => { assert.match(onboardingSource, //); }); test("answer-detail styles are a sibling of technique-audit, not the same class", () => { assert.match(globalStyles, /\.answer-detail \{/); assert.match(globalStyles, /\.answer-detail > summary \{/); assert.match(globalStyles, /\.answer-detail-panel \{/); assert.match(disclosureSource, /className="answer-detail"/); assert.doesNotMatch(disclosureSource, /className="technique-audit"/); }); test("the fold does not introduce a spinner or skeleton class", () => { assert.doesNotMatch(disclosureSource, /spinner|skeleton|starter-loading|InlineSpinner/i); assert.doesNotMatch(contentSource, /正在加载/); assert.doesNotMatch(globalStyles, /\.answer-detail[^{]*spinner/i); }); test("streaming copy is a quiet line, not a loading phrase", () => { assert.match(disclosureSource, /正在写…/); assert.doesNotMatch(disclosureSource, /正在加载/); });