Files
fusion/packages/dashboard/app/components/__tests__/AgentDetailView.editors.test.tsx
Fusion d98c053eda feat(FN-4088): split AgentDetailView tests into seven focused suites with s
Refactors the monolithic `AgentDetailView.test.tsx` into seven focused test suites (core, advanced-settings, chain-of-command, editors, logs-tasks-runs, settings, skills-procedure) plus a shared test helpers module, extracting 5,793 lines from the original 5,406-line file.

Fusion-Task-Id: FN-4088
2026-05-12 07:47:10 -07:00

845 lines
27 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor, fireEvent, act, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import { loadAllAppCss } from "../../test/cssFixture";
import type { AgentHeartbeatRun } from "../../api";
import type { AgentLogEntry } from "@fusion/core";
import { DEFAULT_HEARTBEAT_INTERVAL_MS } from "../../utils/heartbeatIntervals";
import {
MOCK_SKILLS,
createMockAgent,
mockConfirm,
mockDeleteAgent,
mockFetchAgent,
mockFetchAgentBudgetStatus,
mockFetchAgentChildren,
mockFetchAgentLogsWithMeta,
mockFetchAgentMailbox,
mockFetchAgentMemoryFile,
mockFetchAgentMemoryFiles,
mockFetchAgentRunDetail,
mockFetchAgentRunLogs,
mockFetchAgentRuns,
mockFetchAgentTasks,
mockFetchAgents,
mockFetchChainOfCommand,
mockFetchCompanies,
mockFetchDiscoveredSkills,
mockFetchModels,
mockFetchPluginRuntimes,
mockFetchSkillContent,
mockFetchWorkspaceFileContent,
mockMarkMessageRead,
mockResetAgentBudget,
mockSaveAgentMemoryFile,
mockSaveWorkspaceFileContent,
mockStartAgentRun,
mockSubscribeSse,
mockUpdateAgent,
mockUpdateAgentInstructions,
mockUpdateAgentMemory,
mockUpdateAgentSoul,
mockUpdateAgentState,
mockUpdateGlobalSettings,
mockUpgradeAgentHeartbeatProcedure,
setupAgentDetailMocks,
} from "./AgentDetailView.test-helpers";
import { AgentDetailView } from "../AgentDetailView";
describe("AgentDetailView — editors", () => {
beforeEach(() => {
setupAgentDetailMocks();
});
describe("Instructions Tab", () => {
const navigateToInstructions = async (user: ReturnType<typeof userEvent.setup>) => {
await waitFor(() => {
expect(screen.getByText("Instructions")).toBeInTheDocument();
});
await user.click(screen.getByText("Instructions"));
};
it("renders Instructions tab with inline instructions and path fields", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.getByLabelText("Inline Instructions")).toBeInTheDocument();
expect(screen.getByLabelText("Instructions File Path")).toBeInTheDocument();
});
});
it("does not show file editor when instructions path is empty", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.queryByLabelText("File Content")).not.toBeInTheDocument();
});
});
it("shows file editor when instructions path is set", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test-agent.md",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.getByLabelText("File Content")).toBeInTheDocument();
});
});
it("calls fetchWorkspaceFileContent when instructions path is set", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test-agent.md",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(mockFetchWorkspaceFileContent).toHaveBeenCalledWith("project", ".fusion/agents/test-agent.md");
});
});
it("shows file content when fetchWorkspaceFileContent succeeds", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test-agent.md",
}));
mockFetchWorkspaceFileContent.mockResolvedValue({
content: "# Test Agent Instructions\n\nThese are the agent instructions.",
mtime: "2024-01-01T00:00:00.000Z",
size: 60,
});
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.getByLabelText("File Content")).toHaveValue("# Test Agent Instructions\n\nThese are the agent instructions.");
});
});
it("shows error toast when fetchWorkspaceFileContent fails with non-ENOENT error", async () => {
const addToast = vi.fn();
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test-agent.md",
}));
mockFetchWorkspaceFileContent.mockRejectedValue(new Error("Permission denied"));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={addToast}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith(
expect.stringContaining("Failed to load instructions file"),
"error",
);
});
});
it("treats ENOENT as empty file (new file state)", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/new-agent.md",
}));
mockFetchWorkspaceFileContent.mockRejectedValue(new Error("ENOENT: file not found"));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
// Should show empty content (new file state), not show error toast
const fileContent = screen.getByLabelText("File Content") as HTMLTextAreaElement;
expect(fileContent.value).toBe("");
});
});
it("calls updateAgentInstructions with expected payload when saving inline instructions", async () => {
const addToast = vi.fn();
mockUpdateAgentInstructions.mockResolvedValue({} as any);
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={addToast}
/>
);
await navigateToInstructions(user);
const instructionsTextarea = await screen.findByLabelText("Inline Instructions");
await user.clear(instructionsTextarea);
await user.type(instructionsTextarea, "Custom instructions for the agent");
const pathInput = await screen.findByLabelText("Instructions File Path");
await user.clear(pathInput);
await user.type(pathInput, ".fusion/agents/test.md");
await user.click(screen.getByText("Save Instructions"));
await waitFor(() => {
expect(mockUpdateAgentInstructions).toHaveBeenCalledWith(
"agent-001",
{
instructionsText: "Custom instructions for the agent",
instructionsPath: ".fusion/agents/test.md",
},
undefined,
);
});
expect(addToast).toHaveBeenCalledWith("Instructions saved", "success");
});
it("calls saveWorkspaceFileContent when saving file content", async () => {
const addToast = vi.fn();
const onMutationSuccess = vi.fn();
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test.md",
}));
mockFetchWorkspaceFileContent.mockResolvedValue({
content: "Original content",
mtime: "2024-01-01T00:00:00.000Z",
size: 16,
});
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={addToast}
onMutationSuccess={onMutationSuccess}
/>
);
await navigateToInstructions(user);
// Wait for file content to load
await waitFor(() => {
expect(screen.getByLabelText("File Content")).toHaveValue("Original content");
});
// Modify file content
const fileContent = screen.getByLabelText("File Content");
await user.clear(fileContent);
await user.type(fileContent, "Updated content");
// Save file
await user.click(screen.getByText("Save File"));
await waitFor(() => {
expect(mockSaveWorkspaceFileContent).toHaveBeenCalledWith(
"project",
".fusion/agents/test.md",
"Updated content",
);
expect(onMutationSuccess).toHaveBeenCalledWith({ agentId: "agent-001", deleted: false });
});
expect(addToast).toHaveBeenCalledWith("Instructions file saved", "success");
});
it("disables Save Instructions button when no changes", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.getByText("Save Instructions")).toBeDisabled();
});
});
it("disables Save File button when file content is not dirty", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test.md",
}));
mockFetchWorkspaceFileContent.mockResolvedValue({
content: "Original content",
mtime: "2024-01-01T00:00:00.000Z",
size: 16,
});
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
await waitFor(() => {
expect(screen.getByText("Save File")).toBeDisabled();
});
});
it("shows Unsaved changes indicator when file content is dirty", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test.md",
}));
mockFetchWorkspaceFileContent.mockResolvedValue({
content: "Original content",
mtime: "2024-01-01T00:00:00.000Z",
size: 16,
});
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// Wait for file content to load
await waitFor(() => {
expect(screen.getByLabelText("File Content")).toHaveValue("Original content");
});
// Modify file content
const fileContent = screen.getByLabelText("File Content");
await user.clear(fileContent);
await user.type(fileContent, "Modified content");
await waitFor(() => {
expect(screen.getByText("Unsaved changes")).toBeInTheDocument();
});
});
it("forwards projectId to updateAgentInstructions", async () => {
const addToast = vi.fn();
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
projectId="proj_456"
onClose={vi.fn()}
addToast={addToast}
/>
);
await navigateToInstructions(user);
const instructionsTextarea = await screen.findByLabelText("Inline Instructions");
await user.clear(instructionsTextarea);
await user.type(instructionsTextarea, "Custom instructions");
await user.click(screen.getByText("Save Instructions"));
await waitFor(() => {
expect(mockUpdateAgentInstructions).toHaveBeenCalledWith(
"agent-001",
expect.objectContaining({
instructionsText: "Custom instructions",
}),
"proj_456",
);
});
});
it("toggles between edit and preview mode for inline instructions", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsText: "# Test\n\nThis is a test.",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// Default: edit mode should be active - verify textarea is present
await waitFor(() => {
expect(screen.getByLabelText("Inline Instructions")).toBeInTheDocument();
});
// Find and verify the toggle buttons exist
const previewBtn = screen.getByTestId("instructions-preview-toggle");
expect(previewBtn).toBeInTheDocument();
// Click Preview button
await user.click(previewBtn);
// After clicking, the textarea should be gone and preview should appear
await waitFor(() => {
expect(screen.queryByLabelText("Inline Instructions")).not.toBeInTheDocument();
});
// Check for markdown preview
const preview = document.querySelector(".markdown-body");
expect(preview).toBeInTheDocument();
// Click Edit button to go back
const editBtn = screen.getByTestId("instructions-edit-toggle");
await user.click(editBtn);
// Should be back in edit mode
await waitFor(() => {
expect(screen.getByLabelText("Inline Instructions")).toBeInTheDocument();
});
});
it("renders markdown content in preview mode for inline instructions", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsText: "# Test Instructions\n\nThis is **bold** and this is _italic_.",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// Click Preview button
await user.click(screen.getByTestId("instructions-preview-toggle"));
await waitFor(() => {
// Should render markdown elements
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Test Instructions");
expect(document.querySelector(".markdown-body")).toBeInTheDocument();
});
});
it("shows placeholder when inline instructions are empty in preview mode", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// Click Preview button when instructions are empty
await user.click(screen.getByTestId("instructions-preview-toggle"));
await waitFor(() => {
expect(screen.getByText("No inline instructions defined yet. Switch to Edit mode to add instructions.")).toBeInTheDocument();
});
});
it("hides save button when in preview mode for inline instructions", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// Save button should be visible in edit mode
await waitFor(() => {
expect(screen.getByText("Save Instructions")).toBeInTheDocument();
});
// Click Preview button
await user.click(screen.getByTestId("instructions-preview-toggle"));
// Save button should be hidden
await waitFor(() => {
expect(screen.queryByText("Save Instructions")).not.toBeInTheDocument();
});
});
it("does not affect file path section when toggling inline instructions preview", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
instructionsPath: ".fusion/agents/test-agent.md",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToInstructions(user);
// File path section should be visible
await waitFor(() => {
expect(screen.getByLabelText("Instructions File Path")).toBeInTheDocument();
});
// Toggle to preview mode
await user.click(screen.getByTestId("instructions-preview-toggle"));
// File path should still be visible
await waitFor(() => {
expect(screen.getByLabelText("Instructions File Path")).toBeInTheDocument();
});
// Toggle back to edit mode
await user.click(screen.getByTestId("instructions-edit-toggle"));
// File path should still be visible
await waitFor(() => {
expect(screen.getByLabelText("Instructions File Path")).toBeInTheDocument();
});
});
});
// ── Soul Tab ────────────────────────────────────────────────────────────────
describe("Soul Tab", () => {
const navigateToSoul = async (user: ReturnType<typeof userEvent.setup>) => {
await waitFor(() => {
expect(screen.getByText("Soul")).toBeInTheDocument();
});
await user.click(screen.getByText("Soul"));
};
it("renders Soul tab with textarea by default", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToSoul(user);
await waitFor(() => {
expect(screen.getByLabelText("Agent Soul")).toBeInTheDocument();
expect(screen.getByText("Edit")).toBeInTheDocument();
expect(screen.getByText("Preview")).toBeInTheDocument();
});
});
it("toggles between edit and preview mode", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({
soul: "# Agent Soul\n\nThis agent is **helpful** and _creative_.",
}));
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToSoul(user);
// Default: edit mode
await waitFor(() => {
expect(screen.getByLabelText("Agent Soul")).toBeInTheDocument();
});
// Click Preview
await user.click(screen.getByText("Preview"));
await waitFor(() => {
expect(screen.queryByLabelText("Agent Soul")).not.toBeInTheDocument();
expect(document.querySelector(".markdown-body")).toBeInTheDocument();
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Agent Soul");
});
// Click Edit
await user.click(screen.getByText("Edit"));
await waitFor(() => {
expect(screen.getByLabelText("Agent Soul")).toBeInTheDocument();
});
});
it("shows placeholder when soul is empty in preview mode", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToSoul(user);
await user.click(screen.getByText("Preview"));
await waitFor(() => {
expect(screen.getByText("No soul defined yet. Switch to Edit mode to define the agent's personality.")).toBeInTheDocument();
});
});
it("hides save button when in preview mode", async () => {
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={vi.fn()}
/>
);
await navigateToSoul(user);
await waitFor(() => {
expect(screen.getByText("Save Soul")).toBeInTheDocument();
});
await user.click(screen.getByText("Preview"));
await waitFor(() => {
expect(screen.queryByText("Save Soul")).not.toBeInTheDocument();
});
});
it("calls updateAgentSoul when saving soul", async () => {
const addToast = vi.fn();
mockUpdateAgentSoul.mockResolvedValue({} as any);
const user = userEvent.setup();
render(
<AgentDetailView
agentId="agent-001"
onClose={vi.fn()}
addToast={addToast}
/>
);
await navigateToSoul(user);
const textarea = await screen.findByLabelText("Agent Soul");
await user.clear(textarea);
await user.type(textarea, "This is the agent's new soul");
await user.click(screen.getByText("Save Soul"));
await waitFor(() => {
expect(mockUpdateAgentSoul).toHaveBeenCalledWith("agent-001", "This is the agent's new soul", undefined);
expect(addToast).toHaveBeenCalledWith("Soul saved", "success");
});
});
});
// ── Memory Tab ─────────────────────────────────────────────────────────────
describe("Memory Tab", () => {
const navigateToMemory = async (user: ReturnType<typeof userEvent.setup>) => {
await waitFor(() => {
expect(screen.getByText("Agent Memory")).toBeInTheDocument();
});
await user.click(screen.getByText("Agent Memory"));
};
it("renders Memory tab with textarea by default", async () => {
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await waitFor(() => {
expect(screen.getByLabelText("Agent Memory")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Edit mode" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Preview mode" })).toBeInTheDocument();
});
});
it("toggles between edit and preview mode", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({ memory: "# Agent Memory\n\n- Item 1\n- Item 2" }));
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await user.click(screen.getByRole("button", { name: "Preview mode" }));
await waitFor(() => {
expect(screen.queryByLabelText("Agent Memory")).not.toBeInTheDocument();
expect(document.querySelector(".markdown-body")).toBeInTheDocument();
});
await user.click(screen.getByRole("button", { name: "Edit mode" }));
await waitFor(() => {
expect(screen.getByLabelText("Agent Memory")).toBeInTheDocument();
});
});
it("shows placeholder when memory is empty in preview mode", async () => {
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await user.click(screen.getByRole("button", { name: "Preview mode" }));
await waitFor(() => {
expect(screen.getByText("No agent memory defined yet. Switch to Edit mode to add memory content.")).toBeInTheDocument();
});
});
it("hides save button when in preview mode", async () => {
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
expect(screen.getByText("Save Memory")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Preview mode" }));
await waitFor(() => expect(screen.queryByText("Save Memory")).not.toBeInTheDocument());
});
it("hides inline Edit button when agent is running", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "running", memory: "This agent has memory." }));
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await waitFor(() => {
expect(screen.getByRole("button", { name: "Preview mode" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Edit mode" })).not.toBeInTheDocument();
});
});
it("can switch inline memory to preview mode when agent is running", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "running", memory: "Agent memory content" }));
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await user.click(screen.getByRole("button", { name: "Preview mode" }));
await waitFor(() => expect(document.querySelector(".markdown-body")).toBeInTheDocument());
});
it("renders memory file preview markdown and toggles back to edit", async () => {
mockFetchAgentMemoryFile.mockResolvedValue({ path: ".fusion/agent-memory/agent-001/MEMORY.md", content: "# Heading\n\n- entry" } as any);
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await user.click(await screen.findByRole("button", { name: "Memory file preview mode" }));
await waitFor(() => {
expect(screen.queryByPlaceholderText("Select a memory file to view and edit its content...")).not.toBeInTheDocument();
expect(screen.getByText("Heading")).toBeInTheDocument();
expect(screen.queryByText("Save Memory File")).not.toBeInTheDocument();
});
await user.click(screen.getByRole("button", { name: "Memory file edit mode" }));
await waitFor(() => {
expect(screen.getByPlaceholderText("Select a memory file to view and edit its content...")).toBeInTheDocument();
});
});
it("shows memory file preview placeholder when selected file is empty", async () => {
mockFetchAgentMemoryFile.mockResolvedValue({ path: ".fusion/agent-memory/agent-001/MEMORY.md", content: "" } as any);
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await user.click(await screen.findByRole("button", { name: "Memory file preview mode" }));
await waitFor(() => {
expect(screen.getByText("No memory file content yet. Switch to Edit mode to add content.")).toBeInTheDocument();
});
});
it("hides memory file edit button and disables save button for running agents", async () => {
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "running" }));
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={vi.fn()} />);
await navigateToMemory(user);
await waitFor(() => {
expect(screen.getByRole("button", { name: "Memory file preview mode" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Memory file edit mode" })).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save Memory File" })).toBeDisabled();
});
});
it("calls updateAgentMemory when saving memory", async () => {
const addToast = vi.fn();
mockUpdateAgentMemory.mockResolvedValue({} as any);
const user = userEvent.setup();
render(<AgentDetailView agentId="agent-001" onClose={vi.fn()} addToast={addToast} />);
await navigateToMemory(user);
const textarea = await screen.findByLabelText("Agent Memory");
await user.clear(textarea);
await user.type(textarea, "This is the agent's new memory");
await user.click(screen.getByText("Save Memory"));
await waitFor(() => {
expect(mockUpdateAgentMemory).toHaveBeenCalledWith("agent-001", "This is the agent's new memory", undefined);
expect(addToast).toHaveBeenCalledWith("Memory saved", "success");
});
});
});
// ── Skills ─────────────────────────────────────────────────────────────────
});