From 6693600f3b23b333459fb72a0570c881293c4e36 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Fri, 4 Sep 2026 18:38:14 +0200 Subject: [PATCH] test: the DOM run places the caret through the editor and waits for its focus A click lands wherever the element's centre happens to be, and a break before the empty last paragraph yields no page; the editor's focus() lands on the next animation frame, so a key sent before it was lost now and then. --- docs/headless-testing.md | 6 ++++-- tests/dom/run.mjs | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/headless-testing.md b/docs/headless-testing.md index 5e5e5aa..d37c3b4 100644 --- a/docs/headless-testing.md +++ b/docs/headless-testing.md @@ -37,5 +37,7 @@ Then repro minimally and take a stack trace off the **dev** server (sourcemaps) **Budget for false alarms.** Three of four striking signals were the probe's own fault: `Range.getClientRects` also returns container rects (a layout checker built on it is noise), a stale button index reads as a dead control, and clicking `.tiptap > p` by index hits a -different block once an edit has reflowed the document. Reproduce deterministically before -touching any code. +different block once an edit has reflowed the document. Two more from the DOM run: TipTap's +`focus()` lands on the next animation frame, so a key sent right after it is lost unless +`document.activeElement` is awaited; and a page break before the empty last paragraph yields +no page. Reproduce deterministically before touching any code. diff --git a/tests/dom/run.mjs b/tests/dom/run.mjs index fff6a15..2bc669f 100644 --- a/tests/dom/run.mjs +++ b/tests/dom/run.mjs @@ -61,11 +61,21 @@ try { await page.waitForSelector('.tiptap', { timeout: 15_000 }); await settled(opened); - // A page break adds exactly one page, and undo takes it back. - await page.click('.tiptap'); - await page.keyboard.press(`${MOD}+End`); + // The caret is placed through the editor: a click lands wherever the element's centre + // happens to be. The focus itself arrives on the next animation frame, so a key sent + // before it is lost — wait for it. + const caretTo = async (pos) => { + await page.evaluate((p) => document.querySelector('.tiptap').editor.commands.focus(p), pos); + await page.waitForFunction(() => document.activeElement === document.querySelector('.tiptap'), null, { timeout: 5000 }); + }; + const firstParagraphEnd = () => page.evaluate(() => document.querySelector('.tiptap').editor.state.doc.firstChild.nodeSize - 1); + + // A page break after the first paragraph pushes the rest down a page, and undo takes + // it back. (A break before the empty last paragraph yields no page.) + await caretTo(await firstParagraphEnd()); await page.keyboard.press(`${MOD}+Enter`); const broken = await settled(opened + 1); + await caretTo('end'); await page.keyboard.press(`${MOD}+z`); const undone = await settled(opened); check(broken === opened + 1 && undone === opened, @@ -79,9 +89,7 @@ try { .catch(() => {}); return page.evaluate(() => !!document.querySelector('.doc-dirty')); }; - // The repagination after the undo rebuilds the view, so take the caret back first. - await page.click('.tiptap'); - await page.keyboard.press(`${MOD}+End`); + await caretTo('end'); await page.keyboard.type('nachtrag'); const marked = await dot(true); await page.keyboard.press(`${MOD}+z`);