From 2ecd7631f440116a7401766ae5c4a7d8702b5401 Mon Sep 17 00:00:00 2001 From: Logan Johnson Date: Wed, 9 Sep 2026 19:59:12 -0400 Subject: [PATCH] test(desktop): witness native deletion update before scope switch Observe the first real update from authored text and capture persistence after the production callback, then switch before asserting. Retain unsaved and persisted controls. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson --- .../tests/e2e/remote-owned-mentions.spec.ts | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/desktop/tests/e2e/remote-owned-mentions.spec.ts b/desktop/tests/e2e/remote-owned-mentions.spec.ts index 7784239baa..b9d5e9efb8 100644 --- a/desktop/tests/e2e/remote-owned-mentions.spec.ts +++ b/desktop/tests/e2e/remote-owned-mentions.spec.ts @@ -555,30 +555,53 @@ for (const { incoming, savedFirst } of [ await navigate(roots[0]); await expect(input).toHaveText("new authored text"); } - await input.fill(""); - expect(await sourceRecord()).toBeNull(); - // Read, don't poll: use the existing expando round trip to capture the - // actual PM document after native deletion. Assert only AFTER switching, - // so proof of deletion cannot wait away a rapid scope-cleanup race. - const deleted = await input.evaluate((el) => { + // Native fill can return before DOMObserver dispatches the PM edit. + // Arm before deletion; observe the first update FROM the authored document, + // not eventual emptiness/storage quiescence. Production onUpdate was + // registered first and synchronously owns empty-authority persistence. + const deletion = await input.evaluateHandle((el, root) => { el.setAttribute("data-lifecycle-host", "retained"); const editor = ( el as HTMLElement & { editor: import("@tiptap/core").Editor } ).editor; return { - dom: el.textContent, - doc: editor.getJSON(), - from: editor.state.selection.from, - to: editor.state.selection.to, + completed: new Promise((resolve) => { + const onUpdate = ({ + transaction, + }: import("@tiptap/core").EditorEvents["update"]) => { + if (transaction.before.textContent !== "new authored text") return; + editor.off("update", onUpdate); + const key = Object.keys(localStorage).find((key) => + key.startsWith("buzz-drafts.v2"), + ); + resolve({ + dom: el.textContent, + doc: editor.getJSON(), + from: editor.state.selection.from, + to: editor.state.selection.to, + source: key + ? (JSON.parse(localStorage.getItem(key) ?? "{}")[ + `thread:${root}` + ] ?? null) + : "draft storage scope missing", + }); + }; + editor.on("update", onUpdate); + }), }; - }); + }, roots[0]); + await input.fill(""); + const deleted = await deletion.evaluate(({ completed }) => completed); + // No assertion/poll between action completion and outgoing-key cleanup. await navigate(roots[1]); expect(deleted).toEqual({ dom: "", doc: { type: "doc", content: [{ type: "paragraph" }] }, from: 1, to: 1, + source: null, }); + await deletion.dispose(); await expect(input).toHaveAttribute("data-lifecycle-host", "retained"); await expect(input).toHaveText(incoming); await expect(page.getByRole("alertdialog")).toHaveCount(0);