import { fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { InspectorComparison } from "@/components/inspector/InspectorComparison"; import { Inspector } from "@/components/inspector/Inspector"; import { storyItem } from "@/lib/layout"; import { makeStory } from "@/tests/fixtures"; import type { InspectorWorkbenchProjection, SourceBinding } from "@/lib/types"; const binding: SourceBinding = { id: "result-1", sourceId: "event:4607", role: "output", selector: { kind: "json-pointer", path: "/output/content/results/0" }, transform: { kind: "direct" }, evidence: { availability: "returned-to-agent", adoption: "not-recorded", confidence: "exact" }, resolution: "resolved", }; const payload: InspectorWorkbenchProjection = { schemaVersion: "inspector-source-v2", detailRef: "event:4607", cardKind: "query", completeness: "complete", businessProjection: { detailKind: "activity", businessSections: [{ id: "results", title: "返回结果", items: [{ label: "Case 1", value: "命中内容" }] }], }, modules: [{ id: "results", title: "返回结果", businessPath: "/businessSections/0", presentation: "items", rows: [{ id: "case-1", label: "Case 1", businessSelector: "/businessSections/0/items/0/value", bindingIds: [binding.id] }], bindings: [binding], runtimeRefs: ["event:4607"], defaultOpen: true, }], sources: { "event:4607": { id: "event:4607", kind: "runtime-event", label: "search_script_decode_case", locator: { table: "script_build_event + script_build_event_body", eventId: 4607, eventName: "search_script_decode_case" }, status: "ok", completeness: "complete", resultState: "present", selectedValues: [{ bindingId: binding.id, path: "/output/content/results/0", value: "命中内容" }], rawRecord: { output: { content: { results: ["命中内容"] } } }, }, }, notices: [], }; describe("InspectorComparison", () => { it("returns from the source comparison to the readable business Inspector", () => { const onModeChange = vi.fn(); const item = storyItem(makeStory("event:4607", "planning-analysis"), 1); render(); fireEvent.click(screen.getByRole("button", { name: "返回业务详情" })); expect(onModeChange).toHaveBeenCalledWith("business"); }); it("renders business fields from the same projection used by the readable Inspector", () => { const planPayload: InspectorWorkbenchProjection = { ...payload, detailRef: "round:1:plan", cardKind: "implementation-plan", businessProjection: { detailKind: "agent-decision", blocks: [{ type: "implementation-plan", title: "本轮实施路线", mode: "竞争", reasoning: "比较两条路线。", routes: [{ pathIndex: 1, target: "完整段落骨架", method: "产生脉络", emphasis: "先保证结构清楚" }], }], }, modules: [{ id: "block-1", title: "本轮实施路线", businessPath: "/blocks/0", presentation: "plan", rows: [ { id: "mode", label: "规划方式", businessSelector: "/blocks/0/mode", bindingIds: [binding.id] }, { id: "target", label: "路线 1 · 作用范围", businessSelector: "/blocks/0/routes/0/target", bindingIds: [binding.id] }, { id: "method", label: "路线 1 · 实施方法", businessSelector: "/blocks/0/routes/0/method", bindingIds: [binding.id] }, { id: "emphasis", label: "路线 1 · 路线侧重", businessSelector: "/blocks/0/routes/0/emphasis", bindingIds: [binding.id] }, ], bindings: [binding], runtimeRefs: [], defaultOpen: true, }], }; const { container } = render(); expect(screen.getByText("竞争")).toBeInTheDocument(); expect(screen.getByText("完整段落骨架")).toBeInTheDocument(); expect(screen.getByText("产生脉络")).toBeInTheDocument(); expect(screen.getByText("先保证结构清楚")).toBeInTheDocument(); expect(container.querySelector(".comparisonItemBusiness")?.textContent).not.toContain("pathIndex"); }); it("renders one physical row across all three columns", () => { const { container } = render(); expect(screen.getByText("业务详情中的内容")).toBeInTheDocument(); expect(screen.getByText("这一项的数据依据")).toBeInTheDocument(); expect(screen.getByText("对应的原始字段或原文")).toBeInTheDocument(); expect(screen.getByText("返回结果")).toBeInTheDocument(); expect(screen.getAllByText("S1").length).toBeGreaterThanOrEqual(2); expect(screen.getByText("已返回给 Agent,是否采用未记录")).toBeInTheDocument(); expect(screen.getByText("script_build_event + script_build_event_body")).toBeInTheDocument(); expect(container.querySelectorAll(".comparisonItemRow")).toHaveLength(1); }); it("shows the exact selected source value without opening raw JSON", () => { render(); expect(screen.getAllByText("命中内容").length).toBeGreaterThanOrEqual(2); expect(screen.getByText("完整原始记录")).toBeInTheDocument(); expect(screen.getByText("/output/content/results/0").closest("details")).not.toHaveAttribute("open"); expect(screen.queryByText("found")).not.toBeInTheDocument(); }); it("keeps structured business rows readable instead of exposing object keys", () => { const decisionPayload: InspectorWorkbenchProjection = { ...payload, cardKind: "data-decision", businessProjection: { detailKind: "agent-decision", blocks: [{ type: "items", title: "参与判断", items: [{ label: "解构 Case", value: "返回了 2 条可用案例", note: "已读取" }], }], }, modules: [{ ...payload.modules[0], id: "block-1", title: "参与判断", businessPath: "/blocks/0", rows: [{ id: "source-1", label: "解构 Case", businessSelector: "/blocks/0/items/0", bindingIds: [binding.id] }], }], }; const { container } = render(); const business = container.querySelector(".comparisonItemBusiness"); expect(business).toHaveTextContent("解构 Case"); expect(business).toHaveTextContent("返回了 2 条可用案例"); expect(business).toHaveTextContent("说明"); expect(business?.textContent).not.toMatch(/\blabel\b|\bvalue\b|\bnote\b/); }); it("synchronizes binding highlight within the already aligned row", () => { const onChange = vi.fn(); const { container } = render(); fireEvent.click(screen.getByRole("button", { name: /已返回给 Agent/ })); expect(container.querySelectorAll('[data-binding-id="result-1"]').length).toBeGreaterThanOrEqual(2); expect(container.querySelector(".comparisonItemRow.is-active")).toBeInTheDocument(); expect(onChange).toHaveBeenLastCalledWith("result-1"); }); it("unmounts module contents when collapsed", () => { render(); fireEvent.click(screen.getByRole("button", { name: /返回结果/ })); expect(screen.queryByText("命中内容")).not.toBeInTheDocument(); }); it("explains a successful empty result instead of calling it missing", () => { const empty: InspectorWorkbenchProjection = { ...payload, sources: { "event:4607": { ...payload.sources["event:4607"], resultState: "empty", rawRecord: [] }, }, }; render(); expect(screen.getByText("查询完成,返回 0 条")).toBeInTheDocument(); expect(screen.getByText("这不是记录缺失。")).toBeInTheDocument(); }); it("hands focus control to an upper prompt or artifact overlay", () => { const item = storyItem(makeStory("event:4607", "planning-analysis"), 1); const onClose = vi.fn(); const view = (topOverlayOpen: boolean) => <>; const { rerender } = render(view(false)); const outside = screen.getByTestId("upper-overlay-control"); outside.focus(); expect(screen.getByRole("dialog")).toContainElement(document.activeElement as HTMLElement); rerender(view(true)); const upperOverlayControl = screen.getByTestId("upper-overlay-control"); upperOverlayControl.focus(); expect(document.activeElement).toBe(upperOverlayControl); }); });