inspector.test.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import { fireEvent, render, screen } from "@testing-library/react";
  2. import { describe, expect, it, vi } from "vitest";
  3. import { Inspector } from "@/components/inspector/Inspector";
  4. import { finalResultItem, storyItem } from "@/lib/layout";
  5. import { makeStory, makeView } from "@/tests/fixtures";
  6. describe("Inspector information layers", () => {
  7. it("keeps the readable Inspector as the default business surface and opens lineage separately", () => {
  8. const item = storyItem(makeStory("objective", "objective", "decision", { title: "创作目标", detailRef: "run:objective" }));
  9. const onModeChange = vi.fn();
  10. render(<Inspector
  11. item={item}
  12. mode="business"
  13. onModeChange={onModeChange}
  14. onClose={vi.fn()}
  15. detail={{
  16. detailKind: "agent-decision",
  17. question: "这次构建要形成什么创作方向?",
  18. blocks: [{ type: "summary", title: "当前保存的创作方向", value: "把毛驴效应讲清楚,并给出可执行建议。" }],
  19. technical: { source: "database" },
  20. }}
  21. />);
  22. expect(screen.getByText("这次构建要形成什么创作方向?")).toBeInTheDocument();
  23. expect(screen.getByText("把毛驴效应讲清楚,并给出可执行建议。")).toBeInTheDocument();
  24. expect(screen.queryByText("数据依据")).not.toBeInTheDocument();
  25. fireEvent.click(screen.getByRole("button", { name: "打开数据来源" }));
  26. expect(onModeChange).toHaveBeenCalledWith("lineage");
  27. });
  28. it("renders planning analysis as a real pre-build activity with complete prose", () => {
  29. const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution", {
  30. title: "创作规划解析",
  31. detailRef: "event:3760",
  32. }));
  33. const fullThought = "账号结构模式与领域准确性下限已经完成分析。".repeat(20);
  34. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  35. businessSections: [
  36. { id: "summary", title: "规划概要", content: "首轮竞争不同的段落骨架。", variant: "summary", visualRole: "key-result" },
  37. { id: "thought", title: "完整思考", content: fullThought, variant: "report", visualRole: "section" },
  38. { id: "plan", title: "执行计划", content: "保存创作目标,然后开启首轮。", visualRole: "section" },
  39. { id: "action", title: "下一步", content: "写入创作总目标", visualRole: "reason" },
  40. ],
  41. technical: { event: { id: 3760, event_name: "think_and_plan" } },
  42. }} />);
  43. expect(screen.getByText("构建开始前的规划")).toBeInTheDocument();
  44. expect(screen.getByText("创作规划解析")).toBeInTheDocument();
  45. expect(screen.getByText(fullThought)).toBeInTheDocument();
  46. expect(screen.getByText("写入创作总目标")).toBeInTheDocument();
  47. });
  48. it("losslessly breaks a consecutively numbered execution plan into readable lines", () => {
  49. const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution", {
  50. title: "创作规划解析",
  51. detailRef: "event:4565",
  52. }));
  53. const plan = "1)save_script_direction 2)begin_round首轮:立段落结构 3)后续轮次填充元素、金句、行动指南";
  54. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  55. businessSections: [
  56. { id: "plan", title: "执行计划", content: plan, visualRole: "section" },
  57. ],
  58. technical: {},
  59. }} />);
  60. const rendered = container.querySelector(".inspectorPanel .richText");
  61. expect(rendered).toHaveTextContent("1)save_script_direction 2)begin_round首轮:立段落结构 3)后续轮次填充元素、金句、行动指南");
  62. expect(rendered?.querySelectorAll("br")).toHaveLength(2);
  63. });
  64. it("keeps an unnumbered execution plan unchanged", () => {
  65. const item = storyItem(makeStory("run:planning-analysis", "planning-analysis", "execution"));
  66. const plan = "准备阶段:写入总目标后进入轮次循环,首轮先立段落结构。";
  67. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  68. businessSections: [{ id: "plan", title: "执行计划", content: plan }],
  69. technical: {},
  70. }} />);
  71. expect(screen.getByText(plan)).toBeInTheDocument();
  72. expect(container.querySelector(".inspectorPanel .richText br")).toBeNull();
  73. });
  74. it("renders optional implementation-task sections with semantic hierarchy", () => {
  75. const item = storyItem(makeStory("round:2:branch:3:task", "branch-task", "execution", {
  76. title: "实现任务",
  77. }), 2, 3);
  78. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  79. businessSections: [
  80. { id: "task-scope-1", title: "实现范围", content: "五段骨架", visualRole: "section" },
  81. { id: "task-goal-2", title: "目标", content: "补齐每段文字。", variant: "summary", visualRole: "key-result" },
  82. { id: "task-materials-3", title: "必用素材(已核实)", content: "- 领域事实 A", variant: "report", visualRole: "evidence", presentation: "document" },
  83. { id: "task-avoid-4", title: "需要避免", content: "不得改动原文。", variant: "report", visualRole: "notice", presentation: "document" },
  84. ],
  85. technical: {},
  86. }} />);
  87. expect(screen.getByRole("heading", { name: "实现范围" }).closest("section")).toHaveAttribute("data-visual-role", "section");
  88. expect(screen.getByRole("heading", { name: "目标" }).closest("section")).toHaveAttribute("data-visual-role", "key-result");
  89. expect(screen.getByRole("heading", { name: "必用素材(已核实)" }).closest("section")).toHaveAttribute("data-visual-role", "evidence");
  90. expect(screen.getByRole("heading", { name: "需要避免" }).closest("section")).toHaveAttribute("data-visual-role", "notice");
  91. expect(screen.queryAllByRole("heading", { name: "实现任务" })).toHaveLength(1);
  92. });
  93. it("keeps business prose separate from technical records", () => {
  94. const item = storyItem(makeStory("evaluation", "multipath-review"), 1);
  95. render(<Inspector
  96. item={item}
  97. tab="business"
  98. onTab={vi.fn()}
  99. onClose={vi.fn()}
  100. detail={{
  101. businessSections: [
  102. { id: "conclusion", title: "评审结论", content: "部分通过", variant: "summary" },
  103. { id: "report", title: "评审说明", content: "- 结构清晰\n- 仍需补足正文", variant: "report" },
  104. ],
  105. technical: { event_name: "script_multipath_evaluator", input_preview: "raw-json" },
  106. }}
  107. />);
  108. expect(screen.getByText("部分通过")).toBeInTheDocument();
  109. expect(screen.getByText("结构清晰")).toBeInTheDocument();
  110. expect(screen.queryByText(/script_multipath_evaluator/)).not.toBeInTheDocument();
  111. expect(screen.queryByText("运行记录关联")).not.toBeInTheDocument();
  112. });
  113. it("shows raw fields only after opening technical records", () => {
  114. const onTab = vi.fn();
  115. const item = storyItem(makeStory("evaluation", "multipath-review"), 1);
  116. const detail = {
  117. businessSections: [{ id: "conclusion", title: "评审结论", content: "通过", variant: "summary" as const }],
  118. technical: { event_name: "script_multipath_evaluator" },
  119. };
  120. const { rerender } = render(<Inspector item={item} tab="business" onTab={onTab} onClose={vi.fn()} detail={detail} />);
  121. fireEvent.click(screen.getByRole("tab", { name: "技术详情" }));
  122. expect(onTab).toHaveBeenCalledWith("technical");
  123. rerender(<Inspector item={item} tab="technical" onTab={onTab} onClose={vi.fn()} detail={detail} />);
  124. expect(screen.getByText("原始记录")).toBeInTheDocument();
  125. expect(screen.getByText(/script_multipath_evaluator/)).toBeInTheDocument();
  126. });
  127. it("prefers structured business items over a large markdown fallback", () => {
  128. const item = storyItem(makeStory("objective", "objective"), 1);
  129. render(<Inspector
  130. item={item}
  131. tab="business"
  132. onTab={vi.fn()}
  133. onClose={vi.fn()}
  134. detail={{
  135. businessSections: [{
  136. id: "inputs",
  137. title: "决策前读取",
  138. content: "这段巨型 Markdown 不应在存在结构化列表时展示",
  139. items: [
  140. { label: "选题", value: "米豆腐炒肉", detailRef: "event:2146" },
  141. { label: "账号段落模式", value: "成品展示 → 食材与热量 → 教学步骤", sourceNotice: "record-missing" },
  142. ],
  143. }],
  144. technical: { event_ids: [2146] },
  145. }}
  146. />);
  147. expect(screen.getByText("选题")).toBeInTheDocument();
  148. expect(screen.getByText("米豆腐炒肉")).toBeInTheDocument();
  149. expect(screen.getByText("账号段落模式")).toBeInTheDocument();
  150. expect(screen.getByText("记录缺失")).toBeInTheDocument();
  151. expect(screen.queryByText(/巨型 Markdown/)).not.toBeInTheDocument();
  152. expect(screen.queryByText("event:2146")).not.toBeInTheDocument();
  153. });
  154. it("keeps the full build summary when final artifact details load", () => {
  155. const view = makeView();
  156. view.finalResult.summary = "构建总结全文:脚本已经完成结构与表达检查。";
  157. render(<Inspector
  158. item={finalResultItem(view)}
  159. tab="business"
  160. onTab={vi.fn()}
  161. onClose={vi.fn()}
  162. detail={{
  163. businessSections: [
  164. { id: "build-summary", title: "构建总结", content: "构建总结全文:脚本已经完成结构与表达检查。" },
  165. { id: "summary", title: "主脚本规模", content: "3 个段落、5 个元素、4 条关联" },
  166. ],
  167. changes: { summary: "当前主脚本内容", sections: [], exactness: "current-only" },
  168. technical: { source: "database" },
  169. }}
  170. />);
  171. expect(screen.getByText("构建总结全文:脚本已经完成结构与表达检查。")).toBeInTheDocument();
  172. expect(screen.getByText("3 个段落、5 个元素、4 条关联")).toBeInTheDocument();
  173. });
  174. it("renders V8 decision blocks without leaking technical fields into business detail", () => {
  175. const story = makeStory("evaluation", "round-evaluation");
  176. const item = storyItem(story, 1);
  177. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  178. detailKind: "agent-decision",
  179. decisionType: "evaluation",
  180. actor: { role: "overall-evaluator", label: "整体评审 Agent" },
  181. authority: "recommendation",
  182. question: "当前主脚本是否达到目标?",
  183. blocks: [
  184. { type: "summary", title: "总结论", value: "部分通过" },
  185. { type: "items", title: "问题", items: ["结尾仍需补充行动建议"] },
  186. ],
  187. technical: { source: { table: "script_build_event", eventId: 41 } },
  188. }} />);
  189. expect(screen.getByText("部分通过")).toBeInTheDocument();
  190. expect(screen.getByText("结尾仍需补充行动建议")).toBeInTheDocument();
  191. expect(screen.queryByText(/script_build_event|eventId/)).not.toBeInTheDocument();
  192. });
  193. it("keeps arrows and sentence punctuation in the original prose", () => {
  194. const item = storyItem(makeStory("objective", "objective"), 1);
  195. const prose = "概念引入→拆解分析→归纳提炼→行动指南。第二句仍属于同一段。";
  196. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  197. detailKind: "agent-decision",
  198. decisionType: "direction",
  199. blocks: [{ type: "summary", title: "明确理由", value: prose }],
  200. technical: {},
  201. }} />);
  202. expect(screen.getByText(prose)).toBeInTheDocument();
  203. expect(container.querySelector(".decisionFlowList")).toBeNull();
  204. expect(container.querySelectorAll(".decisionReadableText p")).toHaveLength(1);
  205. });
  206. it("only inserts line breaks and indentation around an existing numbered sequence", () => {
  207. const item = storyItem(makeStory("objective", "objective"), 1);
  208. const prose = "根据原有信息形成五个部分。 1. 原文第一项; 2. 原文第二项; 3. 原文第三项。";
  209. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  210. detailKind: "agent-decision",
  211. decisionType: "direction",
  212. blocks: [{ type: "summary", title: "明确理由", value: prose }],
  213. technical: {},
  214. }} />);
  215. expect(screen.getByText("根据原有信息形成五个部分。")).toBeInTheDocument();
  216. expect(screen.getByText("原文第一项;")).toBeInTheDocument();
  217. expect(screen.getByText("原文第二项;")).toBeInTheDocument();
  218. expect(screen.getByText("原文第三项。")).toBeInTheDocument();
  219. expect(container.querySelectorAll(".decisionReadableText ol > li")).toHaveLength(3);
  220. });
  221. it("groups creative steps and multipath evaluation content by its real semantic owner", () => {
  222. const item = storyItem(makeStory("creative", "candidate-output"), 1, 2);
  223. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  224. detailKind: "agent-decision",
  225. decisionType: "creative",
  226. blocks: [
  227. {
  228. type: "creative-process",
  229. title: "创作处理",
  230. steps: [{ stepIndex: 4, action: "新增脚本段落", summary: "起草四元组并开始创建段落骨架。", plan: "1. 创建段落 1:封面导引\n2. 创建段落 2:概念定义", reasoning: "输入依据:账号标准科普模式。" }],
  231. },
  232. {
  233. type: "evaluation-branches",
  234. title: "逐方案评审",
  235. branches: [
  236. { subject: "方案 1", conclusion: "部分通过", achievements: ["领域信息库新增 5 条事实(id 54-58)。"], problems: ["元素创建尚未完整落地。"], evidence: "来源可指位。" },
  237. { subject: "方案 2", conclusion: "部分通过", achievements: ["已搭建五段骨架。"], problems: ["数量红线违规。"] },
  238. ],
  239. },
  240. {
  241. type: "evaluation-matrix",
  242. title: "评审标准与方案对比",
  243. rows: [{ criterion: "结构完备性", candidates: [{ subject: "方案 1", value: "不适用" }, { subject: "方案 2", value: "部分通过" }], basis: "仅方案二涉及" }],
  244. },
  245. ],
  246. technical: {},
  247. }} />);
  248. expect(screen.queryByText("6 个真实执行步骤")).not.toBeInTheDocument();
  249. expect(screen.getByText("1 个真实执行步骤")).toBeInTheDocument();
  250. expect(screen.getByText("新增脚本段落")).toBeInTheDocument();
  251. expect(screen.getByText("创建段落 1:封面导引")).toBeInTheDocument();
  252. expect(screen.getByText("领域信息库新增 5 条事实(id 54-58)。")).toBeInTheDocument();
  253. expect(screen.getByText("数量红线违规。")).toBeInTheDocument();
  254. expect(screen.getByRole("rowheader", { name: "结构完备性" })).toBeInTheDocument();
  255. expect(screen.getByRole("cell", { name: "仅方案二涉及" })).toBeInTheDocument();
  256. expect(container.querySelectorAll(".evaluationBranchList > article")).toHaveLength(2);
  257. });
  258. it("renders the complete saved script direction as its original hierarchy", () => {
  259. const item = storyItem(makeStory("objective", "objective"), 1);
  260. const sourceDocument = `## 选题价值主张
  261. 用经典心理学效应帮助读者理解决策瘫痪,并给出**"先行动再调整"**。
  262. ## 创作总目标
  263. ### 目标 1
  264. - 目标语句:把毛驴效应科普清楚。
  265. - 评估维度:
  266. - name:科普清晰度
  267. - 通过标准:普通读者能准确理解定义与来源。`;
  268. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  269. detailKind: "agent-decision",
  270. decisionType: "direction",
  271. blocks: [
  272. { type: "summary", title: "当前保存的创作方向", value: sourceDocument, visualRole: "key-result", presentation: "document" },
  273. ],
  274. technical: {},
  275. }} />);
  276. expect(screen.getByRole("heading", { name: "选题价值主张" })).toBeInTheDocument();
  277. expect(screen.getByRole("heading", { name: "创作总目标" })).toBeInTheDocument();
  278. expect(screen.getByRole("heading", { name: "目标 1" })).toBeInTheDocument();
  279. expect(screen.getByText("通过标准:普通读者能准确理解定义与来源。")).toBeInTheDocument();
  280. expect(screen.getByText("先行动再调整").tagName).toBe("STRONG");
  281. expect(screen.queryByText(/\*\*"先行动再调整"\*\*/)).not.toBeInTheDocument();
  282. expect(container.querySelector('[data-presentation="document"]')).toBeInTheDocument();
  283. });
  284. it("renders the complete round goal without inferred sentence roles", () => {
  285. const item = storyItem(makeStory("goal", "goal"), 1);
  286. const completeGoal = "立起脚本的段落骨架。竞争不同的框架切分策略。本轮只做段落结构,不填充文案。";
  287. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  288. detailKind: "agent-decision",
  289. decisionType: "direction",
  290. blocks: [{
  291. type: "summary",
  292. title: "本轮目标构成",
  293. visualRole: "key-result",
  294. presentation: "document",
  295. value: completeGoal,
  296. }],
  297. technical: {},
  298. }} />);
  299. expect(screen.getByText(completeGoal)).toBeInTheDocument();
  300. expect(screen.queryByText("核心目标")).not.toBeInTheDocument();
  301. expect(screen.queryByText("实现策略")).not.toBeInTheDocument();
  302. expect(screen.queryByText("范围边界")).not.toBeInTheDocument();
  303. });
  304. it("renders implementation plans as structured routes with collapsible evidence", () => {
  305. const item = storyItem(makeStory("plan", "plan"), 4);
  306. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  307. detailKind: "agent-decision",
  308. decisionType: "direction",
  309. actor: { role: "main", label: "主 Agent" },
  310. authority: "final",
  311. question: "本轮应如何组织实现方案?",
  312. blocks: [
  313. {
  314. type: "implementation-plan",
  315. title: "本轮实施路线",
  316. visualRole: "section",
  317. presentation: "plan",
  318. mode: "单路",
  319. reasoning: "这是收敛阶段的定点修复,不需要竞争。",
  320. routes: [{ pathIndex: 1, pathType: "内容", action: "改", target: "段落 2207、2208 的形式层", method: "产生元素", emphasis: "结构完整性" }],
  321. },
  322. { type: "items", title: "根据以下信息做出的决策", visualRole: "evidence", presentation: "list", collapsible: false, items: [{ label: "本轮目标", value: "完整目标内容", note: "持续约束;本轮必须遵守" }] },
  323. ],
  324. technical: {},
  325. }} />);
  326. expect(screen.getByText("本轮实施路线")).toBeInTheDocument();
  327. expect(screen.getByText("路线 1")).toBeInTheDocument();
  328. expect(screen.getByText("段落 2207、2208 的形式层")).toBeInTheDocument();
  329. expect(screen.getByText("产生元素")).toBeInTheDocument();
  330. expect(screen.getByText("结构完整性")).toBeInTheDocument();
  331. expect(screen.getByText("为什么这样规划")).toBeInTheDocument();
  332. expect(screen.getByText("根据以下信息做出的决策")).toBeInTheDocument();
  333. expect(screen.getByText("完整目标内容")).toBeVisible();
  334. });
  335. it("uses semantic roles instead of Chinese titles for hierarchy and disclosure", () => {
  336. const item = storyItem(makeStory("tradeoff", "data-decision"), 1);
  337. const { container } = render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  338. detailKind: "agent-decision",
  339. decisionType: "tradeoff",
  340. blocks: [
  341. { type: "items", title: "参考资料", visualRole: "evidence", collapsible: true, items: ["完整依据"] },
  342. { type: "summary", title: "执行结论", visualRole: "key-result", value: "采用方案 A" },
  343. ],
  344. technical: {},
  345. }} />);
  346. expect(screen.getByText("完整依据")).not.toBeVisible();
  347. expect(container.querySelector('[data-visual-role="key-result"]')).toHaveClass("is-role-key-result");
  348. fireEvent.click(screen.getByText("参考资料"));
  349. expect(screen.getByText("完整依据")).toBeVisible();
  350. });
  351. it("opens the task and rules from inside a decision Inspector", () => {
  352. const onPrompt = vi.fn();
  353. const item = storyItem(makeStory("creative", "candidate-output"), 1, 2);
  354. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} onPrompt={onPrompt} detail={{
  355. detailKind: "agent-decision",
  356. decisionType: "creative",
  357. actor: { role: "implementer", label: "实现 Agent" },
  358. authority: "implementation-scope",
  359. question: "当前实现方案如何形成候选产出?",
  360. promptRef: "prompt:event:2977",
  361. blocks: [],
  362. technical: {},
  363. }} />);
  364. fireEvent.click(screen.getByRole("button", { name: "查看实现任务与提示词" }));
  365. expect(onPrompt).toHaveBeenCalledWith("prompt:event:2977");
  366. });
  367. it("does not invent a recommendation-handling column without an explicit record", () => {
  368. const item = storyItem(makeStory("tradeoff", "multipath-decision"), 1);
  369. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  370. detailKind: "agent-decision",
  371. decisionType: "tradeoff",
  372. blocks: [{
  373. type: "comparison",
  374. title: "评审建议与最终决定",
  375. rows: [{ candidate: "方案 1", recommendation: "建议采用", finalDecision: "已采用" }],
  376. }],
  377. technical: {},
  378. }} />);
  379. expect(screen.getByText("评审建议")).toBeInTheDocument();
  380. expect(screen.getByText("最终决定")).toBeInTheDocument();
  381. expect(screen.queryByText("对建议的处理")).not.toBeInTheDocument();
  382. expect(screen.queryByText("没有明确记录")).not.toBeInTheDocument();
  383. });
  384. it("renders structured evaluator issues once and keeps nested outcomes readable", () => {
  385. const item = storyItem(makeStory("evaluation", "round-evaluation"), 1);
  386. render(<Inspector item={item} tab="business" onTab={vi.fn()} onClose={vi.fn()} detail={{
  387. detailKind: "agent-decision",
  388. decisionType: "evaluation",
  389. blocks: [{
  390. type: "items",
  391. title: "逐项结论",
  392. items: [{
  393. summary: "结尾仍需补充行动建议",
  394. severity: "中",
  395. achievements: ["主线结构已经清晰"],
  396. problems: ["结尾行动指引不足"],
  397. }],
  398. }],
  399. technical: {},
  400. }} />);
  401. expect(screen.getByText("问题 · 中")).toBeInTheDocument();
  402. expect(screen.getAllByText("结尾仍需补充行动建议")).toHaveLength(1);
  403. expect(screen.getByText("已达成")).toBeInTheDocument();
  404. expect(screen.getByText("主线结构已经清晰")).toBeInTheDocument();
  405. expect(screen.getByText("需关注")).toBeInTheDocument();
  406. expect(screen.getByText("结尾行动指引不足")).toBeInTheDocument();
  407. });
  408. });