Files
EpicNext-Cms/e2e/ui/draft-recovery.spec.ts
T
Simo 88790d1a0d
CI / check (push) Successful in 2m3s
CI / deploy (push) Successful in 1m31s
CI / publish-container (push) Successful in 1m41s
feat(cms): recover drafts and failed imports with verified UI workflows
2026-09-11 08:58:10 +02:00

170 lines
6.1 KiB
TypeScript

import { expect, test } from "@playwright/test";
const key = "cms:draft:v1:7:article:new";
test("browser draft survives session failure and reload, restores explicitly and clears only after save", async ({
page,
}) => {
await page.route("**/fixture/actions/load-recovery", (route) =>
route.fulfill({ status: 401, body: "expired" }),
);
await page.route("**/fixture/actions/article", (route) =>
route.fulfill({
contentType: "application/json",
body: JSON.stringify({ ok: false, error: "Session expired" }),
}),
);
await page.goto("/admin/articles/recovery");
const title = page.locator('input[name="title"]');
await title.fill("Keep this unfinished article");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toContain("Keep this unfinished article");
await page.getByRole("button", { name: "Save article", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("Session expired");
expect(await page.evaluate((k) => localStorage.getItem(k), key)).toContain(
"Keep this unfinished article",
);
page.on("dialog", (dialog) => dialog.accept());
await page.reload();
await expect(title).toHaveValue("Community update");
await page
.getByRole("button", { name: "Restore browser copy", exact: true })
.click();
await expect(title).toHaveValue("Keep this unfinished article");
await page.unroute("**/fixture/actions/article");
await page.route("**/fixture/actions/article", (route) =>
route.fulfill({
contentType: "application/json",
body: JSON.stringify({ ok: true }),
}),
);
await page.getByRole("button", { name: "Save article", exact: true }).click();
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toBeNull();
await page.reload();
await expect(
page.getByRole("button", { name: "Restore browser copy", exact: true }),
).toHaveCount(0);
});
test("pending recovery does not overwrite current edits and discard preserves them", async ({
page,
}) => {
await page.goto("/admin/articles/recovery");
const title = page.locator('input[name="title"]');
await title.fill("Older draft");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toContain("Older draft");
page.on("dialog", (dialog) => dialog.accept());
await page.reload();
await title.fill("New current edits");
await page
.getByRole("button", { name: "Discard browser copy", exact: true })
.click();
await expect(title).toHaveValue("New current edits");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toContain("New current edits");
});
test("unavailable browser storage leaves the form editable", async ({
page,
}) => {
await page.addInitScript(() => {
Storage.prototype.setItem = () => {
throw Error("quota");
};
});
await page.goto("/admin/articles/recovery");
await page.locator('input[name="title"]').fill("Still editable");
await expect(
page.getByText("Browser recovery is unavailable.", { exact: false }),
).toBeVisible();
await expect(page.locator('input[name="title"]')).toHaveValue(
"Still editable",
);
});
test("saving while recovery is pending enables recovery for subsequent edits", async ({
page,
}) => {
await page.route("**/fixture/actions/article", (route) =>
route.fulfill({
contentType: "application/json",
body: JSON.stringify({ ok: true }),
}),
);
await page.goto("/admin/articles/recovery");
const title = page.locator('input[name="title"]');
await title.fill("Previous browser draft");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toContain("Previous browser draft");
page.on("dialog", (dialog) => dialog.accept());
await page.reload();
await expect(
page.getByRole("button", { name: "Restore browser copy", exact: true }),
).toBeVisible();
await page.getByRole("button", { name: "Save article", exact: true }).click();
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toBeNull();
await title.fill("Edits after successful save");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), key))
.toContain("Edits after successful save");
});
test("unmount flush retains an edit before the autosave interval", async ({
page,
}) => {
await page.goto("/admin/articles/recovery");
await page.clock.install();
await page.clock.pauseAt(new Date());
await page.locator('input[name="title"]').fill("Immediate final edit");
await page
.getByRole("button", { name: "Close editor fixture", exact: true })
.click();
expect(await page.evaluate((k) => localStorage.getItem(k), key)).toContain(
"Immediate final edit",
);
});
test("event title and dates recover after reload and clear after successful update", async ({
page,
}) => {
const eventKey = "cms:draft:v1:7:event:12";
await page.route("**/fixture/actions/update-event", (route) =>
route.fulfill({
contentType: "application/json",
body: JSON.stringify({ ok: true, data: { id: 12 } }),
}),
);
await page.goto("/admin/events/recovery");
const title = page.locator("#title");
const dates = page.locator('input[type="datetime-local"]');
await title.fill("Recovered community event");
await dates.nth(0).fill("2030-02-03T14:30");
await dates.nth(1).fill("2030-02-03T16:00");
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), eventKey))
.toContain("2030-02-03T14:30:00.000Z");
page.on("dialog", (dialog) => dialog.accept());
await page.reload();
await expect(title).toHaveValue("Original event");
await page
.getByRole("button", { name: "Restore browser copy", exact: true })
.click();
await expect(title).toHaveValue("Recovered community event");
await expect(dates.nth(0)).toHaveValue("2030-02-03T14:30");
await expect(dates.nth(1)).toHaveValue("2030-02-03T16:00");
const save = page.waitForRequest("**/fixture/actions/update-event");
await page.locator('button[type="submit"]').click();
expect((await save).postDataJSON()).toMatchObject({
id: 12,
title: "Recovered community event",
startsAt: "2030-02-03T14:30:00.000Z",
endsAt: "2030-02-03T16:00:00.000Z",
});
await expect
.poll(() => page.evaluate((k) => localStorage.getItem(k), eventKey))
.toBeNull();
});