Files
joywayer 35216f75e7 feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
2026-09-09 03:13:18 +08:00

27 lines
2.2 KiB
TypeScript

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {KeyedRows} from '../../YouleNexus/assets/framework/presentation/keyed-rows.ts';
import {ResourceLeaseCache} from '../../YouleNexus/assets/framework/presentation/resource-lease-cache.ts';
test('append preserves existing rows; updates, removals and teardown affect only owned rows',()=>{
const created:number[]=[],destroyed:number[]=[];
const rows=new KeyedRows<{id:number;text:string},{id:number}>({key:item=>item.id,create:item=>{created.push(item.id);return {id:item.id};},update:()=>{},remove:row=>destroyed.push(row.id)});
const one={id:1,text:'a'};rows.render([one]);const original=rows.get(1);
rows.render([one,{id:2,text:'b'}]);assert.equal(rows.get(1),original);assert.deepEqual(created,[1,2]);assert.deepEqual(destroyed,[]);
rows.render([{id:2,text:'changed'}]);assert.deepEqual(destroyed,[1]);rows.dispose();assert.deepEqual(destroyed,[1,2]);
});
test('avatar loads are deduplicated, late completion after release is bounded and cleanup is exact',async()=>{
let loadCount=0;const disposed:string[]=[];let resolve!:(value:string)=>void;
const cache=new ResourceLeaseCache<string>({idleCapacity:0,load:()=>{loadCount++;return new Promise(r=>resolve=r);},destroy:value=>disposed.push(value)});
const a=cache.acquire('url'),b=cache.acquire('url');assert.equal(loadCount,1);a.release();resolve('texture');
assert.equal(await b.value,'texture');assert.equal(cache.leases,1);b.release();b.release();assert.equal(cache.leases,0);assert.deepEqual(disposed,['texture']);
});
test('an idle cache retains only the explicitly configured budget and retries failed loads',async()=>{
let calls=0;const destroyed:string[]=[];
const cache=new ResourceLeaseCache<string>({idleCapacity:1,load:async url=>{calls++;if(url==='bad')throw Error('load');return url;},destroy:x=>destroyed.push(x)});
for(const url of ['a','b']){const lease=cache.acquire(url);await lease.value;lease.release();}
assert.deepEqual(destroyed,['a']);assert.equal(cache.size,1);
const bad=cache.acquire('bad');await assert.rejects(bad.value);bad.release();const retry=cache.acquire('bad');await assert.rejects(retry.value);retry.release();assert.equal(calls,4);
cache.clear();assert.deepEqual(destroyed,['a','b']);
});