import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import vm from 'node:vm'; import { createRemoteConfig } from '../../YouleNexus/assets/framework/config/remote-config.ts'; import { resolveLobbyMenuConfig, lobbyMenuState, lobbyTrayState, LOBBY_MENU_ACTIONS, LOBBY_UNAVAILABLE, lobbyLockedTip } from '../../YouleNexus/assets/framework/presentation/lobby-menu.ts'; const identity={agentid:'a',gameid:'g',channelid:'c',marketid:'m',version:1}; const remote=(data:Record)=>createRemoteConfig(data,identity); const source=readFileSync(new URL('../../../projects/Game_Surface_3/js/00_Surface/11_GameUI.js',import.meta.url),'utf8'); const ids=[11,12,13,14,17,7,234,483,568,286,287,10,508,496,509,3233,3232,3256,3264,76,3139,679,99,3120,3161,3162,3016,3018]; test('three locked entries preserve source flags and exact click tips',()=>{ const closed=resolveLobbyMenuConfig(remote({})); assert.deepEqual(closed.locked,{568:true,483:true,3139:true}); assert.equal(lobbyLockedTip(closed,'world-rooms'),'暂未开放,敬请期待'); assert.equal(lobbyLockedTip(closed,'match'),'暂未开放,敬请期待'); assert.equal(lobbyLockedTip(closed,'official-rooms'),'暂未开放,敬请期待'); for(const worldRoomList of [0,1,'0'])for(const vipConfig of [null,[['001234']]])for(const ofcShortCode of [null,'001234']){ const config=resolveLobbyMenuConfig(remote({hall_config:{worldRoomList},vipConfig,ofcShortCode})); assert.deepEqual(config.locked,{568:!worldRoomList,483:!vipConfig,3139:!ofcShortCode}); assert.equal(lobbyLockedTip(config,'world-rooms'),worldRoomList?null:'暂未开放,敬请期待'); assert.equal(lobbyLockedTip(config,'match'),vipConfig?null:'暂未开放,敬请期待'); assert.equal(lobbyLockedTip(config,'official-rooms'),ofcShortCode?null:'暂未开放,敬请期待'); } }); function original(config:ReturnType,versionState:number,launchMode:number) { const visible:Record=Object.fromEntries(ids.map(id=>[id,true])); const left:Record={}; const noop=()=>{}; const ui=new Proxy({onstate:[],isprepare:[]} as Record,{get:(o,k)=>k in o?o[k as string]:noop}); const context=vm.createContext({GameUI:ui,GameData:{...config,Scene:1,versionState,LaunchMode:launchMode, hallConfig:config.hall,sysConfig:config.sys,isLocation:true,submitPhoneInfo:true,submitAddressBook:true}, Game_Config:{sysConfig:config.template,Max:{},Share:{}},C_Player:{},ConstVal:{isGameHall:config.template.isGameHall, Launch_mode:{from_self:0,from_hall:1},sysNotice:{}},Logic:new Proxy({},{get:()=>noop}),Func:new Proxy({},{get:()=>noop}), Utl:new Proxy({},{get:()=>noop}),Game_Modify:{},gameHallImport:{jumpMenuScene:noop}, set_self:(id:number,key:number,value:number)=>{if(key===37)visible[id]=Boolean(value);if(key===18)left[id]=value;}, get_self:(id:number,key:number)=>key===37?Number(visible[id]):0,set_group:noop,shareLink:noop,isArray:Array.isArray}); for(const name of ['JumpMenuScene','adaptSmartBar','adaptSmartBar2','updateActBtn','showStoreBtn']){ const start=source.indexOf(`GameUI.${name} = function`); assert.ok(start>=0,name); const end=source.indexOf('\nGameUI.',start+1); vm.runInContext(source.slice(start,end),context); } ui.JumpMenuScene(); return {visible,left}; } test('menu visibility and visible-only spacing match original JumpMenuScene across configuration combinations',()=>{ for(const versionState of [0,1] as const)for(const isGameHall of [false,true])for(const agentmode of [1,2,3]) for(const androidPay of [0,1])for(const flags of [0,1,2,3,4,7]){ const config=resolveLobbyMenuConfig(remote({agentmode,androidPay,rankList:flags&1,hideTaskBtn:flags&1, hideWxShare:flags&2,miniPro:flags&4,hideRoomCard:flags&2?2:1,cdKey:flags&4?2:1,hall_config:{actData:flags&1?['',0,'','']:0}}), {isGameHall,mainMenuButton:isGameHall,hideNotice:isGameHall,vipInfinite:false}); for(const launchMode of [0,1] as const){ const expected=original(config,versionState,launchMode); const actual=lobbyMenuState(config,{versionState,launchMode}); for(const id of ids){ assert.equal(actual.visible[id]??true,expected.visible[id],JSON.stringify({id,versionState,isGameHall,agentmode,androidPay,flags})); assert.equal(actual.left[id],expected.left[id],`spacing ${id}`); } } } }); test('source hall merge preserves scoped top-level override and rejects invalid data',()=>{ const config=resolveLobbyMenuConfig(remote({hall_config:{androidPay:1,rankList:2},rankList:1})); assert.equal(config.hall.androidPay,1);assert.equal(config.hall.rankList,1); assert.throws(()=>resolveLobbyMenuConfig(remote({hall_config:'bad'})),/hall_config/); assert.throws(()=>resolveLobbyMenuConfig(remote({agentmode:'bad'})),/agentmode/); }); test('legacy hallConfigName actData colon typo leaves activity sourced only from hall_config',()=>{ const config=resolveLobbyMenuConfig(remote({actData:['top'],hall_config:{actData:['hall']}})); assert.deepEqual(config.hall.actData,['hall']); assert.equal(resolveLobbyMenuConfig(remote({actData:['top']})).hall.actData,0); }); test('source truthiness is preserved for string zero flags rather than coerced to numeric zero',()=>{ const config=resolveLobbyMenuConfig(remote({hideTaskBtn:'0',hideWxShare:'0',rankList:'0'})); const actual=lobbyMenuState(config,{versionState:0,launchMode:0}); const expected=original(config,0,0); for(const id of [17,14,3233])assert.equal(actual.visible[id],expected.visible[id]); assert.equal(actual.visible[17],false);assert.equal(actual.visible[3233],true); }); test('main menu override hides all four entries and cdKey runs after review restriction',()=>{ const config=resolveLobbyMenuConfig(remote({cdKey:2}),{isGameHall:false,mainMenuButton:true,hideNotice:false,vipInfinite:true}); const normal=lobbyMenuState(config,{versionState:0,launchMode:0}); for(const id of [11,12,483,568,3139,679])assert.equal(normal.visible[id],false); assert.equal(lobbyMenuState(config,{versionState:1,launchMode:0}).visible[3264],true); }); test('tray excludes hidden entries from placement and keeps original feedback hidden',()=>{ const state=lobbyTrayState(resolveLobbyMenuConfig(remote({agentmode:2,rankList:1,bagUrl:'bag',wareHouse:1,miniPro:1})),{versionState:0,launchMode:0}); assert.equal(state.visible[235],false);assert.equal(state.visible[15],false); assert.deepEqual([state.left[604],state.left[3017],state.left[3057],state.left[5]],[216,371,526,681]); }); test('every menu entry has an existing local action or an explicit unavailable business description',()=>{ const local=['settings','profile','join-room','notice','tray','tasks','share','vip-rooms','qrcode','rank','cdkey','vip-rank','my-rooms','buy-roomcard','buy-bean']; for(const action of Object.values(LOBBY_MENU_ACTIONS))assert.ok(local.includes(action)||LOBBY_UNAVAILABLE[action],action); for(const id of [10,286,287,3161] as const)assert.equal(LOBBY_MENU_ACTIONS[id],'buy-roomcard'); for(const id of [508,509,496,3162] as const)assert.equal(LOBBY_MENU_ACTIONS[id],'buy-bean'); });