Files
Jyotisha/frontend/tests/chat-answer-detail.test.ts
T
jesse-ux 58ccafb67c
Independent Staging Quality Gate / validate (push) Failing after 6m18s
Independent Staging Quality Gate / publish (push) Skipped
fix(rectification): stop re-parsing settled messages on every stream frame
2026-09-16 07:13:12 +08:00

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, /正在加载/);
});