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)); });