Files
Jyotisha/frontend/tests/rectification-timeline-adapter.test.ts
T
Jesse_ChenandClaude Opus 5.5 408b22749e fix(rectification): settled replies carry no 「已完成 N 步」 receipt; steps stay the waiting state (BUG-1137)
Consultation replies keep their receipt (scope-lock test). Two source/DOM
assertions updated with original/new/reason notes.

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

188 lines
9.7 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";
import {
completeActivityTraceStep,
emptyActivityTrace,
startActivityTraceStep,
} from "../src/lib/agent-activity-trace.ts";
import {
RECTIFICATION_TIMELINE_LIVE_ID,
RECTIFICATION_TIMELINE_SOURCE_LIMIT,
rectificationTimelineRows,
} from "../src/lib/rectification-timeline-adapter.ts";
import { RECTIFICATION_ANALYZING_LIVE_LABEL } from "../src/lib/rectification-activity-labels.ts";
import { PUBLIC_RECTIFICATION_METHODS } from "../src/lib/rectification-agentic/v9/public-receipt.ts";
import { rectificationChatSurface } from "./rectification-chat-surface.ts";
const read = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
const chatSource = rectificationChatSurface;
const entrySource = read("src/components/rectification-message-entry.tsx");
const rowSource = read("src/components/chat-message-row.tsx");
const activitySource = read("src/components/agent-activity-status.tsx");
const packageJson = read("package.json");
const globalStyles = read("src/app/globals.css");
test("a started tool is a live calculate row and a completed tool a done row with the done label", () => {
const started = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
assert.deepEqual(rectificationTimelineRows({ trace: started, receipt: undefined, activity: undefined, settled: false }), [
{ id: "activity-rectification-read-case-1", kind: "calculate", status: "live", label: "正在读取校正记录…" },
]);
const done = completeActivityTraceStep(started, "rectification-read-case", "读取校正记录");
assert.deepEqual(rectificationTimelineRows({
trace: done,
receipt: { steps: ["rectification-read-case"], methods: [] },
activity: undefined,
settled: true,
}), [
{ id: "activity-rectification-read-case-1", kind: "calculate", status: "done", label: "读取校正记录" },
]);
});
test("a failed tool keeps its row with a 未完成 suffix and never carries the method chips", () => {
let trace = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
trace = completeActivityTraceStep(trace, "rectification-read-case", "读取校正记录");
trace = startActivityTraceStep(trace, "rectification-compare-candidates", "正在比较候选时间…", 2);
trace = completeActivityTraceStep(trace, "rectification-compare-candidates", "比较候选时间");
const rows = rectificationTimelineRows({
trace,
receipt: { steps: ["rectification-read-case"], methods: ["d1-rashi"], failedTool: "rectification-compare-candidates" },
activity: undefined,
settled: true,
});
assert.equal(rows.length, 2);
// 原值: "比较候选时间未完成"(完成名 + 后缀)/ 新值: "重新对照盘面未完成"(进行中句去掉「正在…」+ 后缀)
// 原因: BUG-1050 完成名改成过去式大白话(「重新对照了盘面」),直接加「未完成」自相矛盾
assert.equal(rows[1]?.label, "重新对照盘面未完成");
assert.equal(rows[1]?.sources, undefined);
assert.deepEqual(rows[0]?.sources, ["D1 本命盘"]);
});
test("method chips are de-duplicated, labelled, capped, and attached to the last completed row", () => {
let trace = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
trace = completeActivityTraceStep(trace, "rectification-read-case", "读取校正记录");
trace = startActivityTraceStep(trace, "rectification-compare-candidates", "正在比较候选时间…", 2);
trace = completeActivityTraceStep(trace, "rectification-compare-candidates", "比较候选时间");
const rows = rectificationTimelineRows({
trace,
receipt: {
steps: ["rectification-read-case", "rectification-compare-candidates"],
methods: [...PUBLIC_RECTIFICATION_METHODS, ...PUBLIC_RECTIFICATION_METHODS],
},
activity: undefined,
settled: true,
});
assert.equal(rows[0]?.sources, undefined);
const chips = rows[1]?.sources ?? [];
assert.equal(chips.length, RECTIFICATION_TIMELINE_SOURCE_LIMIT);
assert.equal(new Set(chips).size, chips.length);
for (const chip of chips) assert.doesNotMatch(chip, /^d\d+-|-/);
});
test("a phase label that names work under way becomes the live row when no tool is live", () => {
const queued = rectificationTimelineRows({
trace: emptyActivityTrace(),
receipt: undefined,
activity: { phase: "evidence-validation", label: "正在处理…" },
settled: false,
});
assert.deepEqual(queued, [{ id: RECTIFICATION_TIMELINE_LIVE_ID, kind: "calculate", status: "live", label: "正在处理…" }]);
const composing = rectificationTimelineRows({
trace: emptyActivityTrace(),
receipt: undefined,
activity: { phase: "answer-composition", label: "正在组织回答…" },
settled: false,
});
assert.equal(composing[0]?.kind, "write");
// A done label left on the activity after a tool completed is not a live step
// when there is no completed trail yet.
const doneLabel = rectificationTimelineRows({
trace: emptyActivityTrace(),
receipt: undefined,
activity: { phase: "chart-calculation", label: "比较候选时间" },
settled: false,
});
assert.deepEqual(doneLabel, []);
// A live tool row already covers the activity, so nothing is appended.
const live = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
assert.equal(rectificationTimelineRows({
trace: live,
receipt: undefined,
activity: { phase: "loading-method", label: "正在读取校正记录…" },
settled: false,
}).length, 1);
});
test("a completed collect tool keeps a live analyzing row until the next tool or answer", () => {
let trace = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
trace = completeActivityTraceStep(trace, "rectification-read-case", "读取校正记录");
const rows = rectificationTimelineRows({
trace,
receipt: { steps: ["rectification-read-case"], methods: [] },
activity: { phase: "loading-method", label: "读取校正记录" },
settled: false,
});
assert.equal(rows.length, 2);
assert.equal(rows[0]?.status, "done");
assert.equal(rows[0]?.label, "读取校正记录");
assert.deepEqual(rows[1], {
id: RECTIFICATION_TIMELINE_LIVE_ID,
kind: "think",
status: "live",
label: RECTIFICATION_ANALYZING_LIVE_LABEL,
});
const withProgress = rectificationTimelineRows({
trace,
receipt: { steps: ["rectification-read-case"], methods: [] },
activity: { phase: "evidence-validation", label: RECTIFICATION_ANALYZING_LIVE_LABEL },
settled: false,
});
assert.equal(withProgress[1]?.kind, "think");
assert.equal(withProgress[1]?.label, RECTIFICATION_ANALYZING_LIVE_LABEL);
assert.equal(rectificationTimelineRows({
trace,
receipt: { steps: ["rectification-read-case"], methods: [] },
activity: { phase: "evidence-validation", label: RECTIFICATION_ANALYZING_LIVE_LABEL },
settled: true,
}).length, 1);
});
test("after attempt.reset the timeline is empty, and a settled reply without a receipt renders no rows", () => {
assert.deepEqual(rectificationTimelineRows({ trace: emptyActivityTrace(), receipt: undefined, activity: undefined, settled: false }), []);
assert.deepEqual(rectificationTimelineRows({ trace: undefined, receipt: { steps: [], methods: [] }, activity: undefined, settled: true }), []);
});
test("both chat surfaces render one timeline, one live marker, and no second receipt block", () => {
// 原值: 容器源码直接 `timeline: rectificationTimelineRows(...)`。
// 新值: 同一调用在行组件内;容器不得再每帧派生。
// 原因: BUG-725 已结算行记忆化。
assert.match(entrySource, /rectificationTimelineRows\(\{/);
// 原值: /timeline: rectificationTimelineRows/(结算后也派生步骤行)。
// 新值: /timeline: settled \? \[\] : rectificationTimelineRows/。
// 原因: BUG-1137(TASK-rectification-message-cleanup-20261001 D3)——校正回复结算后不再显示「已完成 N 步」,生成中照旧。
assert.match(entrySource, /timeline: settled \? \[\] : rectificationTimelineRows/);
assert.doesNotMatch(chatSource, /rectificationTimelineRows\s*\(/);
assert.doesNotMatch(chatSource, /rectification-activity-failure|<CompletedActivityReceipt/);
assert.doesNotMatch(chatSource, /label: "正在组织回答…",\s*\}\),\s*\}\s*:\s*message;/);
assert.match(chatSource, /rememberLiveActivity\(RECTIFICATION_ANALYZING_LIVE_LABEL, null\)/);
// 原值: 停止按钮挂在采集输入区
// 新值: composer-wrap 不再挂 collect-stop;选择题卡仍可先这样
// 原因: BUG-595 决策 2
assert.doesNotMatch(chatSource, /rectification-collect-stop/);
assert.doesNotMatch(rowSource, /ConsultationThinkingReport|showReport|activityTrace/);
assert.doesNotMatch(activitySource, /thinking-orbs|ThinkingOrb|\bTraceStep\b|activityTrace/);
assert.match(activitySource, /<InlineSpinner size=\{12\} \/>/);
assert.doesNotMatch(packageJson, /thinking-orbs/);
assert.doesNotMatch(globalStyles, /\.conversation\.is-rectification \.message-assistant \.agent-thinking-(step|marker)/);
assert.doesNotMatch(globalStyles, /rectification-activity-receipt|rectification-activity-failure|consultation-step-tree/);
assert.doesNotMatch(globalStyles, /rectification-collect-stop/);
});