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);
});
});