import { bootstrapFrontend, type FrontendCloseRequest, } from '../src/lib/frontendBootstrap.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('close-listener registration failure destroys the empty window without mounting hooks', async () => { const registrationFailure = new Error('registration failed'); const destructionStarted = deferred(); const destruction = deferred(); const order: string[] = []; const reported: unknown[] = []; let renders = 0; let settled = false; const bootstrap = bootstrapFrontend({ registerCloseRequested: () => Promise.reject(registrationFailure), disposeOwners: () => { order.push('dispose owners'); return Promise.resolve(); }, drainPersistence: () => { order.push('drain persistence'); return Promise.resolve(); }, destroyWindow: () => { order.push('destroy window'); destructionStarted.resolve(); return destruction.promise; }, render: () => { renders += 1; }, reportFailure: (_message, error) => reported.push(error), }).then(result => { settled = true; return result; }); await destructionStarted.promise; assertEquals(renders, 0, 'React and its hooks must remain unmounted'); assertEquals(settled, false, 'bootstrap must explicitly await window destruction'); assertEquals( JSON.stringify(order), JSON.stringify(['dispose owners', 'drain persistence', 'destroy window']), 'admissions must close and drain before destroying the empty window', ); destruction.resolve(); assertEquals(await bootstrap, false, 'registration failure must report no render'); assertEquals(renders, 0, 'hooks must never mount after failure cleanup'); assertEquals(reported.length, 1, 'the registration failure must be handled once'); assertEquals(reported[0], registrationFailure, 'the original failure must be reported'); }); Deno.test('bootstrap handles failure of the fail-closed destroy action', async () => { const registrationFailure = new Error('registration failed'); const destructionFailure = new Error('destroy failed'); const reported: unknown[] = []; let renders = 0; const rendered = await bootstrapFrontend({ registerCloseRequested: () => Promise.reject(registrationFailure), disposeOwners: () => Promise.resolve(), drainPersistence: () => Promise.resolve(), destroyWindow: () => Promise.reject(destructionFailure), render: () => { renders += 1; }, reportFailure: (_message, error) => reported.push(error), }); assertEquals(rendered, false, 'a failed registration must never render'); assertEquals(renders, 0, 'destroy failure must not fall back to mounting hooks'); assertEquals(reported.length, 2, 'both terminal failures must be handled'); assertEquals(reported[0], registrationFailure, 'registration failure must be preserved'); assertEquals(reported[1], destructionFailure, 'destroy failure must be observed'); }); Deno.test('successful bootstrap mounts only after close registration resolves', async () => { const registered = deferred<(event: FrontendCloseRequest) => void | Promise>(); const registrationAcknowledged = deferred<() => void>(); let renders = 0; const bootstrap = bootstrapFrontend({ registerCloseRequested: handler => { registered.resolve(handler); return registrationAcknowledged.promise; }, disposeOwners: () => Promise.resolve(), drainPersistence: () => Promise.resolve(), destroyWindow: () => Promise.resolve(), render: () => { renders += 1; }, reportFailure: () => {}, }); await registered.promise; assertEquals(renders, 0, 'render must wait for registration acknowledgement'); registrationAcknowledged.resolve(() => {}); assertEquals(await bootstrap, true, 'successful registration should render the app'); assertEquals(renders, 1, 'the app should mount once'); }); Deno.test('close before registration acknowledgement owns late cleanup and suppresses render', async () => { const registered = deferred<(event: FrontendCloseRequest) => void | Promise>(); const registrationAcknowledged = deferred<() => void>(); const cleanupDone = deferred(); const order: string[] = []; let renders = 0; let prevented = 0; const bootstrap = bootstrapFrontend({ registerCloseRequested: handler => { registered.resolve(handler); return registrationAcknowledged.promise; }, disposeOwners: () => { order.push('dispose owners'); return Promise.resolve(); }, drainPersistence: () => { order.push('drain persistence'); return Promise.resolve(); }, destroyWindow: () => { order.push('destroy window'); return Promise.resolve(); }, render: () => { renders += 1; }, reportFailure: () => {}, }); const close = (await registered.promise)({ preventDefault: () => { prevented += 1; }, }); assertEquals(prevented, 1, 'the pre-acknowledgement close must be intercepted'); assertEquals(renders, 0, 'React must remain unmounted while registration is unresolved'); registrationAcknowledged.resolve(async () => { order.push('unlisten'); await cleanupDone.promise; }); await Promise.resolve(); assertEquals(renders, 0, 'registration acknowledgement must not render after close began'); assertEquals( order.includes('destroy window'), false, 'the late listener cleanup must drain before destruction', ); cleanupDone.resolve(); await close; assertEquals(await bootstrap, false, 'a pre-render close must report no mounted app'); assertEquals(renders, 0, 'a closing realm must never mount React'); assertEquals( JSON.stringify(order), JSON.stringify(['dispose owners', 'drain persistence', 'unlisten', 'destroy window']), 'late listener ownership must join the same close finalizer', ); }); Deno.test('render failure drains and unregisters before handled window destruction', async () => { const renderFailure = new Error('render failed'); const unlistenFailure = new Error('unlisten failed'); const destroyFailure = new Error('destroy failed'); const ownerDrained = deferred(); const persistenceDrained = deferred(); const finalizerStarted = deferred(); const order: string[] = []; const reported: unknown[] = []; let cleanups = 0; const bootstrap = bootstrapFrontend({ registerCloseRequested: () => { order.push('register'); return Promise.resolve(async () => { order.push('unlisten'); cleanups += 1; finalizerStarted.resolve(); throw unlistenFailure; }); }, disposeOwners: async () => { order.push('dispose owners'); await ownerDrained.promise; }, drainPersistence: async () => { order.push('drain persistence'); await persistenceDrained.promise; }, destroyWindow: () => { order.push('destroy window'); return Promise.reject(destroyFailure); }, render: () => { order.push('render'); throw renderFailure; }, reportFailure: (_message, error) => reported.push(error), }); await finalizerStarted.promise; assertEquals(cleanups, 1, 'the installed close listener must unregister exactly once'); assertEquals( JSON.stringify(order), JSON.stringify(['register', 'render', 'dispose owners', 'drain persistence', 'unlisten']), 'render failure must synchronously start every frontend finalizer', ); ownerDrained.resolve(); await Promise.resolve(); assertEquals( order.includes('destroy window'), false, 'window destruction must wait for persistence as well as owner cleanup', ); persistenceDrained.resolve(); assertEquals(await bootstrap, false, 'a render failure must be fully handled'); assertEquals( JSON.stringify(order), JSON.stringify([ 'register', 'render', 'dispose owners', 'drain persistence', 'unlisten', 'destroy window', ]), 'destruction must be the terminal finalizer', ); assertEquals(reported.length, 3, 'render, cleanup, and destroy failures must be observed'); assertEquals(reported[0], renderFailure, 'the render failure must be reported first'); assertEquals(reported[1], unlistenFailure, 'listener cleanup rejection must be reported'); assertEquals(reported[2], destroyFailure, 'window destroy rejection must be reported'); });