Files
IdeaSDK/frontend/src/features/terminals/MediatedInput.test.tsx
Blomios eca2ba95c4 feat(agent): conversation par paire + entrée médiée + pivot terminal/MCP
Coeur inter-agents consolidé et surface front réalignée sur la décision
"terminal natif PTY, pas d'UI chat" (Option 1).

Domaine
- nouveaux modules conversation, mailbox, input, fileguard (ports + types)
- orchestrator/profile/events étendus (conversation par paire, FIFO)

Application / Infrastructure
- orchestrator/service + context_guard : sérialisation FIFO par agent,
  garde RW mémoire/contexte, dispatch ask/reply
- adapters in-memory conversation / mailbox / input / fileguard
- registry session + lifecycle agent durcis (1 agent = 1 session vivante)
- outils MCP idea_* alignés sur le nouveau dispatch

Frontend
- MediatedInput + useAgentBusy : entrée utilisateur médiée par IdeA,
  terminal = vue sortie inchangée
- suppression de la vue chat structurée (AgentChatView) — abandonnée
- adapter input + ports mis à jour

Divers
- .ideai/ : mémoire projet + briefs de cadrage versionnés ;
  requests/ runtime ignoré ; agents projet réels (DevBackend/DevFrontend/QA)

Tests : Rust (domain/application/infrastructure/app-tauri) + front (346) verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 07:33:04 +02:00

109 lines
3.6 KiB
TypeScript

/**
* 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<React.ComponentProps<typeof MediatedInput>> = {},
) {
const gateways = {
input,
system: new MockSystemGateway(),
} as unknown as Gateways;
return render(
<DIProvider gateways={gateways}>
<MediatedInput projectId="p1" agentId="a1" {...props} />
</DIProvider>,
);
}
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" },
]);
});
});
});