import {test} from 'node:test'; import assert from 'node:assert/strict'; import {loadTypescript} from './support/load-typescript.mjs'; class El extends EventTarget { hidden=false;disabled=false;open=false;attrs={};textContent='';isConnected=true;dataset={}; setAttribute(k,v){this.attrs[k]=v;}removeAttribute(k){delete this.attrs[k];} focus(){this.focused=true;}showModal(){this.open=true;}close(){this.open=false;this.dispatchEvent(new Event('close'));} fire(type,values={}){const e=new Event(type,{cancelable:true});Object.assign(e,values);this.dispatchEvent(e);} getBoundingClientRect(){return {left:10,right:100,top:10,bottom:100};} } async function harness(){ const {initExportUI}=await loadTypescript(new URL('../src/ui/ExportUI.ts',import.meta.url)); const ids=['scene-menu-open','export-open','export-dialog','export-close','export-photo','export-scene','export-preview','export-status','export-download','export-share']; const els=Object.fromEntries(ids.map(id=>[id,new El()]));const revoked=[],pending=[];let allowed=true; const root={querySelector:s=>els[s.slice(1)],defaultView:{URL:{createObjectURL:()=>`blob:${pending.length}`,revokeObjectURL:url=>revoked.push(url)},navigator:{}}}; const abort=new AbortController();const ui=initExportUI(root,abort.signal,()=>allowed,(kind,signal)=>new Promise((resolve,reject)=>pending.push({kind,signal,resolve,reject})),()=>{}); return {els,pending,revoked,abort,ui,nav:root.defaultView.navigator,setAllowed:v=>allowed=v}; } const tick=()=>new Promise(r=>setTimeout(r,0)); test('export is opt-in, repeated capture is ignored, preview is ready before saving',async()=>{ const h=await harness();assert.equal(h.pending.length,0);h.els['export-open'].fire('click');assert.equal(h.els['export-dialog'].open,true); h.els['export-photo'].fire('click');h.els['export-photo'].fire('click');assert.equal(h.pending.length,1);assert.equal(h.pending[0].kind,'photo'); h.pending[0].resolve(new Blob(['png'],{type:'image/png'}));await tick();assert.equal(h.els['export-download'].hidden,false);assert.equal(h.els['export-preview'].src,'blob:1'); h.els['export-close'].fire('click');assert.deepEqual(h.revoked,['blob:1']);assert.equal(h.els['export-dialog'].open,false); }); test('cancel and abort suppress stale PNG and revoke results',async()=>{ const h=await harness();h.els['export-open'].fire('click');h.els['export-scene'].fire('click');h.els['export-dialog'].fire('cancel'); assert.equal(h.pending[0].signal.aborted,true);h.pending[0].resolve(new Blob(['png']));await tick();assert.equal(h.els['export-download'].hidden,true); h.els['export-open'].fire('click');h.els['export-photo'].fire('click');h.abort.abort();assert.equal(h.pending[1].signal.aborted,true);assert.equal(h.els['export-dialog'].open,false); }); test('unavailable scene cannot open or start export; recoverable errors remain retryable',async()=>{ const h=await harness();h.setAllowed(false);h.els['export-open'].fire('click');assert.equal(h.els['export-dialog'].open,false); h.setAllowed(true);h.els['export-open'].fire('click');h.els['export-photo'].fire('click');h.pending[0].reject(new Error('try again'));await tick();assert.equal(h.els['export-photo'].disabled,false);assert.match(h.els['export-status'].textContent,/try again/); }); test('interrupting a native share does not disable sharing the next preview',async()=>{ const h=await harness();let finish;h.nav.canShare=()=>true;h.nav.share=()=>new Promise(resolve=>finish=resolve); h.els['export-open'].fire('click');h.els['export-photo'].fire('click');h.pending[0].resolve(new Blob(['png']));await tick(); h.els['export-share'].fire('click');assert.equal(h.els['export-share'].disabled,true); h.els['export-close'].fire('click');h.els['export-open'].fire('click');h.els['export-photo'].fire('click');h.pending[1].resolve(new Blob(['png']));await tick(); finish();await tick();assert.equal(h.els['export-share'].disabled,false);assert.equal(h.els['export-share'].hidden,false); }); test('export dismissal restores focus to visible scene menu, not the hidden sheet action',async()=>{ const h=await harness();h.els['export-open'].fire('click');h.els['export-close'].fire('click'); assert.equal(h.els['scene-menu-open'].focused,true);assert.notEqual(h.els['export-open'].focused,true); });