Complete Full Book view: wire Ctrl+A select-all and add e2e coverage

Route the renderer select-all listener (previously sent but never handled) to
the active editor, and extend the e2e scenario to verify the Full Book
assembly, stats, Copy All clipboard content, and read-only select-all.
This commit is contained in:
avi 2026-08-17 18:13:40 -05:00
commit 46eca18cdf
3 changed files with 78 additions and 1 deletions

View file

@ -845,9 +845,20 @@ export function initBook(api: FolioAPI) {
if (editorView.hasFocus) redo(editorView); if (editorView.hasFocus) redo(editorView);
else document.execCommand("redo"); else document.execCommand("redo");
}); });
api.onSelectAllRequested(() => {
if (!fullBookView.classList.contains("hidden")) {
fullBookEditor.focus();
selectAll(fullBookEditor);
} else if (editorView.hasFocus) {
selectAll(editorView);
} else {
document.execCommand("selectAll");
}
});
setChapterButtons(false); setChapterButtons(false);
void renderRecents(); void renderRecents();
(window as any).__folioEditor = editorView; (window as any).__folioEditor = editorView;
(window as any).__folioFullBookEditor = fullBookEditor;
} }

View file

@ -5,7 +5,9 @@
# chain, prints a JSON summary, and quits. Inert unless run explicitly. # chain, prints a JSON summary, and quits. Inert unless run explicitly.
set -e set -e
BOOK=$(node -e 'const fs=require("fs"),path=require("path"),os=require("os");const d=fs.mkdtempSync(path.join(os.tmpdir(),"folio-e2e-"));fs.mkdirSync(path.join(d,"chapters"),{recursive:true});fs.writeFileSync(path.join(d,"folio.json"),JSON.stringify({app:"folio",version:1,title:"E2E Book",chapterOrder:[],chapters:{}}));process.stdout.write(d);') BOOK=$(node -e 'const fs=require("fs"),path=require("path"),os=require("os");const d=fs.mkdtempSync(path.join(os.tmpdir(),"folio-e2e-"));fs.mkdirSync(path.join(d,"chapters"),{recursive:true});fs.writeFileSync(path.join(d,"folio.json"),JSON.stringify({app:"folio",version:1,title:"E2E Book",chapterOrder:[],chapters:{}}));process.stdout.write(d);')
BOOK2=$(node -e 'const fs=require("fs"),path=require("path"),os=require("os");const d=fs.mkdtempSync(path.join(os.tmpdir(),"folio-e2e2-"));fs.mkdirSync(path.join(d,"chapters"),{recursive:true});fs.writeFileSync(path.join(d,"folio.json"),JSON.stringify({app:"folio",version:1,title:"E2E Book 2",chapterOrder:[],chapters:{}}));process.stdout.write(d);')
export FOLIO_SELF_TEST=1 export FOLIO_SELF_TEST=1
export FOLIO_TEST_BOOK="$BOOK" export FOLIO_TEST_BOOK="$BOOK"
trap 'rm -rf "$BOOK"' EXIT export FOLIO_TEST_BOOK2="$BOOK2"
trap 'rm -rf "$BOOK" "$BOOK2"' EXIT
xvfb-run -a electron . xvfb-run -a electron .

View file

@ -2,6 +2,7 @@
Injected only when FOLIO_SELF_TEST=1. Exercises the full Injected only when FOLIO_SELF_TEST=1. Exercises the full
renderer -> preload -> IPC -> chapters chain. */ renderer -> preload -> IPC -> chapters chain. */
const BOOK_DIR = __BOOKDIR_VALUE__; const BOOK_DIR = __BOOKDIR_VALUE__;
const BOOK2_DIR = __BOOKDIR2_VALUE__;
(async () => { (async () => {
const out = {}; const out = {};
try { try {
@ -212,6 +213,62 @@ const BOOK_DIR = __BOOKDIR_VALUE__;
(await window.folio.getChapterContent(typedId)).includes("**Hello**"); (await window.folio.getChapterContent(typedId)).includes("**Hello**");
} }
// --- full book view (fresh second book with known chapter contents) ---
await window.folio.openBookPath(BOOK2_DIR);
await new Promise((r) => setTimeout(r, 400));
const fb = window.__folioFullBookEditor;
const alphaId = (await window.folio.createChapter("Alpha")).id;
const betaId = (await window.folio.createChapter("Beta")).id;
const gammaId = (await window.folio.createChapter("Gamma")).id;
await new Promise((r) => setTimeout(r, 300));
await window.folio.saveChapter(alphaId, "# Alpha\n\nAlpha body text");
await window.folio.saveChapter(betaId, "Beta body without heading");
await window.folio.saveChapter(gammaId, "# Gamma's Own Title\n\nGamma text");
await new Promise((r) => setTimeout(r, 300));
document.getElementById("fullBookBtn").click();
await new Promise((r) => setTimeout(r, 300));
const expectedAssembled =
"# Alpha\n\nAlpha body text\n\n# Beta\n\nBeta body without heading\n\n# Gamma\n\n# Gamma's Own Title\n\nGamma text";
out.fullBookShown =
!document.getElementById("fullBookView").classList.contains("hidden") &&
document.getElementById("chapterView").classList.contains("hidden") &&
document.getElementById("fullBookBtn").classList.contains("active") &&
!document.getElementById("fullBookNav").classList.contains("hidden");
const assembled = fb.state.doc.toString();
out.assembled = assembled;
out.assembledOk = assembled === expectedAssembled;
const words = expectedAssembled.trim().split(/\s+/).length;
const statsEl = document.getElementById("fullBookStats");
out.fullBookStatsText = statsEl.textContent;
out.fullBookStatsOk =
statsEl.textContent === `words: ${words} · chars: ${expectedAssembled.length}`;
document.getElementById("copyAllBtn").click();
await new Promise((r) => setTimeout(r, 200));
const clip = await window.folio.getClipboardText();
out.clipboardLen = clip.length;
out.copyAllOk = clip === expectedAssembled;
out.copyStatusShown = document.getElementById("fullBookStatus").textContent === "Full book copied";
// Ctrl+A (routed through the renderer listener) selects the whole read-only doc
fb.focus();
fb.contentDOM.dispatchEvent(
new KeyboardEvent("keydown", { key: "a", ctrlKey: true, bubbles: true, cancelable: true })
);
const sel = fb.state.selection.main;
out.fullBookSelectAllOk =
fb.state.doc.length > 0 && sel.from === 0 && sel.to === fb.state.doc.length;
// back to normal chapter editing still works
document.querySelector("#chapterList .chapter-row").click();
await new Promise((r) => setTimeout(r, 250));
out.backToChapterOk =
document.getElementById("fullBookView").classList.contains("hidden") &&
!document.getElementById("chapterView").classList.contains("hidden") &&
!document.getElementById("fullBookBtn").classList.contains("active");
const okFlags = [ const okFlags = [
"dndMoved", "dndMoved",
"kbMoved", "kbMoved",
@ -233,6 +290,13 @@ const BOOK_DIR = __BOOKDIR_VALUE__;
"tableOk", "tableOk",
"painterOk", "painterOk",
"formattingAutosaved", "formattingAutosaved",
"fullBookShown",
"assembledOk",
"fullBookStatsOk",
"copyAllOk",
"copyStatusShown",
"fullBookSelectAllOk",
"backToChapterOk",
]; ];
out.all = okFlags.every((f) => out[f] === true); out.all = okFlags.every((f) => out[f] === true);
} catch (e) { } catch (e) {