import assert from 'node:assert/strict'; import {test} from 'node:test'; import {readFile} from 'node:fs/promises'; import ts from 'typescript'; import vm from 'node:vm'; import * as THREE from 'three'; const source=await readFile(new URL('../src/input/MediaLoader.ts',import.meta.url),'utf8'); const tick=async()=>{for(let i=0;i<12;i++)await Promise.resolve();}; function harness(onReady=()=>{}){const pending=[],photos=[],ready=[],statuses=[],rawURLs=[];const exports={};class RawImage{width=8000;height=6000;set src(_v){queueMicrotask(()=>this.onload?.());}}vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,{exports,AbortController,Image:RawImage,queueMicrotask,Error,URL:{createObjectURL(file){rawURLs.push(file);return'blob:original';},revokeObjectURL(){}},require:name=>name==='three'?THREE:{createWorkingPhotoImage(_file,id,signal){return new Promise((resolve,reject)=>pending.push({id,signal,resolve,reject}));}}});const loader=new exports.MediaLoader((texture,aspect)=>{ready.push({texture,aspect});onReady(texture,aspect);},m=>statuses.push(m));return{loader,pending,photos,ready,statuses,rawURLs,resolve(index,width=1024,height=768){const photo={image:{width,height},url:'blob:working'+index,disposed:0,dispose(){this.disposed++;}};photos.push(photo);pending[index].resolve(photo);return photo;}};} test('legacy uploads use only bounded working pixels for the actual Three texture',async()=>{const h=harness();h.loader.load({type:'image/jpeg'});await tick();assert.equal(h.pending.length,1);const p=h.resolve(0);await tick();assert.equal(h.ready[0].texture.image,p.image);assert.equal(h.ready[0].aspect,4/3);assert.equal(h.rawURLs.length,0);h.loader.dispose();assert.equal(p.disposed,1);}); test('replacement/cancel closes stale images without overwriting newer photo or status',async()=>{const h=harness();h.loader.load({type:'image/png'});assert.equal(h.pending.length,1);h.loader.load({type:'image/webp'});assert.equal(h.pending[0].signal.aborted,true);const second=h.resolve(1);await tick();const first=h.resolve(0);await tick();assert.equal(h.ready.length,1);assert.equal(h.ready[0].texture.image,second.image);assert.equal(first.disposed,1);h.loader.load({type:'image/jpeg'});h.loader.cancelPending();const cancelled=h.resolve(2);await tick();assert.equal(cancelled.disposed,1);assert.equal(h.ready.length,1);h.loader.dispose();assert.equal(second.disposed,1);}); test('sequential successful photos release every old decoded image and preview URL',async()=>{const h=harness();for(let i=0;i<8;i++){h.loader.load({type:'image/png'});assert.equal(h.pending.length,i+1);h.resolve(i);await tick();assert.equal(h.photos.filter(p=>!p.disposed).length,1);}h.loader.dispose();assert.ok(h.photos.every(p=>p.disposed===1));}); test('old DOM photo stays readable throughout replacement callback, then releases',async()=>{let previous;const h=harness(()=>{if(previous)assert.equal(previous.disposed,0,'cached depth diagnostic may still draw old image until its setDepth runs');});h.loader.load({type:'image/png'});previous=h.resolve(0);await tick();h.loader.load({type:'image/jpeg'});h.resolve(1);await tick();assert.equal(h.statuses.at(-1),'');assert.equal(previous.disposed,1);h.loader.dispose();});