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

45 lines
3.7 KiB
TypeScript

import {readFileSync} from 'node:fs';
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {PlatformStore} from '../../YouleNexus/assets/framework/platform/stores/platform-store.ts';
import {parseLoginResponse,parseSelfJoinRoomResponse} from '../../YouleNexus/assets/framework/protocol/contracts/index.ts';
import {PlatformHandlers} from '../../YouleNexus/assets/framework/protocol/platform-handlers.ts';
import {RoomBroadcastQueue} from '../../YouleNexus/assets/framework/presentation/room-broadcast-queue.ts';
const login=()=>parseLoginResponse(JSON.parse(readFileSync(new URL('../fixtures/contracts/player-login-room.json',import.meta.url),'utf8')).data);
function setup(){const store=new PlatformStore();store.applyLoginSuccess(login());return store;}
test('marquee preserves duplicate arrival order separately from chat announcements and clears at room/login boundaries',()=>{
const store=setup();store.addRoomBroadcast('一');store.addRoomBroadcast('一');
store.applyRoomSocial('send_text',{text:'聊天公告',type:1});
assert.deepEqual(store.getState().roomBroadcasts,['一','一']);assert.ok(Object.isFrozen(store.getState().roomBroadcasts));
store.applyLoginSuccess(login());assert.equal(store.getState().roomBroadcasts,undefined);
store.addRoomBroadcast('old room');
store.replaceRoom(parseSelfJoinRoomResponse(JSON.parse(readFileSync(new URL('../fixtures/contracts/self-join-room.json',import.meta.url),'utf8')).data));
assert.equal(store.getState().roomBroadcasts,undefined);
store.addRoomBroadcast('二');store.clearRoom();assert.equal(store.getState().roomBroadcasts,undefined);
const outside=store.getState();store.addRoomBroadcast('outside');assert.equal(store.getState(),outside);
});
test('both documented broadcast routes dispatch marquee; omitted msgtype is immediate message; invalid types do not commit',()=>{
const store=setup(),immediate:string[]=[];
const handler=new PlatformHandlers({store,gameSession:{} as never,session:{} as never,connection:{} as never,showMessage:(text,time)=>{assert.equal(time,0);immediate.push(text);}});
for(const route of ['agent','room'])handler.dispatch({route,rpc:'broadcast',data:{msgtype:1,msgcontent:route}} as never);
assert.deepEqual(store.getState().roomBroadcasts,['agent','room']);
handler.dispatch({route:'agent',rpc:'broadcast',data:{msgcontent:'immediate'}} as never);assert.deepEqual(immediate,['immediate']);
const previous=store.getState();
for(const msgtype of [null,'1',2])assert.throws(()=>handler.dispatch({route:'room',rpc:'broadcast',data:{msgtype,msgcontent:'bad'}} as never));
assert.equal(store.getState(),previous);
});
test('marquee uses legacy byte length and speed, plays sequentially and stops after clear',()=>{
const queue=new RoomBroadcastQueue(100);queue.enqueue('A中^');queue.enqueue('B');
assert.deepEqual(queue.current,{text:'A中^',textWidth:90,offsetX:100,durationSeconds:1.9,elapsedSeconds:0});
queue.advance(1);assert.equal(queue.current!.offsetX,0);queue.advance(.9);
assert.equal(queue.current!.text,'B');assert.ok(Math.abs(queue.current!.offsetX-100)<1e-10);
queue.advance(1.18);assert.equal(queue.current,null);
queue.enqueue('stale');queue.enqueue('queued');queue.clear();queue.advance(100);assert.equal(queue.current,null);
queue.enqueue('new');assert.equal((queue as RoomBroadcastQueue).current!.offsetX,100);
});
test('large animation frame advances through queued messages without losing elapsed time',()=>{
const queue=new RoomBroadcastQueue(100);queue.enqueue('A');queue.enqueue('B');queue.advance(1.68);
assert.equal(queue.current!.text,'B');assert.ok(Math.abs(queue.current!.offsetX-50)<1e-10);
assert.throws(()=>queue.advance(-1));assert.throws(()=>new RoomBroadcastQueue(0));
});