feat(cms): recover drafts and failed imports with verified UI workflows
This commit is contained in:
1 parent
410a1e466c
commit
88790d1a0d
84 files changed
+3952
-288
No files matched your search
@@ -0,0 +1,169 @@
|
||||
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();
|
||||
});
|
||||
Reference in new issue
Block a user