74 lines
4.1 KiB
TypeScript
74 lines
4.1 KiB
TypeScript
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, /<details className="answer-detail">/);
|
|
assert.doesNotMatch(disclosureSource, /<details[^>]*\sopen/);
|
|
assert.match(disclosureSource, /完整分析/);
|
|
assert.match(disclosureSource, /answerDetailCaption\(headings\)/);
|
|
assert.doesNotMatch(disclosureSource, />展开</);
|
|
});
|
|
|
|
test("streaming keeps the fold closed and does not render report children", () => {
|
|
assert.match(disclosureSource, /streaming \? "正在写…" : answerDetailCaption\(headings\)/);
|
|
assert.match(disclosureSource, /\{streaming \? null : <div className="answer-detail-panel">\{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*<AnswerDetailDisclosure/);
|
|
assert.match(contentSource, /: foldedAudit/);
|
|
assert.match(contentSource, /showFoldedAudit = rows\.length > 0 && split\.rows\.length === 0/);
|
|
});
|
|
|
|
test("the folded technique audit moves inside the report fold when a report exists", () => {
|
|
// 原值: `{renderProse(report, renderMarkdown)}` 每次重渲全量 parse。
|
|
// 新值: 报告层走 `StableMarkdownPrefix`。
|
|
// 原因: BUG-725 结算态 Markdown 按文本记忆化。
|
|
assert.match(contentSource, /<div className="message-markdown">\s*<StableMarkdownPrefix text=\{report\} renderMarkdown=\{renderMarkdown\} \/>/);
|
|
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*\? <StreamingMarkdown text=\{spoken\} renderMarkdown=\{renderMarkdown\} \/>/);
|
|
});
|
|
|
|
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, /<ChatMessageContent text=\{protectedVisibleText\} \/>/);
|
|
});
|
|
|
|
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, /正在加载/);
|
|
});
|