/** * Autosave E2E Tests * * Tests that autosave updates the existing draft revision in place * rather than creating a new revision on each keystroke. * * Covers issue #5: skipRevision was stripped by Zod validation, * causing every autosave to create a new revision. */ import { test, expect } from "../fixtures"; test.describe("Autosave", () => { let collectionSlug: string; let postId: string; let headers: Record; let baseUrl: string; test.beforeEach(async ({ admin, serverInfo }) => { await admin.devBypassAuth(); baseUrl = serverInfo.baseUrl; headers = { "Content-Type": "application/json", Authorization: `Bearer ${serverInfo.token}`, "X-EmDash-Request": "1", Origin: baseUrl, }; // Create a collection with revision support collectionSlug = `autosave_${Date.now()}`; await fetch(`${baseUrl}/_emdash/api/schema/collections`, { method: "POST", headers, body: JSON.stringify({ slug: collectionSlug, label: "Autosave Test", labelSingular: "Autosave Test", supports: ["revisions", "drafts"], }), }); await fetch(`${baseUrl}/_emdash/api/schema/collections/${collectionSlug}/fields`, { method: "POST", headers, body: JSON.stringify({ slug: "title", type: "string", label: "Title", required: true }), }); // Create and publish a post const createRes = await fetch(`${baseUrl}/_emdash/api/content/${collectionSlug}`, { method: "POST", headers, body: JSON.stringify({ data: { title: "Original" }, slug: "autosave-test" }), }); const createData: any = await createRes.json(); postId = createData.data?.item?.id ?? createData.data?.id; await fetch(`${baseUrl}/_emdash/api/content/${collectionSlug}/${postId}/publish`, { method: "POST", headers, body: JSON.stringify({}), }); }); test.afterEach(async () => { await fetch(`${baseUrl}/_emdash/api/content/${collectionSlug}/${postId}`, { method: "DELETE", headers, }).catch(() => {}); await fetch(`${baseUrl}/_emdash/api/schema/collections/${collectionSlug}`, { method: "DELETE", headers, }).catch(() => {}); }); test("multiple autosaves update draft in place instead of creating new revisions", async ({ admin, }) => { const contentUrl = `/_emdash/api/content/${collectionSlug}/${postId}`; const isPut = (res: any) => res.url().includes(contentUrl) && res.request().method() === "PUT"; const isGet = (res: any) => res.url().includes(contentUrl) && !res.url().includes("/revisions") && res.request().method() === "GET"; await admin.goToEditContent(collectionSlug, postId); await admin.waitForLoading(); const titleInput = admin.page.locator("#field-title"); await expect(titleInput).toHaveValue("Original"); // First edit — listen for both the PUT and the subsequent cache re-fetch GET const firstPut = admin.page.waitForResponse(isPut, { timeout: 10000 }); await titleInput.fill("Edit One"); await firstPut; // Wait for the cache invalidation GET to settle so form doesn't get overwritten const refetchGet = admin.page.waitForResponse(isGet, { timeout: 5000 }).catch(() => {}); await refetchGet; // Extra settle time for React state updates await admin.page.waitForTimeout(500); // Check revision count after first autosave const res1 = await fetch( `${baseUrl}/_emdash/api/content/${collectionSlug}/${postId}/revisions`, { headers }, ); const data1: any = await res1.json(); const countAfterFirst = data1.data.total; // Second edit — set up listener BEFORE typing const secondPut = admin.page.waitForResponse(isPut, { timeout: 10000 }); await titleInput.fill("Edit Two"); await secondPut; // Check revision count — should be same (updated in place, not new revision) const res2 = await fetch( `${baseUrl}/_emdash/api/content/${collectionSlug}/${postId}/revisions`, { headers }, ); const data2: any = await res2.json(); const countAfterSecond = data2.data.total; expect(countAfterSecond).toBe(countAfterFirst); // Verify the latest revision contains the last autosaved data const latestRevision = data2.data.items?.[0]; expect(latestRevision?.data?.title).toBe("Edit Two"); }); });