feat(web): messages progress et usages MCP dépliables/repliables, repliés par défaut — #233
This commit is contained in:
@ -82,6 +82,21 @@ function expectedChatTime(ms: number): string {
|
||||
return `${hour}:${minute}`;
|
||||
}
|
||||
|
||||
function setMeasuredTextOverflow(
|
||||
element: Element,
|
||||
{ scrollHeight, clientHeight }: { scrollHeight: number; clientHeight: number },
|
||||
) {
|
||||
Object.defineProperty(element, "scrollHeight", {
|
||||
value: scrollHeight,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(element, "clientHeight", {
|
||||
value: clientHeight,
|
||||
configurable: true,
|
||||
});
|
||||
fireEvent(window, new Event("resize"));
|
||||
}
|
||||
|
||||
describe("CustomAgentChatView", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@ -1493,6 +1508,127 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.getByText("Tool").parentElement?.textContent).toContain("En cours");
|
||||
});
|
||||
|
||||
it("expands only truncated MCP progress details and preserves expansion across updates", async () => {
|
||||
let emitChunk: ((chunk: unknown) => void) | null = null;
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(
|
||||
(_sessionId: string, _prompt: string, onChunk: (chunk: unknown) => void) =>
|
||||
new Promise<void>((resolve) => {
|
||||
emitChunk = (chunk: unknown) => {
|
||||
onChunk(chunk);
|
||||
if ((chunk as { kind?: string }).kind === "final") resolve();
|
||||
};
|
||||
}),
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="chat-session-1"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={vi.fn()}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
|
||||
target: { value: "inspect mcp payloads" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
await waitFor(() => expect(emitChunk).not.toBeNull());
|
||||
|
||||
act(() => {
|
||||
emitChunk?.({
|
||||
kind: "progress" as const,
|
||||
progress: {
|
||||
progressId: "mcp-large",
|
||||
source: "ideaLocal" as const,
|
||||
kind: "mcp" as const,
|
||||
stage: "started" as const,
|
||||
label: "idea_ticket_read",
|
||||
toolName: "idea_ticket_read",
|
||||
text: "line 1\nline 2\nline 3\nline 4\nline 5",
|
||||
},
|
||||
});
|
||||
emitChunk?.({
|
||||
kind: "progress" as const,
|
||||
progress: {
|
||||
progressId: "mcp-small",
|
||||
source: "ideaLocal" as const,
|
||||
kind: "mcp" as const,
|
||||
stage: "started" as const,
|
||||
label: "idea_memory_read",
|
||||
toolName: "idea_memory_read",
|
||||
text: "short payload",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const details = await screen.findAllByTestId("chat-progress-detail");
|
||||
expect(details).toHaveLength(2);
|
||||
setMeasuredTextOverflow(details[0], { scrollHeight: 120, clientHeight: 48 });
|
||||
setMeasuredTextOverflow(details[1], { scrollHeight: 24, clientHeight: 24 });
|
||||
|
||||
const expand = await screen.findByRole("button", { name: "Afficher plus" });
|
||||
expect(expand.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(expand.getAttribute("aria-controls")).toBe(details[0].id);
|
||||
expect(screen.queryAllByRole("button", { name: "Afficher plus" })).toHaveLength(1);
|
||||
|
||||
fireEvent.click(expand);
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Afficher moins" }).getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(screen.queryByRole("button", { name: "Afficher plus" })).toBeNull();
|
||||
|
||||
act(() => {
|
||||
emitChunk?.({
|
||||
kind: "progress" as const,
|
||||
progress: {
|
||||
progressId: "mcp-large",
|
||||
source: "ideaLocal" as const,
|
||||
kind: "mcp" as const,
|
||||
stage: "delta" as const,
|
||||
label: "idea_ticket_read",
|
||||
toolName: "idea_ticket_read",
|
||||
text: "updated line 1\nupdated line 2\nupdated line 3\nupdated line 4\nupdated line 5",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.getByText(/updated line 5/)).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Afficher moins" }).getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(screen.queryByRole("button", { name: "Afficher plus" })).toBeNull();
|
||||
});
|
||||
|
||||
it("renders progress immediately when the chunk callback fires", async () => {
|
||||
let emitChunk: ((chunk: unknown) => void) | null = null;
|
||||
const agent = {
|
||||
|
||||
Reference in New Issue
Block a user