muse-agent-example/web/tests/交互/评测报告.test.tsx
zizi ae02fd1732 前端:工作台页面、事件标签与旅程用例
- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。
- 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。
- 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。
- 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
2026-09-18 01:15:07 +08:00

293 lines
11 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.

/** 组件传输替身只验证显示与切换隔离;PG和HTTP证据由后端用例提供。 */
import { afterEach, expect, it, vi } from "vitest";
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
within,
} from "@testing-library/react";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { Ui依赖装配 } from "../../src/应用/依赖装配";
import { Ui实验报告, Ui实验页 } from "../../src/功能/效果评测/实验页";
import type { 实验报告 } from "../../src/功能/效果评测/数据";
afterEach(() => {
cleanup();
vi.unstubAllGlobals();
});
function 报告(id = "sample-only"): 实验报告 {
return {
schema_version: "evaluation-report-v1",
experiment_id: id,
conditions_hash: "c".repeat(64),
dataset_hash: "d".repeat(64),
plan_hash: "p".repeat(64),
target: {
target_ref: "B05.generate",
version: "test-release",
content_hash: "a".repeat(64),
},
primary_pair: ["control", "treatment"],
adapter: "direct_writer_prompt_v1",
state: "reconciling",
activation_status: "not_evaluated",
evaluation_goal: "diagnostic",
validation_modes: ["offline_contract"],
coverage: { samples: 1, generated: 0, compared: 0 },
outcomes: { incomplete: 1 },
dimensions: { 清晰度: { incomplete: 1 } },
source_groups: { 独立来源: { samples: 1, outcomes: { incomplete: 1 } } },
runtime: { units: 3, states: { failed: 1, completed: 1, not_started: 1 } },
cost: {
known_total_usd: "0.125",
total_usd: null,
has_unknown: true,
has_pending: false,
over_budget: false,
sent_calls: 2,
released_reservations: 0,
},
literary_quality: { calibration: "unverified", metrics: null },
report_hash: "b".repeat(64),
samples: [
{
sample_id: id,
source_groups: ["独立来源"],
generation_complete: false,
comparison_complete: false,
detection_complete: null,
detections: {},
outcome: "incomplete",
comparisons: { "control:treatment": "incomplete" },
dimensions: { 清晰度: "incomplete" },
decisions: [],
units: [],
incomplete_units: ["missing-unit"],
},
],
};
}
// @muse-case {"case_id":"NC-ui-evaluation-report-null","environment":"前端组件,传输替身","given":"实验报告页面","when":"读取或切换实验并核对显示","then":["未知成本、固定覆盖分母和未标定状态准确呈现"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-report-null:未知成本和未标定不显示为零或通过", () => {
render(<Ui实验报告 内容={报告()} />);
expect(screen.getByText("实际总费用").nextElementSibling?.textContent).toBe(
"待核对",
);
expect(screen.getByText("文学指标").nextElementSibling?.textContent).toBe(
"未标定",
);
expect(screen.getByText("正式启用").nextElementSibling?.textContent).toBe(
"尚未评定",
);
expect(screen.getByText("比较齐全").nextElementSibling?.textContent).toBe(
"0 / 1",
);
expect(
screen.getByRole("heading", { name: /sample-only.*比较未完成/ }),
).toBeTruthy();
expect(screen.getByText("比较未完成 1 例")).toBeTruthy();
});
// @muse-case {"case_id":"NC-ui-evaluation-primary-pair","environment":"前端组件,传输替身","given":"ABC逐例报告","when":"呈现主比较与两组诊断结果","then":["明确显示A/C主比较,不以B在其他对照中的胜出覆盖主结果"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-primary-pair:ABC主比较和诊断比较分开显示", () => {
const r = 报告();
r.primary_pair = ["A", "C"];
r.samples[0].comparisons = { "A:B": "B", "A:C": "C", "B:C": "B" };
r.samples[0].outcome = "C";
render(<Ui实验报告 内容={r} />);
expect(screen.getByText("主比较:A / C")).toBeTruthy();
expect(screen.getByText("A / B:B(卡片索引)胜出")).toBeTruthy();
expect(screen.getByText("A / C:C(索引与正文)胜出")).toBeTruthy();
expect(
screen.getByRole("heading", { name: /sample-only.*C(索引与正文)胜出/ }),
).toBeTruthy();
});
// @muse-case {"case_id":"NC-ui-evaluation-report-switch","environment":"前端组件,传输替身","given":"实验报告页面","when":"读取或切换实验并核对显示","then":["查询隔离后迟到响应不覆盖当前实验"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-report-switch:切换实验后迟到报告不能覆盖当前实验", async () => {
let finishA!: (response: Response) => void;
const delayed = new Promise<Response>((resolve) => {
finishA = resolve;
});
const respond = (value: unknown) =>
new Response(JSON.stringify(value), {
headers: { "Content-Type": "application/json" },
});
const transport = vi.fn(async (request: Request) => {
const path = new URL(request.url).pathname;
if (path === "/api/v1/session")
return respond({ author_id: "fixture-author" });
if (path.endsWith("/experiment-a/report")) return delayed;
if (path.endsWith("/experiment-b/report")) return respond(报告("sample-B"));
throw new Error(`未预期请求:${path}`);
});
vi.stubGlobal("fetch", transport);
render(
<Ui依赖装配>
<MemoryRouter initialEntries={["/evaluations/experiment-a"]}>
<Routes>
<Route path="/evaluations/:experiment_id?" element={<Ui实验页 />} />
</Routes>
</MemoryRouter>
</Ui依赖装配>,
);
await waitFor(() =>
expect(
transport.mock.calls.some(([r]) =>
r.url.endsWith("/experiment-a/report"),
),
).toBe(true),
);
fireEvent.change(screen.getByLabelText("实验编号"), {
target: { value: "experiment-b" },
});
fireEvent.click(screen.getByRole("button", { name: "查看报告" }));
await screen.findByRole("heading", { name: /sample-B/ });
finishA(respond(报告("sample-A")));
await waitFor(() =>
expect(screen.queryByRole("heading", { name: /sample-A/ })).toBeNull(),
);
expect(screen.getByRole("heading", { name: /sample-B/ })).toBeTruthy();
});
// @muse-case {"case_id":"NC-ui-evaluation-quotes","environment":"前端组件传输替身","given":"两侧已经反向的定位报告","when":"展开判断理由和引文","then":["使用固定两侧映射显示实际C/A,保留换行;原文中的HTML按文本显示"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-quotes:换序后的真实实验臂与原字引文正确对应", () => {
const r = 报告();
r.samples[0].decisions = [
{
unit_id: "judge-unit",
task_id: "judge-task",
call_id: "judge-call",
output_hash: "raw-hash",
model: "independent-judge",
pair: ["A", "C"],
choice: "C",
dimensions: { 清晰度: "C" },
sides: { left: "C", right: "A" },
judgment_hash: "located-hash",
judgment: {
choice: "left",
rationale: "动作的因果顺序更清楚。",
dimensions: [
{
dimension: "清晰度",
choice: "left",
rationale: "保留两侧原句,便于核查。",
evidence: [
{
side: "left",
quote: "她推门。\n门外有人。",
start: 3,
end: 13,
},
{
side: "right",
quote: "<script>只是原文</script>",
start: 0,
end: 24,
},
],
},
],
},
},
];
render(<Ui实验报告 内容={r} />);
fireEvent.click(screen.getByText(/A \/ C · independent-judge/));
expect(screen.getByText("C 原文").nextElementSibling?.textContent).toBe(
"她推门。\n门外有人。",
);
expect(screen.getByText("A 原文").nextElementSibling?.textContent).toBe(
"<script>只是原文</script>",
);
expect(document.querySelector("script")).toBeNull();
expect(screen.getByText("动作的因果顺序更清楚。")).toBeTruthy();
});
// @muse-case {"case_id":"NC-ui-evaluation-corrections","environment":"前端组件传输替身","given":"已发生拒绝且纠正上限为零的报告","when":"展开执行和费用记录","then":["零上限、拒绝与已发生费用仍显示,不显示为已启用"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-corrections:零纠正上限和已发生拒绝均可见", () => {
const r = 报告();
r.samples[0].units = [
{
unit_id: "failed-judge",
task_id: "task",
state: "failed",
output_hash: null,
correction_limit: 0,
rejections: [
{
sequence: 1,
attempt_id: "attempt",
call_id: "rejected-call",
failure_code: "PAIRWISE_QUOTE_NOT_FOUND",
record_hash: "r".repeat(64),
side_count: 2,
dimension_count: 1,
},
],
calls: [
{
call_id: "rejected-call",
attempt_id: "attempt",
role_id: "judge",
state: "settled",
actual_amount: "0.125",
reserved_amount: "1",
over_budget: false,
},
],
},
];
render(<Ui实验报告 内容={r} />);
fireEvent.click(screen.getByText("执行与费用记录", { exact: true }));
expect(screen.getByText("引文纠正上限:0 次")).toBeTruthy();
expect(screen.getByText("已保留 1 次判断拒绝。")).toBeTruthy();
expect(screen.getByLabelText("引文校验记录").textContent).toContain(
"第 1 次:引文未在原文中出现",
);
expect(
within(screen.getByRole("region", { name: "逐例结果" })).getByText(
/0.125 USD/,
),
).toBeTruthy();
expect(screen.getByText("正式启用").nextElementSibling?.textContent).toBe(
"尚未评定",
);
});
// @muse-case {"case_id":"NC-ui-evaluation-contract-failure","environment":"前端组件传输替身","given":"已持久化的非引文判断合同拒绝","when":"展开失败原因","then":["说明合同不完整且未自动纠正,不误称引文缺失"],"contract":"docs/系统架构/新版设计/模块设计/B10-效果评测.md"}
it("NC-ui-evaluation-contract-failure:非引文拒绝不误报为缺少引文", () => {
const r = 报告();
r.samples[0].units = [
{
unit_id: "contract-failed",
task_id: "task",
state: "failed",
output_hash: null,
correction_limit: 1,
calls: [],
rejections: [
{
sequence: 1,
attempt_id: "attempt",
call_id: "invalid-contract",
record_hash: "a".repeat(64),
failure_code: "PAIRWISE_NOT_EXECUTED",
side_count: 2,
dimension_count: 1,
},
],
},
];
render(<Ui实验报告 内容={r} />);
fireEvent.click(screen.getByText("执行与费用记录", { exact: true }));
expect(screen.getByLabelText("引文校验记录").textContent).toContain(
"判断合同不完整,未自动纠正",
);
expect(screen.queryByText(/引文未在原文中出现/)).toBeNull();
});