import { fireEvent, render, screen } from "@testing-library/react"; import { ReactFlowProvider } from "@xyflow/react"; import { describe, expect, it, vi } from "vitest"; import { CandidateCard } from "@/components/flow/CandidateCard"; import { RoundSummaryCard } from "@/components/flow/RoundSummaryCard"; import { StoryCard } from "@/components/flow/StoryCard"; import { RunResultCard } from "@/components/flow/RunResultCard"; import { AgentDecisionCard } from "@/components/decision/AgentDecisionCard"; import { MobileTimeline } from "@/components/workbench/MobileTimeline"; import { branchSummaryItem, finalResultItem, multipathDecisionItem, multipathReviewItem, roundSummaryItem, storyItem } from "@/lib/layout"; import { makeBranch, makeRound, makeStory, makeView } from "@/tests/fixtures"; const base = { onOpen: vi.fn(), onToggleRound: vi.fn(), onToggleBranch: vi.fn() }; const nodeProps = { selected: false, zIndex: 1, isConnectable: false, positionAbsoluteX: 0, positionAbsoluteY: 0, dragging: false, draggable: true, selectable: true, deletable: false }; const wrap = (node: React.ReactNode) => render({node}); describe("V8 business cards", () => { it("labels the creation objective action as details while keeping decision actions explicit", () => { const objective = storyItem(makeView().objective); const { unmount } = wrap(); expect(screen.getByRole("button", { name: "查看详情" })).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "查看决策" })).not.toBeInTheDocument(); unmount(); const dataDecision = storyItem(makeStory("data", "data-decision"), 1, 1); wrap(); expect(screen.getByRole("button", { name: "查看决策" })).toBeInTheDocument(); }); it("shows implementer data decisions as data decisions", () => { const item = storyItem(makeStory("data", "data-decision"), 1, 1); wrap(); expect(screen.getByText("数据取舍")).toBeInTheDocument(); expect(screen.getByText("实现 Agent")).toBeInTheDocument(); }); it("distinguishes domain-information output from candidate script", () => { const branch = makeBranch(1, 4); wrap(); expect(screen.getByText(/领域信息/)).toBeInTheDocument(); expect(screen.getByText("领域事实")).toBeInTheDocument(); expect(screen.getByRole("button", { name: /展开方案过程/ })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /查看方案汇总/ })).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "查看完整候选脚本" })).not.toBeInTheDocument(); }); it("opens the same artifact viewer from collapsed and expanded candidate output", () => { const onOpen = vi.fn(); const branch = makeBranch(1, 1); const summaryItem = branchSummaryItem(branch, 1); const { unmount } = wrap(); fireEvent.click(screen.getByRole("button", { name: "查看完整候选脚本" })); expect(onOpen).toHaveBeenCalledWith(summaryItem, "artifact"); unmount(); onOpen.mockClear(); const creativeOutput = makeStory("output", "candidate-output", "decision", { artifactRef: "artifact:branch:1" }); const outputItem = storyItem(creativeOutput, 1, 1); wrap(); fireEvent.click(screen.getByRole("button", { name: "查看完整候选脚本" })); expect(onOpen).toHaveBeenCalledWith(outputItem, "artifact"); expect(screen.queryByRole("button", { name: "查看实现任务与提示词" })).not.toBeInTheDocument(); }); it("keeps the candidate artifact action on an expanded mobile creative decision", () => { const view = makeView(1, 1, 1); const branch = view.rounds[0].branches[0]; branch.output.node = makeStory("mobile-output", "candidate-output", "decision", { artifactRef: "artifact:branch:1" }); const onOpen = vi.fn(); render(); const artifactButtons = screen.getAllByRole("button", { name: "查看完整候选脚本" }); expect(artifactButtons).toHaveLength(2); fireEvent.click(artifactButtons[1]); expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ id: "mobile-output" }), "artifact"); expect(screen.queryByText("查看实现任务与提示词")).not.toBeInTheDocument(); }); it("keeps runtime association in technical detail instead of the business card", () => { const story = makeStory("evaluation", "round-evaluation"); story.sourceNotice = "runtime-associated"; story.detailRef = "event:987"; wrap(); expect(screen.queryByText("运行记录关联")).not.toBeInTheDocument(); expect(screen.queryByText("event:987")).not.toBeInTheDocument(); }); it("keeps objective and step summaries visible on the mobile timeline", () => { const view = makeView(1, 1, 1); view.objective.card = { primary: { label: "核心目标", value: "形成清晰可执行的创作方向" }, secondary: [ { key: "inputs", label: "决策前读取", value: "选题 · 账号段落模式 · 账号人设" }, { key: "shape", label: "目标结构", value: "2 个创作目标 · 1 个领域评估维度" }, ], }; view.objective.decision!.card = view.objective.card; view.rounds[0].plan.node.card = { primary: { label: "实现方式", value: "分工 · 2 路实现" }, secondary: [ { key: "paths", label: "各路任务", value: "方案 1:补充步骤;方案 2:增强信息" }, { key: "reason", label: "规划理由", value: "两个任务没有强依赖" }, ], }; view.rounds[0].plan.node.decision!.card = view.rounds[0].plan.node.card; render(); expect(screen.getByText("选题 · 账号段落模式 · 账号人设")).toBeInTheDocument(); expect(screen.getByText("2 个创作目标 · 1 个领域评估维度")).toBeInTheDocument(); expect(screen.getByText("方案 1:补充步骤;方案 2:增强信息")).toBeInTheDocument(); expect(screen.getByText("两个任务没有强依赖")).toBeInTheDocument(); expect(screen.getAllByText("创作目标")).toHaveLength(1); expect(screen.getAllByText("本轮目标")).toHaveLength(1); expect(screen.getAllByText("实现规划")).toHaveLength(1); }); it("keeps candidate review and main decision as two distinct cards", () => { const batch = makeRound(1, 2, 1).convergenceBatches[0]; const { unmount } = wrap(); expect(screen.getByText("多方案评审 Agent")).toBeInTheDocument(); expect(screen.getAllByText("评审建议").length).toBeGreaterThanOrEqual(1); unmount(); wrap(); expect(screen.getByText("主 Agent")).toBeInTheDocument(); expect(screen.getAllByText("最终决定").length).toBeGreaterThanOrEqual(1); expect(screen.getByText("方案 1、方案 2")).toBeInTheDocument(); }); it("describes a stopped open branch as incomplete process instead of missing data", () => { const branch = makeBranch(1, 1); branch.status = "open"; branch.outcome.status = "open"; branch.outcome.label = "构建停止时尚未处置"; branch.outcome.sourceNotice = "process-incomplete"; wrap(); expect(screen.getByText("尚未处置")).toBeInTheDocument(); expect(screen.getByText("流程未完成")).toBeInTheDocument(); expect(screen.queryByText("记录缺失")).not.toBeInTheDocument(); }); it("keeps round process and detail actions visible", () => { const onOpen = vi.fn(); const round = makeRound(1, 3, 2); const item = roundSummaryItem(round); wrap(); expect(screen.getByRole("button", { name: /展开本轮/ })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /查看详情/ })).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "查看本轮产出脚本表" })); expect(onOpen).toHaveBeenCalledWith(item, "artifact"); expect(screen.getByText("本轮目标 1")).toBeInTheDocument(); expect(screen.getByText("3 项具体任务 · 完整内容见详情")).toBeInTheDocument(); expect(screen.getByText("分工 · 3 个实现方案")).toBeInTheDocument(); expect(screen.getByText("2 个决策批次 · 涉及方案 1、2、3")).toBeInTheDocument(); expect(screen.getByText("形成第 1 轮内容产出")).toBeInTheDocument(); }); it("opens the round artifact from the expanded round result", () => { const onOpen = vi.fn(); const result = makeStory("round:1:result", "round-result", "result", { artifactRef: "artifact:round:1" }); const item = storyItem(result, 1); wrap(); fireEvent.click(screen.getByRole("button", { name: "查看本轮产出脚本表" })); expect(onOpen).toHaveBeenCalledWith(item, "artifact"); }); it("labels a completed round without an overall evaluation explicitly", () => { const result = makeStory("round:1:result", "round-result", "result", { status: "not-evaluated" }); wrap(); expect(screen.getByText("未整体评审")).toBeInTheDocument(); expect(screen.queryByText("尚无结论")).not.toBeInTheDocument(); }); it("uses the run-aware decision summary instead of a generic missing-record label", () => { const round = makeRound(1, 2, 0); round.summary.decision.summary = "构建停止前尚未形成多路决策"; wrap(); expect(screen.getByText("构建停止前尚未形成多路决策")).toBeInTheDocument(); expect(screen.queryByText("未记录主 Agent 多路决策")).not.toBeInTheDocument(); }); it("opens the dedicated artifact viewer from the final result", () => { const onOpen = vi.fn(); const view = makeView(); view.finalResult.summary = "脚本构建终结:已经完成结构、内容和表达检查。\n1. 脚本结构概述:四个段落已经形成完整闭环。"; const item = finalResultItem(view); wrap(); expect(screen.getByText("当前主脚本已经形成")).toBeInTheDocument(); expect(screen.getByText("构建摘要")).toBeInTheDocument(); expect(screen.getByText("3", { selector: ".artifactStats b" })).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "查看结果详情" })); expect(onOpen).toHaveBeenCalledWith(item, "business"); onOpen.mockClear(); fireEvent.click(screen.getByRole("button", { name: "查看完整主脚本" })); expect(onOpen).toHaveBeenCalledWith(item, "artifact"); onOpen.mockClear(); fireEvent.keyDown(screen.getByRole("button", { name: "查看完整主脚本" }), { key: "Enter" }); expect(onOpen).not.toHaveBeenCalledWith(item, "business"); }); });