| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- import { fireEvent, render, screen, within } from "@testing-library/react";
- import { ReactFlowProvider } from "@xyflow/react";
- import { describe, expect, it, vi } from "vitest";
- import { DirectToolGroupCard } from "@/components/flow/DirectToolGroupCard";
- import { RetrievalAgentCard } from "@/components/flow/RetrievalAgentCard";
- import { buildHorizontalLayout, retrievalAgentItem, retrievalDirectItem } from "@/lib/layout";
- import { makeBranch, makeRetrievalStage, makeView } from "@/tests/fixtures";
- const base = { onOpen: vi.fn(), onToggleRound: vi.fn(), onToggleBranch: vi.fn(), onToggleRetrievalAgent: 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 retrieval stage", () => {
- it("shows an immediate Agent retrieval preview on keyboard focus", () => {
- const branch = makeBranch(1, 1);
- const item = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
- const { container } = wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, buildId: "443", item, expanded: false }} />);
- fireEvent.focus(container.querySelector(".retrievalAgentCard")!);
- const preview = screen.getByRole("dialog", { name: /取数预览/ });
- expect(within(preview).getByText("命中了什么")).toBeInTheDocument();
- expect(within(preview).getByText("初筛得到")).toBeInTheDocument();
- });
- it("loads and presents real tool results only after the preview opens", async () => {
- const branch = makeBranch(1, 1);
- const item = retrievalDirectItem(branch.retrievalStage.directToolGroups[0], 1, 1);
- if (item.itemType !== "retrieval-direct") throw new Error("expected retrieval-direct item");
- item.group.calls = [{ id: "event:4047", eventId: 4047, businessLabel: "领域信息", status: "success", resultSummary: "已读取", detailRef: "event:4047" }];
- item.group.callCount = 1;
- item.group.successCount = 1;
- const fetchMock = vi.fn().mockResolvedValue({
- ok: true,
- json: async () => ({ detailKind: "retrieval-event", outcome: { state: "hit", count: 1, message: "读取成功,共返回 1 条记录。" }, items: [{ id: "fact-1", title: "领域事实 1", subtitle: "核实了布里丹寓言的史实背景。", meta: [], sections: [] }], technical: {} }),
- });
- vi.stubGlobal("fetch", fetchMock);
- const { container } = wrap(<DirectToolGroupCard id={item.id} type="directToolGroup" {...nodeProps} data={{ ...base, buildId: "443", item }} />);
- expect(fetchMock).not.toHaveBeenCalled();
- fireEvent.focus(container.querySelector(".directToolCard")!);
- const preview = screen.getByRole("dialog", { name: /取数预览/ });
- expect(await within(preview).findByText("领域事实 1")).toBeInTheDocument();
- expect(fetchMock).toHaveBeenCalledTimes(1);
- vi.unstubAllGlobals();
- });
- it("presents direct tools and delegated agents as peer retrieval operations", () => {
- const branch = makeBranch(1, 1);
- const direct = retrievalDirectItem(branch.retrievalStage.directToolGroups[0], 1, 1);
- const agent = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
- wrap(<><DirectToolGroupCard id={direct.id} type="directToolGroup" {...nodeProps} data={{ ...base, item: direct }} /><RetrievalAgentCard id={agent.id} type="retrievalAgent" {...nodeProps} data={{ ...base, item: agent, expanded: false }} /></>);
- expect(screen.getByText("工具取数")).toBeInTheDocument();
- expect(screen.getByText("Agent 取数")).toBeInTheDocument();
- expect(screen.getByText("初筛整理")).toBeInTheDocument();
- expect(screen.queryByText("数据取舍")).not.toBeInTheDocument();
- });
- it("keeps repeated attempts inside one agent and opens an individual query", () => {
- const branch = makeBranch(1, 1);
- const run = { ...branch.retrievalStage.agentRuns[0], attempts: branch.retrievalStage.agentRuns[0].attempts.concat(Array.from({ length: 10 }, (_, index) => ({ ...branch.retrievalStage.agentRuns[0].attempts[0], id: `event:extra-${index}`, eventId: 900 + index, queryLabel: `额外查询 ${index + 1}`, detailRef: `event:${900 + index}` }))) };
- run.querySummary = { ...run.querySummary, total: 13 };
- const item = retrievalAgentItem(run, 1, 1);
- const onOpen = vi.fn();
- wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, onOpen, item, expanded: true }} />);
- expect(screen.getByText((_, element) => element?.tagName === "P" && element.textContent?.startsWith("13 次") === true)).toBeInTheDocument();
- expect(screen.getAllByRole("listitem")).toHaveLength(13);
- fireEvent.click(screen.getByText("额外查询 1"));
- expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ itemType: "query-attempt", detailRef: "event:900" }), "business");
- });
- it("stacks both parallel and sequential retrieval operations vertically", () => {
- const parallelView = makeView(1, 1, 1);
- parallelView.rounds[0].branches[0].retrievalStage = makeRetrievalStage(1, 1, 2, 1, "parallel");
- const options = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), expandedRetrievalAgents: new Set<string>(), ...base };
- const parallel = buildHorizontalLayout(parallelView, options).nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
- expect(new Set(parallel.map((node) => node.position.x)).size).toBe(1);
- expect(new Set(parallel.map((node) => node.position.y)).size).toBe(parallel.length);
- const sequentialView = makeView(1, 1, 1);
- const sequential = buildHorizontalLayout(sequentialView, options).nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
- expect(new Set(sequential.map((node) => node.position.x)).size).toBe(1);
- expect(new Set(sequential.map((node) => node.position.y)).size).toBe(sequential.length);
- });
- it("keeps unknown-time operations in one neutral column inside the retrieval frame", () => {
- const view = makeView(1, 1, 1);
- const stage = view.rounds[0].branches[0].retrievalStage;
- stage.observedMode = "unknown";
- stage.waves = [];
- stage.directToolGroups.forEach((item) => { item.waveIndex = undefined; item.startedAt = undefined; item.endedAt = undefined; });
- stage.agentRuns.forEach((item) => { item.waveIndex = undefined; item.startedAt = undefined; item.endedAt = undefined; });
- const options = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), expandedRetrievalAgents: new Set<string>(), ...base };
- const layout = buildHorizontalLayout(view, options);
- const operations = layout.nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
- const frame = layout.nodes.find((node) => node.type === "retrievalStageFrame");
- expect(new Set(operations.map((node) => node.position.x)).size).toBe(1);
- expect(Math.max(...operations.map((node) => node.position.x + Number(node.style?.width || 340)))).toBeLessThanOrEqual(Number(frame?.style?.width || 0));
- });
- it("marks expanded query lists so semantic zoom cannot hide a successful expansion", () => {
- const branch = makeBranch(1, 1);
- const item = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
- const { container } = wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, item, expanded: true }} />);
- expect(container.querySelector(".retrievalAgentCard")).toHaveClass("isQueryExpanded");
- expect(container.querySelector(".queryAttemptList")).toBeInTheDocument();
- });
- it("grows the branch frame when query rows are expanded", () => {
- const view = makeView(1, 1, 1);
- const agentId = view.rounds[0].branches[0].retrievalStage.agentRuns[0].id;
- const baseOptions = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), ...base };
- const collapsed = buildHorizontalLayout(view, { ...baseOptions, expandedRetrievalAgents: new Set<string>() });
- const expanded = buildHorizontalLayout(view, { ...baseOptions, expandedRetrievalAgents: new Set([agentId]) });
- const height = (nodes: typeof collapsed.nodes) => Number(nodes.find((node) => node.type === "branchFrame")?.style?.height || 0);
- expect(height(expanded.nodes)).toBeGreaterThan(height(collapsed.nodes));
- });
- });
|