bulk operations fixes
- Frontend: implémente bulkDelete, bulkArchive, bulkRestore - Backend: adapts ticket DTOs pour bulk operations - Infrastructure: MCP server pour gestion bulk tickets - Tests: coverage frontend + backend
This commit is contained in:
@ -178,6 +178,44 @@ describe("MockTicketGateway", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("bulk updates status and reports partial failures in request order", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "A" });
|
||||
const b = await ticket.create(PROJECT_ID, { title: "B" });
|
||||
|
||||
const out = await ticket.bulkUpdateStatus(
|
||||
PROJECT_ID,
|
||||
[a.ref, "#404", b.ref],
|
||||
"QA",
|
||||
);
|
||||
|
||||
expect(out.items.map((item) => [item.ref, item.ok])).toEqual([
|
||||
[a.ref, true],
|
||||
["#404", false],
|
||||
[b.ref, true],
|
||||
]);
|
||||
expect((await ticket.read(PROJECT_ID, a.ref)).status).toBe("QA");
|
||||
expect((await ticket.read(PROJECT_ID, b.ref)).status).toBe("QA");
|
||||
});
|
||||
|
||||
it("bulk deletes existing tickets and keeps missing refs as item failures", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "A" });
|
||||
const b = await ticket.create(PROJECT_ID, { title: "B" });
|
||||
|
||||
const out = await ticket.bulkDelete(PROJECT_ID, [a.ref, "#404", b.ref]);
|
||||
|
||||
expect(out.items.map((item) => [item.ref, item.ok])).toEqual([
|
||||
[a.ref, true],
|
||||
["#404", false],
|
||||
[b.ref, true],
|
||||
]);
|
||||
await expect(ticket.read(PROJECT_ID, a.ref)).rejects.toMatchObject({
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
await expect(ticket.read(PROJECT_ID, b.ref)).rejects.toMatchObject({
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
});
|
||||
|
||||
it("links and unlinks tickets, bumping the version", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "a" });
|
||||
await ticket.create(PROJECT_ID, { title: "b" });
|
||||
@ -692,6 +730,56 @@ describe("TicketsView", () => {
|
||||
expect(await screen.findByText("No tickets.")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("selects several rows and changes their status from the bulk action bar", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "Bulk A" });
|
||||
const b = await ticket.create(PROJECT_ID, { title: "Bulk B" });
|
||||
await ticket.create(PROJECT_ID, { title: "Unselected" });
|
||||
const bulkSpy = vi.spyOn(ticket, "bulkUpdateStatus");
|
||||
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByLabelText(`select ticket row ${a.ref}`));
|
||||
fireEvent.click(screen.getByLabelText(`select ticket row ${b.ref}`));
|
||||
|
||||
expect(screen.getByRole("toolbar", { name: "bulk ticket actions" })).toBeTruthy();
|
||||
fireEvent.click(screen.getByLabelText("Changer le statut"));
|
||||
fireEvent.click(await screen.findByRole("option", { name: "QA" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(bulkSpy).toHaveBeenCalledWith(PROJECT_ID, [a.ref, b.ref], "QA"),
|
||||
);
|
||||
await waitFor(async () => {
|
||||
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();
|
||||
});
|
||||
|
||||
it("selects several rows and deletes them after bulk confirmation", async () => {
|
||||
const a = await ticket.create(PROJECT_ID, { title: "Delete A" });
|
||||
const b = await ticket.create(PROJECT_ID, { title: "Delete B" });
|
||||
await ticket.create(PROJECT_ID, { title: "Keep me" });
|
||||
const bulkSpy = vi.spyOn(ticket, "bulkDelete");
|
||||
|
||||
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(screen.getByRole("button", { name: "Supprimer" }));
|
||||
|
||||
const confirm = await screen.findByRole("dialog", {
|
||||
name: "confirmer la suppression groupée",
|
||||
});
|
||||
fireEvent.click(within(confirm).getByLabelText("confirm bulk delete"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(bulkSpy).toHaveBeenCalledWith(PROJECT_ID, [a.ref, b.ref]),
|
||||
);
|
||||
await waitFor(() => expect(screen.queryByText("Delete A")).toBeNull());
|
||||
expect(screen.queryByText("Delete B")).toBeNull();
|
||||
expect(screen.getByText("Keep me")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("cancelling the delete confirmation keeps the ticket (#6)", async () => {
|
||||
const t = await ticket.create(PROJECT_ID, { title: "Keeper" });
|
||||
const delSpy = vi.spyOn(ticket, "delete");
|
||||
|
||||
Reference in New Issue
Block a user