feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): lot utilisateur #325 #326 — icônes projet avec watermark CLI custom et localisation FR/EN (QA verte)

Lot en un seul commit: les deux features partagent les mêmes contrats
(domain/settings.rs, DTO, commandes Tauri/HTTP, web-server) et les mêmes
surfaces UI — la séparation par fichiers était impossible sans chirurgie
de hunks risquant des états intermédiaires non compilables (convention
lot des batches #310-#313 et #315).

#325 — icône configurable par projet, watermark CLI custom:
- domaine: ProjectIconSet (devicon, simple-icons, lucide, idea),
  ProjectIconRef validé (nom ASCII 1..=64, -_. internes), champ icon
  optionnel sur ProjectSettings; sérialisation camelCase et try_from
  strict
- application: UpdateProjectSettings.execute accepte un patch
  theme_color OU icon (absent = inchangé, null = défaut produit), tests
  in-dependants avec store mémoire
- backend/app-tauri/web-server: ProjectIconRefDto, UpdateProjectSettingsRequestDto
  avec icon optionnel (désérialisation additive), commandes
  update_project_settings alignées Tauri/HTTP, tests DTO et HTTP
  (défaut null, set, clear, nom invalide rejeté)
- frontend: catalogue projectIcons (recherche EN), ProjectIconPicker en
  grille multi-lignes dans Paramètres projet, affichage de l'icône dans
  ProjectTabs et watermark grand format à faible opacité en fond de CLI
  custom; ports ProjectSettings.icon, Partial<ProjectSettings>,
  adapters tauri/mock/http et tests alignés

#326 — localisation FR/EN et traduction des textes IdeA:
- domaine: IdeaLanguage (fr | en), champ language optionnel sur
  IdeaUserSettings (absence = défaut produit, série additive validée)
- backend/web-server: UserSettingsDto.language, update_user_settings
  accepte language, tests HTTP alignés; fixtures structured_launch_d3
  mises à jour
- frontend: i18n/ (catalogue FR/EN, I18nProvider, scan anti-régression
  des sources visibles), GeneralSettings avec sélecteur de langue dans
  les paramètres, setupI18n dans vitest; migration de l'ensemble des
  textes hardcodés vers le catalogue sur toutes les surfaces (agents,
  layout, projets, tickets, sprints, web, plugins, permissions, git,
  templates, skills, mémoire, model servers, embedder, first-run,
  devices, scheduled tasks, workstate, dialogs), FR conservé par défaut;
  recherche d'icônes #325 en anglais conformément au ticket

QA verte sur feature/batch-325-326-project-icons-localisation. Aucun push.
This commit is contained in:
2026-09-11 21:53:56 +02:00
parent cd744b5f18
commit 1a1429f10e
157 changed files with 4531 additions and 3129 deletions

View File

@ -11,6 +11,7 @@ import {
__resetProjectCliInteractionActivityForTests,
hasProjectCliInteraction,
} from "@/features/projects/projectCliInteractionActivity";
import { publishProjectIconChanged } from "@/features/projects/projectIcons";
import {
PluginCommandRegistry,
PluginLayoutRegistry,
@ -197,6 +198,23 @@ describe("CustomAgentChatView", () => {
__resetProjectCliInteractionActivityForTests();
});
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: [] })),
sendAgentChat: vi.fn(), cancelAgentChat: vi.fn(), closeAgentChat: vi.fn(),
};
const project = { readProjectSettings: vi.fn(async () => ({ themeColor: null, icon: { set: "devicon" as const, name: "python" } })) };
render(<DIProvider gateways={{ agent, project, 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-1" conversationId="conversation-1" onSessionId={vi.fn()} onConversationId={vi.fn()} />
</DIProvider>);
const watermark = await screen.findByTestId("project-icon-watermark");
expect(watermark.style.pointerEvents).toBe("none");
expect(watermark.textContent).toBe("Py");
publishProjectIconChanged({ projectId: "project-1", icon: { set: "lucide", name: "star" } });
await waitFor(() => expect(watermark.textContent).toBe("★"));
});
it("settles an isolated conversation id without recovered turns as an actionable unavailable state (#307/#314)", async () => {
const agent = {
launchAgentChat: vi.fn(async () => ({
@ -246,7 +264,7 @@ describe("CustomAgentChatView", () => {
expect(
await screen.findByText("La conversation liée n’a pas pu être restaurée."),
).toBeTruthy();
expect(screen.getByText("Son transcript n’est plus disponible après la reprise.")).toBeTruthy();
expect(screen.getByText("Sa transcription n’est plus disponible après la reprise.")).toBeTruthy();
expect(
screen.queryByText("Conversation liée — récupération du transcript en cours."),
).toBeNull();
@ -370,10 +388,10 @@ describe("CustomAgentChatView", () => {
});
expect(
await screen.findByText("------- redemarrage de l'agent -------"),
await screen.findByText("------- redémarrage de l’agent -------"),
).not.toBeNull();
expect(
screen.getByText("------- redemarrage de l'agent termine -------"),
screen.getByText("------- redémarrage de l’agent terminé -------"),
).not.toBeNull();
expect(screen.getAllByTestId("agent-restart-separator")).toHaveLength(2);
@ -403,7 +421,7 @@ describe("CustomAgentChatView", () => {
});
});
expect(
screen.getByText("------- redemarrage de l'agent echoue -------"),
screen.getByText("------- redémarrage de l’agent échoué -------"),
).not.toBeNull();
expect(screen.getByText("runtime unavailable")).not.toBeNull();
});
@ -676,7 +694,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
const composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = await screen.findByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
await waitFor(() => expect(agent.reattachAgentChat).toHaveBeenCalled());
fireEvent.change(composer, { target: { value: "first prompt" } });
@ -712,7 +730,7 @@ describe("CustomAgentChatView", () => {
it("recalls history from the exact end of a multiline draft and restores it verbatim", async () => {
const agent = renderHistoryChat();
const composer = (await screen.findByLabelText(
/message CLI custom/,
/Message CLI personnalisée/,
)) as HTMLTextAreaElement;
await waitFor(() => expect(agent.reattachAgentChat).toHaveBeenCalled());
await sendHistoryPrompt(composer, agent, "historic prompt");
@ -742,7 +760,7 @@ describe("CustomAgentChatView", () => {
it("keeps ArrowUp native away from a simple end caret or during modified/IME input", async () => {
const agent = renderHistoryChat();
const composer = (await screen.findByLabelText(
/message CLI custom/,
/Message CLI personnalisée/,
)) as HTMLTextAreaElement;
await waitFor(() => expect(agent.reattachAgentChat).toHaveBeenCalled());
await sendHistoryPrompt(composer, agent, "historic prompt");
@ -778,7 +796,7 @@ describe("CustomAgentChatView", () => {
it("does not consume ArrowUp with empty history and never mutates recalled entries", async () => {
const agent = renderHistoryChat();
const composer = (await screen.findByLabelText(
/message CLI custom/,
/Message CLI personnalisée/,
)) as HTMLTextAreaElement;
await waitFor(() => expect(agent.reattachAgentChat).toHaveBeenCalled());
@ -838,7 +856,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
let composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
let composer = await screen.findByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "remember me" } });
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledWith(
@ -859,7 +877,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
composer = await screen.findByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.keyDown(composer, { key: "ArrowUp" });
await waitFor(() => expect(composer.value).toBe("remember me"));
});
@ -897,7 +915,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
const composer = await screen.findByLabelText(/message CLI custom/);
const composer = await screen.findByLabelText(/Message CLI personnalisée/);
fireEvent.change(composer, { target: { value: "persist before send" } });
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -911,7 +929,7 @@ describe("CustomAgentChatView", () => {
});
try {
const agent = renderHistoryChat();
const composer = await screen.findByLabelText(/message CLI custom/);
const composer = await screen.findByLabelText(/Message CLI personnalisée/);
fireEvent.change(composer, { target: { value: "still send" } });
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -1020,7 +1038,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "Initial prompt" },
});
nowMs = userReceivedAtMs;
@ -1100,7 +1118,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "Initial prompt" },
});
nowMs = userReceivedAtMs;
@ -1235,9 +1253,9 @@ describe("CustomAgentChatView", () => {
try {
render(<Harness />);
const composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = await screen.findByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
const handle = screen.getByRole("button", {
name: "redimensionner le composer node-1",
name: "Redimensionner l’éditeur node-1",
});
expect(composer.style.height).toBe("64px");
expect(handle.style.cursor).toBe("ns-resize");
@ -1401,7 +1419,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getByText("Initial reply")).toBeTruthy();
expect(screen.getByRole("button", { name: "Envoyer" }).hasAttribute("disabled")).toBe(true);
expect(screen.getByLabelText(/message CLI custom/).hasAttribute("disabled")).toBe(true);
expect(screen.getByLabelText(/Message CLI personnalisée/).hasAttribute("disabled")).toBe(true);
act(() => {
secondReattach.resolve({ sessionId: "chat-session-1", scrollback });
@ -1411,7 +1429,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getByText("Partial answer while away")).toBeTruthy();
expect(screen.queryByText(/Reprise effectuée:/)).toBeNull();
expect(screen.getByRole("button", { name: "Stop" })).toBeTruthy();
expect(screen.getByLabelText(/message CLI custom/).hasAttribute("disabled")).toBe(true);
expect(screen.getByLabelText(/Message CLI personnalisée/).hasAttribute("disabled")).toBe(true);
});
it("restores visible conversation from conversationId cache when layout returns before sessionId", async () => {
@ -1594,7 +1612,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first turn" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -1619,12 +1637,12 @@ describe("CustomAgentChatView", () => {
);
expect(agent.cancelAgentChat).toHaveBeenCalledTimes(1);
resolveCancel();
await waitFor(() => expect(screen.getByText("Request stopped.")).toBeTruthy());
await waitFor(() => expect(screen.getByText("Demande arrêtée.")).toBeTruthy());
expect(hasProjectCliInteraction("project-1")).toBe(false);
expect(onSessionId).not.toHaveBeenCalledWith(null);
expect(agent.stopLiveAgent).not.toHaveBeenCalled();
expect(agent.closeAgentChat).not.toHaveBeenCalled();
const composer = screen.getByLabelText(/message CLI custom/);
const composer = screen.getByLabelText(/Message CLI personnalisée/);
expect(composer).toHaveProperty("disabled", false);
fireEvent.change(composer, {
@ -1696,7 +1714,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
const composer = await screen.findByLabelText(/message CLI custom/);
const composer = await screen.findByLabelText(/Message CLI personnalisée/);
await waitFor(() => expect(composer).toHaveProperty("disabled", true));
expect(screen.getByRole("button", { name: "Stop" })).toBeTruthy();
@ -1791,7 +1809,7 @@ describe("CustomAgentChatView", () => {
);
expect(await screen.findByText("Main attend Architect")).toBeTruthy();
expect(screen.getByLabelText(/message CLI custom/)).toHaveProperty("disabled", true);
expect(screen.getByLabelText(/Message CLI personnalisée/)).toHaveProperty("disabled", true);
});
it("falls back to a fresh launch when reattach reports NOT_FOUND", async () => {
@ -1895,7 +1913,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "hello agent" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -1974,13 +1992,13 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await screen.findByText("final first request");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2117,16 +2135,16 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "ship progress UI" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await screen.findByText("Progress");
await screen.findByText("Progression");
expect(screen.getAllByTestId("chat-turn-marker")).toHaveLength(2);
expect(screen.getByText("turn.started")).toBeTruthy();
expect(screen.getByText("turn.completed")).toBeTruthy();
expect(screen.getByText("Progress").closest("[data-testid='chat-progress-status']")).toBeTruthy();
expect(screen.getByText("Progression").closest("[data-testid='chat-progress-status']")).toBeTruthy();
expect(screen.getByText("Analyse")).toBeTruthy();
expect(screen.getByText("Lecture du contexte disponible.")).toBeTruthy();
expect(screen.getByText("MCP")).toBeTruthy();
@ -2203,13 +2221,13 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await screen.findByText("done first");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2251,7 +2269,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2271,7 +2289,7 @@ describe("CustomAgentChatView", () => {
});
});
expect(screen.getByText("Running first request")).toBeTruthy();
expect(screen.getByText("Tool").parentElement?.textContent).toContain("En cours");
expect(screen.getByText("Outil").parentElement?.textContent).toContain("En cours");
act(() => {
harness.emit(0, {
@ -2289,7 +2307,7 @@ describe("CustomAgentChatView", () => {
});
await screen.findByText("final first request");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2308,7 +2326,7 @@ describe("CustomAgentChatView", () => {
});
});
expect(screen.getByText("Second request context loaded.")).toBeTruthy();
expect(screen.getByText("Progress").parentElement?.textContent).toContain("En cours");
expect(screen.getByText("Progression").parentElement?.textContent).toContain("En cours");
act(() => {
harness.emit(1, {
@ -2379,7 +2397,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2412,7 +2430,7 @@ describe("CustomAgentChatView", () => {
});
await screen.findByText("done first request");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2487,7 +2505,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2522,7 +2540,7 @@ describe("CustomAgentChatView", () => {
});
await screen.findByText("done first request");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2599,7 +2617,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2636,7 +2654,7 @@ describe("CustomAgentChatView", () => {
});
expect(screen.queryByText("Late first request completion")).toBeNull();
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2727,7 +2745,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "delegate after intent" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2831,7 +2849,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "delegate once" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -2882,7 +2900,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getAllByText("validate")).toHaveLength(2);
expect(screen.queryByText("Appel inter-agent")).toBeNull();
expect(screen.queryByText("reply delivered")).toBeNull();
expect(screen.queryByText("Tool")).toBeNull();
expect(screen.queryByText("Outil")).toBeNull();
expect(screen.queryByText("started")).toBeNull();
await waitFor(() =>
expect(screen.queryByRole("status", { name: "Loading" })).toBeNull(),
@ -2939,7 +2957,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "read many tickets" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3032,7 +3050,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "read ticket" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3108,7 +3126,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "download model" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3134,7 +3152,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getAllByTestId("chat-tool-progress")).toHaveLength(1);
expect(screen.queryByText("Downloading 10%")).toBeNull();
expect(screen.getByText("Downloading 75%")).toBeTruthy();
expect(screen.getByText("Tool").parentElement?.textContent).toContain("En cours");
expect(screen.getByText("Outil").parentElement?.textContent).toContain("En cours");
});
it("expands only truncated MCP progress details and preserves expansion across updates", async () => {
@ -3187,7 +3205,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "inspect mcp payloads" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3308,7 +3326,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "stream progress" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3382,7 +3400,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "run tools" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3473,7 +3491,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "call tool" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3499,7 +3517,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getAllByTestId("chat-tool-progress")).toHaveLength(1);
expect(screen.queryByText("Searching...")).toBeNull();
expect(screen.getByText("Found results")).toBeTruthy();
expect(screen.getByText("Tool").parentElement?.textContent).toContain("Terminé");
expect(screen.getByText("Outil").parentElement?.textContent).toContain("Terminé");
act(() => {
emitChunk?.({ kind: "final", content: "done" });
@ -3560,7 +3578,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "ship progress UI" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3580,7 +3598,7 @@ describe("CustomAgentChatView", () => {
});
expect(screen.queryByRole("status", { name: "Loading" })).toBeNull();
expect(screen.getByText("Progress").parentElement?.textContent).toContain("En cours");
expect(screen.getByText("Progression").parentElement?.textContent).toContain("En cours");
act(() => {
emitChunk?.({ kind: "final", content: "done" });
@ -3591,7 +3609,7 @@ describe("CustomAgentChatView", () => {
expect(screen.queryByRole("status", { name: "Loading" })).toBeNull(),
);
expect(screen.getByText("Lecture du contexte disponible.")).toBeTruthy();
expect(screen.getByText("Progress").parentElement?.textContent).toContain("Terminé");
expect(screen.getByText("Progression").parentElement?.textContent).toContain("Terminé");
});
it("ignores late progress chunks after the final answer without reopening the turn", async () => {
@ -3644,7 +3662,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "ship ordered stream" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -3734,7 +3752,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "/c" } });
const menu = await screen.findByRole("listbox", {
@ -3804,7 +3822,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "hello /c" } });
expect(screen.queryByRole("listbox", { name: "suggestions commandes slash" })).toBeNull();
expect(agent.listSlashCommands).not.toHaveBeenCalled();
@ -3889,7 +3907,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "/explain focus this" } });
fireEvent.keyDown(composer, { key: "Enter" });
@ -3958,7 +3976,7 @@ describe("CustomAgentChatView", () => {
expect(await screen.findByText("old conversation")).toBeTruthy();
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "/clean" } });
fireEvent.keyDown(composer, { key: "Enter" });
@ -4053,7 +4071,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "/help" } });
fireEvent.keyDown(composer, { key: "Enter" });
@ -4135,7 +4153,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/);
const composer = screen.getByLabelText(/Message CLI personnalisée/);
fireEvent.change(composer, { target: { value: "/explain" } });
fireEvent.keyDown(composer, { key: "Enter" });
@ -4219,7 +4237,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "/profile" } });
fireEvent.keyDown(composer, { key: "Enter" });
@ -4232,14 +4250,14 @@ describe("CustomAgentChatView", () => {
expect(agent.sendAgentChat).not.toHaveBeenCalled();
const confirmButton = screen.getByRole("button", {
name: "Reset et changer le profil",
name: "Réinitialiser et changer le profil",
}) as HTMLButtonElement;
expect(confirmButton.disabled).toBe(true);
fireEvent.change(screen.getByLabelText("profil cible"), {
fireEvent.change(screen.getByLabelText("Profil cible"), {
target: { value: "codex-high" },
});
fireEvent.click(screen.getByLabelText("Je confirme que la session courante sera reset."));
fireEvent.click(screen.getByLabelText("Je confirme que la session courante sera réinitialisée."));
expect(confirmButton.disabled).toBe(false);
fireEvent.click(confirmButton);
@ -4314,7 +4332,7 @@ describe("CustomAgentChatView", () => {
Object.defineProperty(file, "arrayBuffer", {
value: vi.fn(async () => new Uint8Array([1, 2, 3]).buffer),
});
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.paste(composer, {
clipboardData: {
items: [
@ -4332,7 +4350,7 @@ describe("CustomAgentChatView", () => {
expect(screen.getByTestId("attachment-preview-clipboard.png")).toBeTruthy();
await waitFor(() => expect(composer.value).toBe("[Image 1]"));
fireEvent.click(screen.getByRole("button", { name: "retirer clipboard.png" }));
fireEvent.click(screen.getByRole("button", { name: "Retirer clipboard.png" }));
await waitFor(() =>
expect(screen.queryByText("Fichier joint: clipboard.png")).toBeNull(),
@ -4402,7 +4420,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.paste(screen.getByLabelText(/message CLI custom/), {
fireEvent.paste(screen.getByLabelText(/Message CLI personnalisée/), {
clipboardData: {
items: [],
files: [],
@ -4492,7 +4510,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.paste(screen.getByLabelText(/message CLI custom/), {
fireEvent.paste(screen.getByLabelText(/Message CLI personnalisée/), {
clipboardData: {
items: [],
files: [],
@ -4564,7 +4582,7 @@ describe("CustomAgentChatView", () => {
Object.defineProperty(file, "arrayBuffer", {
value: vi.fn(async () => new Uint8Array([1, 2, 3]).buffer),
});
fireEvent.paste(screen.getByLabelText(/message CLI custom/), {
fireEvent.paste(screen.getByLabelText(/Message CLI personnalisée/), {
clipboardData: {
items: [
{
@ -4645,7 +4663,7 @@ describe("CustomAgentChatView", () => {
Object.defineProperty(file, "arrayBuffer", {
value: vi.fn(async () => new Uint8Array([4, 5, 6]).buffer),
});
fireEvent.paste(screen.getByLabelText(/message CLI custom/), {
fireEvent.paste(screen.getByLabelText(/Message CLI personnalisée/), {
clipboardData: {
items: [],
files: [file],
@ -4716,7 +4734,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "prefix suffix" } });
composer.setSelectionRange(7, 13);
@ -4802,7 +4820,7 @@ describe("CustomAgentChatView", () => {
),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "voir maintenant" } });
composer.setSelectionRange(5, 5);
const file = new File(["ignored"], "cursor.png", { type: "image/png" });
@ -4904,7 +4922,7 @@ describe("CustomAgentChatView", () => {
files: [file],
getData: () => "",
};
const composer = screen.getByLabelText(/message CLI custom/);
const composer = screen.getByLabelText(/Message CLI personnalisée/);
fireEvent.paste(composer, { clipboardData });
await screen.findByText("Fichier joint: duplicate.pdf");
@ -4994,7 +5012,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
await waitFor(() =>
expect((screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement).value).toBe(""),
expect((screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement).value).toBe(""),
);
rerender(
@ -5028,7 +5046,7 @@ describe("CustomAgentChatView", () => {
</DIProvider>,
);
await waitFor(() =>
expect((screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement).value).toBe(""),
expect((screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement).value).toBe(""),
);
});
@ -5083,7 +5101,7 @@ describe("CustomAgentChatView", () => {
await waitFor(() =>
expect(agent.readChatDraft).toHaveBeenCalledWith(scope),
);
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
const composer = screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement;
fireEvent.change(composer, { target: { value: "draft with file" } });
const file = new File(["ignored"], "not-persisted.png", { type: "image/png" });
@ -5159,7 +5177,7 @@ describe("CustomAgentChatView", () => {
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledTimes(1));
expect(agent.clearChatDraft).toHaveBeenCalledWith(scope);
expect((screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement).value).toBe("");
expect((screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement).value).toBe("");
});
it("clears the persisted draft when a slash command is consumed", async () => {
@ -5224,7 +5242,7 @@ describe("CustomAgentChatView", () => {
}),
);
expect(agent.clearChatDraft).toHaveBeenCalledWith(scope);
expect((screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement).value).toBe("");
expect((screen.getByLabelText(/Message CLI personnalisée/) as HTMLTextAreaElement).value).toBe("");
});
it("keeps the chat shell bounded with a scrollable message area and fixed composer", async () => {
@ -5457,7 +5475,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "please recover" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -5531,7 +5549,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "please recover" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -5595,7 +5613,7 @@ describe("CustomAgentChatView", () => {
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
fireEvent.change(screen.getByLabelText(/Message CLI personnalisée/), {
target: { value: "long turn" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
@ -5603,7 +5621,7 @@ describe("CustomAgentChatView", () => {
fireEvent.click(await screen.findByRole("button", { name: "Stop" }));
expect(await screen.findByText("Request already stopped.")).toBeTruthy();
expect(await screen.findByText("Demande déjà arrêtée.")).toBeTruthy();
expect(onSessionId).not.toHaveBeenCalledWith(null);
expect(agent.cancelAgentChat).toHaveBeenCalledWith("gone-session");
expect(agent.stopLiveAgent).not.toHaveBeenCalled();