/* Chapters — folders inside a notebook. * * The claim the feature rests on is the one under test: a note filed into a * chapter is *in* it and not also loose in the notebook, and removing a chapter * never removes what it held. Both look fine on screen while being wrong on the * server, so the server is asked directly. */ import { api, check, createNote, createNotebook, launch, report, setTitle, withFreshUser, } from "./lib.mjs"; const browser = await launch(); let session; try { session = await withFreshUser(browser, "chapters"); const p = session.page; const titles = () => p.locator("[data-note-row] [data-note-title]").evaluateAll((els) => els.map((e) => e.textContent.trim())); const addChapter = async (name) => { await p.locator("[data-add-chapter]").click(); await p.waitForTimeout(600); await p.getByLabel("Chapter name").fill(name); await p.getByRole("button", { name: "Create", exact: true }).click(); await p.waitForTimeout(2000); }; const moveTo = async (noteTitle, target) => { await p.locator("[data-note-row]").filter({ hasText: noteTitle }).first().click({ button: "right", force: true }); await p.waitForTimeout(600); await p.locator("[data-submenu-toggle]").first().click(); await p.waitForTimeout(500); await p.locator("[data-submenu] button").filter({ hasText: target }).first().click(); await p.waitForTimeout(2500); }; await createNotebook(p, "Haus"); await p.locator("button").filter({ hasText: "Haus" }).first().click(); await p.waitForTimeout(700); for (const name of ["Zählerstand", "Stromrechnung"]) { await createNote(p); await setTitle(p, name); await p.waitForTimeout(600); } await p.waitForTimeout(1000); /* ── making one ── */ check((await p.locator("[data-add-chapter]").count()) === 1, "a notebook can be given a chapter"); await addChapter("Rechnungen"); check((await p.locator("[data-chapter-row]").count()) === 1, "and it appears in the list"); const server = (await api(p, "GET", "/chapters")).body; check(server.length === 1 && server[0].name === "Rechnungen", "the server has it", JSON.stringify(server)); /* ── filing a note ── */ await moveTo("Stromrechnung", "Rechnungen"); check( (await titles()).join(",") === "Zählerstand", "a note filed into a chapter leaves the notebook's own list", (await titles()).join(", ") ); await p.locator("[data-chapter-row]").first().click(); await p.waitForTimeout(1200); check((await titles()).join(",") === "Stromrechnung", "and is inside the chapter", (await titles()).join(", ")); check((await p.locator("[data-chapter-back]").count()) === 1, "with a way back out"); const filed = (await api(p, "GET", "/notes")).body.find((n) => n.title === "Stromrechnung"); check(!!filed?.chapter_id, "the server filed it too", String(filed?.chapter_id)); /* A note made inside a chapter belongs to it, without being moved. */ await createNote(p); await setTitle(p, "Wasserrechnung"); await p.waitForTimeout(1500); const made = (await api(p, "GET", "/notes")).body.find((n) => n.title === "Wasserrechnung"); check(made?.chapter_id === filed?.chapter_id, "a note made inside a chapter is filed there", String(made?.chapter_id)); await p.locator("[data-chapter-back]").click(); await p.waitForTimeout(1200); check( (await titles()).join(",") === "Zählerstand", "and going back shows the notebook again, without the chapter's notes", (await titles()).join(", ") ); /* ── a chapter is not a wastebasket ── * Removing one must never remove what it held: the notes come back to the * notebook, where they can be seen and filed again. */ await p.locator("[data-chapter-row]").first().click({ button: "right", force: true }); await p.waitForTimeout(600); await p.getByText("Remove chapter", { exact: true }).click(); await p.waitForTimeout(700); check(/stay/i.test(await p.locator("body").innerText()), "removing one says out loud that the notes stay"); await p.getByRole("button", { name: "Remove", exact: true }).click(); await p.waitForTimeout(2500); check((await p.locator("[data-chapter-row]").count()) === 0, "the chapter is gone"); const after = (await titles()).sort().join(","); check( after === "Stromrechnung,Wasserrechnung,Zählerstand", "and every note it held is back in the notebook", after ); const freed = (await api(p, "GET", "/notes")).body; check( freed.every((n) => !n.chapter_id), "with nothing on the server still pointing at a chapter that is gone", JSON.stringify(freed.map((n) => [n.title, n.chapter_id])) ); } catch (e) { check(false, "suite ran to the end", e.message); } finally { await session?.cleanup(); const code = report(); await browser.close(); process.exit(code); }