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.
This commit is contained in:
Steffen Becker committed 2026-09-04 19:30:13 +02:00
1 parent 131943dab1
commit 6693600f3b
2 files changed
+18 -8

No files matched your search

+4 -2
View File
@@ -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.
+14 -6
View File
@@ -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`);