120 lines
7.3 KiB
TypeScript
120 lines
7.3 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
async function install(page: Page, language = "en", rejectSave = false) {
|
|
let revision = 1;
|
|
let raw: any = null;
|
|
let rejected = false;
|
|
const writes: any[] = [];
|
|
const errors: string[] = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
const version = () => ({ id: "order-version", campaign_id: "order-campaign", version_number: 1,
|
|
edit_revision: revision, strong_etag: `"order-version:${revision}"`, editor_state: {},
|
|
workflow_state: "editing", current_flow: "manual", current_step: "recipients", is_complete: false,
|
|
updated_at: "2026-09-07T10:00:00Z", raw_json: raw });
|
|
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
|
if (route.request().method() === "POST" && route.request().url().endsWith("/autosave")) {
|
|
const body = route.request().postDataJSON(); writes.push(body);
|
|
if (rejectSave && !rejected) {
|
|
rejected = true;
|
|
return route.fulfill({ status: 422, json: { detail: "Fixture policy rejected this save" } });
|
|
}
|
|
raw = body.campaign_json; revision += 1;
|
|
return route.fulfill({ json: version() });
|
|
}
|
|
if (route.request().method() === "GET") return route.fulfill({ json: version() });
|
|
return route.abort();
|
|
});
|
|
await page.goto(`/?campaign-recipient-order&language=${language}`);
|
|
await expect(page.getByTestId("recipient-order")).toContainText("alpha@example.test");
|
|
return { writes, errors, raw: () => raw };
|
|
}
|
|
|
|
async function moveLastToFirst(page: Page) {
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.getByRole("button", { name: "Move address up", exact: true }).nth(2).click();
|
|
await dialog.getByRole("button", { name: "Move address up", exact: true }).nth(1).click();
|
|
await expect(dialog.locator('input[type="email"]').first()).toHaveValue("zulu@example.test");
|
|
}
|
|
|
|
for (const language of ["en", "de"]) {
|
|
test(`individual recipient order survives dialog Save, reopen and actual campaign POST in ${language}`, async ({ page }) => {
|
|
const fixture = await install(page, language);
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await moveLastToFirst(page);
|
|
await page.getByRole("dialog").getByRole("button", { name: language === "de" ? "Speichern" : "Save", exact: true }).click();
|
|
await expect(page.getByTestId("primary-address")).toHaveText("zulu@example.test");
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("true");
|
|
expect(fixture.writes).toHaveLength(0); // Dialog Save applies the page draft, not a hidden network mutation.
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await expect(page.getByRole("dialog").locator('input[type="email"]').first()).toHaveValue("zulu@example.test");
|
|
await page.getByRole("dialog").getByRole("button", { name: language === "de" ? "Abbrechen" : "Cancel", exact: true }).click();
|
|
await page.getByRole("button", { name: "Save campaign", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("false");
|
|
expect(fixture.writes[0].campaign_json.entries.inline[0].to.map((item: any) => item.email)).toEqual([
|
|
"zulu@example.test", "alpha@example.test", "beta@example.test"
|
|
]);
|
|
expect(fixture.raw().entries.inline[0].email).toBe("zulu@example.test");
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await expect(page.getByRole("dialog").locator('input[type="email"]').first()).toHaveValue("zulu@example.test");
|
|
expect(fixture.errors).toEqual([]);
|
|
});
|
|
}
|
|
|
|
test("global header addresses preserve their explicitly selected order", async ({ page }) => {
|
|
const fixture = await install(page);
|
|
await page.getByRole("button", { name: "Edit global addresses", exact: true }).click();
|
|
await moveLastToFirst(page);
|
|
await page.getByRole("dialog").getByRole("button", { name: "Save", exact: true }).click();
|
|
await page.getByRole("button", { name: "Save campaign", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("false");
|
|
expect(fixture.raw().recipients.to.map((item: any) => item.email)).toEqual([
|
|
"zulu@example.test", "alpha@example.test", "beta@example.test"
|
|
]);
|
|
await page.getByRole("button", { name: "Edit global addresses", exact: true }).click();
|
|
await expect(page.getByRole("dialog").locator('input[type="email"]').first()).toHaveValue("zulu@example.test");
|
|
});
|
|
|
|
test("paste deduplicates without alphabetically rearranging existing or newly added addresses", async ({ page }) => {
|
|
const fixture = await install(page);
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await moveLastToFirst(page);
|
|
await page.getByRole("dialog").locator(".recipient-address-category").evaluate((section) => {
|
|
const clipboard = new DataTransfer();
|
|
clipboard.setData("text/plain", "Zulu <ZULU@example.test>; Omega <omega@example.test>; Charlie <charlie@example.test>");
|
|
section.dispatchEvent(new ClipboardEvent("paste", { clipboardData: clipboard, bubbles: true, cancelable: true }));
|
|
});
|
|
await expect(page.getByRole("dialog").locator('input[type="email"]')).toHaveCount(5);
|
|
await page.getByRole("dialog").getByRole("button", { name: "Save", exact: true }).click();
|
|
await page.getByRole("button", { name: "Save campaign", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("false");
|
|
expect(fixture.raw().entries.inline[0].to.map((item: any) => item.email)).toEqual([
|
|
"zulu@example.test", "alpha@example.test", "beta@example.test", "omega@example.test", "charlie@example.test"
|
|
]);
|
|
});
|
|
|
|
test("failed campaign save retains the reordered dialog result for explicit retry", async ({ page }) => {
|
|
const fixture = await install(page, "en", true);
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await moveLastToFirst(page);
|
|
await page.getByRole("dialog").getByRole("button", { name: "Save", exact: true }).click();
|
|
await page.getByRole("button", { name: "Save campaign", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-error")).toContainText("Fixture policy rejected");
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("true");
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await expect(page.getByRole("dialog").locator('input[type="email"]').first()).toHaveValue("zulu@example.test");
|
|
await page.getByRole("dialog").getByRole("button", { name: "Cancel", exact: true }).click();
|
|
await page.getByRole("button", { name: "Save campaign", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("false");
|
|
expect(fixture.writes).toHaveLength(2);
|
|
expect(fixture.writes[1].campaign_json.entries.inline[0].to).toEqual(fixture.writes[0].campaign_json.entries.inline[0].to);
|
|
});
|
|
|
|
test("Cancel deliberately discards only the dialog's unconfirmed reorder", async ({ page }) => {
|
|
await install(page);
|
|
await page.getByRole("button", { name: "Edit individual addresses", exact: true }).click();
|
|
await moveLastToFirst(page);
|
|
await page.getByRole("dialog").getByRole("button", { name: "Cancel", exact: true }).click();
|
|
await expect(page.getByTestId("recipient-dirty")).toHaveText("false");
|
|
await expect(page.getByTestId("primary-address")).toHaveText("alpha@example.test");
|
|
});
|