| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- 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(<ReactFlowProvider>{node}</ReactFlowProvider>);
- 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(<AgentDecisionCard id="objective" type="agentDecision" {...nodeProps} data={{ ...base, item: objective }} />);
- expect(screen.getByRole("button", { name: "查看详情" })).toBeInTheDocument();
- expect(screen.queryByRole("button", { name: "查看决策" })).not.toBeInTheDocument();
- unmount();
- const dataDecision = storyItem(makeStory("data", "data-decision"), 1, 1);
- wrap(<AgentDecisionCard id="data" type="agentDecision" {...nodeProps} data={{ ...base, item: dataDecision }} />);
- expect(screen.getByRole("button", { name: "查看决策" })).toBeInTheDocument();
- });
- it("shows implementer data decisions as data decisions", () => {
- const item = storyItem(makeStory("data", "data-decision"), 1, 1);
- wrap(<AgentDecisionCard id="data" type="agentDecision" {...nodeProps} data={{ ...base, item }} />);
- expect(screen.getByText("数据取舍")).toBeInTheDocument();
- expect(screen.getByText("实现 Agent")).toBeInTheDocument();
- });
- it("distinguishes domain-information output from candidate script", () => {
- const branch = makeBranch(1, 4);
- wrap(<CandidateCard id="branch" type="candidateCard" {...nodeProps} data={{ ...base, item: branchSummaryItem(branch, 1), expanded: false }} />);
- 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(<CandidateCard id="branch" type="candidateCard" {...nodeProps} data={{ ...base, onOpen, item: summaryItem, expanded: false }} />);
- 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(<AgentDecisionCard id="output" type="agentDecision" {...nodeProps} data={{ ...base, onOpen, onPrompt: vi.fn(), item: outputItem }} />);
- 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(<MobileTimeline
- view={view}
- expandedRounds={new Set([1])}
- expandedBranches={new Set(["1:1"])}
- expandedRetrievalAgents={new Set()}
- onToggleRound={vi.fn()}
- onToggleBranch={vi.fn()}
- onToggleRetrievalAgent={vi.fn()}
- onOpen={onOpen}
- onCanvas={vi.fn()}
- />);
- 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(<StoryCard id="evaluation" type="storyCard" {...nodeProps} data={{ ...base, item: storyItem(story, 1) }} />);
- 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(<MobileTimeline
- view={view}
- expandedRounds={new Set([1])}
- expandedBranches={new Set()}
- expandedRetrievalAgents={new Set()}
- onToggleRound={vi.fn()}
- onToggleBranch={vi.fn()}
- onToggleRetrievalAgent={vi.fn()}
- onOpen={vi.fn()}
- onCanvas={vi.fn()}
- />);
- 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(<AgentDecisionCard id="review" type="agentDecision" {...nodeProps} data={{ ...base, item: multipathReviewItem(batch.review!, 1) }} />);
- expect(screen.getByText("多方案评审 Agent")).toBeInTheDocument();
- expect(screen.getAllByText("评审建议").length).toBeGreaterThanOrEqual(1);
- unmount();
- wrap(<AgentDecisionCard id="decision" type="agentDecision" {...nodeProps} data={{ ...base, item: multipathDecisionItem(batch.decision!, 1) }} />);
- 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(<CandidateCard id="branch" type="candidateCard" {...nodeProps} data={{ ...base, item: branchSummaryItem(branch, 1), expanded: false }} />);
- 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(<RoundSummaryCard id="round" type="roundSummary" {...nodeProps} data={{ ...base, onOpen, item }} />);
- 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(<StoryCard id="round-result" type="storyCard" {...nodeProps} data={{ ...base, onOpen, item }} />);
- 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(<StoryCard id="round-result" type="storyCard" {...nodeProps} data={{ ...base, item: storyItem(result, 1) }} />);
- 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(<RoundSummaryCard id="round" type="roundSummary" {...nodeProps} data={{ ...base, item: roundSummaryItem(round) }} />);
- 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(<RunResultCard id="final" type="finalResult" {...nodeProps} data={{ ...base, onOpen, item }} />);
- 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");
- });
- });
|