feat: integrate platform room entry, UI migration and erqiwang documentation
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/** Test extracted panels with local clipboard, image and command fixtures only. */
|
||||
export async function verifyFeedbackSplitPreview(e){
|
||||
const checks=[],assert=(v,m)=>{if(!v)throw Error(m);checks.push(m);},sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
||||
const load=id=>new Promise((r,j)=>e.assetManager.loadAny(id,(err,a)=>err?j(err):r(a)));
|
||||
const refs=(await load('1c10c2ee-adc2-496f-9005-d3e0e6ebbb56')).scene.getComponentInChildren('LobbyAssets');
|
||||
assert(refs.redeemCode.data.name==='RedeemCode'&&refs.redeemCode.data.children.length===1&&refs.redeemCode.data.children[0].name==='group-74','redeem prefab contains only group-74');
|
||||
assert(refs.qrCode.data.name==='QrCode'&&refs.qrCode.data.children.length===1&&refs.qrCode.data.children[0].name==='group-42','QR prefab contains only group-42');
|
||||
assert(!('notices' in refs),'obsolete shared asset field is removed');
|
||||
const C=Array.from(System.entries()).map(([,m])=>m).find(m=>m.LobbyPanel).LobbyPanel,panel=Object.create(C.prototype);
|
||||
const host=new e.Node('FeedbackSplitTest');host.parent=e.director.getScene();
|
||||
const redeemed=[],shares=[],tips=[],images=[];const loadRemote=e.assetManager.loadRemote;
|
||||
panel.assets=refs;panel.model={canAct:true};panel.modal=null;panel.modalReleases=[];panel.disposed=false;
|
||||
panel.extras={paste:async()=>'00Ab-12',redeem:code=>redeemed.push(code)};panel.nativeCall=(name,data)=>shares.push({name,data});panel.tip=message=>tips.push(message);
|
||||
let qrEnabled=true;
|
||||
panel.config={identity:{gameid:'test',agentid:'test',channelid:'test',marketid:'test'},remoteConfig:{getValue:key=>({WechatPublicNo_ewm:qrEnabled?'https://example.invalid/fixture.png':'',sharename:'测试',shareDesc:'说明'})[key]}};
|
||||
panel.close=()=>{for(const off of panel.modalReleases.splice(0))off();panel.modal?.destroy();panel.modal=null;};
|
||||
panel.open=(asset,kind)=>{panel.close();panel.modal=e.instantiate(asset);panel.modal.parent=host;panel.modalKind=kind;return panel.modal;};
|
||||
e.assetManager.loadRemote=(url,options,callback)=>images.push({url,callback});
|
||||
try{
|
||||
panel.openCdKey();let g=panel.modal.getChildByName('group-74'),edit=g.getChildByName('CardInput').getComponent(e.EditBox);
|
||||
assert(edit.string==='','redeem input starts empty');
|
||||
g.getChildByName('Spirit3266').emit(e.Button.EventType.CLICK);assert(redeemed.length===0&&panel.modal!==null,'empty redeem is ignored');
|
||||
g.getChildByName('Spirit3265').emit(e.Button.EventType.CLICK);await sleep(20);assert(edit.string==='00Ab-12','paste preserves case and leading zeroes');
|
||||
g.getChildByName('Spirit3266').emit(e.Button.EventType.CLICK);assert(redeemed[0]==='00Ab-12'&&panel.modal===null,'redeem command receives exact input and closes');
|
||||
panel.openCdKey();g=panel.modal.getChildByName('group-74');assert(g.getChildByName('CardInput').getComponent(e.EditBox).string==='','redeem reopening clears input');
|
||||
g.getChildByName('反馈遮罩').emit(e.Button.EventType.CLICK);assert(panel.modal===null,'redeem shade closes');
|
||||
qrEnabled=false;panel.openQrCode();assert(panel.modal===null&&images.length===0,'missing QR config retains no-op behavior');
|
||||
qrEnabled=true;panel.openQrCode();g=panel.modal.getChildByName('group-42');assert(images[0].url==='https://example.invalid/fixture.png','QR request uses configured URL');
|
||||
const bitmap=document.createElement('canvas');bitmap.width=bitmap.height=2;bitmap.getContext('2d').fillRect(0,0,2,2);const image=new e.ImageAsset(bitmap);
|
||||
images[0].callback(null,image);const sprite=g.getChildByName('Spirit343').getComponent(e.Sprite);assert(sprite.spriteFrame?.texture.image===image,'QR image callback populates sprite');
|
||||
g.getChildByName('Spirit345').emit(e.Button.EventType.CLICK);assert(shares[0].name==='friendsSharetypeUrlToptitleDescript'&&shares[0].data.webpageUrl===images[0].url,'QR share retains existing bridge payload');
|
||||
g.getChildByName('二维码遮罩').emit(e.Button.EventType.CLICK);assert(panel.modal===null&&sprite.spriteFrame===null,'QR close releases image frame');
|
||||
panel.openQrCode();panel.close();images[1].callback(null,image);assert(panel.modal===null&&tips.length===0,'late image callback cannot reopen a closed panel');image.destroy();
|
||||
return {checks};
|
||||
}finally{panel.close();host.destroy();e.assetManager.loadRemote=loadRemote;}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/** Run through Cocos MCP in an isolated Game View. Records packets; never binds a real account. */
|
||||
export async function verifyInvitePreview(){
|
||||
const e=await System.import('cc'),modules=Array.from(System.entries()).map(([,m])=>m);
|
||||
const exported=name=>{const m=modules.find(m=>m[name]);if(!m)throw Error(`Missing ${name}`);return m[name];};
|
||||
const source=e.director.getScene().getChildByName('PlatformServices').getComponent('PlatformStartup');
|
||||
const config=source.config,assets=source.getComponent('LobbyAssets');
|
||||
source.unsubscribe?.();source.runtime?.stop();source.panel?.dispose();source.panel=null;
|
||||
await new Promise((resolve,reject)=>e.director.loadScene('PlatformLobby',err=>err?reject(err):resolve()));
|
||||
const sent=[],tips=[],checks=[];let receive,panel,pasteResolve;
|
||||
const assert=(ok,message)=>{if(!ok)throw Error(message);checks.push(message);};
|
||||
const runtime=new (exported('PlatformRuntime'))({
|
||||
gameEntry:exported('createLoginStageGameEntry')(config.identity.gameid,()=>{}),resolveRuntimeConfig:async()=>config,
|
||||
createWireClient:()=>({subscribe:cb=>{receive=cb;return ()=>{};},start:()=>{},stop:()=>{},send:packet=>sent.push(packet),switchServer:()=>{},reconnectCurrent:()=>{}}),
|
||||
scene:{showLoading:()=>{},showLogin:()=>{},showLobby:()=>{},showRoom:()=>{},showReconnect:()=>{},showKicked:()=>{},showServerDenial:()=>{},showFatal:err=>{throw err;},
|
||||
showTips:(msg,time,kind)=>{if(kind==='show_message')panel?.cancelInviteRequest();tips.push(msg);}},
|
||||
loadResources:async()=>{},waitForMinimumDisplay:async()=>{},getLoginDeviceSnapshot:()=>{throw Error('Inbound fixture only');},
|
||||
});
|
||||
await runtime.start();receive({type:'open',server:config.servers[0]});
|
||||
const packet=(rpc,data)=>receive({type:'message',message:{route:'agent',rpc,data}});
|
||||
const login={state:0,playerid:7,nickname:'邀请码接线验证',avatar:'',sex:0,bean:0,roomcard:3,taskstate:0,score:0,sign:null,ip:'127.0.0.1',openid:'invite-openid',unionid:'invite-unionid',invitecode:null};
|
||||
packet('player_login',login);
|
||||
const canvas=e.director.getScene().getChildByName('Canvas'),popup=new e.Node('InviteTestPopup');popup.parent=canvas;popup.layer=canvas.layer;popup.addComponent(e.UITransform).setContentSize(1600,720);
|
||||
const remote=exported('createRemoteConfig')({gameNoticeShow:-1,agentmode:2},config.identity);
|
||||
panel=new (exported('LobbyPanel'))(canvas,popup,assets,config,exported('resolveLobbyPolicy')(remote),runtime.state,msg=>tips.push(msg),()=>{},()=>null,()=>{},
|
||||
exported('resolveLobbyMenuConfig')(remote),{versionState:0,launchMode:0},()=>{},()=>{},()=>{},undefined,
|
||||
{query:()=>runtime.getPlayerInvitecode(),bind:code=>runtime.bindInvitecode(code),paste:()=>new Promise(resolve=>{pasteResolve=resolve;})});
|
||||
popup.setSiblingIndex(canvas.children.length-1);
|
||||
const off=runtime.subscribeState(state=>panel.render(state),err=>{throw err;});
|
||||
const click=()=>{const node=panel.menuNodes.get(235);if(!node.parent.active)panel.menuNodes.get(3120).emit(e.Button.EventType.CLICK);assert(node.activeInHierarchy,'invite entry is visible');node.emit(e.Button.EventType.CLICK);};
|
||||
const button=id=>panel.modal.getChildByPath(`group-80/Spirit${id}`).emit(e.Button.EventType.CLICK);
|
||||
click();click();assert(sent.length===1&&sent[0].rpc==='get_player_invitecode','query suppresses duplicate clicks');
|
||||
assert(JSON.stringify(sent[0].data)===JSON.stringify({agentid:config.identity.agentid,playerid:7,unionid:'invite-unionid',openid:'invite-openid'}),'query preserves exact identity fields');
|
||||
assert(panel.modal===null,'query waits for response');
|
||||
packet('get_player_invitecode',{invitecode:''});assert(panel.modalKind==='invite','query opens authored Bind prefab');
|
||||
let edit=panel.modal.getChildByPath('group-80/Spirit365/InputEditBox').getComponent(e.EditBox);
|
||||
assert(edit.textLabel&&edit.placeholderLabel&&edit.maxLength===-1,'native input has persisted label references and no six-digit limit');
|
||||
for(const l of [edit.textLabel,edit.placeholderLabel]){const anchor=l.node.getComponent(e.UITransform).anchorPoint;assert(anchor.x===0&&anchor.y===1,'EditBox labels use native top-left anchors');}
|
||||
edit.string='00x1234567';edit.node.emit(e.EditBox.EventType.TEXT_CHANGED);assert(edit.string==='001234567','input removes non-digits while retaining leading zeros');
|
||||
button(366);pasteResolve('00123');await Promise.resolve();await Promise.resolve();assert(edit.string==='00123','paste callback updates input');
|
||||
const submit=panel.modal.getChildByPath('group-80/Spirit367');submit.emit(e.Button.EventType.CLICK);submit.emit(e.Button.EventType.CLICK);
|
||||
assert(panel.modal===null&&sent.length===2,'submit closes dialog and sends once');
|
||||
assert(JSON.stringify(sent[1].data)===JSON.stringify({agentid:config.identity.agentid,playerid:7,invitecode:'00123'}),'binding preserves exact fields and leading zeros');
|
||||
packet('binding_invitecode',{state:'1',error:'邀请码无效'});assert(tips.includes('邀请码无效')&&runtime.state.players.entities[7].invitecode==='','string failure state displays server text without changing code');
|
||||
click();packet('get_player_invitecode',{invitecode:''});assert(panel.modalKind==='invite','unchanged query result reopens dialog');
|
||||
edit=panel.modal.getChildByPath('group-80/Spirit365/InputEditBox').getComponent(e.EditBox);edit.string='00123';button(367);
|
||||
packet('binding_invitecode',{state:'0',invitecode:'00123',error:'绑定成功'});assert(runtime.state.players.entities[7].invitecode==='00123'&&tips.includes('绑定成功'),'string success state updates canonical player and shows server text');
|
||||
click();packet('get_player_invitecode',{invitecode:'00123'});
|
||||
assert([365,366,367].every(id=>!panel.modal.getChildByPath(`group-80/Spirit${id}`).active),'bound state hides input paste and submit');
|
||||
assert(panel.modal.getChildByPath('group-80/Spirit368').getComponent(e.Label).string==='您已绑定邀请码,所绑定邀请码为:00123','bound state shows exact code');
|
||||
await new Promise(resolve=>e.director.once(e.Director.EVENT_AFTER_DRAW,resolve));
|
||||
const messageBox=panel.modal.getChildByPath('group-80/Spirit368').getComponent(e.UITransform).getBoundingBoxToWorld();
|
||||
const backgroundBox=panel.modal.getChildByPath('group-80/Spirit363').getComponent(e.UITransform).getBoundingBoxToWorld();
|
||||
assert(messageBox.x>=backgroundBox.x&&messageBox.xMax<=backgroundBox.xMax,'bound message stays inside authored background');
|
||||
button(362);assert(panel.modal===null,'mask closes dialog');
|
||||
click();packet('show_message',{msg:'暂不可用',time:0});assert(panel.invitePending===null,'generic denial releases pending request');
|
||||
click();packet('get_player_invitecode',{invitecode:''});button(366);button(362);pasteResolve('late');await Promise.resolve();assert(panel.modal===null,'late paste cannot reopen a closed dialog');
|
||||
click();packet('get_player_invitecode',{invitecode:''});
|
||||
edit=panel.modal.getChildByPath('group-80/Spirit365/InputEditBox').getComponent(e.EditBox);edit.string='00123';
|
||||
panel.requestInvite('query',()=>runtime.getPlayerInvitecode());receive({type:'slow'});assert(panel.invitePending===null,'disconnect releases pending request');
|
||||
const count=sent.length;panel.menuNodes.get(235).emit(e.Button.EventType.CLICK);assert(sent.length===count,'disconnect blocks sending');
|
||||
await new Promise(resolve=>e.director.once(e.Director.EVENT_AFTER_DRAW,resolve));
|
||||
globalThis.__inviteVerification={panel,runtime,checks,close:()=>{off();panel.dispose();runtime.stop();popup.destroy();}};
|
||||
return {checks,packets:sent.map(({rpc,data})=>({rpc,fields:Object.keys(data)}))};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Cocos Game View verification: mocked wire, no real ranking/card requests. */
|
||||
export async function verifyLoadingPreview(){
|
||||
const e=await System.import('cc'),modules=Array.from(System.entries()).map(([,m])=>m);
|
||||
const exported=name=>{const m=modules.find(m=>m[name]);if(!m)throw Error(`Missing ${name}`);return m[name];};
|
||||
const source=e.director.getScene().getChildByName('PlatformServices').getComponent('PlatformStartup');
|
||||
const config=source.config,assets=source.getComponent('LobbyAssets');
|
||||
source.unsubscribe?.();source.runtime?.stop();source.panel?.dispose();source.panel=null;
|
||||
await new Promise((resolve,reject)=>e.director.loadScene('PlatformLobby',err=>err?reject(err):resolve()));
|
||||
const sent=[],tips=[],checks=[];let receive,panel;
|
||||
const assert=(ok,msg)=>{if(!ok)throw Error(msg);checks.push(msg);};
|
||||
const runtime=new (exported('PlatformRuntime'))({
|
||||
gameEntry:exported('createLoginStageGameEntry')(config.identity.gameid,()=>{}),resolveRuntimeConfig:async()=>config,
|
||||
createWireClient:()=>({subscribe:cb=>{receive=cb;return ()=>{};},start:()=>{},stop:()=>{},send:p=>sent.push(p),switchServer:()=>{},reconnectCurrent:()=>{}}),
|
||||
scene:{showLoading:()=>{},showLogin:()=>{},showLobby:()=>{},showRoom:()=>{},showReconnect:()=>{},showKicked:()=>{},showServerDenial:()=>{},showFatal:err=>{throw err;},
|
||||
showTips:(msg,time,kind)=>{if(kind==='show_message')panel?.cancelRankRequest();tips.push(msg);}},
|
||||
loadResources:async()=>{},waitForMinimumDisplay:async()=>{},getLoginDeviceSnapshot:()=>{throw Error('Fixture only');},
|
||||
});
|
||||
await runtime.start();receive({type:'open',server:config.servers[0]});
|
||||
const packet=(rpc,data)=>{const message={route:'agent',rpc,data};panel?.requestReceived(message);receive({type:'message',message});};
|
||||
packet('player_login',{state:0,playerid:7,nickname:'接线验证',avatar:'',sex:0,bean:0,roomcard:3,taskstate:0,score:0,sign:null,ip:'127.0.0.1',openid:'test',unionid:'test',invitecode:null});
|
||||
const canvas=e.director.getScene().getChildByName('Canvas'),popup=new e.Node('OwnedVipTestPopup');popup.parent=canvas;popup.layer=canvas.layer;popup.addComponent(e.UITransform).setContentSize(1600,720);
|
||||
const remote=exported('createRemoteConfig')({gameNoticeShow:-1,rankList:2,cdKey:2,vipRankLimit:3},config.identity);
|
||||
const feedback=new (exported('FeedbackPanels'))(canvas,{tips:source.tipsPrefab,loading:source.waitPrefab,reconnect:source.reconnectPrefab,kick:source.kickPrefab});
|
||||
panel=new (exported('LobbyPanel'))(canvas,popup,assets,config,exported('resolveLobbyPolicy')(remote),runtime.state,msg=>tips.push(msg),()=>{},()=>null,()=>{},
|
||||
exported('resolveLobbyMenuConfig')(remote),{versionState:0,launchMode:0},action=>{if(action==='tasks')runtime.getPlayerTasks();},id=>runtime.claimTask(id),()=>{},undefined,undefined,
|
||||
{rank:()=>runtime.getTreasureList(),redeem:card=>runtime.redeemCard(card),paste:async()=>'00Ab-12',loading:show=>feedback.loading(show),vipRank:limit=>runtime.getVipRankList(limit),joinOwned:code=>runtime.joinOwnedRoom(code,{city:'test'})});
|
||||
popup.setSiblingIndex(canvas.children.length-1);
|
||||
const off=runtime.subscribeState(state=>panel.render(state),err=>{throw err;});
|
||||
const click=id=>panel.menuNodes.get(id).emit(e.Button.EventType.CLICK);
|
||||
|
||||
|
||||
const overlay=feedback.loadingNode,animation=overlay.getChildByPath('group-40/载入动画').getComponent(e.Animation);
|
||||
assert(animation&&animation.defaultClip?.name==='RequestLoading','Layer614 owns serialized RequestLoading clip');
|
||||
click(17);assert(overlay.activeInHierarchy&&sent.at(-1).rpc==='get_player_task','task send shows Layer614');
|
||||
assert(animation.getState('RequestLoading').isPlaying,'loading starts native Animation');
|
||||
const angle=animation.node.angle;await new Promise(r=>setTimeout(r,200));assert(animation.node.angle!==angle,'animation advances rotation');
|
||||
packet('get_player_task',{tasks:[{taskid:1,title:'test',award:1,finish:1,total:1,state:1}]});
|
||||
assert(!overlay.active&&!animation.getState('RequestLoading').isPlaying,'task response hides overlay and stops clip');
|
||||
panel.requestTask('award',()=>runtime.claimTask(1));assert(overlay.active&&sent.at(-1).rpc==='get_task_award','award send shows Layer614');
|
||||
packet('get_task_award',{taskid:1,taskstate:0});assert(!overlay.active,'award response hides Layer614');
|
||||
panel.close();click(3232);assert(overlay.active&&sent.at(-1).rpc==='getVipRankList','VIP rank send shows Layer614');
|
||||
packet('getVipRankList',{state:1,error:'失败'});assert(!overlay.active,'VIP failure response hides Layer614');
|
||||
panel.cancelRankRequest();click(3232);packet('getVipRankList',{list:[]});assert(!overlay.active,'empty VIP success hides Layer614');
|
||||
click(3232);packet('show_message',{msg:'服务拒绝',time:0});assert(!overlay.active,'generic failure closes request loading');
|
||||
click(17);panel.cancelTaskRequest();assert(!overlay.active,'timeout cancellation hides Layer614');
|
||||
let caught=false;try{panel.requestTask('list',()=>{throw Error('send failed');});}catch{caught=true;}
|
||||
assert(caught&&!overlay.active,'synchronous send failure hides Layer614');
|
||||
click(17);panel.requestReceived({route:'agent',rpc:'get_player_task',data:null});assert(!overlay.active,'malformed task reply closes before parsing');panel.cancelTaskRequest();
|
||||
click(3232);assert(overlay.active,'loading can restart');
|
||||
globalThis.__loadingVerification={panel,runtime,feedback,checks,close:()=>{off();panel.dispose();runtime.stop();feedback.dispose();popup.destroy();}};
|
||||
return {checks};
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/** Run in the Cocos Game View; uses local text fixtures and never requests remote content. */
|
||||
export async function verifyNoticeTabsPreview(e,boardId) {
|
||||
const checks=[],assert=(value,message)=>{if(!value)throw Error(message);checks.push(message);};
|
||||
const load=id=>new Promise((resolve,reject)=>e.assetManager.loadAny(id,(err,a)=>err?reject(err):resolve(a)));
|
||||
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
const asset=await load(boardId),root=e.instantiate(asset);root.parent=e.director.getScene();
|
||||
const host=new e.Node('NoticeFlowVerification');host.parent=e.director.getScene();
|
||||
let panel;
|
||||
try {
|
||||
assert(root.children.length===1&&root.children[0].name==='group-12','standalone prefab contains only group-12');
|
||||
const view=root.getChildByPath('group-12/NoticeTabs').getComponent('NoticeTabsView');
|
||||
assert(view&&view.tabs.length===3,'three native tabs are serialized');
|
||||
assert(view.getComponent(e.ToggleContainer)?.allowSwitchOff===false,'container enforces single selection');
|
||||
const selected=[];let release=view.bindPages([true,true,true],index=>selected.push(index));await sleep(60);
|
||||
const state=index=>{
|
||||
assert(view.tabs.every((tab,i)=>tab.isChecked===(i===index)),'exactly the selected tab is checked');
|
||||
assert(view.backgrounds.every((s,i)=>s.spriteFrame.name===(i===index?'00216_1':'00216_2')),'backgrounds follow original selected frames');
|
||||
assert(view.titles.every((s,i)=>s.spriteFrame.name==='00217_'+(i*2+(i===index?2:1))),'titles follow original selected frames');
|
||||
};
|
||||
state(0);assert(selected.join(',')==='0','opening shows first page once');
|
||||
view.tabs[1].node.emit(e.Button.EventType.CLICK);state(1);assert(selected.at(-1)===1,'native click selects second content page');
|
||||
view.tabs[2].node.emit(e.Button.EventType.CLICK);state(2);assert(selected.at(-1)===2,'native click selects third content page');
|
||||
view.tabs[2].node.emit(e.Button.EventType.CLICK);state(2);
|
||||
release();const count=selected.length;view.tabs[0].node.emit(e.Button.EventType.CLICK);assert(selected.length===count,'released callback does not run');
|
||||
release=view.bindPages([true,false,true],index=>selected.push(index));await sleep(60);state(0);
|
||||
assert(!view.tabs[1].node.active,'empty second page hides its entire tab');
|
||||
const first=view.tabs[0].node,third=view.tabs[2].node;
|
||||
assert(Math.abs(first.position.y-third.position.y-first.getComponent(e.UITransform).height-view.getComponent(e.Layout).spacingY)<.1,'hidden middle tab leaves no layout gap');
|
||||
view.tabs[2].node.emit(e.Button.EventType.CLICK);state(2);assert(selected.at(-1)===2,'hidden tab does not change page identity');release();
|
||||
const startup=await load('1c10c2ee-adc2-496f-9005-d3e0e6ebbb56'),refs=startup.scene.getComponentInChildren('LobbyAssets');
|
||||
assert(refs.noticeBoard.uuid===boardId,'startup references the standalone board');
|
||||
assert(refs.ownedRoomList.data.getChildByName('group-98')&&refs.qrCode.data.getChildByName('group-42'),'independent room list and QR page remain wired');
|
||||
assert(!refs.qrCode.data.getChildByName('group-12'),'QR prefab does not duplicate group-12');
|
||||
const LobbyPanel=Array.from(System.entries()).map(([,module])=>module).find(module=>module.LobbyPanel)?.LobbyPanel;
|
||||
assert(LobbyPanel,'LobbyPanel module is available');panel=Object.create(LobbyPanel.prototype);
|
||||
panel.assets={noticeBoard:asset};panel.model={canAct:true};panel.disposed=false;panel.modal=null;panel.modalReleases=[];
|
||||
panel.policy={notices:[{type:0,text:'第一页',image:'',url:''},{type:0,text:'',image:'',url:''},{type:0,text:'第三页',image:'',url:''}]};
|
||||
panel.close=()=>{for(const off of panel.modalReleases.splice(0))off();panel.modal?.destroy();panel.modal=null;panel.modalKind='';};
|
||||
panel.open=(prefab,kind)=>{panel.close();panel.modal=e.instantiate(prefab);panel.modal.parent=host;panel.modalKind=kind;return panel.modal;};
|
||||
panel.openNotice();await sleep(60);
|
||||
const board=panel.modal,g=board.getChildByName('group-12'),actual=g.getChildByName('NoticeTabs').getComponent('NoticeTabsView');
|
||||
const text=g.getChildByPath('NoticeViewport/NoticeContent/通知内容').getComponent(e.Label);
|
||||
assert(text.string==='第一页','actual notice entry renders page one');
|
||||
actual.tabs[2].node.emit(e.Button.EventType.CLICK);assert(text.string==='第三页','actual tab event renders matching content');
|
||||
assert(!actual.tabs[1].node.active,'actual entry uses source visibility rules');
|
||||
g.getChildByName('通知关闭').emit(e.Button.EventType.CLICK);assert(panel.modal===null,'close button closes independent board');
|
||||
panel.openNotice();await sleep(40);
|
||||
assert(panel.modal.getChildByPath('group-12/NoticeTabs').getComponent('NoticeTabsView').tabs[0].isChecked,'reopening resets selection');
|
||||
// Route coordinate touches through Cocos hit testing, including the background behind controls.
|
||||
root.active=false;
|
||||
const canvas=e.director.getScene().getComponentInChildren(e.Canvas);
|
||||
const mount=async()=>{panel.openNotice();panel.modal.parent=canvas.node;panel.modal.setPosition(0,0,0);const widget=panel.modal.getComponent(e.Widget);widget.target=canvas.node;widget.updateAlignment();await sleep(70);};
|
||||
let touchId=100;
|
||||
const tap=async(node,dx=0,dy=0)=>{
|
||||
const position=node.worldPosition.clone();position.x+=dx;position.y+=dy;
|
||||
const screen=canvas.cameraComponent.worldToScreen(position),touch=new e.Touch(screen.x,screen.y,touchId++);
|
||||
for(const type of [e.Input.EventType.TOUCH_START,e.Input.EventType.TOUCH_END])e.input._dispatchEventTouch(new e.EventTouch([touch],false,type,[touch]));
|
||||
await sleep(25);
|
||||
};
|
||||
await mount();let surface=panel.modal.getChildByName('group-12');
|
||||
await tap(surface.getChildByName('通知背景'),-350,-200);assert(panel.modal!==null,'panel blank area does not close the board');
|
||||
await tap(surface.getChildByName('通知标题内容'));assert(panel.modal!==null,'title does not close the board');
|
||||
await tap(surface.getChildByPath('NoticeViewport/NoticeContent/通知内容'),30,-10);assert(panel.modal!==null,'content does not close the board');
|
||||
await tap(surface.getChildByPath('NoticeTabs/NoticeTab3'));assert(panel.modal!==null&&surface.getChildByPath('NoticeTabs').getComponent('NoticeTabsView').tabs[2].isChecked,'coordinate tab touch selects without closing');
|
||||
await tap(surface.getChildByName('通知关闭'));assert(panel.modal===null,'coordinate close-button touch closes the board');
|
||||
await mount();surface=panel.modal.getChildByName('group-12');
|
||||
await tap(surface.getChildByName('通知遮罩'),-700,0);assert(panel.modal===null,'coordinate backdrop touch closes the board');
|
||||
panel.close();return {checks};
|
||||
} finally {panel?.close();root.destroy();host.destroy();}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/** Run in Cocos Game View, with the imported cc module and prefab UUIDs from asset-db.
|
||||
* Uses real Animation states and isolated nodes; no wire messages or application settings.
|
||||
*/
|
||||
export async function verifyOptionalAnimationPreview(e, prefabIds) {
|
||||
const checks=[];
|
||||
const assert=(ok,message)=>{if(!ok)throw Error(message);checks.push(message);};
|
||||
const load=uuid=>new Promise((resolve,reject)=>e.assetManager.loadAny(uuid,(err,asset)=>err?reject(err):resolve(asset)));
|
||||
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
const root=new e.Node('OptionalAnimationVerification');root.parent=e.director.getScene();
|
||||
const disposable=[];
|
||||
const restoreStorage=[];
|
||||
try {
|
||||
const menu=await load(prefabIds.menu),notice=await load(prefabIds.notice),settings=await load(prefabIds.settings);
|
||||
const find=(node,name)=>{if(node.name===name)return node;for(const child of node.children){const found=find(child,name);if(found)return found;}return null;};
|
||||
const cases=[[menu,['Spirit479','Spirit3120','Spirit76','Spirit11','Spirit12','Spirit14','Spirit17','Spirit13','Spirit7']],
|
||||
[notice,['Spirit3121','Spirit3017','Spirit5','Spirit235','Spirit604','Spirit3057','Spirit15']]];
|
||||
for(const [asset,names] of cases)for(const name of names){
|
||||
const node=find(asset.data,name);
|
||||
assert(node?.getComponent(e.Animation)&&node.getComponent('OptionalUiAnimation'),`${asset.name}/${name} has authored animation support`);
|
||||
}
|
||||
for(const name of ['音乐底','音效底','Spirit357','Spirit388']){
|
||||
const node=find(settings.data,name);
|
||||
assert(node?.getComponent(e.Animation)&&node.getComponent('OptionalSwitchAnimation'),`settings/${name} has authored switch animation support`);
|
||||
}
|
||||
const Switch=e.js.getClassByName('OptionalSwitchAnimation');
|
||||
const bareSwitch=new e.Node('BareSwitch');bareSwitch.addComponent(Switch);bareSwitch.parent=root;
|
||||
assert(bareSwitch.getComponent(e.Animation)&&bareSwitch.getComponent(e.Sprite),'mounting switch support provides both required components');
|
||||
const horn=find(menu.data,'Spirit479'),hornClip=horn.getComponent(e.Animation).defaultClip;
|
||||
assert(hornClip&&hornClip.wrapMode===e.AnimationClip.WrapMode.Loop,'horn owns a looping clip');
|
||||
const idle=e.instantiate(find(menu.data,'Spirit11'));idle.parent=root;
|
||||
const idleSprite=idle.getComponent(e.Sprite),original=idleSprite.spriteFrame;
|
||||
const idleAnimation=idle.getComponent(e.Animation);
|
||||
await sleep(80);
|
||||
assert(idleSprite.spriteFrame===original&&!idleAnimation.defaultClip,'unassigned clip preserves the authored sprite');
|
||||
idle.active=false;idleAnimation.defaultClip=hornClip;idle.active=true;
|
||||
await sleep(100);
|
||||
const idleState=idleAnimation.getState(hornClip.name);
|
||||
assert(idleState.isPlaying&&idleState.time>0,'assigned clip starts on show and advances');
|
||||
idle.active=false;
|
||||
assert(!idleState.isPlaying,'hiding stops the animation');
|
||||
const stoppedTime=idleState.time;await sleep(80);
|
||||
assert(idleState.time===stoppedTime,'hidden animation does not advance');
|
||||
idle.active=true;await sleep(40);
|
||||
assert(idleState.isPlaying&&idleState.time<0.3,'showing again restarts the clip');
|
||||
// Manual sampling verifies the authored frame schedule, independent of wall-clock jitter.
|
||||
const sampleNode=e.instantiate(horn);sampleNode.parent=root;
|
||||
const sampleAnimation=sampleNode.getComponent(e.Animation);sampleAnimation.stop();
|
||||
const hornState=sampleAnimation.getState(hornClip.name),sampleSprite=sampleNode.getComponent(e.Sprite);
|
||||
const samples=[];
|
||||
for(const time of [0,0.49,0.5,0.99,1,1.99]){hornState.time=time;hornState.sample();samples.push(sampleSprite.spriteFrame);}
|
||||
assert(samples[0]===samples[1]&&samples[0]!==samples[2]&&samples[2]===samples[3]&&samples[0]===samples[4]&&samples[0]===samples[5],
|
||||
'horn preserves the original two-second frame schedule');
|
||||
const onFrame=samples[0],offFrame=samples[2];
|
||||
const switchNode=e.instantiate(find(settings.data,'音乐底'));switchNode.parent=root;
|
||||
const view=switchNode.getComponent(Switch),sprite=switchNode.getComponent(e.Sprite),animation=switchNode.getComponent(e.Animation);
|
||||
view.setState(true,onFrame,false);view.setState(false,offFrame,true);
|
||||
assert(sprite.spriteFrame===offFrame,'switch without clips changes immediately');
|
||||
// Intentionally author the wrong final frame and Loop: the state remains authoritative.
|
||||
const onClip=e.AnimationClip.createWithSpriteFrames([offFrame,offFrame],20);onClip.name='TestSwitchOn';onClip.wrapMode=e.AnimationClip.WrapMode.Loop;
|
||||
const offClip=e.AnimationClip.createWithSpriteFrames([onFrame,onFrame],20);offClip.name='TestSwitchOff';
|
||||
disposable.push(onClip,offClip);view.onClip=onClip;view.offClip=offClip;
|
||||
view.setState(true,onFrame,false);
|
||||
assert(sprite.spriteFrame===onFrame,'initial state does not play a transition');
|
||||
view.setState(false,offFrame,true);await sleep(35);
|
||||
assert(animation.getState('TestSwitchOff').isPlaying,'off transition starts');
|
||||
view.setState(true,onFrame,true);await sleep(250);
|
||||
assert(sprite.spriteFrame===onFrame&&!animation.getState('TestSwitchOn').isPlaying,'rapid reversal finishes at latest state and plays once');
|
||||
view.setState(false,offFrame,true);switchNode.active=false;
|
||||
assert(sprite.spriteFrame===offFrame&&!animation.getState('TestSwitchOff').isPlaying,'hide cancels transition and restores latest state');
|
||||
switchNode.active=true;await sleep(60);
|
||||
assert(sprite.spriteFrame===offFrame&&!animation.getState('TestSwitchOff').isPlaying,'reopen preserves state without replaying stale transition');
|
||||
view.offClip=null;view.setState(true,onFrame,true);view.setState(false,offFrame,true);await sleep(180);
|
||||
assert(sprite.spriteFrame===offFrame,'missing destination clip cancels the old transition and keeps the new state');
|
||||
// Exercise the real settings click handlers against isolated storage keys.
|
||||
const LobbyPanel=Array.from(System.entries()).map(([,m])=>m).find(m=>m.LobbyPanel)?.LobbyPanel;
|
||||
assert(LobbyPanel,'LobbyPanel is loaded for settings integration');
|
||||
const panel=Object.create(LobbyPanel.prototype),tips=[];
|
||||
panel.assets={settings,switches:[onFrame,offFrame]};panel.config={identity:{gameid:987654321,agentid:765432109}};
|
||||
panel.context={launchMode:0};panel.model={canAct:true};panel.disposed=false;panel.modalReleases=[];
|
||||
panel.tip=message=>tips.push(message);
|
||||
panel.open=()=>{panel.modal=e.instantiate(settings);panel.modal.parent=root;return panel.modal;};
|
||||
panel.close=()=>{for(const off of panel.modalReleases)off();panel.modalReleases=[];panel.modal.destroy();};
|
||||
const musicKey='tsgame_bgm_987654321',soundKey='tsgame_sound_987654321765432109';
|
||||
for(const key of [musicKey,soundKey]){
|
||||
const previous=localStorage.getItem(key);restoreStorage.push(()=>previous===null?localStorage.removeItem(key):localStorage.setItem(key,previous));
|
||||
localStorage.setItem(key,'1');
|
||||
}
|
||||
panel.openSettings();
|
||||
let group=panel.modal.getChildByName('group-6');
|
||||
const music=group.getChildByName('音乐底'),sound=group.getChildByName('音效底');
|
||||
const musicView=music.getComponent(Switch);musicView.onClip=onClip;musicView.offClip=offClip;
|
||||
assert(music.getComponent(e.Sprite).spriteFrame===onFrame&&sound.getComponent(e.Sprite).spriteFrame===onFrame,'settings initialize from persisted state');
|
||||
music.emit(e.Button.EventType.CLICK);
|
||||
assert(localStorage.getItem(musicKey)==='0'&&music.getComponent(e.Animation).getState('TestSwitchOff').isPlaying,'music click persists state and starts configured transition');
|
||||
music.emit(e.Button.EventType.CLICK);await sleep(200);
|
||||
assert(localStorage.getItem(musicKey)==='1'&&music.getComponent(e.Sprite).spriteFrame===onFrame,'rapid music clicks retain the latest persisted state');
|
||||
sound.emit(e.Button.EventType.CLICK);
|
||||
assert(localStorage.getItem(soundKey)==='0'&&sound.getComponent(e.Sprite).spriteFrame===offFrame,'sound click without a clip keeps immediate switching');
|
||||
for(const name of ['Spirit357','Spirit388']){
|
||||
const node=group.getChildByName(name),frame=node.getComponent(e.Sprite).spriteFrame;
|
||||
node.emit(e.Button.EventType.CLICK);
|
||||
assert(node.getComponent(e.Sprite).spriteFrame===frame&&tips.at(-1)==='原生震动与语音开关尚未接入',`${name} does not invent an unsupported switch state`);
|
||||
}
|
||||
assert(tips.length===2,'settings click handlers produce no unexpected error tips');
|
||||
panel.close();panel.openSettings();group=panel.modal.getChildByName('group-6');
|
||||
assert(group.getChildByName('音乐底').getComponent(e.Sprite).spriteFrame===onFrame&&group.getChildByName('音效底').getComponent(e.Sprite).spriteFrame===offFrame,
|
||||
'reopening settings restores the latest music and sound states');
|
||||
panel.close();
|
||||
return {checks};
|
||||
} finally {
|
||||
for(const restore of restoreStorage)restore();
|
||||
root.destroy();await sleep(50);for(const clip of disposable)clip.destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/** Cocos Game View verification: mocked wire, no real ranking/card requests. */
|
||||
export async function verifyOwnedVipPreview(){
|
||||
const e=await System.import('cc'),modules=Array.from(System.entries()).map(([,m])=>m);
|
||||
const exported=name=>{const m=modules.find(m=>m[name]);if(!m)throw Error(`Missing ${name}`);return m[name];};
|
||||
const source=e.director.getScene().getChildByName('PlatformServices').getComponent('PlatformStartup');
|
||||
const config=source.config,assets=source.getComponent('LobbyAssets');
|
||||
source.unsubscribe?.();source.runtime?.stop();source.panel?.dispose();source.panel=null;
|
||||
await new Promise((resolve,reject)=>e.director.loadScene('PlatformLobby',err=>err?reject(err):resolve()));
|
||||
const sent=[],tips=[],checks=[];let receive,panel;
|
||||
const assert=(ok,msg)=>{if(!ok)throw Error(msg);checks.push(msg);};
|
||||
const runtime=new (exported('PlatformRuntime'))({
|
||||
gameEntry:exported('createLoginStageGameEntry')(config.identity.gameid,()=>{}),resolveRuntimeConfig:async()=>config,
|
||||
createWireClient:()=>({subscribe:cb=>{receive=cb;return ()=>{};},start:()=>{},stop:()=>{},send:p=>sent.push(p),switchServer:()=>{},reconnectCurrent:()=>{}}),
|
||||
scene:{showLoading:()=>{},showLogin:()=>{},showLobby:()=>{},showRoom:()=>{},showReconnect:()=>{},showKicked:()=>{},showServerDenial:()=>{},showFatal:err=>{throw err;},
|
||||
showTips:(msg,time,kind)=>{if(kind==='show_message')panel?.cancelRankRequest();tips.push(msg);}},
|
||||
loadResources:async()=>{},waitForMinimumDisplay:async()=>{},getLoginDeviceSnapshot:()=>{throw Error('Fixture only');},
|
||||
});
|
||||
await runtime.start();receive({type:'open',server:config.servers[0]});
|
||||
const packet=(rpc,data)=>receive({type:'message',message:{route:'agent',rpc,data}});
|
||||
packet('player_login',{state:0,playerid:7,nickname:'接线验证',avatar:'',sex:0,bean:0,roomcard:3,taskstate:0,score:0,sign:null,ip:'127.0.0.1',openid:'test',unionid:'test',invitecode:null});
|
||||
const canvas=e.director.getScene().getChildByName('Canvas'),popup=new e.Node('OwnedVipTestPopup');popup.parent=canvas;popup.layer=canvas.layer;popup.addComponent(e.UITransform).setContentSize(1600,720);
|
||||
const remote=exported('createRemoteConfig')({gameNoticeShow:-1,rankList:2,cdKey:2,vipRankLimit:3},config.identity);
|
||||
panel=new (exported('LobbyPanel'))(canvas,popup,assets,config,exported('resolveLobbyPolicy')(remote),runtime.state,msg=>tips.push(msg),()=>{},()=>null,()=>{},
|
||||
exported('resolveLobbyMenuConfig')(remote),{versionState:0,launchMode:0},()=>{},()=>{},()=>{},undefined,undefined,
|
||||
{rank:()=>runtime.getTreasureList(),redeem:card=>runtime.redeemCard(card),paste:async()=>'00Ab-12',loading:()=>{},vipRank:limit=>runtime.getVipRankList(limit),joinOwned:code=>runtime.joinOwnedRoom(code,{city:'test'})});
|
||||
popup.setSiblingIndex(canvas.children.length-1);
|
||||
const off=runtime.subscribeState(state=>panel.render(state),err=>{throw err;});
|
||||
const click=id=>panel.menuNodes.get(id).emit(e.Button.EventType.CLICK);
|
||||
|
||||
const key='tsgame_roomlist_'+config.identity.gameid+config.identity.agentid,previous=localStorage.getItem(key);
|
||||
try{
|
||||
const before=tips.length;for(const id of [10,286,287,3161,508,509,496,3162])click(id);
|
||||
assert(tips.length===before&&sent.length===0&&panel.modal===null,'retired payment entries neither notify nor send');
|
||||
localStorage.setItem(key,JSON.stringify({data:[100001,100002,100003,100004,100005,100006]}));
|
||||
click(99);assert(panel.modalKind==='my-rooms','Spirit99 opens authored owned room panel');
|
||||
let group=panel.modal.getChildByName('group-98');const rows=panel.modal.getComponent('OwnerRoomListView').content.children;
|
||||
assert(rows.length===6,'owned rooms show all cached entries');
|
||||
assert(rows[0].getChildByName('Digits').children.length===6,'owned room uses six original digit sprites');
|
||||
rows[1].emit(e.Button.EventType.CLICK);assert(panel.modal===null,'owned room click closes list');
|
||||
const join=sent.at(-1);assert(join.rpc==='self_join_room'&&join.data.roomcode===100002&&join.data.joinmode===1,'owned room click sends original join mode');
|
||||
assert(join.data.location.city==='test'&&join.data.ip==='127.0.0.1','owned room join retains native location and login IP');
|
||||
localStorage.setItem(key,JSON.stringify({data:[]}));click(99);assert(panel.modalKind==='my-rooms','empty owned room list still opens');
|
||||
panel.modal.getChildByPath('group-98/Spirit103').emit(e.Button.EventType.CLICK);assert(panel.modal===null,'owned room shade closes');
|
||||
const count=sent.length;click(3232);click(3232);
|
||||
assert(sent.length===count+1&&sent.at(-1).rpc==='getVipRankList','VIP rank suppresses duplicate request');
|
||||
assert(JSON.stringify(sent.at(-1).data)===JSON.stringify({agentid:config.identity.agentid,limit:3}),'VIP rank uses configured limit and exact fields');
|
||||
packet('get_treasurelist',{list:[]});assert(panel.rankPending==='vip-rank','wealth response cannot consume pending VIP request');
|
||||
const list=[{name:'VIP一',headimg:'',playerid:1,star:88,signature:'签名一',destwo:'附注',notice:'公告一'},{name:'VIP二',headimg:'',playerid:2,star:66,signature:'签名二',notice:'公告二'}];
|
||||
packet('getVipRankList',{list});assert(panel.modalKind==='vip-rank','VIP response opens shared rank view');
|
||||
group=panel.modal.getChildByName('group-84');assert(group.getChildByName('Spirit3234').getComponent(e.Sprite).spriteFrame===assets.vipRankTitle,'VIP title uses original frame');
|
||||
assert(group.getChildByName('Spirit640').getComponent(e.Label).string==='公告:\n(附注)\n公告一','VIP detail renders notice and supplemental text');
|
||||
group.getComponentInChildren(e.ScrollView).content.children[1].emit(e.Button.EventType.CLICK);
|
||||
assert(group.getChildByName('Spirit640').getComponent(e.Label).string==='公告:\n公告二','VIP row selection updates announcement');
|
||||
panel.close();click(3232);packet('getVipRankList',{});assert(panel.modal===null&&!panel.rankPending,'missing VIP list completes silently like legacy');
|
||||
click(3232);packet('getVipRankList',{list:[]});assert(panel.modal===null&&!panel.rankPending,'empty VIP list stays closed');
|
||||
click(3232);packet('getVipRankList',{list});
|
||||
await new Promise(resolve=>e.director.once(e.Director.EVENT_AFTER_DRAW,resolve));
|
||||
globalThis.__ownedVipVerification={panel,runtime,checks,close:()=>{off();panel.dispose();runtime.stop();popup.destroy();}};
|
||||
return {checks,packets:sent.map(p=>({rpc:p.rpc,fields:Object.keys(p.data)}))};
|
||||
}finally{if(previous===null)localStorage.removeItem(key);else localStorage.setItem(key,previous);}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/** Real Cocos layout and coordinate-input checks. Only a dedicated local fixture history is written. */
|
||||
export async function verifyOwnerRoomPreview(e,listId){
|
||||
const checks=[],assert=(v,m)=>{if(!v)throw Error(m);checks.push(m);},sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
||||
const load=id=>new Promise((r,j)=>e.assetManager.loadAny(id,(err,a)=>err?j(err):r(a)));
|
||||
const source=await load('1c10c2ee-adc2-496f-9005-d3e0e6ebbb56'),refs=source.scene.getComponentInChildren('LobbyAssets');
|
||||
const asset=await load(listId),canvas=e.director.getScene().getComponentInChildren(e.Canvas);
|
||||
assert(refs.ownedRoomList.uuid===listId,'startup points at independent room list');
|
||||
assert(!refs.qrCode.data.getChildByName('group-98')&&refs.qrCode.data.getChildByName('group-42'),'room and QR groups use separate prefabs');
|
||||
const view=asset.data.getComponent('OwnerRoomListView');
|
||||
assert(asset.data.children.length===1&&asset.data.children[0].name==='group-98','room prefab contains only group-98');
|
||||
assert(view.itemPrefab.data.name==='OwnerRoomItem','list references the requested OwnerRoomItem prefab');
|
||||
const LobbyPanel=Array.from(System.entries()).map(([,m])=>m).find(m=>m.LobbyPanel).LobbyPanel,panel=Object.create(LobbyPanel.prototype),joined=[];
|
||||
panel.assets={ownedRoomList:asset,ownedRoomDigits:refs.ownedRoomDigits};panel.model={canAct:true};panel.config={identity:{gameid:'__owner_preview_',agentid:'local'}};
|
||||
panel.disposed=false;panel.modal=null;panel.modalReleases=[];panel.extras={joinOwned:code=>joined.push(code)};
|
||||
panel.close=()=>{for(const off of panel.modalReleases.splice(0))off();panel.modal?.destroy();panel.modal=null;};
|
||||
panel.open=(prefab,kind)=>{panel.close();const n=e.instantiate(prefab);n.parent=canvas.node;n.setPosition(0,0,0);const w=n.getComponent(e.Widget);w.target=canvas.node;w.updateAlignment();panel.modal=n;panel.modalKind=kind;return n;};
|
||||
const key='tsgame_roomlist___owner_preview_local',previous=localStorage.getItem(key),codes=Array.from({length:9},(_,i)=>100001+i);
|
||||
let id=200;
|
||||
const send=(touch,type)=>e.input._dispatchEventTouch(new e.EventTouch([touch],false,type,[touch]));
|
||||
const touchAt=(node,dx=0,dy=0)=>{const p=node.worldPosition.clone();p.x+=dx;p.y+=dy;const s=canvas.cameraComponent.worldToScreen(p);return new e.Touch(s.x,s.y,id++);};
|
||||
const tap=async(node,dx=0,dy=0)=>{const t=touchAt(node,dx,dy);send(t,e.Input.EventType.TOUCH_START);send(t,e.Input.EventType.TOUCH_END);await sleep(30);};
|
||||
try{
|
||||
localStorage.setItem(key,JSON.stringify({data:codes}));panel.openOwnedRooms();await sleep(100);
|
||||
let v=panel.modal.getComponent('OwnerRoomListView'),rows=v.content.children;
|
||||
assert(rows.length===9,'more than five cached records are displayed');
|
||||
assert(v.scroll.getMaxScrollOffset().y>0,'long room list is scrollable');
|
||||
assert(rows.every((n,i)=>n.getChildByName('Digits').children.map(d=>refs.ownedRoomDigits.indexOf(d.getComponent(e.Sprite).spriteFrame)).join('')===String(codes[i])),'all room numbers retain digit artwork and ordering');
|
||||
const layout=v.content.getComponent(e.Layout);
|
||||
assert(rows.slice(1).every((n,i)=>Math.abs(rows[i].position.y-n.position.y-80-layout.spacingY)<.1),'room spacing comes from Layout');
|
||||
await tap(panel.modal.getChildByPath('group-98/房间列表背景'),0,240);assert(panel.modal!==null,'panel header blank area does not close');
|
||||
const t=touchAt(rows[1]);send(t,e.Input.EventType.TOUCH_START);const p=t.getLocation();
|
||||
t.setTouchInfo(t.getID(),p.x,p.y+120);send(t,e.Input.EventType.TOUCH_MOVE);send(t,e.Input.EventType.TOUCH_END);await sleep(80);
|
||||
assert(panel.modal!==null&&joined.length===0,'dragging a row never joins or closes');
|
||||
assert(v.scroll.getScrollOffset().y>0,'coordinate drag moves scroll content');
|
||||
v.scroll.stopAutoScroll();v.scroll.scrollToBottom(0);await sleep(60);
|
||||
await tap(rows.at(-1));assert(joined.at(-1)===codes.at(-1)&&panel.modal===null,'last scrolled row joins its own room and closes');
|
||||
panel.openOwnedRooms();await sleep(60);v=panel.modal.getComponent('OwnerRoomListView');
|
||||
assert(Math.abs(v.scroll.getScrollOffset().y)<.1,'reopening starts at top');
|
||||
v.render([],refs.ownedRoomDigits);await sleep(40);assert(v.content.children.length===0,'empty update removes all rows');
|
||||
assert(v.content.getComponent(e.UITransform).height===0,'empty update removes stale scroll height');
|
||||
v.render(codes,refs.ownedRoomDigits);await sleep(40);assert(v.content.children.length===9&&v.scroll.getMaxScrollOffset().y>0,'refilling empty list restores layout and scrolling');
|
||||
panel.close();localStorage.setItem(key,JSON.stringify({data:[]}));panel.openOwnedRooms();await sleep(60);
|
||||
assert(panel.modal.getComponent('OwnerRoomListView').content.children.length===0,'empty history opens an empty list');
|
||||
await tap(panel.modal.getChildByPath('group-98/Spirit103'),600,0);assert(panel.modal===null,'outside shade closes room list');
|
||||
return {checks};
|
||||
}finally{panel.close();if(previous===null)localStorage.removeItem(key);else localStorage.setItem(key,previous);}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/** Execute in Cocos Game View. All input fixtures stay local; no server submission. */
|
||||
export async function verifyProfilePreview(e,profileId) {
|
||||
const checks=[],assert=(ok,message)=>{if(!ok)throw Error(message);checks.push(message);};
|
||||
const load=id=>new Promise((resolve,reject)=>e.assetManager.loadAny(id,(err,a)=>err?reject(err):resolve(a)));
|
||||
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
const asset=await load(profileId),root=e.instantiate(asset);root.parent=e.director.getScene();
|
||||
const host=new e.Node('ProfileFlowVerification');host.parent=e.director.getScene();
|
||||
try {
|
||||
const view=root.getComponent('ProfileInfoListView');
|
||||
assert(view,'profile prefab owns ProfileInfoListView');
|
||||
assert(view.textItemPrefab&&view.authPrefab,'profile owns text-item and auth prefab references');
|
||||
view.render(['昵称','ID:42','IP:127.0.0.1','积分:0','星星:10','城市','个性签名:'],'短签名');
|
||||
await sleep(80);
|
||||
const content=view.content,layout=content.getComponent(e.Layout);
|
||||
const checkSpacing=()=>{
|
||||
const children=content.children.filter(n=>n.active);
|
||||
for(let i=1;i<children.length;i++){
|
||||
const prev=children[i-1],next=children[i],pu=prev.getComponent(e.UITransform),nu=next.getComponent(e.UITransform);
|
||||
const bottom=prev.position.y-pu.height*pu.anchorY,top=next.position.y+nu.height*(1-nu.anchorY);
|
||||
assert(Math.abs(bottom-top-layout.spacingY)<0.1,'visible rows use the container spacing');
|
||||
}
|
||||
};
|
||||
checkSpacing();const initialHeight=content.getComponent(e.UITransform).height;
|
||||
view.render(['很长的昵称'.repeat(25),'ID:42','个性签名:'],'长签名内容'.repeat(100));await sleep(80);checkSpacing();
|
||||
assert(content.children.filter(n=>n.active).length===4,'removed fields leave no empty rows');
|
||||
assert(content.children[0].getComponent(e.UITransform).height>30,'long nickname wraps and increases its row height');
|
||||
assert(view.signatureBox.height>=view.signature.node.getComponent(e.UITransform).height+20,'signature background grows with its text');
|
||||
assert(content.getComponent(e.UITransform).height>initialHeight&&view.scroll.getMaxScrollOffset().y>0,'long content becomes scrollable');
|
||||
view.scroll.scrollToBottom(0);await sleep(30);
|
||||
view.render(['昵称','个性签名:'],'短签名');await sleep(60);checkSpacing();
|
||||
assert(content.getComponent(e.UITransform).height<initialHeight,'shorter content shrinks the list again');
|
||||
assert(view.scroll.getScrollOffset().y<=Math.max(0,view.scroll.getMaxScrollOffset().y)+0.1,'content shrink clamps scrolling');
|
||||
const LobbyPanel=Array.from(System.entries()).map(([,m])=>m).find(m=>m.LobbyPanel)?.LobbyPanel;
|
||||
assert(LobbyPanel,'LobbyPanel is loaded');
|
||||
const panel=Object.create(LobbyPanel.prototype),tips=[];
|
||||
panel.assets={profile:asset};panel.disposed=false;panel.modal=null;panel.modalReleases=[];
|
||||
panel.policy={starName:'星星'};panel.model={nickname:'资料接线测试',playerid:42,ip:'127.0.0.1',score:0,bean:10,sign:'签名测试',canAct:true};
|
||||
panel.avatarFrame=asset.data.getChildByPath('group-18/Spirit118').getComponent(e.Sprite).spriteFrame;
|
||||
panel.location=()=>null;panel.tip=message=>tips.push(message);
|
||||
panel.close=()=>{for(const off of panel.modalReleases.splice(0))off();panel.modal?.destroy();panel.modal=null;panel.modalKind='';};
|
||||
panel.open=(prefab,kind)=>{panel.close();panel.modal=e.instantiate(prefab);panel.modal.parent=host;panel.modalKind=kind;return panel.modal;};
|
||||
panel.openProfile();
|
||||
const profile=panel.modal,profileView=profile.getComponent('ProfileInfoListView');
|
||||
assert(profileView.content.children[0].getComponent(e.Label).string==='资料接线测试'&&profileView.signature.string==='签名测试','profile renders data through the new hierarchy');
|
||||
const auth=()=>{
|
||||
panel.modal.getChildByPath('group-18/Spirit484').emit(e.Button.EventType.CLICK);
|
||||
assert(panel.modal?.name==='Layer18_Auth'&&panel.modalKind==='auth','real-name button opens the auth prefab');
|
||||
const group=panel.modal.getChildByName('group-57');
|
||||
return {group,name:group.getChildByName('NameInput').getComponent(e.EditBox),id:group.getChildByName('IdInput').getComponent(e.EditBox),submit:group.getChildByName('Spirit493')};
|
||||
};
|
||||
let inputs=auth();await sleep(40);
|
||||
assert(!e.isValid(profile),'opening auth closes the profile');
|
||||
assert(inputs.name.string===''&&inputs.id.string==='','auth opens with empty inputs');
|
||||
assert(inputs.id.inputMode===e.EditBox.InputMode.SINGLE_LINE&&inputs.id.inputFlag===e.EditBox.InputFlag.DEFAULT,'ID input allows the X checksum character');
|
||||
for(const edit of [inputs.name,inputs.id])for(const label of [edit.textLabel,edit.placeholderLabel]){
|
||||
const ui=label.node.getComponent(e.UITransform);
|
||||
assert(Math.abs(label.node.position.y+(0.5-ui.anchorY)*ui.height)<0.1&&label.verticalAlign===e.Label.VerticalAlign.CENTER,'input text stays vertically centered inside its hit area');
|
||||
}
|
||||
inputs.name.string='张';inputs.id.string='11010519491231002X';inputs.submit.emit(e.Button.EventType.CLICK);
|
||||
assert(tips.at(-1)==='姓名不正确!'&&panel.modal!==null,'invalid name shows source tip and keeps auth open');
|
||||
inputs.name.string='测试';inputs.id.string='110105194912310020';inputs.submit.emit(e.Button.EventType.CLICK);
|
||||
assert(tips.at(-1)==='身份证信息不正确!'&&panel.modal!==null,'invalid ID shows source tip and keeps auth open');
|
||||
inputs.id.string='11010519491231002X';inputs.submit.emit(e.Button.EventType.CLICK);
|
||||
assert(tips.at(-1)==='认证成功!'&&panel.modal===null,'valid local input closes auth and shows source success tip');
|
||||
panel.openProfile();inputs=auth();
|
||||
assert(inputs.name.string===''&&inputs.id.string==='','reopening does not retain identity inputs');
|
||||
inputs.group.getChildByName('Spirit485').emit(e.Button.EventType.CLICK);
|
||||
assert(panel.modal===null,'auth backdrop closes the panel');
|
||||
assert(tips.length===3,'flow produced only the three expected validation tips');
|
||||
return {checks};
|
||||
} finally {root.destroy();host.destroy();await sleep(50);}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/** Cocos Game View verification: mocked wire, no real ranking/card requests. */
|
||||
export async function verifyRankCardPreview(){
|
||||
const e=await System.import('cc'),modules=Array.from(System.entries()).map(([,m])=>m);
|
||||
const exported=name=>{const m=modules.find(m=>m[name]);if(!m)throw Error(`Missing ${name}`);return m[name];};
|
||||
const source=e.director.getScene().getChildByName('PlatformServices').getComponent('PlatformStartup');
|
||||
const config=source.config,assets=source.getComponent('LobbyAssets');
|
||||
source.unsubscribe?.();source.runtime?.stop();source.panel?.dispose();source.panel=null;
|
||||
await new Promise((resolve,reject)=>e.director.loadScene('PlatformLobby',err=>err?reject(err):resolve()));
|
||||
const sent=[],tips=[],checks=[];let receive,panel;
|
||||
const assert=(ok,msg)=>{if(!ok)throw Error(msg);checks.push(msg);};
|
||||
const runtime=new (exported('PlatformRuntime'))({
|
||||
gameEntry:exported('createLoginStageGameEntry')(config.identity.gameid,()=>{}),resolveRuntimeConfig:async()=>config,
|
||||
createWireClient:()=>({subscribe:cb=>{receive=cb;return ()=>{};},start:()=>{},stop:()=>{},send:p=>sent.push(p),switchServer:()=>{},reconnectCurrent:()=>{}}),
|
||||
scene:{showLoading:()=>{},showLogin:()=>{},showLobby:()=>{},showRoom:()=>{},showReconnect:()=>{},showKicked:()=>{},showServerDenial:()=>{},showFatal:err=>{throw err;},
|
||||
showTips:(msg,time,kind)=>{if(kind==='show_message')panel?.cancelRankRequest();tips.push(msg);}},
|
||||
loadResources:async()=>{},waitForMinimumDisplay:async()=>{},getLoginDeviceSnapshot:()=>{throw Error('Fixture only');},
|
||||
});
|
||||
await runtime.start();receive({type:'open',server:config.servers[0]});
|
||||
const packet=(rpc,data)=>receive({type:'message',message:{route:'agent',rpc,data}});
|
||||
packet('player_login',{state:0,playerid:7,nickname:'接线验证',avatar:'',sex:0,bean:0,roomcard:3,taskstate:0,score:0,sign:null,ip:'127.0.0.1',openid:'test',unionid:'test',invitecode:null});
|
||||
const canvas=e.director.getScene().getChildByName('Canvas'),popup=new e.Node('RankCardTestPopup');popup.parent=canvas;popup.layer=canvas.layer;popup.addComponent(e.UITransform).setContentSize(1600,720);
|
||||
const remote=exported('createRemoteConfig')({gameNoticeShow:-1,rankList:2,cdKey:2},config.identity);
|
||||
panel=new (exported('LobbyPanel'))(canvas,popup,assets,config,exported('resolveLobbyPolicy')(remote),runtime.state,msg=>tips.push(msg),()=>{},()=>null,()=>{},
|
||||
exported('resolveLobbyMenuConfig')(remote),{versionState:0,launchMode:0},()=>{},()=>{},()=>{},undefined,undefined,
|
||||
{rank:()=>runtime.getTreasureList(),redeem:card=>runtime.redeemCard(card),paste:async()=>'00Ab-12'});
|
||||
popup.setSiblingIndex(canvas.children.length-1);
|
||||
const off=runtime.subscribeState(state=>panel.render(state),err=>{throw err;});
|
||||
const click=id=>panel.menuNodes.get(id).emit(e.Button.EventType.CLICK);
|
||||
click(3233);click(3233);assert(sent.length===1&&sent[0].rpc==='get_treasurelist','rank query suppresses duplicate clicks');
|
||||
assert(panel.modal===null,'rank waits for server');
|
||||
const list=Array.from({length:8},(_,i)=>({name:`测试${i+1}`,headimg:'',playerid:i+1,star:100-i,signature:`签名${i+1}`}));
|
||||
packet('get_treasurelist',{list});assert(panel.modalKind==='rank','server opens authored rank prefab');
|
||||
let group=panel.modal.getChildByName('group-84'),rows=group.getComponentInChildren(e.ScrollView).content;
|
||||
assert(rows.children.length===8,'all rank rows rendered');
|
||||
assert(rows.children.every(n=>n._prefab?.asset===assets.rankItem),'all rank rows instantiate VipRankItem prefab');
|
||||
assert(rows.children.every(n=>n.getChildByPath('Spirit630/Spirit631')),'prefab avatar hierarchy is preserved');
|
||||
const authoredLayout=rows.getComponent(e.Layout);assert(authoredLayout&&rows.getComponent(e.UITransform).height>511,'authored layout grows scroll content');
|
||||
const viewport=group.getComponentInChildren(e.ScrollView);viewport.scrollToBottom(0);assert(viewport.getScrollOffset().y>0,'authored ScrollView reaches lower rows');viewport.scrollToTop(0);
|
||||
assert(group.getComponentInChildren(e.ScrollView).content===rows,'native scroll content is bound');
|
||||
rows.children[1].emit(e.Button.EventType.CLICK);
|
||||
assert(group.getChildByName('Spirit637').getComponent(e.Label).string==='ID:2','row selection updates detail ID');
|
||||
assert(group.getChildByName('Spirit641').getComponent(e.Label).string==='个性签名:\n签名2','row selection updates signature');
|
||||
group.getChildByName('Spirit605').emit(e.Button.EventType.CLICK);assert(panel.modal===null,'rank shade closes');
|
||||
click(3233);packet('get_treasurelist',{list:[]});assert(panel.modal===null,'empty rank does not open');
|
||||
click(3233);packet('show_message',{msg:'暂不可用',time:0});assert(!panel.rankPending,'denial releases rank request');
|
||||
click(3264);assert(panel.modalKind==='cdkey','card entry opens original feedback group');
|
||||
group=panel.modal.getChildByName('group-74');const edit=group.getChildByName('CardInput').getComponent(e.EditBox);
|
||||
assert(edit.textLabel&&edit.maxLength===-1,'native card input is bound');
|
||||
const anchor=edit.textLabel.node.getComponent(e.UITransform).anchorPoint;assert(anchor.x===0&&anchor.y===1,'card label uses native input anchor');
|
||||
const count=sent.length;edit.string=' ';group.getChildByName('Spirit3266').emit(e.Button.EventType.CLICK);assert(sent.length===count,'blank card is not sent');
|
||||
group.getChildByName('Spirit3265').emit(e.Button.EventType.CLICK);await Promise.resolve();await Promise.resolve();
|
||||
assert(edit.string==='00Ab-12','paste preserves original card text');
|
||||
group.getChildByName('Spirit3266').emit(e.Button.EventType.CLICK);assert(panel.modal===null,'redeem closes original dialog');
|
||||
assert(sent.at(-1).rpc==='topup_card'&&sent.at(-1).data.cardno==='00Ab-12','redemption sends exact card');
|
||||
packet('topup_card',{});packet('update_bean',{bean:200});assert(panel.model.bean===200,'server bean update reaches lobby');
|
||||
const sentBeforeConfig=sent.length;
|
||||
const cfgRemote=exported('createRemoteConfig')({rankList:1,rankList1:list,nameImgFrame_2:2},config.identity);
|
||||
panel.config={...config,remoteConfig:cfgRemote};panel.menuConfig=exported('resolveLobbyMenuConfig')(cfgRemote);
|
||||
click(3233);assert(panel.modalKind==='rank'&&sent.length===sentBeforeConfig,'config rank opens without server request');
|
||||
assert(panel.modal.getChildByPath('group-84/Spirit639').getComponent(e.Sprite).spriteFrame===assets.rankSymbols[1],'configured currency symbol is used');
|
||||
await new Promise(resolve=>e.director.once(e.Director.EVENT_AFTER_DRAW,resolve));
|
||||
globalThis.__rankCardVerification={panel,runtime,checks,close:()=>{off();panel.dispose();runtime.stop();popup.destroy();}};
|
||||
return {checks,packets:sent.map(p=>({rpc:p.rpc,fields:Object.keys(p.data)}))};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/** Run in Cocos Game View. The actual socket is stopped before all local fixtures. */
|
||||
export async function verifyRoomEntryPreview(e,loginFixture){
|
||||
const checks=[],assert=(ok,text)=>{if(!ok)throw Error(text);checks.push(text);};
|
||||
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
||||
const load=id=>new Promise((r,j)=>e.assetManager.loadAny(id,(err,a)=>err?j(err):r(a)));
|
||||
const exported=name=>Array.from(System.entries()).map(([,m])=>m).find(m=>m[name])?.[name];
|
||||
const startup=e.director.getScene().getComponentInChildren('PlatformStartup');
|
||||
assert(startup,'persistent Startup loaded');startup.unsubscribe?.();startup.unsubscribe=null;startup.runtime.stop();startup.panel?.dispose();startup.panel=null;
|
||||
const canvas=e.director.getScene().getChildByName('Canvas');
|
||||
let node=null;const tips=[],sent=[];let cancel=0;
|
||||
const mount=async id=>{if(node){node.destroy();await sleep(50);}node=e.instantiate(await load(id));node.parent=canvas;node.setPosition(0,0,0);const w=node.getComponent(e.Widget);if(w){w.target=canvas;w.updateAlignment();}await sleep(100);return node;};
|
||||
let pointer=30;
|
||||
const click=async n=>{const camera=canvas.getComponent(e.Canvas).cameraComponent,p=camera.worldToScreen(n.worldPosition);const touch=new e.Touch(p.x,p.y,pointer++);
|
||||
e.input._dispatchEventTouch(new e.EventTouch([touch],false,e.Input.EventType.TOUCH_START,[touch]));
|
||||
e.input._dispatchEventTouch(new e.EventTouch([touch],false,e.Input.EventType.TOUCH_END,[touch]));await sleep(25);};
|
||||
const keys=['tsgame_roomtype_','tsgame_shortcode_'].map(k=>k+startup.config.identity.gameid+startup.config.identity.agentid);
|
||||
const old=keys.map(k=>localStorage.getItem(k));
|
||||
let runtime=null;
|
||||
try{
|
||||
await mount('98e42432-0731-4b9e-a158-72032b2c157d');const join=node.getComponent('JoinRoomView');
|
||||
assert(join&&join.slots.every(s=>!!s.font),'join prefab has wired BitmapFont slots');
|
||||
join.open({initialCode:'',submit:n=>{sent.push(n);join.setBusy(true);},cancel:()=>cancel++,paste:async()=> '房号123-456789',reportError:t=>tips.push(t)});
|
||||
for(const d of [0,1,2,3,4,5])await click(join.digitButtons[d].node);
|
||||
assert(sent.length===1&&sent[0]===12345,'coordinate keypad submits numeric code on sixth digit');
|
||||
assert(join.slots.map(s=>s.string).join('')==='012345','join slots render all six digits including leading zero');
|
||||
await click(join.submitButton.node);assert(sent.length===1,'busy submit does not duplicate request');
|
||||
await click(join.cancelButtons[1].node);assert(cancel===1,'close callback works while busy');
|
||||
join.setBusy(false);await click(join.deleteButton.node);assert(join.slots.filter(s=>s.node.active).length===5,'delete hides final slot');
|
||||
await click(join.clearButton.node);assert(join.slots.every(s=>!s.node.active),'clear hides all slots');
|
||||
await click(join.pasteButton.node);assert(sent[1]===123456,'clipboard strips separators and truncates to six digits');
|
||||
let finishPaste;const late=[];
|
||||
join.open({initialCode:'',submit:n=>late.push(n),cancel(){},paste:()=>new Promise(r=>finishPaste=r),reportError:t=>tips.push(t)});
|
||||
await click(join.pasteButton.node);
|
||||
const panel=Object.create(exported('LobbyPanel').prototype);panel.roomPage=join;panel.modal=node;panel.modalKind='join';panel.modalReleases=[];
|
||||
panel.cancelRankRequest=panel.cancelInviteRequest=panel.cancelTaskRequest=()=>{};
|
||||
panel.cancelRoomPage();node=null;finishPaste('123456');await sleep(40);
|
||||
assert(late.length===0&&panel.roomPage===null,'disconnect closes room page and invalidates deferred clipboard auto-submit');
|
||||
await mount('584dbb85-4acc-4bea-9d9b-e0cbeb8741da');const create=node.getComponent('CreateRoomPage');
|
||||
assert(create&&create.constructor.name==='TemplateCreateRoomView','platform resolves subgame through base page component');
|
||||
const packets=[];create.open({previousRoomtype:null,submit:r=>{packets.push(r);create.setBusy(true);},cancel:()=>cancel++,reportError:t=>tips.push(t)});
|
||||
assert(create.options[0].isChecked&&create.options[2].isChecked,'template initial options match source');
|
||||
await click(create.options[1].node);await click(create.options[3].node);
|
||||
assert(!create.options[0].isChecked&&create.options[1].isChecked&&!create.options[2].isChecked&&create.options[3].isChecked,'Cocos ToggleContainer enforces each option group');
|
||||
await click(create.options[1].node);assert(create.options[1].isChecked,'clicking the selected option keeps it selected like the source');
|
||||
await click(create.submitButton.node);await click(create.submitButton.node);
|
||||
const roomtype=[1,4,1,2,2,[1,1,[1,2000,10],null,null,1],[1,0,5]];
|
||||
assert(packets.length===1&&JSON.stringify(packets[0])===JSON.stringify(roomtype),'create page submits source fixed array once');
|
||||
create.close();create.open({previousRoomtype:roomtype,submit:()=>{},cancel:()=>{},reportError:t=>tips.push(t)});
|
||||
assert(!create.options[2].isChecked&&!create.options[3].isChecked,'saved template index 4 does not invent a matching option');
|
||||
node.destroy();node=null;await sleep(50);
|
||||
// Exercise the real runtime + GameEntry + Startup scene callback with a local wire.
|
||||
const game=exported('createTemplateGame')(startup.config.identity.gameid),listeners=new Set(),wire={sent:[],subscribe:fn=>{listeners.add(fn);return()=>listeners.delete(fn);},start(){},stop(){},send(p){this.sent.push(p);},switchServer(){},reconnectCurrent(){},emit(ev){for(const fn of [...listeners])fn(ev);}};
|
||||
startup.game=game;startup.loginResponse=null;let roomPromise=Promise.resolve(),lobbies=0;const fatal=[];
|
||||
runtime=new (exported('PlatformRuntime'))({gameEntry:game.entry,resolveRuntimeConfig:async()=>startup.config,createWireClient:()=>wire,
|
||||
scene:{showLoading(){},showLogin(){},showLobby(){lobbies++;},showRoom(){roomPromise=startup.showRoom();},showReconnect(){startup.endRoomRequest();},showTips:t=>tips.push(t),showServerDenial(){startup.endRoomRequest();},showKicked(){},showFatal:error=>fatal.push(String(error))},
|
||||
loadResources:async()=>{},waitForMinimumDisplay:async()=>{},getLoginDeviceSnapshot:()=>({ip:'127.0.0.1',location:null,machineid:'preview',machineroom:'',deviceLogin:{enabled:false},loginPlayerId:{enabled:false}})});
|
||||
startup.runtime=runtime;await runtime.start();wire.emit({type:'open'});
|
||||
runtime.login({openid:'o1',unionid:'test_unionid',nickname:'测试号',avatar:'',sex:0,province:'江西',city:'南昌'});
|
||||
wire.emit({type:'message',message:{...loginFixture,data:{...loginFixture.data,avatar:''}}});
|
||||
assert(runtime.state.room.kind==='outside'&&lobbies===1,'local login commits lobby state');
|
||||
const originalTimer=window.setTimeout;let expire;
|
||||
try{window.setTimeout=(fn,ms,...args)=>ms===15000?(expire=fn,999999):originalTimer(fn,ms,...args);
|
||||
startup.requestRoom('create_room',()=>{});
|
||||
}finally{window.setTimeout=originalTimer;}
|
||||
const originalTip=startup.tip;startup.tip=t=>tips.push(t);expire();startup.tip=originalTip;
|
||||
assert(startup.roomPending===null&&tips.pop()==='房间请求超时,请重试','room timeout releases guard and reports retry');
|
||||
startup.requestRoom('create_room',()=>runtime.createRoom({roomtype,ip:'127.0.0.1',location:null}));
|
||||
startup.requestRoom('create_room',()=>{throw Error('duplicate request invoked');});
|
||||
assert(wire.sent.filter(p=>p.rpc==='create_room').length===1,'Startup suppresses duplicate room requests');
|
||||
wire.emit({type:'message',message:{route:'agent',rpc:'create_room',data:{state:7,showerror:1,error:'测试拒绝'}}});
|
||||
assert(runtime.state.room.kind==='outside'&&startup.roomPending===null,'denial releases wait and keeps lobby');
|
||||
startup.requestRoom('create_room',()=>runtime.createRoom({roomtype,ip:'127.0.0.1',location:null}));
|
||||
const created={state:0,roomcode:'654321',seat:0,roomtype,makewar:0,asetcount:10};startup.roomReceipt={rpc:'create_room',data:created};
|
||||
wire.emit({type:'message',message:{route:'agent',rpc:'create_room',data:created}});await roomPromise;await sleep(100);
|
||||
assert(fatal.length===0&&runtime.state.room.kind==='inside'&&e.director.getScene().name==='TemplateRoom','create success crosses real Startup scene load');
|
||||
const roomRoot=e.director.getScene().getChildByName('Canvas').getChildByName('TemplateRoom'),roomView=roomRoot.getComponent('TemplateRoomView');
|
||||
assert(roomView.roomLabel.string.includes('654321')&&roomView.playersLabel.string.includes('测试号'),'room view consumes module snapshot');
|
||||
wire.emit({type:'message',message:{route:'agent',rpc:'create_room',data:created}});await sleep(50);
|
||||
assert(e.isValid(roomRoot),'duplicate successful reply does not remount room');
|
||||
roomView.prepareButton.node.emit(e.Button.EventType.CLICK);assert(wire.sent.at(-1).rpc==='player_prepare','subgame prepare travels through GameHost');
|
||||
wire.emit({type:'message',message:{route:'room',rpc:'player_prepare',data:{seat:0}}});
|
||||
roomView.exitButton.node.emit(e.Button.EventType.CLICK);assert(wire.sent.at(-1).rpc==='self_exit_room','subgame exit travels through GameHost');
|
||||
wire.emit({type:'message',message:{route:'room',rpc:'self_exit_room',data:{seat:0}}});
|
||||
assert(runtime.state.room.kind==='outside'&&lobbies===2&&roomView.actions===null,'exit clears canonical room and disposes view binding');
|
||||
startup.requestRoom('self_join_room',()=>runtime.joinRoom({roomcode:123456,ip:'127.0.0.1',location:null}));
|
||||
assert(wire.sent.at(-1).data.roomcode===123456,'manual join preserves numeric wire roomcode');
|
||||
const players=Array(10).fill(null);players[2]={...loginFixture.data,avatar:'',isprepare:0,onstate:0};
|
||||
const deskinfo={round:2,cards:[1,2,3]};
|
||||
wire.emit({type:'message',message:{route:'agent',rpc:'self_join_room',data:{state:0,roomcode:'123456',seat:2,roomtype,players,deskinfo,deskwar:1,needprepare:1,infinite:0}}});
|
||||
await roomPromise;await sleep(80);
|
||||
assert(fatal.length===0&&runtime.state.room.selfSeat===2&&game.restoredSnapshot===deskinfo,'join and reconnect snapshot reach template module unchanged');
|
||||
assert(e.director.getScene().getChildByName('Canvas').getChildByName('TemplateRoom').getComponent('TemplateRoomView').roomLabel.string.includes('123456'),'join success binds its own room scene');
|
||||
assert(tips.length===0&&fatal.length===0,'no unexpected fixture UI errors');
|
||||
return {checks,count:checks.length};
|
||||
}finally{node?.destroy();runtime?.stop();startup.endRoomRequest();keys.forEach((key,i)=>old[i]===null?localStorage.removeItem(key):localStorage.setItem(key,old[i]));}
|
||||
}
|
||||
Reference in New Issue
Block a user