/** * F1 — {@link MediatedInput} wired to {@link MockInputGateway} through the real * {@link DIProvider}. Asserts the mediated-input contract (cadrage §4.2/§6): * * - "Envoyer" routes to `InputGateway.submit` with the right args; * - "Interrompre" routes to `InputGateway.interrupt`; * - while busy, "Envoyer" is dimmed/aria-disabled but the enqueue STILL goes * through (forward/fallback — never block the user), and "Interrompre" stays * active. * * Fully offline: gateways are mocks, no backend. */ import { describe, it, expect } from "vitest"; import { render, screen, waitFor, fireEvent } from "@testing-library/react"; import type { Gateways } from "@/ports"; import { MockInputGateway, MockSystemGateway } from "@/adapters/mock"; import { DIProvider } from "@/app/di"; import { MediatedInput } from "./MediatedInput"; function renderInput( input: MockInputGateway, props: Partial> = {}, ) { const gateways = { input, system: new MockSystemGateway(), } as unknown as Gateways; return render( , ); } describe("MediatedInput (with MockInputGateway)", () => { it("mounts and renders the input strip", () => { renderInput(new MockInputGateway()); expect(screen.getByTestId("mediated-input")).toBeTruthy(); }); it("Envoyer routes to gateway.submit with project/agent/text", async () => { const input = new MockInputGateway(); renderInput(input); fireEvent.change(screen.getByLabelText("Message à l'agent"), { target: { value: "hello agent" }, }); fireEvent.click(screen.getByRole("button", { name: "Envoyer" })); await waitFor(() => { expect(input.submits).toEqual([ { projectId: "p1", agentId: "a1", text: "hello agent" }, ]); }); // No interrupt was triggered. expect(input.interrupts).toEqual([]); }); it("does not submit blank/whitespace-only text", async () => { const input = new MockInputGateway(); renderInput(input); fireEvent.change(screen.getByLabelText("Message à l'agent"), { target: { value: " " }, }); fireEvent.click(screen.getByRole("button", { name: "Envoyer" })); expect(input.submits).toEqual([]); }); it("Interrompre routes to gateway.interrupt (not an enqueue)", async () => { const input = new MockInputGateway(); renderInput(input); fireEvent.click(screen.getByRole("button", { name: "Interrompre" })); await waitFor(() => { expect(input.interrupts).toEqual([{ projectId: "p1", agentId: "a1" }]); }); expect(input.submits).toEqual([]); }); it("while busy: Envoyer is aria-disabled but the enqueue still goes through, Interrompre stays active", async () => { const input = new MockInputGateway(); renderInput(input, { busy: true }); const send = screen.getByRole("button", { name: "Envoyer" }); const interrupt = screen.getByRole("button", { name: "Interrompre" }); // Visually disabled (forward/fallback: dimmed, not hard-disabled). expect(send.getAttribute("aria-disabled")).toBe("true"); // Interrompre is never disabled. expect(interrupt.hasAttribute("disabled")).toBe(false); // The enqueue path is NOT blocked while busy. fireEvent.change(screen.getByLabelText("Message à l'agent"), { target: { value: "queued while busy" }, }); fireEvent.click(send); await waitFor(() => { expect(input.submits).toEqual([ { projectId: "p1", agentId: "a1", text: "queued while busy" }, ]); }); }); });