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(); 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(); 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(); 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(); 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(); 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(); 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(); fireEvent.click(screen.getByRole("tab", { name: "技术详情" })); expect(onTab).toHaveBeenCalledWith("technical"); rerender(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); }); });