Files
Jyotisha/frontend/tests/chat-answer-detail.test.ts
T
Jesse_ChenandClaude Opus 5.5 da226db1d4
Independent Staging Quality Gate / validate (push) Failing after 18m5s
Independent Staging Quality Gate / publish (push) Skipped
fix(consult): blank-line paragraphs, drop the H2 contract line, remove the assistant avatar
Acceptance fix for 28113fa0 (TASK-consult-conversational-answer-fix-20261006):
- F1: chat-answer-detail no longer pins 'Every H2 must start its own line'
  (first turns carry no H2), assertion change noted in three columns.
- F2: Good examples separate paragraphs with a blank line and the shape says
  so; .message p is pre-wrap, so a single newline only wraps.
- Product request: remove the assistant avatar on every surface (consult,
  rectification, onboarding); --assistant-content-inset back to 0px.

Full suite 4941 / fail 24, identical to baseline 86cafe68 minus a timing flake;
build keeps / Static; first-load gzip -0.1%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-06 15:58:40 +08:00

74 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
/**
* 原值:这份文件断言回答的第二层是一个默认收起的 `<details class="answer-detail">`
* 「完整分析」控件(`chat-answer-detail.tsx`),并锁住它的 caption、流式期
* 不渲染子节点、以及它与 technique-audit 是两套样式。
* 新值:控件与组件文件一并删除,报告层直接渲染进正文 `.answer-report-body`。
* 原因:产品在真机走查后拍板——首个 H2 之后的全部内容(也就是带推理的那一半)
* 默认不可见,用户「输出根本看不到」。回答本身就是产品,不该藏在一次点击后面。
* 技法审计表保留它自己的折叠:那是给人核对的证据,不是回复正文。
*
* 下面保留的断言是原文件里与折叠无关、仍然成立的那些(记忆化、复制取原文、
* onboarding 走同一渲染链),并新增两条锁住「不得再把回答折起来」。
*/
const contentSource = readFileSync(new URL("../src/components/chat-message-content.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 report layer renders inline; the answer is never folded away", () => {
assert.match(contentSource, /<div className="answer-report-body">\{reportBody\}<\/div>/);
assert.doesNotMatch(contentSource, /AnswerDetailDisclosure/);
// 只查真正的折叠元素——解释这段历史的注释本身会提到「完整分析」四个字。
assert.doesNotMatch(contentSource, /<details/);
assert.match(globalStyles, /\.answer-report-body \{/);
assert.doesNotMatch(globalStyles, /\.answer-detail\b/);
});
test("the model is told the answer renders inline, so it stops writing for a fold", () => {
const voice = readFileSync(new URL("../src/mastra/product-voice.ts", import.meta.url), "utf8");
assert.match(voice, /renders the whole answer inline/);
assert.doesNotMatch(voice, /folds everything from the first H2/);
// 原值: assert.match(voice, /Every H2 must start its own line/)
// 新值: 不再要求(首轮正文不写 ##,这句没有对象了)
// 原因: TASK-consult-conversational-answer-20261006 D1(BUG-1244);修复单 F1
assert.doesNotMatch(voice, /Every H2 must start its own line/);
});
test("ChatMessageContent still splits spoken from report and keeps the audit fold", () => {
assert.match(contentSource, /splitSpokenAndReport\(split\.spoken\)/);
assert.match(contentSource, /: foldedAudit/);
assert.match(contentSource, /showFoldedAudit = rows\.length > 0 && split\.rows\.length === 0/);
});
test("the settled report layer stays memoised by text", () => {
// 原值/新值不变,原因仍是 BUG-725:结算态 Markdown 按文本记忆化,不每次重 parse。
assert.match(contentSource, /<div className="message-markdown">\s*<StableMarkdownPrefix text=\{report\} renderMarkdown=\{renderMarkdown\} \/>/);
assert.match(contentSource, /const reportBody = !streaming && report/);
});
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("assistant turns carry no avatar; the reply starts at the column edge (2026-10-06)", () => {
const row = readFileSync(new URL("../src/components/chat-message-row.tsx", import.meta.url), "utf8");
const onboarding = readFileSync(new URL("../src/components/onboarding-chat-message.tsx", import.meta.url), "utf8");
for (const source of [row, onboarding, globalStyles]) {
assert.doesNotMatch(source, /AgentAvatar|agent-avatar/);
}
assert.doesNotMatch(globalStyles, /--assistant-content-inset: calc\(32px/);
});