import {
act,
fireEvent,
render,
screen,
waitFor
} from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { setAgentGuiI18nTestLocale } from "../../../i18n/testUtils";
import type { WorkspaceAgentSessionDetailToolCall } from "../../workspaceAgentSessionDetailViewModel";
import { projectAgentToolCall } from "../projection/agentToolProjection";
import { AgentAskUserQuestionCard } from "./AgentAskUserQuestionCard";
import { AgentEnterPlanModeCard } from "./AgentEnterPlanModeCard";
import { AgentExitPlanModeCard } from "./AgentExitPlanModeCard";
import { AgentTaskCallCard } from "./AgentTaskCallCard";
import { AgentToolCallCard } from "./AgentToolCallCard";
describe("Agent specialized tool cards", () => {
afterEach(async () => {
setAgentGuiI18nTestLocale("zh-CN");
});
it("expands waiting ask-user cards by default", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("Questions")).toBeTruthy();
expect(screen.getByText("Which scope should we use?")).toBeTruthy();
expect(screen.queryByRole("button", { expanded: true })).toBeNull();
});
it("renders enter-plan cards as always-visible detail blocks", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(
screen.getByText("Exploring codebase and planning the renderer rewrite.")
).toBeTruthy();
expect(screen.queryByRole("button", { expanded: true })).toBeNull();
});
it("renders exit-plan cards as always-visible detail blocks", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText(/Finalize the projection layer/)).toBeTruthy();
expect(screen.queryByRole("button", { expanded: true })).toBeNull();
});
it("renders running task cards as always-visible detail blocks", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("Steps")).toBeTruthy();
expect(screen.queryByRole("button", { expanded: true })).toBeNull();
});
it("shows failed task status and fallback error when provider omits details", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(
screen.getByRole("button", { name: /Agent Failed Generate exactly/i })
).toBeTruthy();
expect(screen.getAllByText(/Failed/i).length).toBeGreaterThan(0);
expect(screen.getByText("Error")).toBeTruthy();
expect(
screen.getByText("The provider reported failure without details.")
).toBeTruthy();
expect(screen.queryByText("Output")).toBeNull();
});
it("keeps task step output hidden until the step expands", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.queryByText("Loaded docs chunk")).toBeNull();
fireEvent.click(screen.getByRole("button", { name: /Read Completed/i }));
expect(screen.getByText("Loaded docs chunk")).toBeTruthy();
});
it("shows diff stats in the tool call header for structured edit patches", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("+1")).toBeTruthy();
expect(screen.getByText("-1")).toBeTruthy();
});
it("does not show edit diff stats on approval tool headers", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.queryByText("+1")).toBeNull();
expect(screen.queryByText("-1")).toBeNull();
});
it("uses explicit MCP target fields as the approval-wrapped tool title", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("playwright / browser_close")).toBeTruthy();
expect(screen.queryByText("Approval")).toBeNull();
});
it("renders the image generation prompt without duplicating artifact output", async () => {
setAgentGuiI18nTestLocale("en");
const readFile = vi.fn().mockResolvedValue({
bytes: new Uint8Array([137, 80, 78, 71])
});
window.agentHostApi = {
...(window.agentHostApi ?? {}),
workspace: {
...(window.agentHostApi?.workspace ?? {}),
readFile
}
} as typeof window.agentHostApi;
Object.defineProperty(URL, "createObjectURL", {
configurable: true,
value: vi.fn(() => "blob:tool-image-preview")
});
Object.defineProperty(URL, "revokeObjectURL", {
configurable: true,
value: vi.fn()
});
render(
);
expect(screen.getByText(/Revised prompt:/)).toBeTruthy();
expect(
screen.queryByRole("img", { name: "Image generation preview" })
).toBeNull();
expect(screen.queryByText("Output")).toBeNull();
expect(readFile).not.toHaveBeenCalled();
});
it("does not duplicate historical Codex image output from savedPath", async () => {
setAgentGuiI18nTestLocale("en");
const savedPath =
"/Users/demo/.tutti/agent/runs/session/codex-home/generated_images/thread/ig_legacy.png";
const readFile = vi.fn().mockResolvedValue({
bytes: new Uint8Array([137, 80, 78, 71])
});
window.agentHostApi = {
...(window.agentHostApi ?? {}),
workspace: {
...(window.agentHostApi?.workspace ?? {}),
readFile
}
} as typeof window.agentHostApi;
Object.defineProperty(URL, "createObjectURL", {
configurable: true,
value: vi.fn(() => "blob:legacy-tool-image-preview")
});
Object.defineProperty(URL, "revokeObjectURL", {
configurable: true,
value: vi.fn()
});
render(
);
expect(
screen.queryByRole("img", { name: "Image generation preview" })
).toBeNull();
expect(readFile).not.toHaveBeenCalled();
});
it("does not show an output loading placeholder for active image generation cards", async () => {
setAgentGuiI18nTestLocale("en");
const readFile = vi
.fn()
.mockRejectedValue(new Error("ENOENT: generated image not ready"));
window.agentHostApi = {
...(window.agentHostApi ?? {}),
workspace: {
...(window.agentHostApi?.workspace ?? {}),
readFile
}
} as typeof window.agentHostApi;
render(
);
expect(screen.getByText(/Revised prompt:/)).toBeTruthy();
expect(screen.queryByText("Loading preview...")).toBeNull();
expect(
screen.queryByRole("img", { name: "Image generation preview" })
).toBeNull();
expect(readFile).not.toHaveBeenCalled();
});
it("does not render image-only output inside the tool call card", async () => {
setAgentGuiI18nTestLocale("en");
const readFile = vi.fn().mockResolvedValue({
bytes: new Uint8Array([137, 80, 78, 71])
});
window.agentHostApi = {
...(window.agentHostApi ?? {}),
workspace: {
...(window.agentHostApi?.workspace ?? {}),
readFile
}
} as typeof window.agentHostApi;
Object.defineProperty(URL, "createObjectURL", {
configurable: true,
value: vi.fn(() => "blob:image-only-preview")
});
Object.defineProperty(URL, "revokeObjectURL", {
configurable: true,
value: vi.fn()
});
render(
);
expect(
screen.queryByRole("img", { name: "Image generation preview" })
).toBeNull();
expect(screen.queryByText("Input")).toBeNull();
expect(screen.queryByText("Output")).toBeNull();
expect(readFile).not.toHaveBeenCalled();
});
it("does not allow expanding web fetch cards without displayable content", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("Fetch webpage")).toBeTruthy();
expect(screen.getByText("running")).toBeTruthy();
expect(screen.queryByRole("button", { expanded: false })).toBeNull();
expect(screen.queryByRole("button", { name: /Fetch webpage/ })).toBeNull();
expect(
document.querySelector(".workspace-agents-status-panel__detail-tool-body")
).not.toBeInTheDocument();
});
it("does not allow expanding edit cards without displayable file content", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("Edit file")).toBeTruthy();
expect(screen.getByText("completed")).toBeTruthy();
expect(screen.queryByRole("button", { expanded: false })).toBeNull();
expect(
document.querySelector(".workspace-agents-status-panel__detail-tool-body")
).not.toBeInTheDocument();
});
it("shows added-line stats for write content without a diff patch", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
expect(screen.getByText("+2")).toBeTruthy();
});
it("hides diff stats for failed writes that still carry proposed content", async () => {
setAgentGuiI18nTestLocale("en");
render(
File has not been read yet. Read it first before writing to it."
}
}
})
)}
/>
);
expect(screen.queryByText("+1")).toBeNull();
expect(screen.getByText("failed")).toBeTruthy();
});
it("keeps edit cards collapsed by default and expands detail on demand", async () => {
setAgentGuiI18nTestLocale("en");
render(
);
const toggle = screen.getByRole("button", { expanded: false });
expect(toggle).toHaveAttribute("aria-expanded", "false");
expect(
document.querySelector(".workspace-agents-status-panel__detail-tool-body")
).not.toBeInTheDocument();
fireEvent.click(toggle);
await flushCollapsibleRevealFrames();
expect(toggle).toHaveAttribute("aria-expanded", "true");
expect(screen.getByText("app.ts")).toBeTruthy();
const changedLine = screen.getByText("const ready = true");
expect(changedLine).toBeTruthy();
await waitFor(() =>
expect(changedLine.closest(".agent-collapsible-reveal")).toHaveAttribute(
"data-expanded",
"true"
)
);
});
it("keeps collapsed tool card details unmounted while idle", async () => {
setAgentGuiI18nTestLocale("en");
let idleCallback: IdleRequestCallback | null = null;
vi.stubGlobal("requestIdleCallback", (callback: IdleRequestCallback) => {
idleCallback = callback;
return 1;
});
vi.stubGlobal("cancelIdleCallback", () => undefined);
render(
);
expect(screen.queryByText("@@ -1 +1 @@")).toBeNull();
act(() => {
idleCallback?.({
didTimeout: false,
timeRemaining: () => 50
});
});
expect(
document.querySelector(".workspace-agents-status-panel__detail-tool-body")
).not.toBeInTheDocument();
});
});
async function flushCollapsibleRevealFrames(): Promise {
await flushAnimationFrame();
await flushAnimationFrame();
}
async function flushAnimationFrame(): Promise {
await act(async () => {
await new Promise((resolve) => {
requestAnimationFrame(() => resolve());
});
});
}
function toolCall(
overrides: Partial = {}
): WorkspaceAgentSessionDetailToolCall {
return {
id: "call:1",
name: "Tool",
toolName: null,
callType: "tool",
status: "Completed",
statusKind: "completed",
summary: "",
payload: null,
...overrides
};
}