Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

131 lines
4.1 KiB
TypeScript

/**
* 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<string, string>;
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");
});
});