import { acceptGameDirectory, hydrateGameDirectory, } from '../src/lib/gameDirectory.ts'; const assertEquals = (actual: T, expected: T, message: string) => { if (actual !== expected) { throw new Error(`${message}: expected ${expected}, got ${actual}`); } }; const deferred = () => { let resolve!: (value: T) => void; let reject!: (reason: unknown) => void; const promise = new Promise((resolvePromise, rejectPromise) => { resolve = resolvePromise; reject = rejectPromise; }); return { promise, resolve, reject }; }; Deno.test('game directory is neither persisted nor used before backend acknowledgement', async () => { const acknowledgement = deferred(); const persisted: string[] = []; let displayedPath = '/old/path'; const update = acceptGameDirectory('/picked/../path', { updateBackend: requestedPath => { assertEquals( requestedPath, '/picked/../path', 'backend should receive the requested path', ); return acknowledgement.promise; }, persist: acceptedPath => { persisted.push(acceptedPath); return Promise.resolve(); }, reportPersistenceError: () => {}, }); assertEquals(displayedPath, '/old/path', 'pending update should retain the old UI path'); assertEquals(persisted.length, 0, 'pending update should not write the requested path'); acknowledgement.resolve('/canonical/path'); displayedPath = await update; assertEquals(displayedPath, '/canonical/path', 'UI should use the acknowledged path'); assertEquals(persisted.length, 1, 'acknowledged path should be persisted once'); assertEquals(persisted[0], '/canonical/path', 'store should receive the canonical path'); }); Deno.test('backend rejection leaves the old game directory untouched', async () => { const rejected = new Error('directory rejected'); const persisted: string[] = []; let displayedPath = '/old/path'; try { displayedPath = await acceptGameDirectory('/rejected/path', { updateBackend: () => Promise.reject(rejected), persist: acceptedPath => { persisted.push(acceptedPath); return Promise.resolve(); }, reportPersistenceError: () => {}, }); throw new Error('expected backend rejection'); } catch (error) { assertEquals(error, rejected, 'backend error should be preserved'); } assertEquals(displayedPath, '/old/path', 'rejection should retain the old UI path'); assertEquals(persisted.length, 0, 'rejection should not change persistent state'); }); Deno.test('invalid backend acknowledgement is not persisted', async () => { let persisted = false; let rejected = false; try { await acceptGameDirectory('/picked/path', { updateBackend: () => Promise.resolve(' '), persist: () => { persisted = true; return Promise.resolve(); }, reportPersistenceError: () => {}, }); } catch { rejected = true; } assertEquals(rejected, true, 'empty acknowledgement should be rejected'); assertEquals(persisted, false, 'invalid acknowledgement should not be persisted'); }); Deno.test('persistence failure does not undo an acknowledged backend update', async () => { const persistenceError = new Error('store unavailable'); let reportedError: unknown; const acceptedPath = await acceptGameDirectory('/picked/path', { updateBackend: () => Promise.resolve('/canonical/path'), persist: () => Promise.reject(persistenceError), reportPersistenceError: error => { reportedError = error; }, }); assertEquals(acceptedPath, '/canonical/path', 'backend acknowledgement should remain accepted'); assertEquals(reportedError, persistenceError, 'persistence failure should be reported'); }); Deno.test('saved game-directory hydration settles only after backend acceptance', async () => { const acceptance = deferred(); let settled = false; const hydration = hydrateGameDirectory({ loadSavedPath: () => Promise.resolve('/saved/path'), acceptSavedPath: path => { assertEquals(path, '/saved/path', 'saved path should be restored'); return acceptance.promise; }, reportLoadError: () => {}, }).then(() => { settled = true; }); await Promise.resolve(); assertEquals(settled, false, 'hydration must wait for backend acceptance'); acceptance.resolve(); await hydration; assertEquals(settled, true, 'hydration should settle after acceptance'); }); Deno.test('missing or failed game-directory state still completes hydration', async () => { let accepts = 0; await hydrateGameDirectory({ loadSavedPath: () => Promise.resolve(undefined), acceptSavedPath: () => { accepts += 1; return Promise.resolve(); }, reportLoadError: () => {}, }); assertEquals(accepts, 0, 'missing state should not invoke the backend'); const failure = new Error('store unavailable'); let reported: unknown; await hydrateGameDirectory({ loadSavedPath: () => Promise.reject(failure), acceptSavedPath: () => Promise.resolve(), reportLoadError: error => { reported = error; }, }); assertEquals(reported, failure, 'load failure should be reported before ready'); });