Files
govoplan-core/webui/conformance/tests/campaign-bulk-review.spec.ts
T

104 lines
5.9 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
async function setup(page: Page, options: { firstFailure?: boolean; delay?: Promise<void> } = {}) {
const writes: Record<string, unknown>[] = [];
await page.route("**/api/v1/conformance/review-state", async (route) => {
writes.push(route.request().postDataJSON());
if (options.delay) await options.delay;
if (options.firstFailure && writes.length === 1) {
await route.fulfill({ status: 409, json: { detail: "Synthetic review revision conflict" } }); return;
}
await route.fulfill({ json: { saved: true } });
});
return writes;
}
async function openAttachmentGroup(page: Page, query = "") {
page.on("pageerror", (error) => { throw error; });
await page.goto(`/?campaign-bulk-review${query}`);
await page.getByRole("button", { name: "Open grouped review" }).click();
if (await page.getByRole("combobox").isEnabled()) await page.getByRole("combobox").selectOption("same-attachment-condition");
return page.getByRole("dialog");
}
test("grouped review accepts only exact selected eligible IDs with shared required reason", async ({ page }) => {
const writes = await setup(page);
const dialog = await openAttachmentGroup(page);
await expect(dialog.getByRole("checkbox")).toHaveCount(3);
await expect(dialog.getByRole("button", { name: "Accept 3 selected messages" })).toBeDisabled();
await dialog.getByRole("checkbox", { name: "job-001@example.test" }).focus();
await page.keyboard.press("Space");
await dialog.getByRole("textbox").fill("The optional file is intentionally omitted for these recipients.");
await dialog.getByRole("button", { name: "Accept 2 selected messages" }).click();
await expect(dialog).not.toBeVisible();
expect(writes).toEqual([{ buildToken: "build-one", categoryKey: "same-attachment-condition", jobIds: ["job-000", "job-002"], reason: "The optional file is intentionally omitted for these recipients." }]);
});
test("failed grouped save retains reason and selection, and retries explicitly", async ({ page }) => {
const writes = await setup(page, { firstFailure: true });
const dialog = await openAttachmentGroup(page);
await dialog.getByRole("textbox").fill("Accepted after inspecting frozen evidence.");
await dialog.getByRole("checkbox", { name: "job-001@example.test" }).focus();
await page.keyboard.press("Space");
await dialog.getByRole("button", { name: "Accept 2 selected messages" }).click();
await expect(dialog.getByRole("alert")).toContainText("Synthetic review revision conflict");
await expect(dialog.getByRole("textbox")).toHaveValue("Accepted after inspecting frozen evidence.");
await expect(dialog.getByRole("checkbox", { name: "job-001@example.test" })).not.toBeChecked();
expect(writes).toHaveLength(1);
await dialog.getByRole("button", { name: "Accept 2 selected messages" }).click();
await expect(dialog).not.toBeVisible();
expect(writes).toHaveLength(2);
expect(writes[1]).toEqual(writes[0]);
});
test("grouped save blocks double clicks and dismissal until acknowledged", async ({ page }) => {
let acknowledge!: () => void;
const writes = await setup(page, { delay: new Promise<void>((resolve) => { acknowledge = resolve; }) });
const dialog = await openAttachmentGroup(page);
await dialog.getByRole("textbox").fill("Shared operational exception.");
const accept = dialog.getByRole("button", { name: "Accept 3 selected messages" });
await accept.evaluate((button: HTMLButtonElement) => { button.click(); button.click(); });
await expect.poll(() => writes.length).toBe(1);
await expect(dialog.getByRole("button", { name: /^(Cancel|Abbrechen)$/ })).toBeDisabled();
await page.keyboard.press("Escape");
await expect(dialog).toBeVisible();
acknowledge();
await expect(dialog).not.toBeVisible();
});
test("grouped review is bounded and clearly counts the remaining matching messages", async ({ page }) => {
const writes = await setup(page);
const dialog = await openAttachmentGroup(page, "&large");
await expect(dialog.getByRole("checkbox")).toHaveCount(200);
await expect(dialog).toContainText("Showing 200 of 205 matching unreviewed messages in this category; 200 selected.");
await dialog.getByRole("textbox").fill("Confirmed shared exception.");
await dialog.getByRole("button", { name: "Accept 200 selected messages" }).click();
await expect(dialog).not.toBeVisible();
expect(writes[0].jobIds).toHaveLength(200);
});
test("a replaced build or missing permission cannot be bulk accepted", async ({ page }) => {
const writes = await setup(page);
let dialog = await openAttachmentGroup(page);
await dialog.getByRole("textbox").fill("Do not save stale build evidence.");
await page.getByTestId("replace-build").evaluate((button: HTMLButtonElement) => button.click());
await expect(dialog.getByRole("alert")).toContainText("The build changed");
await expect(dialog.getByRole("button", { name: "Accept 3 selected messages" })).toBeDisabled();
dialog = await openAttachmentGroup(page, "&read-only");
await expect(dialog.getByRole("button", { name: /Accept .* selected messages/ })).toBeDisabled();
expect(writes).toHaveLength(0);
});
test("German bulk review labels and category changes preserve explicit scope", async ({ page }) => {
const writes = await setup(page);
const dialog = await openAttachmentGroup(page, "&language=de");
await expect(dialog).toHaveAccessibleName("Gleichartige Prüfbedingungen bestätigen");
await expect(dialog.getByRole("button", { name: "3 ausgewählte Nachrichten bestätigen" })).toBeDisabled();
await dialog.getByRole("combobox").selectOption("other-condition");
await expect(dialog.getByRole("checkbox")).toHaveCount(1);
await expect(dialog.getByRole("textbox", { name: "Gemeinsamer Prüfvermerk" })).toHaveValue("");
await dialog.getByRole("button", { name: "1 ausgewählte Nachrichten bestätigen" }).click();
await expect(dialog).not.toBeVisible();
expect(writes[0]).toMatchObject({ categoryKey: "other-condition", jobIds: ["other-category"], reason: "" });
});