diff --git a/frontend/src/features/agents/CustomAgentChatView.test.tsx b/frontend/src/features/agents/CustomAgentChatView.test.tsx index cf55fd86..9f3afc69 100644 --- a/frontend/src/features/agents/CustomAgentChatView.test.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.test.tsx @@ -212,6 +212,7 @@ describe("CustomAgentChatView", () => { { width: 240, mode: "full-width" }, ])("keeps its conversation access clear of cell controls at $width px ($mode)", ({ width, mode }) => { const agent = { + launchAgentChat: vi.fn(), reattachAgentChat: vi.fn(async (sessionId: string) => ({ sessionId, scrollback: [] })), sendAgentChat: vi.fn(), cancelAgentChat: vi.fn(), @@ -263,6 +264,104 @@ describe("CustomAgentChatView", () => { } }); + it.each([ + { width: 600, layout: "inline" }, + { width: 360, layout: "equal-width-row" }, + { width: 240, layout: "narrow-wrap" }, + ])("keeps composer actions usable and visually aligned at $width px ($layout)", async ({ width, layout }) => { + const agent = { + launchAgentChat: vi.fn(), + reattachAgentChat: vi.fn(async (sessionId: string) => ({ sessionId, scrollback: [] })), + sendAgentChat: vi.fn(), + cancelAgentChat: vi.fn(), + closeAgentChat: vi.fn(), + }; + + render( +
+ null) } } as unknown as Gateways}> + + +
, + ); + + const attach = screen.getByRole("button", { name: "Joindre un fichier" }); + const send = screen.getByRole("button", { name: "Envoyer" }); + const actions = attach.parentElement!; + const composer = actions.parentElement!; + expect(screen.getByTestId("composer-cell").style.width).toBe(`${width}px`); + expect(attach.textContent).toBe("Joindre"); + expect(attach.className).toContain("min-h-11"); + expect(attach.className).toContain("border-border"); + expect(send.className).toContain("min-h-11"); + expect(send.className).toContain("bg-primary"); + await waitFor(() => expect((attach as HTMLButtonElement).disabled).toBe(false)); + expect((send as HTMLButtonElement).disabled).toBe(true); + expect(actions.className).toContain("custom-agent-chat-composer-actions"); + expect(composer.className).toContain("custom-agent-chat-composer"); + + if (layout === "equal-width-row") { + expect(themeCss).toContain(".custom-agent-chat-composer-action {\n flex: 1 1 0;"); + } else if (layout === "narrow-wrap") { + expect(themeCss).toContain(".custom-agent-chat-composer-actions {\n flex-wrap: wrap;"); + expect(themeCss).toContain('.custom-agent-chat-composer-actions[data-busy="true"]'); + } + }); + + it("keeps file selection behavior while exposing its in-progress state", async () => { + const picked = deferred(); + const pickFile = vi.fn(() => picked.promise); + const agent = { + launchAgentChat: vi.fn(), + reattachAgentChat: vi.fn(async (sessionId: string) => ({ sessionId, scrollback: [] })), + sendAgentChat: vi.fn(), + cancelAgentChat: vi.fn(), + closeAgentChat: vi.fn(), + }; + render( + + + , + ); + + await waitFor(() => + expect(agent.reattachAgentChat).toHaveBeenCalledWith("chat-1", expect.any(Function)), + ); + await waitFor(() => + expect((screen.getByRole("button", { name: "Joindre un fichier" }) as HTMLButtonElement).disabled).toBe(false), + ); + fireEvent.click(screen.getByRole("button", { name: "Joindre un fichier" })); + expect(pickFile).toHaveBeenCalledTimes(1); + expect(screen.getByRole("button", { name: "Joindre un fichier" }).textContent).toContain("Ajout…"); + expect((screen.getByRole("button", { name: "Joindre un fichier" }) as HTMLButtonElement).disabled).toBe(true); + + await act(async () => picked.resolve("/tmp/report.pdf")); + expect(await screen.findByText("Fichier joint: report.pdf")).toBeTruthy(); + expect(screen.getByRole("button", { name: "Joindre un fichier" }).textContent).toBe("Joindre"); + }); + it("renders the project icon as a non-interactive custom-CLI watermark and updates it immediately", async () => { const agent = { reattachAgentChat: vi.fn(async (sessionId: string) => ({ sessionId, scrollback: [] })), diff --git a/frontend/src/features/agents/CustomAgentChatView.tsx b/frontend/src/features/agents/CustomAgentChatView.tsx index 24b031a3..fc8b7bf5 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -1093,6 +1093,7 @@ export function CustomAgentChatView({ ? (chatAttachmentDraftCache.get(initialAttachmentCacheKey) ?? []) : [], ); + const [attachmentPicking, setAttachmentPicking] = useState(false); const [queuedMessages, setQueuedMessages] = useState( () => chatMessageQueueCache.get(initialMessageQueueKey) ?? [], ); @@ -2597,16 +2598,21 @@ export function CustomAgentChatView({ } async function pickAttachment() { - const path = await system.pickFile(); - if (path) { - setAttachments((prev) => [ - ...prev, - { - id: `path-${Date.now()}-${Math.random().toString(36).slice(2)}`, - label: pathBasename(path), - input: { path, sourceKind: "localFile" }, - }, - ]); + setAttachmentPicking(true); + try { + const path = await system.pickFile(); + if (path) { + setAttachments((prev) => [ + ...prev, + { + id: `path-${Date.now()}-${Math.random().toString(36).slice(2)}`, + label: pathBasename(path), + input: { path, sourceKind: "localFile" }, + }, + ]); + } + } finally { + setAttachmentPicking(false); } } @@ -3608,8 +3614,8 @@ export function CustomAgentChatView({ {composerHistoryWarning}

)} -
-
+
+