import {test} from 'node:test'; import assert from 'node:assert/strict'; import {readFileSync} from 'node:fs'; import {runInNewContext} from 'node:vm'; import {buildRoomMenuModel, roomMenuAction, roomEscapeConfirmation, type RoomMenuContext} from '../../YouleNexus/assets/framework/presentation/room-menu-model.ts'; import {resolveRoomChatPhrase, validateRoomChatText, roomChatByteLength, resolveRoomChatPolicy} from '../../YouleNexus/assets/framework/presentation/room-chat-config.ts'; import {getGameDefinition} from '../fixtures/game-definitions.ts'; const getRoomChatConfig=(key:string)=>getGameDefinition(key).chat; import {resolveRoomMenuContext} from '../../YouleNexus/assets/framework/presentation/room-menu-context.ts'; const context: RoomMenuContext = {status:1,canexit:1,stage:0,playercnt:2,roomMode:1,infinite:0,shortcode:0,matchId:0,h5Version:1,appdownload:'https://example.test/app',hideEscape:false,mainSceneButton:false,vipInfinite:false,versionState:0,roomCardName:'房卡',bean:200,starName:'金币'}; const ids=(patch:Partial)=>buildRoomMenuModel({...context,...patch}).map(button=>button.spiritId); test('legacy panel keeps ordered, compact visible buttons and final infinite override',()=>{ assert.deepEqual(ids({}),[151,150,185,233,513,656]); assert.deepEqual(buildRoomMenuModel(context).map(button=>button.y),[75,168,261,354,447,540]); assert.deepEqual(ids({stage:1,canexit:0}),[151,150,185,656]); assert.deepEqual(ids({stage:1,canexit:1}),[151,150,233,656]); assert.deepEqual(ids({stage:1,canexit:0,infinite:1}),[151,150,233,3014,656]); assert.deepEqual(ids({infinite:1,shortcode:123}),[151,150,233,656]); assert.deepEqual(ids({infinite:1,vipInfinite:true}),[151,150,233,656]); }); test('legacy escape guards and match hide ordering are preserved',()=>{ for(const patch of [{status:2},{playercnt:3},{roomMode:0},{hideEscape:true},{mainSceneButton:true}])assert.ok(!ids(patch).includes(513)); assert.ok(ids({matchId:3}).includes(185)); assert.ok(!ids({h5Version:0}).includes(656)); assert.ok(!ids({appdownload:''}).includes(656)); }); test('menu actions distinguish owner exit, pregame break and in-game dissolution',()=>{ assert.deepEqual(roomMenuAction(233,context),{kind:'confirm',checkType:2,message:'房间仍存在,是否退出?',command:'room.exit'}); assert.deepEqual(roomMenuAction(233,{...context,status:2}),{kind:'command',command:'room.exit'}); assert.deepEqual(roomMenuAction(185,context),{kind:'confirm',checkType:1,message:'解散房间不扣房卡,是否解散?',command:'room.break'}); assert.equal(roomMenuAction(185,{...context,versionState:1}).message,' 是否解散房间?'); assert.deepEqual(roomMenuAction(185,{...context,stage:1,canexit:0}),{kind:'command',command:'room.apply-dissolution'}); assert.throws(()=>roomMenuAction(3014,context),/not visible/); }); test('escape asks for quantity then keeps legacy confirmation and balance error',()=>{ assert.deepEqual(roomMenuAction(513,context),{kind:'input',message:'请输入投降扣除数量(底翻)'}); assert.deepEqual(roomEscapeConfirmation(context,30),{kind:'confirm',checkType:3,message:'将扣除30金币,确定逃跑?',command:'room.escape',count:30}); assert.deepEqual(roomEscapeConfirmation(context,300),{kind:'tip',message:'输入大于您拥有的300金币数:200!'}); assert.throws(()=>roomEscapeConfirmation(context,NaN)); }); test('chat config uses exact game source, paired audio and one-based incoming indexes',()=>{ const template=getRoomChatConfig('Game_Surface_3'),eqw=getRoomChatConfig('erqiwang'); assert.equal(resolveRoomChatPhrase(template,1).text,'你好'); assert.equal(resolveRoomChatPhrase(eqw,1).text,'快点吧,我等的花儿都谢了。'); assert.equal(resolveRoomChatPhrase(eqw,7).text,'我先走了,后会有期'); assert.ok(eqw.phrases.every(phrase=>phrase.audio==='')); assert.equal(eqw.limitLength,40);assert.equal(eqw.showChatMs,2000); assert.throws(()=>getRoomChatConfig('unknown'),/Unknown/); assert.equal(resolveRoomChatPhrase(eqw,8).text,eqw.phrases[0].text); for(const index of [0,1.5,NaN])assert.throws(()=>resolveRoomChatPhrase(eqw,index),/index/); }); test('chat validation preserves source byte count and substring filtering',()=>{ const config=getRoomChatConfig('Game_Surface_3'); assert.equal(roomChatByteLength('a^你😀'),9); assert.doesNotThrow(()=>validateRoomChatText(config,'你好')); assert.throws(()=>validateRoomChatText(config,' '),/请输入/); assert.doesNotThrow(()=>validateRoomChatText(config,'你'.repeat(40))); assert.throws(()=>validateRoomChatText(config,'你'.repeat(41)),/40/); assert.doesNotThrow(()=>validateRoomChatText(config,'你'.repeat(21),true)); assert.throws(()=>validateRoomChatText(config,'毛泽东'),/内容含有敏感词汇请重新输入!/); assert.equal(config.announcementLimit,10000);assert.equal(config.voiceHistoryTag,20000); assert.deepEqual(resolveRoomChatPolicy({raw:{},getValue:()=>undefined}),{noticeLimit:-1,noticeCost:0}); assert.deepEqual(resolveRoomChatPolicy({raw:{},getValue:key=>key==='noticelimit'?100:2}),{noticeLimit:100,noticeCost:2}); }); test('room menu source resolver reports missing authoritative room state',()=>{ assert.throws(()=>resolveRoomMenuContext({room:{kind:'outside'}} as any,{} as any,getGameDefinition('Game_Surface_3').roomMenu,{h5Version:1,versionState:0}),/inside room/); const state:any={room:{kind:'inside',roomcode:'123456',stage:0,infinite:0,roomMode:1,status:1,canexit:1,shortcode:null,matchId:null,seatPlayerIds:[1,null]},players:{selfPlayerId:1,entities:{1:{bean:100}}}}; const config:any={identity:{},remoteConfig:{raw:{},getValue:()=>undefined}}; const resolved=resolveRoomMenuContext(state,config,getGameDefinition('Game_Surface_3').roomMenu,{h5Version:1,versionState:0}); assert.equal(resolved.playercnt,1);assert.equal(resolved.shortcode,null);assert.equal(resolved.bean,100); assert.equal(resolved.roomCardName,'房卡');assert.equal(resolved.appdownload,''); delete state.room.status; assert.throws(()=>resolveRoomMenuContext(state,config,getGameDefinition('Game_Surface_3').roomMenu,{h5Version:1,versionState:0}),/status/); }); test('both configured banned corpora match their own authoritative legacy source',()=>{ for(const gameKey of ['Game_Surface_3','erqiwang']){ const source=readFileSync(new URL(`../../../projects/${gameKey}/js/00_Surface/03_Banwords.js`,import.meta.url),'utf8'); const literal=/var banwords\s*=\s*("[\s\S]*?");/.exec(source); assert.ok(literal); const words=String(runInNewContext(literal[1])).split('、'); assert.deepEqual(getRoomChatConfig(gameKey).bannedWords,words); } });