| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- import { fireEvent, render, screen } from "@testing-library/react";
- import { describe, expect, it, vi } from "vitest";
- import { Inspector } from "@/components/inspector/Inspector";
- import { finalResultItem, storyItem } from "@/lib/layout";
- import { makeStory, makeView } from "@/tests/fixtures";
- describe("Inspector information layers", () => {
- it("keeps the readable Inspector as the default business surface and opens lineage separately", () => {
- const item = storyItem(makeStory("objective", "objective", "decision", { title: "创作目标", detailRef: "run:objective" }));
- const onModeChange = vi.fn();
- render(<Inspector
- item={item}
- mode="business"
- onModeChange={onModeChange}
- onClose={vi.fn()}
- detail={{
- detailKind: "agent-decision",
- question: "这次构建要形成什么创作方向?",
- blocks: [{ type: "summary", title: "当前保存的创作方向", value: "把毛驴效应讲清楚,并给出可执行建议。" }],
- technical: { source: "database" },
- }}
- />);
- expect(screen.getByText("这次构建要形成什么创作方向?")).toBeInTheDocument();
- expect(screen.getByText("把毛驴效应讲清楚,并给出可执行建议。")).toBeInTheDocument();
- expect(screen.queryByText("数据依据")).not.toBeInTheDocument();
- fireEvent.click(screen.getByRole("button", { name: "打开数据来源" }));
- expect(onModeChange).toHaveBeenCalledWith("lineage");
- });
- it("renders planning analysis as a real pre-build activity with complete prose", () => {
- const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution", {
- title: "创作规划解析",
- detailRef: "event:3760",
- }));
- const fullThought = "账号结构模式与领域准确性下限已经完成分析。".repeat(20);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- businessSections: [
- { id: "summary", title: "规划概要", content: "首轮竞争不同的段落骨架。", variant: "summary", visualRole: "key-result" },
- { id: "thought", title: "完整思考", content: fullThought, variant: "report", visualRole: "section" },
- { id: "plan", title: "执行计划", content: "保存创作目标,然后开启首轮。", visualRole: "section" },
- { id: "action", title: "下一步", content: "写入创作总目标", visualRole: "reason" },
- ],
- technical: { event: { id: 3760, event_name: "think_and_plan" } },
- }} />);
- expect(screen.getByText("构建开始前的规划")).toBeInTheDocument();
- expect(screen.getByText("创作规划解析")).toBeInTheDocument();
- expect(screen.getByText(fullThought)).toBeInTheDocument();
- expect(screen.getByText("写入创作总目标")).toBeInTheDocument();
- });
- it("losslessly breaks a consecutively numbered execution plan into readable lines", () => {
- const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution", {
- title: "创作规划解析",
- detailRef: "event:4565",
- }));
- const plan = "1)save_script_direction 2)begin_round首轮:立段落结构 3)后续轮次填充元素、金句、行动指南";
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- businessSections: [
- { id: "plan", title: "执行计划", content: plan, visualRole: "section" },
- ],
- technical: {},
- }} />);
- const rendered = container.querySelector(".inspectorPanel .richText");
- expect(rendered).toHaveTextContent("1)save_script_direction 2)begin_round首轮:立段落结构 3)后续轮次填充元素、金句、行动指南");
- expect(rendered?.querySelectorAll("br")).toHaveLength(2);
- });
- it("keeps an unnumbered execution plan unchanged", () => {
- const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution"));
- const plan = "准备阶段:写入总目标后进入轮次循环,首轮先立段落结构。";
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- businessSections: [{ id: "plan", title: "执行计划", content: plan }],
- technical: {},
- }} />);
- expect(screen.getByText(plan)).toBeInTheDocument();
- expect(container.querySelector(".inspectorPanel .richText br")).toBeNull();
- });
- it("renders optional implementation-task sections with semantic hierarchy", () => {
- const item = storyItem(makeStory("round:2:branch:3:task", "branch-task", "execution", {
- title: "实现任务",
- }), 2, 3);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- businessSections: [
- { id: "task-scope-1", title: "实现范围", content: "五段骨架", visualRole: "section" },
- { id: "task-goal-2", title: "目标", content: "补齐每段文字。", variant: "summary", visualRole: "key-result" },
- { id: "task-materials-3", title: "必用素材(已核实)", content: "- 领域事实 A", variant: "report", visualRole: "evidence", presentation: "document" },
- { id: "task-avoid-4", title: "需要避免", content: "不得改动原文。", variant: "report", visualRole: "notice", presentation: "document" },
- ],
- technical: {},
- }} />);
- expect(screen.getByRole("heading", { name: "实现范围" }).closest("section")).toHaveAttribute("data-visual-role", "section");
- expect(screen.getByRole("heading", { name: "目标" }).closest("section")).toHaveAttribute("data-visual-role", "key-result");
- expect(screen.getByRole("heading", { name: "必用素材(已核实)" }).closest("section")).toHaveAttribute("data-visual-role", "evidence");
- expect(screen.getByRole("heading", { name: "需要避免" }).closest("section")).toHaveAttribute("data-visual-role", "notice");
- expect(screen.queryAllByRole("heading", { name: "实现任务" })).toHaveLength(1);
- });
- it("keeps business prose separate from technical records", () => {
- const item = storyItem(makeStory("evaluation", "multipath-review"), 1);
- render(<Inspector
- item={item}
- tab="business"
- onTab={vi.fn()}
- onClose={vi.fn()}
- detail={{
- businessSections: [
- { id: "conclusion", title: "评审结论", content: "部分通过", variant: "summary" },
- { id: "report", title: "评审说明", content: "- 结构清晰\n- 仍需补足正文", variant: "report" },
- ],
- technical: { event_name: "script_multipath_evaluator", input_preview: "raw-json" },
- }}
- />);
- expect(screen.getByText("部分通过")).toBeInTheDocument();
- expect(screen.getByText("结构清晰")).toBeInTheDocument();
- expect(screen.queryByText(/script_multipath_evaluator/)).not.toBeInTheDocument();
- expect(screen.queryByText("运行记录关联")).not.toBeInTheDocument();
- });
- it("shows raw fields only after opening technical records", () => {
- const onTab = vi.fn();
- const item = storyItem(makeStory("evaluation", "multipath-review"), 1);
- const detail = {
- businessSections: [{ id: "conclusion", title: "评审结论", content: "通过", variant: "summary" as const }],
- technical: { event_name: "script_multipath_evaluator" },
- };
- const { rerender } = render(<Inspector item={item} tab="business" onTab={onTab} onClose={vi.fn()} detail={detail} />);
- fireEvent.click(screen.getByRole("tab", { name: "技术详情" }));
- expect(onTab).toHaveBeenCalledWith("technical");
- rerender(<Inspector item={item} tab="technical" onTab={onTab} onClose={vi.fn()} detail={detail} />);
- expect(screen.getByText("原始记录")).toBeInTheDocument();
- expect(screen.getByText(/script_multipath_evaluator/)).toBeInTheDocument();
- });
- it("prefers structured business items over a large markdown fallback", () => {
- const item = storyItem(makeStory("objective", "objective"), 1);
- render(<Inspector
- item={item}
- tab="business"
- onTab={vi.fn()}
- onClose={vi.fn()}
- detail={{
- businessSections: [{
- id: "inputs",
- title: "决策前读取",
- content: "这段巨型 Markdown 不应在存在结构化列表时展示",
- items: [
- { label: "选题", value: "米豆腐炒肉", detailRef: "event:2146" },
- { label: "账号段落模式", value: "成品展示 → 食材与热量 → 教学步骤", sourceNotice: "record-missing" },
- ],
- }],
- technical: { event_ids: [2146] },
- }}
- />);
- expect(screen.getByText("选题")).toBeInTheDocument();
- expect(screen.getByText("米豆腐炒肉")).toBeInTheDocument();
- expect(screen.getByText("账号段落模式")).toBeInTheDocument();
- expect(screen.getByText("记录缺失")).toBeInTheDocument();
- expect(screen.queryByText(/巨型 Markdown/)).not.toBeInTheDocument();
- expect(screen.queryByText("event:2146")).not.toBeInTheDocument();
- });
- it("keeps the full build summary when final artifact details load", () => {
- const view = makeView();
- view.finalResult.summary = "构建总结全文:脚本已经完成结构与表达检查。";
- render(<Inspector
- item={finalResultItem(view)}
- tab="business"
- onTab={vi.fn()}
- onClose={vi.fn()}
- detail={{
- businessSections: [
- { id: "build-summary", title: "构建总结", content: "构建总结全文:脚本已经完成结构与表达检查。" },
- { id: "summary", title: "主脚本规模", content: "3 个段落、5 个元素、4 条关联" },
- ],
- changes: { summary: "当前主脚本内容", sections: [], exactness: "current-only" },
- technical: { source: "database" },
- }}
- />);
- expect(screen.getByText("构建总结全文:脚本已经完成结构与表达检查。")).toBeInTheDocument();
- expect(screen.getByText("3 个段落、5 个元素、4 条关联")).toBeInTheDocument();
- });
- it("renders V8 decision blocks without leaking technical fields into business detail", () => {
- const story = makeStory("evaluation", "round-evaluation");
- const item = storyItem(story, 1);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "evaluation",
- actor: { role: "overall-evaluator", label: "整体评审 Agent" },
- authority: "recommendation",
- question: "当前主脚本是否达到目标?",
- blocks: [
- { type: "summary", title: "总结论", value: "部分通过" },
- { type: "items", title: "问题", items: ["结尾仍需补充行动建议"] },
- ],
- technical: { source: { table: "script_build_event", eventId: 41 } },
- }} />);
- expect(screen.getByText("部分通过")).toBeInTheDocument();
- expect(screen.getByText("结尾仍需补充行动建议")).toBeInTheDocument();
- expect(screen.queryByText(/script_build_event|eventId/)).not.toBeInTheDocument();
- });
- it("keeps arrows and sentence punctuation in the original prose", () => {
- const item = storyItem(makeStory("objective", "objective"), 1);
- const prose = "概念引入→拆解分析→归纳提炼→行动指南。第二句仍属于同一段。";
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "direction",
- blocks: [{ type: "summary", title: "明确理由", value: prose }],
- technical: {},
- }} />);
- expect(screen.getByText(prose)).toBeInTheDocument();
- expect(container.querySelector(".decisionFlowList")).toBeNull();
- expect(container.querySelectorAll(".decisionReadableText p")).toHaveLength(1);
- });
- it("only inserts line breaks and indentation around an existing numbered sequence", () => {
- const item = storyItem(makeStory("objective", "objective"), 1);
- const prose = "根据原有信息形成五个部分。 1. 原文第一项; 2. 原文第二项; 3. 原文第三项。";
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "direction",
- blocks: [{ type: "summary", title: "明确理由", value: prose }],
- technical: {},
- }} />);
- expect(screen.getByText("根据原有信息形成五个部分。")).toBeInTheDocument();
- expect(screen.getByText("原文第一项;")).toBeInTheDocument();
- expect(screen.getByText("原文第二项;")).toBeInTheDocument();
- expect(screen.getByText("原文第三项。")).toBeInTheDocument();
- expect(container.querySelectorAll(".decisionReadableText ol > li")).toHaveLength(3);
- });
- it("groups creative steps and multipath evaluation content by its real semantic owner", () => {
- const item = storyItem(makeStory("creative", "candidate-output"), 1, 2);
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "creative",
- blocks: [
- {
- type: "creative-process",
- title: "创作处理",
- steps: [{ stepIndex: 4, action: "新增脚本段落", summary: "起草四元组并开始创建段落骨架。", plan: "1. 创建段落 1:封面导引\n2. 创建段落 2:概念定义", reasoning: "输入依据:账号标准科普模式。" }],
- },
- {
- type: "evaluation-branches",
- title: "逐方案评审",
- branches: [
- { subject: "方案 1", conclusion: "部分通过", achievements: ["领域信息库新增 5 条事实(id 54-58)。"], problems: ["元素创建尚未完整落地。"], evidence: "来源可指位。" },
- { subject: "方案 2", conclusion: "部分通过", achievements: ["已搭建五段骨架。"], problems: ["数量红线违规。"] },
- ],
- },
- {
- type: "evaluation-matrix",
- title: "评审标准与方案对比",
- rows: [{ criterion: "结构完备性", candidates: [{ subject: "方案 1", value: "不适用" }, { subject: "方案 2", value: "部分通过" }], basis: "仅方案二涉及" }],
- },
- ],
- technical: {},
- }} />);
- expect(screen.queryByText("6 个真实执行步骤")).not.toBeInTheDocument();
- expect(screen.getByText("1 个真实执行步骤")).toBeInTheDocument();
- expect(screen.getByText("新增脚本段落")).toBeInTheDocument();
- expect(screen.getByText("创建段落 1:封面导引")).toBeInTheDocument();
- expect(screen.getByText("领域信息库新增 5 条事实(id 54-58)。")).toBeInTheDocument();
- expect(screen.getByText("数量红线违规。")).toBeInTheDocument();
- expect(screen.getByRole("rowheader", { name: "结构完备性" })).toBeInTheDocument();
- expect(screen.getByRole("cell", { name: "仅方案二涉及" })).toBeInTheDocument();
- expect(container.querySelectorAll(".evaluationBranchList > article")).toHaveLength(2);
- });
- it("renders the complete saved script direction as its original hierarchy", () => {
- const item = storyItem(makeStory("objective", "objective"), 1);
- const sourceDocument = `## 选题价值主张
- 用经典心理学效应帮助读者理解决策瘫痪,并给出**"先行动再调整"**。
- ## 创作总目标
- ### 目标 1
- - 目标语句:把毛驴效应科普清楚。
- - 评估维度:
- - name:科普清晰度
- - 通过标准:普通读者能准确理解定义与来源。`;
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "direction",
- blocks: [
- { type: "summary", title: "当前保存的创作方向", value: sourceDocument, visualRole: "key-result", presentation: "document" },
- ],
- technical: {},
- }} />);
- expect(screen.getByRole("heading", { name: "选题价值主张" })).toBeInTheDocument();
- expect(screen.getByRole("heading", { name: "创作总目标" })).toBeInTheDocument();
- expect(screen.getByRole("heading", { name: "目标 1" })).toBeInTheDocument();
- expect(screen.getByText("通过标准:普通读者能准确理解定义与来源。")).toBeInTheDocument();
- expect(screen.getByText("先行动再调整").tagName).toBe("STRONG");
- expect(screen.queryByText(/\*\*"先行动再调整"\*\*/)).not.toBeInTheDocument();
- expect(container.querySelector('[data-presentation="document"]')).toBeInTheDocument();
- });
- it("renders the complete round goal without inferred sentence roles", () => {
- const item = storyItem(makeStory("goal", "goal"), 1);
- const completeGoal = "立起脚本的段落骨架。竞争不同的框架切分策略。本轮只做段落结构,不填充文案。";
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "direction",
- blocks: [{
- type: "summary",
- title: "本轮目标构成",
- visualRole: "key-result",
- presentation: "document",
- value: completeGoal,
- }],
- technical: {},
- }} />);
- expect(screen.getByText(completeGoal)).toBeInTheDocument();
- expect(screen.queryByText("核心目标")).not.toBeInTheDocument();
- expect(screen.queryByText("实现策略")).not.toBeInTheDocument();
- expect(screen.queryByText("范围边界")).not.toBeInTheDocument();
- });
- it("renders implementation plans as structured routes with collapsible evidence", () => {
- const item = storyItem(makeStory("plan", "plan"), 4);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "direction",
- actor: { role: "main", label: "主 Agent" },
- authority: "final",
- question: "本轮应如何组织实现方案?",
- blocks: [
- {
- type: "implementation-plan",
- title: "本轮实施路线",
- visualRole: "section",
- presentation: "plan",
- mode: "单路",
- reasoning: "这是收敛阶段的定点修复,不需要竞争。",
- routes: [{ pathIndex: 1, pathType: "内容", action: "改", target: "段落 2207、2208 的形式层", method: "产生元素", emphasis: "结构完整性" }],
- },
- { type: "items", title: "根据以下信息做出的决策", visualRole: "evidence", presentation: "list", collapsible: false, items: [{ label: "本轮目标", value: "完整目标内容", note: "持续约束;本轮必须遵守" }] },
- ],
- technical: {},
- }} />);
- expect(screen.getByText("本轮实施路线")).toBeInTheDocument();
- expect(screen.getByText("路线 1")).toBeInTheDocument();
- expect(screen.getByText("段落 2207、2208 的形式层")).toBeInTheDocument();
- expect(screen.getByText("产生元素")).toBeInTheDocument();
- expect(screen.getByText("结构完整性")).toBeInTheDocument();
- expect(screen.getByText("为什么这样规划")).toBeInTheDocument();
- expect(screen.getByText("根据以下信息做出的决策")).toBeInTheDocument();
- expect(screen.getByText("完整目标内容")).toBeVisible();
- });
- it("uses semantic roles instead of Chinese titles for hierarchy and disclosure", () => {
- const item = storyItem(makeStory("tradeoff", "data-decision"), 1);
- const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "tradeoff",
- blocks: [
- { type: "items", title: "参考资料", visualRole: "evidence", collapsible: true, items: ["完整依据"] },
- { type: "summary", title: "执行结论", visualRole: "key-result", value: "采用方案 A" },
- ],
- technical: {},
- }} />);
- expect(screen.getByText("完整依据")).not.toBeVisible();
- expect(container.querySelector('[data-visual-role="key-result"]')).toHaveClass("is-role-key-result");
- fireEvent.click(screen.getByText("参考资料"));
- expect(screen.getByText("完整依据")).toBeVisible();
- });
- it("opens the task and rules from inside a decision Inspector", () => {
- const onPrompt = vi.fn();
- const item = storyItem(makeStory("creative", "candidate-output"), 1, 2);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} onPrompt={onPrompt} detail={{
- detailKind: "agent-decision",
- decisionType: "creative",
- actor: { role: "implementer", label: "实现 Agent" },
- authority: "implementation-scope",
- question: "当前实现方案如何形成候选产出?",
- promptRef: "prompt:event:2977",
- blocks: [],
- technical: {},
- }} />);
- fireEvent.click(screen.getByRole("button", { name: "查看实现任务与提示词" }));
- expect(onPrompt).toHaveBeenCalledWith("prompt:event:2977");
- });
- it("does not invent a recommendation-handling column without an explicit record", () => {
- const item = storyItem(makeStory("tradeoff", "multipath-decision"), 1);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "tradeoff",
- blocks: [{
- type: "comparison",
- title: "评审建议与最终决定",
- rows: [{ candidate: "方案 1", recommendation: "建议采用", finalDecision: "已采用" }],
- }],
- technical: {},
- }} />);
- expect(screen.getByText("评审建议")).toBeInTheDocument();
- expect(screen.getByText("最终决定")).toBeInTheDocument();
- expect(screen.queryByText("对建议的处理")).not.toBeInTheDocument();
- expect(screen.queryByText("没有明确记录")).not.toBeInTheDocument();
- });
- it("renders structured evaluator issues once and keeps nested outcomes readable", () => {
- const item = storyItem(makeStory("evaluation", "round-evaluation"), 1);
- render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
- detailKind: "agent-decision",
- decisionType: "evaluation",
- blocks: [{
- type: "items",
- title: "逐项结论",
- items: [{
- summary: "结尾仍需补充行动建议",
- severity: "中",
- achievements: ["主线结构已经清晰"],
- problems: ["结尾行动指引不足"],
- }],
- }],
- technical: {},
- }} />);
- expect(screen.getByText("问题 · 中")).toBeInTheDocument();
- expect(screen.getAllByText("结尾仍需补充行动建议")).toHaveLength(1);
- expect(screen.getByText("已达成")).toBeInTheDocument();
- expect(screen.getByText("主线结构已经清晰")).toBeInTheDocument();
- expect(screen.getByText("需关注")).toBeInTheDocument();
- expect(screen.getByText("结尾行动指引不足")).toBeInTheDocument();
- });
- });
|