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:
@ -48,23 +48,23 @@ async function openAgentDetails(agentName: string): Promise<HTMLElement> {
|
||||
const name = await screen.findByText(agentName);
|
||||
const row = name.closest("li") as HTMLElement | null;
|
||||
expect(row).toBeTruthy();
|
||||
const button = within(row!).queryByRole("button", { name: "Details" });
|
||||
const button = within(row!).queryByRole("button", { name: "Détails" });
|
||||
if (button) fireEvent.click(button);
|
||||
return row!;
|
||||
}
|
||||
|
||||
async function openBackgroundTasks(agentName: string): Promise<HTMLElement> {
|
||||
const row = await openAgentDetails(agentName);
|
||||
const toggle = within(row).getByRole("button", { name: /background task/i });
|
||||
const toggle = within(row).getByRole("button", { name: /tâche.*de fond/i });
|
||||
fireEvent.click(toggle);
|
||||
return await within(row).findByLabelText(`${agentName} background tasks`);
|
||||
return await within(row).findByLabelText(`Tâches de fond de ${agentName}`);
|
||||
}
|
||||
|
||||
describe("ProjectWorkStatePanel", () => {
|
||||
it("renders the empty state", async () => {
|
||||
renderPanel();
|
||||
|
||||
expect(await screen.findByText("No agent work state.")).toBeTruthy();
|
||||
expect(await screen.findByText("Aucun état de travail d'agent.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders an idle offline agent", async () => {
|
||||
@ -83,9 +83,9 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Planner")).toBeTruthy();
|
||||
expect(screen.getByText("Offline")).toBeTruthy();
|
||||
expect(screen.queryByText("Idle")).toBeNull();
|
||||
expect(screen.queryByLabelText("Planner tickets")).toBeNull();
|
||||
expect(screen.getByText("Hors ligne")).toBeTruthy();
|
||||
expect(screen.queryByText("Inactif")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Planner")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders a live busy agent with a short ticket", async () => {
|
||||
@ -113,7 +113,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Builder")).toBeTruthy();
|
||||
expect(screen.getByText("Busy")).toBeTruthy();
|
||||
expect(screen.getByText("Occupé")).toBeTruthy();
|
||||
expect(screen.getByText("Ticket 4c65d981")).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -150,8 +150,8 @@ describe("ProjectWorkStatePanel", () => {
|
||||
const stopped = within(childRow).getByText("Arrêté");
|
||||
expect(stopping.className).not.toContain("text-warning");
|
||||
expect(stopped.className).not.toContain("text-warning");
|
||||
expect(within(mainRow).queryByText("Busy")).toBeNull();
|
||||
expect(within(childRow).queryByText("Busy")).toBeNull();
|
||||
expect(within(mainRow).queryByText("Occupé")).toBeNull();
|
||||
expect(within(childRow).queryByText("Occupé")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders an in-progress human ticket", async () => {
|
||||
@ -186,14 +186,14 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Wire the workstate queue UI")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Implementer tickets")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Implementer")).toBeNull();
|
||||
await openAgentDetails("Implementer");
|
||||
const list = await screen.findByLabelText("Implementer tickets");
|
||||
expect(within(list).getByText("#1 In progress")).toBeTruthy();
|
||||
expect(within(list).getByText("Human")).toBeTruthy();
|
||||
const list = await screen.findByLabelText("Tickets de Implementer");
|
||||
expect(within(list).getByText("#1 En cours")).toBeTruthy();
|
||||
expect(within(list).getByText("Humain")).toBeTruthy();
|
||||
expect(within(list).getByText("Wire the workstate queue UI")).toBeTruthy();
|
||||
expect(within(list).getByLabelText("ticket ticket-i")).toBeTruthy();
|
||||
expect(within(list).getByLabelText("9 more characters")).toBeTruthy();
|
||||
expect(within(list).getByLabelText("9 caractères supplémentaires")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps long ticket intent out of the default agent row", async () => {
|
||||
@ -233,7 +233,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
expect(screen.queryByText(longPreview)).toBeNull();
|
||||
expect(screen.getByText("Goal: inspect the entire repository and p...")).toBeTruthy();
|
||||
await openAgentDetails("Git");
|
||||
const list = await screen.findByLabelText("Git tickets");
|
||||
const list = await screen.findByLabelText("Tickets de Git");
|
||||
expect(within(list).getByText(longPreview)).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -275,13 +275,13 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Reviewer")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Reviewer tickets")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Reviewer")).toBeNull();
|
||||
await openAgentDetails("Reviewer");
|
||||
const list = await screen.findByLabelText("Reviewer tickets");
|
||||
const list = await screen.findByLabelText("Tickets de Reviewer");
|
||||
expect(within(list).getByText("QA (agent-qa)")).toBeTruthy();
|
||||
expect(within(list).getByText("Main (agent-ma)")).toBeTruthy();
|
||||
expect(list.textContent?.indexOf("#1 In progress")).toBeLessThan(
|
||||
list.textContent?.indexOf("#2 Queued") ?? -1,
|
||||
expect(list.textContent?.indexOf("#1 En cours")).toBeLessThan(
|
||||
list.textContent?.indexOf("#2 En attente") ?? -1,
|
||||
);
|
||||
});
|
||||
|
||||
@ -381,42 +381,42 @@ describe("ProjectWorkStatePanel", () => {
|
||||
|
||||
expect(await screen.findByText("Runner")).toBeTruthy();
|
||||
expect(screen.getByText("6 bg")).toBeTruthy();
|
||||
expect(screen.queryByText("6 background tasks (3 active, 3 failed)")).toBeNull();
|
||||
expect(screen.queryByLabelText("Runner background tasks")).toBeNull();
|
||||
expect(screen.queryByText("6 tâches de fond (3 actives, 3 en échec)")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Runner")).toBeNull();
|
||||
const list = await openBackgroundTasks("Runner");
|
||||
const cancelButtons = within(list).getAllByRole("button", { name: "Cancel" });
|
||||
const retryButtons = within(list).getAllByRole("button", { name: "Retry" });
|
||||
const cancelButtons = within(list).getAllByRole("button", { name: "Annuler" });
|
||||
const retryButtons = within(list).getAllByRole("button", { name: "Réessayer" });
|
||||
// waiting + queued + cancelling are cancellable; failed + expired + cancelFailed are retryable.
|
||||
expect(cancelButtons.filter((b) => !(b as HTMLButtonElement).disabled)).toHaveLength(3);
|
||||
expect(retryButtons.filter((b) => !(b as HTMLButtonElement).disabled)).toHaveLength(3);
|
||||
expect(list.textContent).toContain("Cancelling");
|
||||
expect(list.textContent).toContain("Cancel failed");
|
||||
expect(list.textContent).toContain("Annulation…");
|
||||
expect(list.textContent).toContain("Échec de l'annulation");
|
||||
const rows = within(list).getAllByRole("listitem");
|
||||
const rowTexts = rows.map((row) => row.textContent ?? "");
|
||||
expect(rowTexts).toHaveLength(6);
|
||||
const cancelFailedRow = rows[0] as HTMLElement;
|
||||
const cancellingRow = rows[1] as HTMLElement;
|
||||
expect(within(cancellingRow).getByText("Cancelling")).toBeTruthy();
|
||||
expect(within(cancellingRow).getByText("Annulation…")).toBeTruthy();
|
||||
expect(
|
||||
(within(cancellingRow).getByRole("button", {
|
||||
name: "Cancel",
|
||||
name: "Annuler",
|
||||
}) as HTMLButtonElement).disabled,
|
||||
).toBe(false);
|
||||
expect(within(cancelFailedRow).getByText("Cancel failed")).toBeTruthy();
|
||||
expect(within(cancelFailedRow).getByText("Échec de l'annulation")).toBeTruthy();
|
||||
expect(
|
||||
(within(cancelFailedRow).getByRole("button", {
|
||||
name: "Retry",
|
||||
name: "Réessayer",
|
||||
}) as HTMLButtonElement).disabled,
|
||||
).toBe(false);
|
||||
|
||||
// Rows are ordered most-recently-updated first (updatedAtMs desc), taskId as
|
||||
// the deterministic tiebreak — locks the fix for the dead finishedAtMs sort.
|
||||
expect(rowTexts[0]).toContain("Cancel failed");
|
||||
expect(rowTexts[1]).toContain("Cancelling");
|
||||
expect(rowTexts[2]).toContain("Cancelled");
|
||||
expect(rowTexts[3]).toContain("Failed");
|
||||
expect(rowTexts[4]).toContain("Pending");
|
||||
expect(rowTexts[5]).toContain("Pending");
|
||||
expect(rowTexts[0]).toContain("Échec de l'annulation");
|
||||
expect(rowTexts[1]).toContain("Annulation…");
|
||||
expect(rowTexts[2]).toContain("Annulée");
|
||||
expect(rowTexts[3]).toContain("Échec");
|
||||
expect(rowTexts[4]).toContain("En attente");
|
||||
expect(rowTexts[5]).toContain("En attente");
|
||||
});
|
||||
|
||||
it("shows a terminal task summary and hides it when absent or non-terminal (#5)", async () => {
|
||||
@ -470,20 +470,20 @@ describe("ProjectWorkStatePanel", () => {
|
||||
|
||||
expect(await screen.findByText("Reporter")).toBeTruthy();
|
||||
expect(screen.getByText("4 bg")).toBeTruthy();
|
||||
expect(screen.queryByText("4 background tasks (1 active, 1 failed)")).toBeNull();
|
||||
expect(screen.queryByLabelText("Reporter background tasks")).toBeNull();
|
||||
expect(screen.queryByText("4 tâches de fond (1 active, 1 en échec)")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tâches de fond de Reporter")).toBeNull();
|
||||
const list = await openBackgroundTasks("Reporter");
|
||||
// Terminal tasks with a summary render it discreetly.
|
||||
expect(
|
||||
within(list).getByLabelText("task done-wit summary").textContent,
|
||||
within(list).getByLabelText("résumé de la tâche done-wit").textContent,
|
||||
).toBe("Build succeeded in 12s");
|
||||
expect(
|
||||
within(list).getByText("Compile error: missing semicolon"),
|
||||
).toBeTruthy();
|
||||
// A completed task without a summary shows no summary line.
|
||||
expect(within(list).queryByLabelText("task done-no- summary")).toBeNull();
|
||||
expect(within(list).queryByLabelText("résumé de la tâche done-no-")).toBeNull();
|
||||
// A still-running task never shows its (non-terminal) summary.
|
||||
expect(within(list).queryByLabelText("task run-with summary")).toBeNull();
|
||||
expect(within(list).queryByLabelText("résumé de la tâche run-with")).toBeNull();
|
||||
expect(
|
||||
within(list).queryByText("should-not-appear-while-running"),
|
||||
).toBeNull();
|
||||
@ -519,29 +519,29 @@ describe("ProjectWorkStatePanel", () => {
|
||||
const view = renderPanel(workState);
|
||||
const list = await openBackgroundTasks("Runner");
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Live" }));
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Direct" }));
|
||||
|
||||
expect(attachSpy).toHaveBeenCalledWith("task-live-output", expect.any(Function));
|
||||
expect(
|
||||
(await within(list).findByLabelText("task task-liv live output")).textContent,
|
||||
(await within(list).findByLabelText("sortie directe de la tâche task-liv")).textContent,
|
||||
).toContain("already printed");
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(1);
|
||||
|
||||
workState._emitBackgroundTaskOutput("task-live-output", "new line\n");
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
within(list).getByLabelText("task task-liv live output").textContent,
|
||||
within(list).getByLabelText("sortie directe de la tâche task-liv").textContent,
|
||||
).toContain("new line"),
|
||||
);
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Detach" }));
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Détacher" }));
|
||||
await waitFor(() =>
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(0),
|
||||
);
|
||||
expect(within(list).queryByLabelText("task task-liv live output")).toBeNull();
|
||||
expect(within(list).queryByLabelText("sortie directe de la tâche task-liv")).toBeNull();
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Live" }));
|
||||
await within(list).findByLabelText("task task-liv live output");
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Direct" }));
|
||||
await within(list).findByLabelText("sortie directe de la tâche task-liv");
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(1);
|
||||
view.unmount();
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(0);
|
||||
@ -563,9 +563,9 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Legacy")).toBeTruthy();
|
||||
expect(screen.getByText("Offline")).toBeTruthy();
|
||||
expect(screen.queryByText("Idle")).toBeNull();
|
||||
expect(screen.queryByLabelText("Legacy tickets")).toBeNull();
|
||||
expect(screen.getByText("Hors ligne")).toBeTruthy();
|
||||
expect(screen.queryByText("Inactif")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Legacy")).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores a stray top-level backgroundTasks array (nested tasks intact) (#5)", async () => {
|
||||
@ -631,10 +631,10 @@ describe("ProjectWorkStatePanel", () => {
|
||||
);
|
||||
|
||||
expect(screen.getByRole("alert").textContent).toContain(
|
||||
"Work panel failed to render.",
|
||||
"Le panneau Travail n'a pas pu s'afficher.",
|
||||
);
|
||||
expect(screen.getByText("render exploded")).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Retry" })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Réessayer" })).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders conversation objective and summary joined by conversation id", async () => {
|
||||
@ -695,18 +695,18 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Continue implementation")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Summarizer tickets")).toBeNull();
|
||||
expect(screen.queryByText("Goal: Ship the Work panel summaries")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Summarizer")).toBeNull();
|
||||
expect(screen.queryByText("Objectif : Ship the Work panel summaries")).toBeNull();
|
||||
expect(
|
||||
screen.queryByText("Previous turn established the adapter boundary."),
|
||||
).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "View" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Copy" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Afficher" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Copier" })).toBeNull();
|
||||
await openAgentDetails("Summarizer");
|
||||
const list = await screen.findByLabelText("Summarizer tickets");
|
||||
expect(within(list).getAllByText("Summary")).toHaveLength(2);
|
||||
const list = await screen.findByLabelText("Tickets de Summarizer");
|
||||
expect(within(list).getAllByText("Résumé")).toHaveLength(2);
|
||||
expect(
|
||||
within(list).getByText("Goal: Ship the Work panel summaries"),
|
||||
within(list).getByText("Objectif : Ship the Work panel summaries"),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText(
|
||||
@ -781,18 +781,18 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
expect(await screen.findByText("Validate partial context")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Contextualizer tickets")).toBeNull();
|
||||
expect(screen.queryByLabelText("Tickets de Contextualizer")).toBeNull();
|
||||
await openAgentDetails("Contextualizer");
|
||||
const list = await screen.findByLabelText("Contextualizer tickets");
|
||||
expect(within(list).getByText("#1 In progress")).toBeTruthy();
|
||||
const list = await screen.findByLabelText("Tickets de Contextualizer");
|
||||
expect(within(list).getByText("#1 En cours")).toBeTruthy();
|
||||
expect(within(list).getByText("Validate partial context")).toBeTruthy();
|
||||
expect(within(list).getByText("Partial")).toBeTruthy();
|
||||
expect(within(list).getByText("Partiel")).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText("Last: Only the latest prompt was available."),
|
||||
within(list).getByText("Dernier : Only the latest prompt was available."),
|
||||
).toBeTruthy();
|
||||
expect(within(list).getByText("#2 Queued")).toBeTruthy();
|
||||
expect(within(list).getByText("#2 En attente")).toBeTruthy();
|
||||
expect(within(list).getByText("Validate unavailable context")).toBeTruthy();
|
||||
expect(within(list).getByText("Unavailable")).toBeTruthy();
|
||||
expect(within(list).getByText("Indisponible")).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText("Summary service unavailable right now."),
|
||||
).toBeTruthy();
|
||||
@ -803,7 +803,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
const system = new MockSystemGateway();
|
||||
renderPanel(workState, system);
|
||||
|
||||
await screen.findByText("No agent work state.");
|
||||
await screen.findByText("Aucun état de travail d'agent.");
|
||||
const spy = vi.spyOn(workState, "getProjectWorkState");
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
@ -831,7 +831,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
const system = new MockSystemGateway();
|
||||
renderPanel(workState, system);
|
||||
|
||||
await screen.findByText("No agent work state.");
|
||||
await screen.findByText("Aucun état de travail d'agent.");
|
||||
const spy = vi.spyOn(workState, "getProjectWorkState");
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
@ -865,7 +865,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
|
||||
expect(await screen.findByText("Delegatee")).toBeTruthy();
|
||||
await openAgentDetails("Delegatee");
|
||||
expect(screen.getByText("#1 In progress")).toBeTruthy();
|
||||
expect(screen.getByText("#1 En cours")).toBeTruthy();
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
@ -895,7 +895,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState, new MockSystemGateway(), { layout, agent });
|
||||
|
||||
await openAgentDetails("Visible");
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Open" }));
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Ouvrir" }));
|
||||
|
||||
expect(host.style.outline).toContain("2px solid");
|
||||
expect(launchSpy).not.toHaveBeenCalled();
|
||||
@ -936,7 +936,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState, new MockSystemGateway(), { layout, agent });
|
||||
|
||||
await openAgentDetails("Hidden");
|
||||
const attach = await screen.findByRole("button", { name: "Attach" });
|
||||
const attach = await screen.findByRole("button", { name: "Attacher" });
|
||||
await waitFor(() => expect((attach as HTMLButtonElement).disabled).toBe(false));
|
||||
fireEvent.click(attach);
|
||||
|
||||
@ -985,9 +985,9 @@ describe("ProjectWorkStatePanel", () => {
|
||||
});
|
||||
|
||||
await openAgentDetails("Hidden");
|
||||
const attach = await screen.findByRole("button", { name: "Attach" });
|
||||
const attach = await screen.findByRole("button", { name: "Attacher" });
|
||||
await waitFor(() => expect((attach as HTMLButtonElement).disabled).toBe(true));
|
||||
expect(await screen.findByText("No empty visible cell.")).toBeTruthy();
|
||||
expect(await screen.findByText("Aucune cellule visible vide.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Stop confirms with a busy warning, stops the live agent, and refreshes Work", async () => {
|
||||
@ -1050,11 +1050,11 @@ describe("ProjectWorkStatePanel", () => {
|
||||
|
||||
await openAgentDetails("Runner");
|
||||
const row = await openAgentDetails("Runner");
|
||||
fireEvent.click(within(row).getByRole("button", { name: "Stop" }));
|
||||
fireEvent.click(within(row).getByRole("button", { name: "Arrêter" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(confirmSpy).toHaveBeenCalledWith(
|
||||
"Stop Runner? The current turn will be interrupted.",
|
||||
"Arrêter Runner ? Le tour en cours sera interrompu.",
|
||||
),
|
||||
);
|
||||
await waitFor(() =>
|
||||
@ -1067,7 +1067,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
const stopping = within(row).getByRole("button", { name: "Arrêt en cours…" });
|
||||
expect((stopping as HTMLButtonElement).disabled).toBe(true);
|
||||
expect(within(row).getAllByText("Arrêt en cours…")).toHaveLength(2);
|
||||
expect(within(row).queryByText("Busy")).toBeNull();
|
||||
expect(within(row).queryByText("Occupé")).toBeNull();
|
||||
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
@ -1096,7 +1096,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
});
|
||||
await waitFor(() => expect(refreshSpy).toHaveBeenCalled());
|
||||
await waitFor(() => expect(within(row).getAllByText("Arrêté")).toHaveLength(1));
|
||||
expect(within(row).queryByText("Busy")).toBeNull();
|
||||
expect(within(row).queryByText("Occupé")).toBeNull();
|
||||
await waitFor(async () => {
|
||||
const leaf = leaves(await layout.loadLayout(PROJECT_ID))[0];
|
||||
expect(leaf.session).toBeNull();
|
||||
@ -1130,7 +1130,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState, new MockSystemGateway(), { agent });
|
||||
|
||||
const row = await openAgentDetails("Main");
|
||||
fireEvent.click(within(row).getByRole("button", { name: "Stop" }));
|
||||
fireEvent.click(within(row).getByRole("button", { name: "Arrêter" }));
|
||||
|
||||
expect(await within(row).findByText("Arrêt incomplet")).toBeTruthy();
|
||||
expect(within(row).getByRole("alert").textContent).toBe(
|
||||
@ -1197,12 +1197,15 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
await openAgentDetails("Viewer");
|
||||
const list = await screen.findByLabelText("Viewer tickets");
|
||||
fireEvent.click(within(list).getByRole("button", { name: "View" }));
|
||||
const list = await screen.findByLabelText("Tickets de Viewer");
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Afficher" }));
|
||||
const details = within(list).getByLabelText(
|
||||
"détails de la conversation conversation-view",
|
||||
);
|
||||
|
||||
expect(within(list).getByText("Goal:")).toBeTruthy();
|
||||
expect(within(list).getByText("Keep it compact")).toBeTruthy();
|
||||
expect(within(list).getByText("Summary:")).toBeTruthy();
|
||||
expect(within(details).getByText(/^Objectif/)).toBeTruthy();
|
||||
expect(within(details).getByText("Keep it compact")).toBeTruthy();
|
||||
expect(within(details).getByText(/^Résumé/)).toBeTruthy();
|
||||
expect(
|
||||
within(list).getByText("Only the exposed summary preview."),
|
||||
).toBeTruthy();
|
||||
@ -1260,13 +1263,13 @@ describe("ProjectWorkStatePanel", () => {
|
||||
renderPanel(workState);
|
||||
|
||||
await openAgentDetails("Copier");
|
||||
const list = await screen.findByLabelText("Copier tickets");
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Copy" }));
|
||||
const list = await screen.findByLabelText("Tickets de Copier");
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Copier" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(writeText).toHaveBeenCalledWith("Copy this summary preview only."),
|
||||
);
|
||||
expect(await within(list).findByRole("button", { name: "Copied" })).toBeTruthy();
|
||||
expect(await within(list).findByRole("button", { name: "Copié" })).toBeTruthy();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user