import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { test } from 'node:test'; import ts from 'typescript'; const awaitSource = await readFile(new URL('../src/rendering/OriginalGlassRenderer.ts',import.meta.url),'utf8'); const main = await readFile(new URL('../src/main.ts', import.meta.url), 'utf8'); test('the scene has explicit context-loss and restoration handling', () => { assert.match(main, /new RenderRecovery\(/, 'a lost WebGL context must not leave the UI ready over a white canvas'); }); async function harness({ restoreThrows = false } = {}) { const source = await readFile(new URL('../src/ui/RenderRecovery.ts', import.meta.url), 'utf8'); const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ES2020, target: ts.ScriptTarget.ES2020 } }).outputText; const { RenderRecovery } = await import(`data:text/javascript;base64,${Buffer.from(output).toString('base64')}`); class Element extends EventTarget { style = { visibility: '' }; open = false; focusCount = 0; showModal() { this.open = true; } close() { this.open = false; } focus() { this.focusCount++; } } const canvas = new Element(), dialog = new Element(), button = new Element(); let losses = 0, restores = 0, reloads = 0; const recovery = new RenderRecovery(canvas, dialog, button, () => { losses++; }, () => { restores++; if (restoreThrows) throw new Error('restore failed'); }, () => { reloads++; }); const emit = type => { const event = new Event(type, { cancelable: true }); canvas.dispatchEvent(event); return event; }; return { recovery, canvas, dialog, button, emit, get losses() { return losses; }, get restores() { return restores; }, get reloads() { return reloads; } }; } test('loss is visible and blocks dismissal; duplicate events do not restart work or reload', async () => { const h = await harness(); assert.equal(h.recovery.lost, false); assert.equal(h.emit('webglcontextlost').defaultPrevented, true); assert.equal(h.recovery.lost, true); assert.equal(h.canvas.style.visibility, 'hidden'); assert.equal(h.dialog.open, true); h.emit('webglcontextlost'); assert.equal(h.losses, 1); assert.equal(h.reloads, 0); const cancel = new Event('cancel', { cancelable: true }); h.dialog.dispatchEvent(cancel); assert.equal(cancel.defaultPrevented, true); h.button.dispatchEvent(new Event('click')); assert.equal(h.reloads, 1); h.recovery.dispose(); }); test('restoration resets app resources before revealing the same canvas and tolerates another loss', async () => { const h = await harness(); h.emit('webglcontextrestored'); assert.equal(h.restores, 0); h.emit('webglcontextlost'); h.emit('webglcontextrestored'); assert.equal(h.restores, 1); assert.equal(h.recovery.lost, false); assert.equal(h.canvas.style.visibility, ''); assert.equal(h.dialog.open, false); assert.equal(h.reloads, 0); h.emit('webglcontextlost'); assert.equal(h.losses, 2); h.recovery.dispose(); h.emit('webglcontextrestored'); assert.equal(h.restores, 1); }); test('failed app restoration keeps recovery visible without retry loops', async () => { const h = await harness({ restoreThrows: true }); h.emit('webglcontextlost'); h.emit('webglcontextrestored'); assert.equal(h.recovery.lost, true); assert.equal(h.dialog.open, true); assert.equal(h.canvas.style.visibility, 'hidden'); assert.equal(h.reloads, 0); h.recovery.dispose(); }); test('GPU work pauses during inference, and restored render targets invalidate cached history', () => { assert.match(main, /if \(renderLost \|\| aiDepthBusy \|\| document\.hidden\)/); assert.match(main, /originalRenderer\?\.invalidate\(\); meshRenderer\.restore\(\)/); const original=awaitSource; assert.match(original, /regionSignature = ''; splitHistorySignature = ''; splitHistoryInitialized = false/); assert.match(main, /renderRecovery\.dispose\(\)/); });