fix(web): preserve drafts during memo creation

This commit is contained in:
tianma-if committed 2026-08-19 18:53:46 +08:00
1 parent c674719e80
commit 2e6cc5d71c
2 files changed
+119 -1

No files matched your search

+66
View File
@@ -6,8 +6,10 @@ import type { MemoUpdateSyncPayload } from "../apps/web/src/lib/local-db";
const { localDb } = await import("../apps/web/src/lib/local-db");
const {
discardWebMemoConflict,
getMemoCreateQueueId,
getMemoUpdateQueueId,
isMemoUpdateAlreadyApplied,
queueMemoCreate,
queueMemoUpdate,
syncQueuedChanges,
} = await import("../apps/web/src/lib/sync-queue");
@@ -83,6 +85,7 @@ describe("web sync queue concurrency", () => {
expect(queued).toBeDefined();
expect(isMemoUpdateAlreadyApplied(memo("already saved"), queued!)).toBe(true);
expect(isMemoUpdateAlreadyApplied(memo("already saved", 0), queued!)).toBe(false);
expect(isMemoUpdateAlreadyApplied(memo("different remote text"), queued!)).toBe(false);
});
@@ -141,6 +144,69 @@ describe("web sync queue concurrency", () => {
expect(queued?.payload.expectedContentHash).toBe("hash-1");
});
test("queues a draft saved while memo creation is in flight after remapping its id", async () => {
const scope = "test-scope";
const temporaryId = "local_create_race";
const remoteMemo = { ...memo("", 1), id: "memo_created", title: null };
const draftTitle = "written during create";
const draftContent = payload(draftTitle).contentJson;
await queueMemoCreate(scope, {
temporaryId,
notebookId: "nb_inbox",
title: "",
contentMarkdown: "",
tags: [],
createdAt: "2026-07-15T00:00:00.000Z",
updatedAt: "2026-07-15T00:00:00.000Z",
});
await localDb.drafts.put({
memoId: temporaryId,
title: draftTitle,
contentJson: draftContent,
tagsText: "",
updatedAt: "2026-07-15T00:00:02.000Z",
});
const requests: Array<{ method: string; url: string }> = [];
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
const method = init?.method ?? "GET";
requests.push({ method, url });
if (method === "POST" && url.endsWith("/api/v1/memos")) {
return jsonResponse({ memo: remoteMemo }, 201);
}
if (url.endsWith("/edit-sessions")) {
return jsonResponse({ editSession: { ...session(1), memoId: remoteMemo.id } });
}
return jsonResponse({ memo: { ...remoteMemo, title: draftTitle, contentJson: draftContent, revision: 2, contentHash: "hash-2" } });
}) as typeof fetch;
const createResult = await syncQueuedChanges({ scope });
const queuedUpdate = await localDb.syncQueue.get(getMemoUpdateQueueId(remoteMemo.id));
expect(createResult).toEqual({ attempted: 1, synced: 1, failed: 0, conflicted: 0 });
expect(await localDb.syncQueue.get(getMemoCreateQueueId(temporaryId))).toBeUndefined();
expect(await localDb.drafts.get(temporaryId)).toBeUndefined();
expect(await localDb.drafts.get(remoteMemo.id)).toMatchObject({ title: draftTitle, contentJson: draftContent });
expect(queuedUpdate).toMatchObject({
kind: "memo.update",
scope,
memoId: remoteMemo.id,
status: "pending",
payload: {
expectedRevision: 1,
expectedContentHash: "hash-1",
title: draftTitle,
contentJson: draftContent,
},
});
const updateResult = await syncQueuedChanges({ scope });
expect(updateResult).toEqual({ attempted: 1, synced: 1, failed: 0, conflicted: 0 });
expect(requests.some(({ method, url }) => method === "PATCH" && url.endsWith(`/api/v1/memos/${remoteMemo.id}`))).toBe(true);
expect(await localDb.syncQueue.get(getMemoUpdateQueueId(remoteMemo.id))).toBeUndefined();
});
test("keeps a genuine server revision mismatch as a conflict", async () => {
await queueMemoUpdate(payload("offline edit"));
globalThis.fetch = (async () => jsonResponse({ editSession: session(1) })) as typeof fetch;