diff --git a/app/src/main.ts b/app/src/main.ts index 05fa035..3aef280 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -24,33 +24,51 @@ const engine = createWasmEngine(worker.run); const localStorage = attempt(() => window.localStorage); const settings = createSettingsStore(localStorage ?? memoryStorage()); -// Les parties enregistrées (IndexedDB, ou localStorage en repli). -const { library } = await openGameLibrary({ - indexedDB: attempt(() => window.indexedDB), - localStorage, -}); -// Le niveau de l'ordinateur : celui de Game.start (STANDARD_PLY). -const session = createGameSession( - library, - { start: () => Game.start(engine), resume: (journal) => Game.resume(engine, journal) }, - "2ply", -); -const saved = await session.restore(); +// Les parties enregistrées (IndexedDB, ou localStorage en repli), ouvertes +// pendant que le moteur se charge : le plateau est dessiné sans les +// attendre. L'écran de jeu ne s'en sert qu'une fois engineReady résolue. +const opening = (async () => { + const { library } = await openGameLibrary({ + indexedDB: attempt(() => window.indexedDB), + localStorage, + }); + // Le niveau de l'ordinateur : celui de Game.start (STANDARD_PLY). + const session = createGameSession( + library, + { start: () => Game.start(engine), resume: (journal) => Game.resume(engine, journal) }, + "2ply", + ); + const saved = await session.restore(); + return { library, session, saved }; +})(); +let stored: Awaited | null = null; +/** Le stockage, ouvert (toujours le cas une fois engineReady résolue). */ +function storage(): Awaited { + if (stored === null) { + throw new Error("stockage pas encore ouvert"); + } + return stored; +} startApp({ - engineReady: worker.ready, - newGame: () => session.newGame(), - savedGame: () => saved, - resumePrevious: () => session.resumePrevious(), - saveGame: (journal) => session.save(journal), + engineReady: Promise.all([ + worker.ready, + opening.then((opened) => { + stored = opened; + }), + ]), + newGame: () => storage().session.newGame(), + savedGame: () => storage().saved, + resumePrevious: () => storage().session.resumePrevious(), + saveGame: (journal) => storage().session.save(journal), games: { - list: () => library.list(), - journal: async (id) => (await library.load(id))?.journal ?? null, - shownId: () => session.shownId, - open: (id, branchHead) => session.open(id, branchHead), - remove: (id) => library.remove(id), + list: () => storage().library.list(), + journal: async (id) => (await storage().library.load(id))?.journal ?? null, + shownId: () => storage().session.shownId, + open: (id, branchHead) => storage().session.open(id, branchHead), + remove: (id) => storage().library.remove(id), exportSgf: async (id) => { - const game = await library.load(id); + const game = await storage().library.load(id); if (game === null) { throw new Error("partie introuvable"); } diff --git a/app/src/ui/app.ts b/app/src/ui/app.ts index 1ec137a..df692ae 100644 --- a/app/src/ui/app.ts +++ b/app/src/ui/app.ts @@ -447,6 +447,7 @@ export function startApp(options: AppOptions): void { ]) { button.disabled = busy; } + ui.menuGames.disabled = false; ui.cancel.disabled = busy || game.pendingHalfMoves.length === 0; renderPanel(game, landscape, doubled); renderMenu(); @@ -491,6 +492,7 @@ export function startApp(options: AppOptions): void { } ui.moveHint.hidden = true; ui.menuNewGame.disabled = true; + ui.menuGames.disabled = true; renderMenu(); } diff --git a/tests/browser/play.mjs b/tests/browser/play.mjs index fb9af56..b2d73ad 100644 --- a/tests/browser/play.mjs +++ b/tests/browser/play.mjs @@ -154,6 +154,26 @@ async function readJournal() { ); } +/** Le nombre de parties enregistrées dans IndexedDB. */ +async function savedGameCount() { + await savedJournal(); // les enregistrements en cours terminés + return page.evaluate( + () => + new Promise((resolve, reject) => { + const open = indexedDB.open("ustagammon"); + open.onerror = () => reject(open.error); + open.onsuccess = () => { + const db = open.result; + const count = db.transaction("games").objectStore("games").count(); + count.onsuccess = () => { + db.close(); + resolve(count.result); + }; + }; + }), + ); +} + /** Le journal enregistré, une fois les enregistrements en cours terminés * (l'application enregistre sans attendre) : deux lectures identiques. */ async function savedJournal() { @@ -1057,6 +1077,7 @@ for (let actions = 0; ; actions++) { check(await page.isVisible("#menu"), "M n'ouvre pas le menu"); await page.keyboard.press("Escape"); const pips = await page.textContent("#strip-player .count"); + const games = await savedGameCount(); const old = await newGameFromMenu(); await page.click("#resume-old"); await waitForPlayer(); @@ -1066,6 +1087,8 @@ for (let actions = 0; ; actions++) { check((await page.textContent("#strip-player .count")) === pips, "« Reprendre l'ancienne » : plateau différent"); check(!(await page.isVisible("#banner")), "le bandeau reste après la reprise"); + check((await savedGameCount()) === games, + "« Reprendre l'ancienne » : la nouvelle partie n'a pas été supprimée"); log(`nouvelle partie puis « Reprendre l'ancienne » : journal identique (${old.events.length} événements)`); } } @@ -1142,8 +1165,10 @@ step("mes parties"); await page.waitForSelector("#games-veil:not([hidden]) .games-item"); check(!(await page.isVisible("#menu")), "le menu reste ouvert sous « Mes parties »"); const items = page.locator("#games .games-item"); - // La partie abandonnée par « Reprendre l'ancienne » a été supprimée. - check((await items.count()) === 2, `${await items.count()} parties listées au lieu de 2`); + // Toutes les parties enregistrées, la dernière jouée d'abord : la + // nouvelle, puis celle qui vient de finir. + const listed = await items.count(); + check(listed >= 2 && listed === (await savedGameCount()), `${listed} parties listées`); check(!(await items.nth(0).locator('[data-action="remove"]').isVisible()), "la partie à l'écran peut être supprimée"); const oldest = items.nth(1); @@ -1173,7 +1198,7 @@ step("mes parties"); "la page a été rechargée"); await page.click("#panel-secondary"); log( - `mes parties : 2 parties listées (${branchCount} branche(s) pour la première), ` + + `mes parties : ${listed} parties listées (${branchCount} branche(s) pour la première), ` + `SGF exporté (${sgf.length} octets), première partie reprise sans rechargement`, ); }