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:
@ -307,7 +307,7 @@ describe("TicketsView", () => {
|
||||
|
||||
it("shows the empty state then a created ticket", async () => {
|
||||
renderView(ticket, system, agent);
|
||||
expect(await screen.findByText("No tickets.")).toBeTruthy();
|
||||
expect(await screen.findByText("Aucun ticket.")).toBeTruthy();
|
||||
|
||||
await ticket.create(PROJECT_ID, { title: "Live created" });
|
||||
// The panel refreshes on the issueCreated event.
|
||||
@ -319,21 +319,21 @@ describe("TicketsView", () => {
|
||||
const setSpy = vi.spyOn(ticket, "setTicketSprint");
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "New" }));
|
||||
fireEvent.change(screen.getByLabelText("new ticket title"), {
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Nouveau" }));
|
||||
fireEvent.change(screen.getByLabelText("Titre du nouveau ticket"), {
|
||||
target: { value: "With sprint" },
|
||||
});
|
||||
|
||||
// Field defaults to "Sans sprint"; open the picker and pick Alpha.
|
||||
const sprintField = screen.getByLabelText("choose sprint for new ticket");
|
||||
const sprintField = screen.getByLabelText("Choisir un sprint pour le nouveau ticket");
|
||||
expect(sprintField.textContent).toContain("Sans sprint");
|
||||
fireEvent.click(sprintField);
|
||||
fireEvent.click(await screen.findByLabelText("select sprint Alpha"));
|
||||
fireEvent.click(await screen.findByLabelText("Sélectionner le sprint Alpha"));
|
||||
expect(
|
||||
screen.getByLabelText("choose sprint for new ticket").textContent,
|
||||
screen.getByLabelText("Choisir un sprint pour le nouveau ticket").textContent,
|
||||
).toContain("Alpha");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Créer" }));
|
||||
|
||||
// create → setTicketSprint(projectId, ref, "s1", version).
|
||||
await waitFor(() => expect(setSpy).toHaveBeenCalledTimes(1));
|
||||
@ -350,11 +350,11 @@ describe("TicketsView", () => {
|
||||
const setSpy = vi.spyOn(ticket, "setTicketSprint");
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "New" }));
|
||||
fireEvent.change(screen.getByLabelText("new ticket title"), {
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Nouveau" }));
|
||||
fireEvent.change(screen.getByLabelText("Titre du nouveau ticket"), {
|
||||
target: { value: "No sprint" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Créer" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const list = await ticket.list(PROJECT_ID);
|
||||
@ -371,13 +371,13 @@ describe("TicketsView", () => {
|
||||
});
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "New" }));
|
||||
fireEvent.change(screen.getByLabelText("new ticket title"), {
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Nouveau" }));
|
||||
fireEvent.change(screen.getByLabelText("Titre du nouveau ticket"), {
|
||||
target: { value: "Kept anyway" },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText("choose sprint for new ticket"));
|
||||
fireEvent.click(await screen.findByLabelText("select sprint Alpha"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create" }));
|
||||
fireEvent.click(screen.getByLabelText("Choisir un sprint pour le nouveau ticket"));
|
||||
fireEvent.click(await screen.findByLabelText("Sélectionner le sprint Alpha"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Créer" }));
|
||||
|
||||
// The ticket was created despite the failed assignment (no data lost)…
|
||||
await waitFor(async () => {
|
||||
@ -415,14 +415,14 @@ describe("TicketsView", () => {
|
||||
|
||||
// Tick two statuses → OR within the facet ⇒ open|closed ⇒ Alpha + Bravo,
|
||||
// Charlie (QA) drops out.
|
||||
fireEvent.click(screen.getByLabelText("filter status Open"));
|
||||
fireEvent.click(screen.getByLabelText("filter status Closed"));
|
||||
fireEvent.click(screen.getByLabelText("Filtrer le statut Ouvert"));
|
||||
fireEvent.click(screen.getByLabelText("Filtrer le statut Fermé"));
|
||||
await waitFor(() => expect(screen.queryByText("Charlie")).toBeNull());
|
||||
expect(screen.getByText("Alpha")).toBeTruthy();
|
||||
expect(screen.getByText("Bravo")).toBeTruthy();
|
||||
|
||||
// Add a priority → AND across facets ⇒ (open|closed) AND high ⇒ only Alpha.
|
||||
fireEvent.click(screen.getByLabelText("filter priority High"));
|
||||
fireEvent.click(screen.getByLabelText("Filtrer la priorité Haute"));
|
||||
await waitFor(() => expect(screen.queryByText("Bravo")).toBeNull());
|
||||
expect(screen.getByText("Alpha")).toBeTruthy();
|
||||
|
||||
@ -435,7 +435,7 @@ describe("TicketsView", () => {
|
||||
);
|
||||
|
||||
// « Effacer » empties both facets ⇒ all three back.
|
||||
fireEvent.click(screen.getByLabelText("clear filters"));
|
||||
fireEvent.click(screen.getByLabelText("Effacer les filtres"));
|
||||
await waitFor(() => expect(screen.getByText("Charlie")).toBeTruthy());
|
||||
expect(screen.getByText("Bravo")).toBeTruthy();
|
||||
expect(screen.getByText("Alpha")).toBeTruthy();
|
||||
@ -453,7 +453,7 @@ describe("TicketsView", () => {
|
||||
expect(listSpy.mock.calls.at(-1)?.[1]?.sort).toBeUndefined();
|
||||
|
||||
// Choose a field ⇒ ascending by default, relayed in the query.
|
||||
fireEvent.click(screen.getByLabelText("sort tickets by"));
|
||||
fireEvent.click(screen.getByLabelText("Trier les tickets par"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Titre" }));
|
||||
await waitFor(() =>
|
||||
expect(listSpy.mock.calls.at(-1)?.[1]?.sort).toEqual({
|
||||
@ -464,7 +464,7 @@ describe("TicketsView", () => {
|
||||
|
||||
// The direction toggle flips asc → desc.
|
||||
fireEvent.click(
|
||||
screen.getByLabelText("sort direction ascending"),
|
||||
screen.getByLabelText("Sens de tri croissant"),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(listSpy.mock.calls.at(-1)?.[1]?.sort).toEqual({
|
||||
@ -474,7 +474,7 @@ describe("TicketsView", () => {
|
||||
);
|
||||
|
||||
// Back to « Par défaut » ⇒ `sort` dropped from the query again.
|
||||
fireEvent.click(screen.getByLabelText("sort tickets by"));
|
||||
fireEvent.click(screen.getByLabelText("Trier les tickets par"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Par défaut" }));
|
||||
await waitFor(() =>
|
||||
expect(listSpy.mock.calls.at(-1)?.[1]?.sort).toBeUndefined(),
|
||||
@ -496,10 +496,10 @@ describe("TicketsView", () => {
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
await screen.findByText("Created by user");
|
||||
expect(screen.getByText("Créé par : Utilisateur")).toBeTruthy();
|
||||
expect(screen.getByText("Créé par : Agent : CreatorAgent")).toBeTruthy();
|
||||
expect(screen.getByText("Créé par: Utilisateur")).toBeTruthy();
|
||||
expect(screen.getByText("Créé par: Agent : CreatorAgent")).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByLabelText("filter by creator"));
|
||||
fireEvent.click(screen.getByLabelText("Filtrer par créateur"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "CreatorAgent" }));
|
||||
|
||||
await waitFor(() =>
|
||||
@ -513,10 +513,10 @@ describe("TicketsView", () => {
|
||||
|
||||
fireEvent.click(screen.getByText("Created by agent"));
|
||||
const dialog = await screen.findByRole("dialog", {
|
||||
name: `ticket ${agentTicket.ref}`,
|
||||
name: `Ticket ${agentTicket.ref}`,
|
||||
});
|
||||
expect(
|
||||
within(dialog).getByText("Créé par : Agent : CreatorAgent"),
|
||||
within(dialog).getByText("Créé par: Agent : CreatorAgent"),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -527,8 +527,8 @@ describe("TicketsView", () => {
|
||||
fireEvent.click(await screen.findByText("Editable"));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
|
||||
fireEvent.click(within(dialog).getByLabelText("ticket status"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "In progress" }));
|
||||
fireEvent.click(within(dialog).getByLabelText("Statut du ticket"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "En cours" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
@ -544,10 +544,10 @@ describe("TicketsView", () => {
|
||||
fireEvent.click(await screen.findByText("WithCarnet"));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText("ticket carnet"), {
|
||||
fireEvent.change(within(dialog).getByLabelText("Carnet du ticket"), {
|
||||
target: { value: "## notes\nscoped body" },
|
||||
});
|
||||
fireEvent.click(within(dialog).getByText("Save carnet"));
|
||||
fireEvent.click(within(dialog).getByText("Enregistrer le carnet"));
|
||||
|
||||
await waitFor(async () => {
|
||||
const carnet = await ticket.readCarnet(PROJECT_ID, t.ref);
|
||||
@ -560,7 +560,7 @@ describe("TicketsView", () => {
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByText("Attachable"));
|
||||
const dialog = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
const dialog = await screen.findByRole("dialog", { name: `Ticket ${t.ref}` });
|
||||
|
||||
expect(within(dialog).getByText("Pièces jointes")).toBeTruthy();
|
||||
expect(within(dialog).getByText("Aucune pièce jointe.")).toBeTruthy();
|
||||
@ -569,16 +569,16 @@ describe("TicketsView", () => {
|
||||
|
||||
expect(await within(dialog).findByText("mock-attachment.txt")).toBeTruthy();
|
||||
expect(within(dialog).getByText(/text\/plain/)).toBeTruthy();
|
||||
expect(within(dialog).getByText("Résumé : non")).toBeTruthy();
|
||||
expect(within(dialog).getByText("Résumé: non")).toBeTruthy();
|
||||
|
||||
const attached = await ticket.read(PROJECT_ID, t.ref);
|
||||
expect(attached.attachments).toHaveLength(1);
|
||||
|
||||
fireEvent.click(
|
||||
within(dialog).getByLabelText("marquer résumé mock-attachment.txt"),
|
||||
within(dialog).getByLabelText("Marquer mock-attachment.txt comme résumé"),
|
||||
);
|
||||
|
||||
expect(await within(dialog).findByText("Résumé : oui")).toBeTruthy();
|
||||
expect(await within(dialog).findByText("Résumé: oui")).toBeTruthy();
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
expect(fresh.attachments[0].summarizedInCarnet).toBe(true);
|
||||
@ -593,9 +593,9 @@ describe("TicketsView", () => {
|
||||
fireEvent.click(await screen.findByText("Assignable"));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
|
||||
fireEvent.click(within(dialog).getByLabelText("assign agent"));
|
||||
fireEvent.click(within(dialog).getByLabelText("Assigner un agent"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Backend" }));
|
||||
fireEvent.click(within(dialog).getByText("Assign"));
|
||||
fireEvent.click(within(dialog).getByText("Assigner"));
|
||||
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
@ -627,19 +627,19 @@ describe("TicketsView", () => {
|
||||
expectedVersion: t.version,
|
||||
});
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText("Title"), {
|
||||
fireEvent.change(within(dialog).getByLabelText("Titre"), {
|
||||
target: { value: "Racy edited" },
|
||||
});
|
||||
fireEvent.click(within(dialog).getByText("Save changes"));
|
||||
fireEvent.click(within(dialog).getByText("Enregistrer les modifications"));
|
||||
|
||||
expect(
|
||||
await within(dialog).findByText(/modified elsewhere and reloaded/i),
|
||||
await within(dialog).findByText(/modifié ailleurs et rechargé/i),
|
||||
).toBeTruthy();
|
||||
// After the reload the detail field reflects the reloaded (title unchanged)
|
||||
// ticket rather than the abandoned local edit.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
(within(dialog).getByLabelText("Title") as HTMLInputElement)
|
||||
(within(dialog).getByLabelText("Titre") as HTMLInputElement)
|
||||
.value,
|
||||
).toBe("Racy"),
|
||||
);
|
||||
@ -656,13 +656,13 @@ describe("TicketsView", () => {
|
||||
const desc = within(dialog).getByLabelText("Description") as HTMLTextAreaElement;
|
||||
fireEvent.change(desc, { target: { value: "draft in progress" } });
|
||||
// Also edit the title to cover both draft fields.
|
||||
const titleInput = within(dialog).getByLabelText("Title") as HTMLInputElement;
|
||||
const titleInput = within(dialog).getByLabelText("Titre") as HTMLInputElement;
|
||||
fireEvent.change(titleInput, { target: { value: "Persisted (wip)" } });
|
||||
|
||||
// Change the priority — an immediate-apply mutation that re-fetches the ticket
|
||||
// and bumps its version. The unsaved draft must survive.
|
||||
fireEvent.click(within(dialog).getByLabelText("ticket priority"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "High" }));
|
||||
fireEvent.click(within(dialog).getByLabelText("Priorité du ticket"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Haute" }));
|
||||
|
||||
// The backend applied the priority bump…
|
||||
await waitFor(async () => {
|
||||
@ -676,7 +676,7 @@ describe("TicketsView", () => {
|
||||
(within(dialog).getByLabelText("Description") as HTMLTextAreaElement).value,
|
||||
).toBe("draft in progress");
|
||||
expect(
|
||||
(within(dialog).getByLabelText("Title") as HTMLInputElement).value,
|
||||
(within(dialog).getByLabelText("Titre") as HTMLInputElement).value,
|
||||
).toBe("Persisted (wip)");
|
||||
// The description was never persisted.
|
||||
const persisted = await ticket.read(PROJECT_ID, t.ref);
|
||||
@ -698,34 +698,34 @@ describe("TicketsView", () => {
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
const detail = await screen.findByRole("dialog", { name: `Ticket ${t.ref}` });
|
||||
|
||||
// Clean state → Close fires onClose directly, no confirmation dialog.
|
||||
fireEvent.click(within(detail).getByText("Close"));
|
||||
fireEvent.click(within(detail).getByText("Fermer"));
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(screen.queryByRole("dialog", { name: "unsaved changes" })).toBeNull();
|
||||
expect(screen.queryByRole("dialog", { name: "Modifications non enregistrées" })).toBeNull();
|
||||
|
||||
// Now make the draft dirty and try to close again.
|
||||
fireEvent.change(within(detail).getByLabelText("Description"), {
|
||||
target: { value: "unsaved body" },
|
||||
});
|
||||
fireEvent.click(within(detail).getByText("Close"));
|
||||
fireEvent.click(within(detail).getByText("Fermer"));
|
||||
|
||||
// The confirmation appears and onClose was NOT called again.
|
||||
const confirm = await screen.findByRole("dialog", { name: "unsaved changes" });
|
||||
const confirm = await screen.findByRole("dialog", { name: "Modifications non enregistrées" });
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
// "Annuler" dismisses the popup and keeps the editor open.
|
||||
fireEvent.click(within(confirm).getByLabelText("cancel close"));
|
||||
fireEvent.click(within(confirm).getByLabelText("Annuler la fermeture"));
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole("dialog", { name: "unsaved changes" })).toBeNull(),
|
||||
expect(screen.queryByRole("dialog", { name: "Modifications non enregistrées" })).toBeNull(),
|
||||
);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Re-open the confirmation and choose "Fermer sans sauvegarder".
|
||||
fireEvent.click(within(detail).getByText("Close"));
|
||||
const confirm2 = await screen.findByRole("dialog", { name: "unsaved changes" });
|
||||
fireEvent.click(within(confirm2).getByLabelText("close without saving"));
|
||||
fireEvent.click(within(detail).getByText("Fermer"));
|
||||
const confirm2 = await screen.findByRole("dialog", { name: "Modifications non enregistrées" });
|
||||
fireEvent.click(within(confirm2).getByLabelText("Fermer sans enregistrer"));
|
||||
expect(onClose).toHaveBeenCalledTimes(2);
|
||||
// The unsaved description was discarded (never persisted).
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
@ -747,14 +747,14 @@ describe("TicketsView", () => {
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
const detail = await screen.findByRole("dialog", { name: `Ticket ${t.ref}` });
|
||||
fireEvent.change(within(detail).getByLabelText("Description"), {
|
||||
target: { value: "final body" },
|
||||
});
|
||||
fireEvent.click(within(detail).getByText("Close"));
|
||||
fireEvent.click(within(detail).getByText("Fermer"));
|
||||
|
||||
const confirm = await screen.findByRole("dialog", { name: "unsaved changes" });
|
||||
fireEvent.click(within(confirm).getByLabelText("save and close"));
|
||||
const confirm = await screen.findByRole("dialog", { name: "Modifications non enregistrées" });
|
||||
fireEvent.click(within(confirm).getByLabelText("Enregistrer et fermer"));
|
||||
|
||||
// The change is persisted and the panel closes.
|
||||
await waitFor(async () => {
|
||||
@ -777,21 +777,21 @@ describe("TicketsView", () => {
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
// Sections appear in sprint `order`, then the "Sans sprint" bucket last.
|
||||
await screen.findByRole("region", { name: "sprint section Sprint One" });
|
||||
await screen.findByRole("region", { name: "Section du sprint Sprint One" });
|
||||
const sections = screen
|
||||
.getAllByRole("region")
|
||||
.map((r) => r.getAttribute("aria-label"))
|
||||
.filter((l) => l?.startsWith("sprint section"));
|
||||
.filter((l) => l?.startsWith("Section du sprint"));
|
||||
expect(sections).toEqual([
|
||||
"sprint section Sprint One",
|
||||
"sprint section Sprint Two",
|
||||
"sprint section Sans sprint",
|
||||
"Section du sprint Sprint One",
|
||||
"Section du sprint Sprint Two",
|
||||
"Section du sprint Sans sprint",
|
||||
]);
|
||||
|
||||
// The loose ticket lives in the bucket, the others under their sprint.
|
||||
const bucket = screen.getByRole("region", { name: "sprint section Sans sprint" });
|
||||
const bucket = screen.getByRole("region", { name: "Section du sprint Sans sprint" });
|
||||
expect(within(bucket).getByText("Loose")).toBeTruthy();
|
||||
const one = screen.getByRole("region", { name: "sprint section Sprint One" });
|
||||
const one = screen.getByRole("region", { name: "Section du sprint Sprint One" });
|
||||
expect(within(one).getByText("In one")).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -803,12 +803,12 @@ describe("TicketsView", () => {
|
||||
|
||||
// It starts in the "Sans sprint" bucket.
|
||||
const bucket = await screen.findByRole("region", {
|
||||
name: "sprint section Sans sprint",
|
||||
name: "Section du sprint Sans sprint",
|
||||
});
|
||||
expect(within(bucket).getByText("Movable")).toBeTruthy();
|
||||
|
||||
// Pick the sprint in the row selector → assign it.
|
||||
fireEvent.click(screen.getByLabelText(`sprint for ${t.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`Sprint de ${t.ref}`));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Sprint One" }));
|
||||
|
||||
// The gateway recorded the membership…
|
||||
@ -819,12 +819,12 @@ describe("TicketsView", () => {
|
||||
// …and the UI regrouped it under Sprint One, emptying the bucket.
|
||||
await waitFor(() => {
|
||||
const one = screen.getByRole("region", {
|
||||
name: "sprint section Sprint One",
|
||||
name: "Section du sprint Sprint One",
|
||||
});
|
||||
expect(within(one).getByText("Movable")).toBeTruthy();
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole("region", { name: "sprint section Sans sprint" }),
|
||||
screen.queryByRole("region", { name: "Section du sprint Sans sprint" }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
@ -834,15 +834,15 @@ describe("TicketsView", () => {
|
||||
|
||||
fireEvent.click(await screen.findByText("Deletable"));
|
||||
const detail = await screen.findByRole("dialog", {
|
||||
name: `ticket ${t.ref}`,
|
||||
name: `Ticket ${t.ref}`,
|
||||
});
|
||||
|
||||
// Open the confirmation, then confirm the deletion.
|
||||
fireEvent.click(within(detail).getByText("Supprimer"));
|
||||
const confirm = await screen.findByRole("dialog", {
|
||||
name: "confirmer la suppression",
|
||||
name: "Confirmer la suppression",
|
||||
});
|
||||
fireEvent.click(within(confirm).getByLabelText("confirm delete"));
|
||||
fireEvent.click(within(confirm).getByLabelText("Confirmer la suppression"));
|
||||
|
||||
// The gateway removed the ticket…
|
||||
await waitFor(() =>
|
||||
@ -853,12 +853,12 @@ describe("TicketsView", () => {
|
||||
// …the detail closed (via the `issueDeleted` event → `deleted` flag)…
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.queryByRole("dialog", { name: `ticket ${t.ref}` }),
|
||||
screen.queryByRole("dialog", { name: `Ticket ${t.ref}` }),
|
||||
).toBeNull(),
|
||||
);
|
||||
// …and the row disappeared from the list (event-driven refresh).
|
||||
await waitFor(() => expect(screen.queryByText("Deletable")).toBeNull());
|
||||
expect(await screen.findByText("No tickets.")).toBeTruthy();
|
||||
expect(await screen.findByText("Aucun ticket.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("selects several rows and changes their status from the bulk action bar", async () => {
|
||||
@ -869,10 +869,10 @@ describe("TicketsView", () => {
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket row ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`select ticket row ${b.ref}`));
|
||||
fireEvent.click(await screen.findByLabelText(`Sélectionner le ticket ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`Sélectionner le ticket ${b.ref}`));
|
||||
|
||||
expect(screen.getByRole("toolbar", { name: "bulk ticket actions" })).toBeTruthy();
|
||||
expect(screen.getByRole("toolbar", { name: "Actions groupées sur les tickets" })).toBeTruthy();
|
||||
fireEvent.click(screen.getByLabelText("Changer le statut"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "QA" }));
|
||||
|
||||
@ -883,7 +883,7 @@ describe("TicketsView", () => {
|
||||
expect((await ticket.read(PROJECT_ID, a.ref)).status).toBe("QA");
|
||||
expect((await ticket.read(PROJECT_ID, b.ref)).status).toBe("QA");
|
||||
});
|
||||
expect(screen.queryByRole("toolbar", { name: "bulk ticket actions" })).toBeNull();
|
||||
expect(screen.queryByRole("toolbar", { name: "Actions groupées sur les tickets" })).toBeNull();
|
||||
});
|
||||
|
||||
it("selects several rows and assigns their sprint from the bulk action bar (#112)", async () => {
|
||||
@ -895,12 +895,12 @@ describe("TicketsView", () => {
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket row ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`select ticket row ${b.ref}`));
|
||||
fireEvent.click(await screen.findByLabelText(`Sélectionner le ticket ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`Sélectionner le ticket ${b.ref}`));
|
||||
|
||||
const toolbar = screen.getByRole("toolbar", { name: "bulk ticket actions" });
|
||||
const toolbar = screen.getByRole("toolbar", { name: "Actions groupées sur les tickets" });
|
||||
fireEvent.click(within(toolbar).getByRole("button", { name: "Sprint" }));
|
||||
fireEvent.click(await screen.findByLabelText("select sprint Target"));
|
||||
fireEvent.click(await screen.findByLabelText("Sélectionner le sprint Target"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setSpy).toHaveBeenCalledWith(PROJECT_ID, a.ref, "s1", a.version);
|
||||
@ -913,7 +913,7 @@ describe("TicketsView", () => {
|
||||
expect(screen.getByRole("status").textContent).toMatch(
|
||||
/Sprint mis à jour pour 2 tickets/i,
|
||||
);
|
||||
expect(screen.queryByRole("toolbar", { name: "bulk ticket actions" })).toBeNull();
|
||||
expect(screen.queryByRole("toolbar", { name: "Actions groupées sur les tickets" })).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps failed refs selected after a partial bulk sprint assignment (#112)", async () => {
|
||||
@ -932,11 +932,11 @@ describe("TicketsView", () => {
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket row ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`select ticket row ${b.ref}`));
|
||||
const toolbar = screen.getByRole("toolbar", { name: "bulk ticket actions" });
|
||||
fireEvent.click(await screen.findByLabelText(`Sélectionner le ticket ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`Sélectionner le ticket ${b.ref}`));
|
||||
const toolbar = screen.getByRole("toolbar", { name: "Actions groupées sur les tickets" });
|
||||
fireEvent.click(within(toolbar).getByRole("button", { name: "Sprint" }));
|
||||
fireEvent.click(await screen.findByLabelText("select sprint Target"));
|
||||
fireEvent.click(await screen.findByLabelText("Sélectionner le sprint Target"));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect((await ticket.read(PROJECT_ID, a.ref)).sprintId).toBe("s1");
|
||||
@ -947,11 +947,11 @@ describe("TicketsView", () => {
|
||||
);
|
||||
expect(screen.getByRole("status").textContent).toMatch(/1\/2 tickets/i);
|
||||
expect(
|
||||
(screen.getByLabelText(`select ticket row ${b.ref}`) as HTMLInputElement)
|
||||
(screen.getByLabelText(`Sélectionner le ticket ${b.ref}`) as HTMLInputElement)
|
||||
.checked,
|
||||
).toBe(true);
|
||||
expect(
|
||||
(screen.getByLabelText(`select ticket row ${a.ref}`) as HTMLInputElement)
|
||||
(screen.getByLabelText(`Sélectionner le ticket ${a.ref}`) as HTMLInputElement)
|
||||
.checked,
|
||||
).toBe(false);
|
||||
});
|
||||
@ -964,14 +964,14 @@ describe("TicketsView", () => {
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket row ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`select ticket row ${b.ref}`));
|
||||
fireEvent.click(await screen.findByLabelText(`Sélectionner le ticket ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`Sélectionner le ticket ${b.ref}`));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Supprimer" }));
|
||||
|
||||
const confirm = await screen.findByRole("dialog", {
|
||||
name: "confirmer la suppression groupée",
|
||||
name: "Confirmer la suppression groupée",
|
||||
});
|
||||
fireEvent.click(within(confirm).getByLabelText("confirm bulk delete"));
|
||||
fireEvent.click(within(confirm).getByLabelText("Confirmer la suppression groupée"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(bulkSpy).toHaveBeenCalledWith(PROJECT_ID, [a.ref, b.ref]),
|
||||
@ -988,24 +988,24 @@ describe("TicketsView", () => {
|
||||
|
||||
fireEvent.click(await screen.findByText("Keeper"));
|
||||
const detail = await screen.findByRole("dialog", {
|
||||
name: `ticket ${t.ref}`,
|
||||
name: `Ticket ${t.ref}`,
|
||||
});
|
||||
|
||||
fireEvent.click(within(detail).getByText("Supprimer"));
|
||||
const confirm = await screen.findByRole("dialog", {
|
||||
name: "confirmer la suppression",
|
||||
name: "Confirmer la suppression",
|
||||
});
|
||||
fireEvent.click(within(confirm).getByLabelText("cancel delete"));
|
||||
fireEvent.click(within(confirm).getByLabelText("Annuler la suppression"));
|
||||
|
||||
// The confirmation is dismissed, delete was never called, ticket stays.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.queryByRole("dialog", { name: "confirmer la suppression" }),
|
||||
screen.queryByRole("dialog", { name: "Confirmer la suppression" }),
|
||||
).toBeNull(),
|
||||
);
|
||||
expect(delSpy).not.toHaveBeenCalled();
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: `ticket ${t.ref}` }),
|
||||
screen.getByRole("dialog", { name: `Ticket ${t.ref}` }),
|
||||
).toBeTruthy();
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
expect(fresh.title).toBe("Keeper");
|
||||
@ -1018,7 +1018,7 @@ describe("TicketsView", () => {
|
||||
|
||||
fireEvent.click(await screen.findByText("Delegate me"));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByText("Delegation context"));
|
||||
fireEvent.click(within(dialog).getByText("Contexte de délégation"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(writeText).toHaveBeenCalledWith(
|
||||
@ -1044,12 +1044,12 @@ describe("TicketsView", () => {
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
const detail = await screen.findByRole("dialog", { name: `Ticket ${t.ref}` });
|
||||
|
||||
// The assistant is opened from the prominent bottom-right floating button;
|
||||
// it is not inline anymore (#17).
|
||||
expect(within(detail).queryByLabelText("profil de l'assistant")).toBeNull();
|
||||
fireEvent.click(within(detail).getByLabelText("ouvrir l'assistant IA"));
|
||||
fireEvent.click(within(detail).getByLabelText("Ouvrir l’assistant IA"));
|
||||
|
||||
// It opens in its own nested window.
|
||||
const assistant = await screen.findByRole("dialog", {
|
||||
@ -1117,18 +1117,18 @@ describe("TicketsView", () => {
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const detail = await screen.findByRole("dialog", { name: `ticket ${a.ref}` });
|
||||
const detail = await screen.findByRole("dialog", { name: `Ticket ${a.ref}` });
|
||||
// The old manual "#id" input is gone.
|
||||
expect(within(detail).queryByLabelText("link target ref")).toBeNull();
|
||||
|
||||
// Choose the link kind, then open the picker and select the target ticket.
|
||||
fireEvent.click(within(detail).getByLabelText("link kind"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "blocks" }));
|
||||
fireEvent.click(within(detail).getByLabelText("add link"));
|
||||
fireEvent.click(within(detail).getByLabelText("Type de lien"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "bloque" }));
|
||||
fireEvent.click(within(detail).getByLabelText("Ajouter un lien"));
|
||||
|
||||
// The picker excludes the ticket itself; the target is offered.
|
||||
expect(screen.queryByLabelText(`select ticket ${a.ref}`)).toBeNull();
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket ${b.ref}`));
|
||||
expect(screen.queryByLabelText(`Sélectionner le ticket ${a.ref}`)).toBeNull();
|
||||
fireEvent.click(await screen.findByLabelText(`Sélectionner le ticket ${b.ref}`));
|
||||
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, a.ref);
|
||||
@ -1153,27 +1153,27 @@ describe("SprintManager (#11)", () => {
|
||||
renderView(ticket, system, agent);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "manage sprints" }),
|
||||
screen.getByRole("button", { name: /gérer les sprints/i }),
|
||||
).toBeTruthy(),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "manage sprints" }));
|
||||
return screen.findByRole("dialog", { name: "manage sprints" });
|
||||
fireEvent.click(screen.getByRole("button", { name: /gérer les sprints/i }));
|
||||
return screen.findByRole("dialog", { name: /gérer les sprints/i });
|
||||
}
|
||||
|
||||
it("creates a sprint", async () => {
|
||||
const dialog = await openManager();
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText("new sprint name"), {
|
||||
fireEvent.change(within(dialog).getByLabelText("nom du nouveau sprint"), {
|
||||
target: { value: "Alpha" },
|
||||
});
|
||||
fireEvent.click(within(dialog).getByText("Create sprint"));
|
||||
fireEvent.click(within(dialog).getByText("Créer le sprint"));
|
||||
|
||||
await waitFor(async () => {
|
||||
const sprints = await ticket.listSprints(PROJECT_ID);
|
||||
expect(sprints.map((s) => s.name)).toEqual(["Alpha"]);
|
||||
});
|
||||
expect(
|
||||
await within(dialog).findByLabelText("sprint row Alpha"),
|
||||
await within(dialog).findByLabelText("ligne du sprint Alpha"),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -1182,17 +1182,17 @@ describe("SprintManager (#11)", () => {
|
||||
|
||||
// Leave the name field empty and create → default "Sprint N" (here N=1).
|
||||
expect(
|
||||
(within(dialog).getByLabelText("new sprint name") as HTMLInputElement)
|
||||
(within(dialog).getByLabelText("nom du nouveau sprint") as HTMLInputElement)
|
||||
.value,
|
||||
).toBe("");
|
||||
fireEvent.click(within(dialog).getByText("Create sprint"));
|
||||
fireEvent.click(within(dialog).getByText("Créer le sprint"));
|
||||
|
||||
await waitFor(async () => {
|
||||
const sprints = await ticket.listSprints(PROJECT_ID);
|
||||
expect(sprints.map((s) => s.name)).toEqual(["Sprint 1"]);
|
||||
});
|
||||
expect(
|
||||
await within(dialog).findByLabelText("sprint row Sprint 1"),
|
||||
await within(dialog).findByLabelText("ligne du sprint Sprint 1"),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -1201,10 +1201,10 @@ describe("SprintManager (#11)", () => {
|
||||
const dialog = await openManager();
|
||||
|
||||
fireEvent.change(
|
||||
await within(dialog).findByLabelText("rename sprint Old"),
|
||||
await within(dialog).findByLabelText("renommer le sprint Old"),
|
||||
{ target: { value: "New" } },
|
||||
);
|
||||
fireEvent.click(within(dialog).getByLabelText("save sprint Old"));
|
||||
fireEvent.click(within(dialog).getByLabelText("enregistrer le sprint Old"));
|
||||
|
||||
await waitFor(async () => {
|
||||
const [s] = await ticket.listSprints(PROJECT_ID);
|
||||
@ -1218,7 +1218,7 @@ describe("SprintManager (#11)", () => {
|
||||
const dialog = await openManager();
|
||||
|
||||
fireEvent.click(
|
||||
await within(dialog).findByLabelText("move sprint One down"),
|
||||
await within(dialog).findByLabelText("descendre le sprint One"),
|
||||
);
|
||||
|
||||
await waitFor(async () => {
|
||||
@ -1236,14 +1236,14 @@ describe("SprintManager (#11)", () => {
|
||||
const dialog = await openManager();
|
||||
|
||||
fireEvent.click(
|
||||
await within(dialog).findByLabelText("delete sprint Doomed"),
|
||||
await within(dialog).findByLabelText("supprimer le sprint Doomed"),
|
||||
);
|
||||
// The confirmation explains tickets are unassigned, not deleted.
|
||||
const confirm = await screen.findByRole("dialog", {
|
||||
name: "delete sprint confirmation",
|
||||
name: "confirmation de suppression du sprint",
|
||||
});
|
||||
expect(confirm.textContent).toMatch(/ne seront pas supprimés/i);
|
||||
fireEvent.click(within(confirm).getByLabelText("confirm delete sprint"));
|
||||
fireEvent.click(within(confirm).getByLabelText("confirmer la suppression du sprint"));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await ticket.listSprints(PROJECT_ID)).toHaveLength(0);
|
||||
@ -1263,13 +1263,16 @@ describe("SprintManager (#11)", () => {
|
||||
|
||||
// Open the add-ticket picker for the Target sprint (multi-select).
|
||||
fireEvent.click(
|
||||
await within(dialog).findByLabelText("add ticket to sprint Target"),
|
||||
await within(dialog).findByLabelText("ajouter un ticket au sprint Target"),
|
||||
);
|
||||
const picker = await screen.findByRole("dialog", {
|
||||
name: /Ajouter des tickets au sprint/,
|
||||
});
|
||||
|
||||
// The picker toggles rows without assigning; confirm applies the batch.
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket ${t1.ref}`));
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket ${t2.ref}`));
|
||||
fireEvent.click(screen.getByLabelText("confirm ticket selection"));
|
||||
fireEvent.click(await within(picker).findByLabelText(`Sélectionner le ticket ${t1.ref}`));
|
||||
fireEvent.click(await within(picker).findByLabelText(`Sélectionner le ticket ${t2.ref}`));
|
||||
fireEvent.click(within(picker).getByLabelText("Confirmer la sélection de tickets"));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect((await ticket.read(PROJECT_ID, t1.ref)).sprintId).toBe("s1");
|
||||
@ -1285,15 +1288,18 @@ describe("SprintManager (#11)", () => {
|
||||
|
||||
const dialog = await openManager();
|
||||
fireEvent.click(
|
||||
await within(dialog).findByLabelText("add ticket to sprint Target"),
|
||||
await within(dialog).findByLabelText("ajouter un ticket au sprint Target"),
|
||||
);
|
||||
const picker = await screen.findByRole("dialog", {
|
||||
name: /Ajouter des tickets au sprint/,
|
||||
});
|
||||
|
||||
// The backlog ticket is offered; the one already in the sprint is excluded.
|
||||
expect(
|
||||
await screen.findByLabelText(`select ticket ${backlog.ref}`),
|
||||
await within(picker).findByLabelText(`Sélectionner le ticket ${backlog.ref}`),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.queryByLabelText(`select ticket ${member.ref}`),
|
||||
within(picker).queryByLabelText(`Sélectionner le ticket ${member.ref}`),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
@ -1305,7 +1311,7 @@ describe("SprintManager (#11)", () => {
|
||||
const dialog = await openManager();
|
||||
|
||||
fireEvent.click(
|
||||
await within(dialog).findByLabelText(`remove ${t.ref} from sprint`),
|
||||
await within(dialog).findByLabelText(`retirer ${t.ref} du sprint`),
|
||||
);
|
||||
|
||||
await waitFor(async () => {
|
||||
@ -1337,16 +1343,16 @@ describe("SprintManager (#11)", () => {
|
||||
await seedSprintWithOpenAndClosed();
|
||||
const dialog = await openManager();
|
||||
|
||||
const row = await within(dialog).findByLabelText("sprint row Holder");
|
||||
const row = await within(dialog).findByLabelText("ligne du sprint Holder");
|
||||
// Both the open and the closed ticket are listed (closed is NOT hidden).
|
||||
expect(within(row).getByText("Open member")).toBeTruthy();
|
||||
expect(within(row).getByText("Closed member")).toBeTruthy();
|
||||
// Each row carries its status + priority badge (scoped to the sprint row so
|
||||
// the facet-bar labels don't collide).
|
||||
expect(within(row).getByText("Closed")).toBeTruthy();
|
||||
expect(within(row).getByText("Open")).toBeTruthy();
|
||||
expect(within(row).getByText("High")).toBeTruthy();
|
||||
expect(within(row).getByText("Low")).toBeTruthy();
|
||||
expect(within(row).getByText("Fermé")).toBeTruthy();
|
||||
expect(within(row).getByText("Ouvert")).toBeTruthy();
|
||||
expect(within(row).getByText("Haute")).toBeTruthy();
|
||||
expect(within(row).getByText("Faible")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("filters the sprint tickets by status via its own facets bar (#37)", async () => {
|
||||
@ -1357,14 +1363,14 @@ describe("SprintManager (#11)", () => {
|
||||
expect(within(dialog).getByText("Closed member")).toBeTruthy();
|
||||
|
||||
// Constrain the sprint view to Closed only → the open ticket drops out.
|
||||
fireEvent.click(within(dialog).getByLabelText("filter status Closed"));
|
||||
fireEvent.click(within(dialog).getByLabelText("Filtrer le statut Fermé"));
|
||||
await waitFor(() =>
|
||||
expect(within(dialog).queryByText("Open member")).toBeNull(),
|
||||
);
|
||||
expect(within(dialog).getByText("Closed member")).toBeTruthy();
|
||||
|
||||
// Clearing the facet brings the open ticket back.
|
||||
fireEvent.click(within(dialog).getByLabelText("filter status Closed"));
|
||||
fireEvent.click(within(dialog).getByLabelText("Filtrer le statut Fermé"));
|
||||
await waitFor(() =>
|
||||
expect(within(dialog).getByText("Open member")).toBeTruthy(),
|
||||
);
|
||||
@ -1376,20 +1382,20 @@ describe("SprintManager (#11)", () => {
|
||||
renderView(ticket, system, agent);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "manage sprints" }),
|
||||
screen.getByRole("button", { name: /gérer les sprints/i }),
|
||||
).toBeTruthy(),
|
||||
);
|
||||
|
||||
// Constrain the MAIN Tickets panel to Open only → the closed ticket leaves
|
||||
// the main list (there is a single main facets bar on screen at this point).
|
||||
fireEvent.click(screen.getByLabelText("filter status Open"));
|
||||
fireEvent.click(screen.getByLabelText("Filtrer le statut Ouvert"));
|
||||
await waitFor(() => expect(screen.queryByText("Closed member")).toBeNull());
|
||||
|
||||
// Open the sprint manager: its own query ignores the main filter, so the
|
||||
// closed ticket is still shown inside its sprint.
|
||||
fireEvent.click(screen.getByRole("button", { name: "manage sprints" }));
|
||||
const dialog = await screen.findByRole("dialog", { name: "manage sprints" });
|
||||
const row = await within(dialog).findByLabelText("sprint row Holder");
|
||||
fireEvent.click(screen.getByRole("button", { name: /gérer les sprints/i }));
|
||||
const dialog = await screen.findByRole("dialog", { name: /gérer les sprints/i });
|
||||
const row = await within(dialog).findByLabelText("ligne du sprint Holder");
|
||||
expect(await within(row).findByText("Closed member")).toBeTruthy();
|
||||
expect(within(row).getByText("Open member")).toBeTruthy();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user