Stage 4: drag-and-drop + keyboard chapter reordering (moveInList), chapters-changed broadcast, DnD/keyboard e2e

This commit is contained in:
avi 2026-08-16 14:43:42 -05:00
commit 4b8d78458c
7 changed files with 217 additions and 34 deletions

View file

@ -38,6 +38,7 @@ const {
deleteChapter,
duplicateChapter,
reorderChapters,
moveInOrder,
} = chapters;
let failures = 0;
@ -105,6 +106,11 @@ threw = false;
try { createChapter(dir, " "); } catch { threw = true; }
ok(threw, "createChapter rejects empty title");
// moveInOrder (pure reorder math used by drag & keyboard reorder)
ok(JSON.stringify(moveInOrder(["a", "b", "c", "d"], 0, 2)) === JSON.stringify(["b", "c", "a", "d"]), "move item to middle");
ok(JSON.stringify(moveInOrder(["a", "b", "c", "d"], 3, 0)) === JSON.stringify(["d", "a", "b", "c"]), "move last to top");
ok(JSON.stringify(moveInOrder(["a", "b", "c", "d"], 1, 1)) === JSON.stringify(["a", "b", "c", "d"]), "no-op on same index");
fs.rmSync(projOut, { force: true });
fs.rmSync(chapOut, { force: true });
fs.rmSync(dir, { recursive: true, force: true });

View file

@ -31,6 +31,44 @@ const BOOK_DIR = __BOOKDIR_VALUE__;
out.dupContent = await window.folio.getChapterContent(did);
// --- drag & drop reorder test ---
const a = (await window.folio.createChapter("Alpha")).id;
await new Promise((r) => setTimeout(r, 80));
const b = (await window.folio.createChapter("Bravo")).id;
await new Promise((r) => setTimeout(r, 80));
const c = (await window.folio.createChapter("Charlie")).id;
await new Promise((r) => setTimeout(r, 200));
const orderBefore = (await window.folio.getBookMeta()).chapterOrder;
out.orderBeforeDnD = orderBefore;
const rows = document.getElementById("chapterList").querySelectorAll(".chapter-row");
out.dndRowCount = rows.length;
const draggedId = rows[0].dataset.id;
const targetId = rows[2].dataset.id;
const dt = new DataTransfer();
rows[0].dispatchEvent(new DragEvent("dragstart", { dataTransfer: dt, bubbles: true }));
rows[2].dispatchEvent(new DragEvent("dragover", { dataTransfer: dt, bubbles: true }));
rows[2].dispatchEvent(new DragEvent("drop", { dataTransfer: dt, bubbles: true }));
rows[0].dispatchEvent(new DragEvent("dragend", { dataTransfer: dt, bubbles: true }));
await new Promise((r) => setTimeout(r, 250));
const orderAfter = (await window.folio.getBookMeta()).chapterOrder;
out.orderAfterDnD = orderAfter;
out.dndMoved =
orderAfter[0] === orderBefore[1] &&
orderAfter[2] === orderBefore[0] &&
orderAfter[3] === orderBefore[3];
// keyboard reorder: select first visible row, Alt+ArrowDown moves it down
const fresh1 = document.getElementById("chapterList").querySelectorAll(".chapter-row");
fresh1[0].dispatchEvent(new MouseEvent("click", { bubbles: true }));
await new Promise((r) => setTimeout(r, 120));
const fresh2 = document.getElementById("chapterList").querySelectorAll(".chapter-row");
fresh2[0].focus();
fresh2[0].dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", altKey: true, bubbles: true }));
await new Promise((r) => setTimeout(r, 250));
const orderKb = (await window.folio.getBookMeta()).chapterOrder;
out.orderAfterKb = orderKb;
out.kbMoved = orderKb[0] === orderAfter[1] && orderKb[1] === orderAfter[0];
out.all = true;
} catch (e) {
out.error = String((e && e.message) || e);