/* Editor: tables, task lists, links between notes, and whether any of it * survives a reload — the place where a stale server response used to eat * freshly typed content. */ import { OUT, check, createNote, createNotebook, insertTable, launch, reopenNote, report, setTitle, typeInEditor, withFreshUser, } from "./lib.mjs"; const browser = await launch(); let session; try { session = await withFreshUser(browser, "editor"); const p = session.page; await createNotebook(p, "Editor Book"); await createNote(p); await typeInEditor(p, "Grundtext"); /* ── tables ── */ await insertTable(p); check((await p.locator(".ProseMirror table").count()) > 0, "table inserted"); check((await p.locator(".ProseMirror table th").count()) >= 3, "table has a header row"); /* ── task list ── */ await p.locator(".ProseMirror").click(); await p.keyboard.press("Control+End"); await p.keyboard.press("Enter"); await p.getByTitle("Task list").click(); await p.waitForTimeout(400); await p.keyboard.type("Milch kaufen"); await p.waitForTimeout(1200); check((await p.locator('.ProseMirror input[type="checkbox"]').count()) > 0, "task list inserted"); await p.locator('.ProseMirror input[type="checkbox"]').first().click(); await p.waitForTimeout(1500); await p.screenshot({ path: `${OUT}/editor-content.png` }); /* ── a second note to link to ── */ await setTitle(p, "Quelle"); await createNote(p); await setTitle(p, "Ziel"); await typeInEditor(p, "Ich bin das Ziel."); await p.locator("button").filter({ hasText: "Quelle" }).first().click(); await p.waitForSelector(".ProseMirror", { timeout: 15000 }); await p.waitForTimeout(1200); /* ── note link via the @ menu ── */ // Control+End lands inside the task list, where a plain Enter would only make // another task. A second Enter on the empty item leaves the list, so the link // goes into a paragraph of its own and the checked task above stays untouched. await p.locator(".ProseMirror").click(); await p.keyboard.press("Control+End"); await p.keyboard.press("Enter"); await p.keyboard.press("Enter"); await p.waitForTimeout(300); await p.keyboard.type("@Ziel"); await p.waitForTimeout(1400); const menuOpen = (await p.locator("text=Ziel").count()) > 0; check(menuOpen, "@ opens the link menu"); await p.keyboard.press("Enter"); await p.waitForTimeout(1800); const linkCount = await p.locator('.ProseMirror [data-note-link], .ProseMirror .note-link').count(); check(linkCount > 0, "note link inserted into the document", `${linkCount} found`); await p.screenshot({ path: `${OUT}/editor-link.png` }); /* ── the server learned about it ── */ await p.waitForTimeout(1500); await p.locator("button").filter({ hasText: "Ziel" }).first().click(); await p.waitForSelector(".ProseMirror", { timeout: 15000 }); await p.waitForTimeout(2000); const backlink = await p.locator("text=Quelle").count(); check(backlink > 0, "the target shows a backlink to the source"); /* ── everything survives a cold reload ── */ await reopenNote(p, "Quelle"); check((await p.locator(".ProseMirror table").count()) > 0, "table survived a reload"); check( (await p.locator('.ProseMirror [data-note-link], .ProseMirror .note-link').count()) > 0, "note link survived a reload" ); check((await p.locator('.ProseMirror input[type="checkbox"]').count()) > 0, "task list survived a reload"); check( (await p.locator('.ProseMirror input[type="checkbox"]:checked').count()) === 1, "the ticked task stayed ticked, and only that one" ); check( (await p.locator(".ProseMirror li").filter({ hasText: "Ziel" }).count()) === 0, "the note link went into a paragraph, not into the task list" ); check((await p.locator(".ProseMirror").innerText()).includes("Grundtext"), "plain text survived a reload"); await p.screenshot({ path: `${OUT}/editor-after-reload.png` }); } catch (e) { check(false, "suite ran to the end", e.message); } finally { await session?.cleanup(); const code = report(); await browser.close(); process.exit(code); }