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();
});
});