Code4Scene / tests /scene-assets.test.mjs
KoeYe's picture
Unify task examples, add chart tooltips and bound scene loading
7d419bd
Raw History Blame Contribute Delete
2.74 kB
import test from 'node:test';import assert from 'node:assert/strict';import {AssetQueue,fetchSceneBytes} from '../assets/scene-assets.js';
const tick=()=>new Promise(r=>setTimeout(r,0));
test('shared requests deduplicate, concurrency is capped, and releasing the last viewer cancels stale work',async()=>{
let started=[],resolve={},aborted=[],disposed=[];
const q=new AssetQueue({maxConcurrent:2,maxIdle:0,dispose:a=>disposed.push(a.key),load:(key,signal)=>new Promise((r,j)=>{started.push(key);resolve[key]=()=>r({key,bytes:1});signal.addEventListener('abort',()=>{aborted.push(key);j(new DOMException('Aborted','AbortError'));});})});
const a=q.retain('A',()=>{}),a2=q.retain('A',()=>{}),b=q.retain('B',()=>{}),c=q.retain('C',()=>{});await tick();assert.deepEqual(started,['A','B']);assert.equal(q.peakRunning,2);
a.release();assert.equal(aborted.length,0);a2.release();await tick();assert.deepEqual(aborted,['A']);assert.deepEqual(started,['A','B','C']);
resolve.B();resolve.C();await tick();b.release();c.release();assert.equal(q.running,0);assert.equal(q.entries.size,0);assert.deepEqual(disposed.sort(),['B','C']);
});
test('a transient error retries and a later subscriber reuses the asset',async()=>{
let attempts=0;const states=[];const q=new AssetQueue({load:async key=>{if(++attempts===1)throw Error('503');return {key,bytes:2};},dispose:()=>{}});
const a=q.retain('A',e=>states.push(e.state));await new Promise(r=>setTimeout(r,850));assert.equal(attempts,2);assert.ok(states.includes('retrying'));assert.equal(states.at(-1),'ready');
const b=q.retain('A',()=>{});await tick();assert.equal(attempts,2);a.release();b.release();
});
test('a stalled fetch reaches a bounded error, and partial responses are rejected',async()=>{
const stalled=(_url,{signal})=>new Promise((resolve,reject)=>signal.addEventListener('abort',()=>reject(new DOMException('Abort','AbortError'))));
await assert.rejects(fetchSceneBytes('test',new AbortController().signal,()=>{},{idleMs:15,totalMs:100,fetcher:stalled}),/stopped responding/);
await assert.rejects(fetchSceneBytes('test',new AbortController().signal,()=>{},{fetcher:async()=>new Response(new Uint8Array([1,2]),{headers:{'content-length':'4'}})}),/incomplete/);
});
test('download progress retains every byte and aborts propagate',async()=>{
const p=[];const b=await fetchSceneBytes('test',new AbortController().signal,x=>p.push(x),{fetcher:async()=>new Response(new Uint8Array([1,2,3]),{headers:{'content-length':'3'}})});assert.deepEqual([...b],[1,2,3]);assert.equal(p.at(-1).received,3);
const c=new AbortController();c.abort();await assert.rejects(fetchSceneBytes('test',c.signal,()=>{},{fetcher:async(_,{signal})=>{signal.throwIfAborted();}}),{name:'AbortError'});
});