feat: wire lobby task and VIP prefab flows and sync UI assets

This commit is contained in:
2026-09-06 18:46:37 +08:00
parent 842c68ef55
commit 9c4510697c
184 changed files with 8348 additions and 19133 deletions
@@ -59,6 +59,50 @@ function makeCommands(store: PlatformStore): {
};
}
test('VIP query and both entry forms use the exact agent protocol',()=>{
const store=makeStore('outside'),{commands,sent}=makeCommands(store);
commands.getVipRooms(['123456']);commands.joinVipRoom('123456',null);commands.joinVipRoom(900001,{city:'测试'});
assert.deepEqual(sent[0],{app:'youle',route:'agent',rpc:'getInfoByShortCode',data:{agentid:'agent-from-config',gameid:'game-from-config',shortcodeList:['123456']}});
assert.deepEqual(sent[1],{app:'youle',route:'agent',rpc:'self_join_room',data:{agentid:'agent-from-config',playerid:430511,gameid:'game-from-config',roomcode:'123456',joinmode:0,location:null,ip:store.getState().players.entities[430511].ip}});
assert.equal((sent[2].data as {roomcode:number}).roomcode,900001);
assert.throws(()=>makeCommands(new PlatformStore()).commands.getVipRooms([]),/logged-in/);
});
test('task requests preserve legacy identity fields and reject unclaimable entries',()=>{
const store=makeStore('outside');const {commands,sent}=makeCommands(store);
commands.getPlayerTasks();
store.applyTaskList({tasks:[{taskid:7,title:'任务',award:1,finish:1,total:1,state:1}]});
commands.claimTask(7);
assert.deepEqual(sent,[
{app:'youle',route:'agent',rpc:'get_player_task',data:{agentid:'agent-from-config',playerid:430511,gameid:'game-from-config'}},
{app:'youle',route:'agent',rpc:'get_task_award',data:{agentid:'agent-from-config',playerid:430511,taskid:7}},
]);
store.applyTaskAward({taskid:7,taskstate:0});
assert.equal(store.getState().tasks?.[0].state,2);
assert.equal(store.getState().players.entities[430511].taskstate,0);
assert.throws(()=>commands.claimTask(7),/claimable/);
const before=store.getState();
assert.throws(()=>store.applyTaskList({tasks:[{taskid:7}]}));
assert.equal(store.getState(),before);
assert.throws(()=>makeCommands(new PlatformStore()).commands.getPlayerTasks(),/logged-in/);
});
test('task numeric strings normalize at ingestion and invalid values never commit',()=>{
const store=makeStore('outside');
const task={taskid:'7',title:'任务',award:'3',finish:'2',total:'5',state:'0'};
store.applyTaskList({tasks:[task]});
assert.deepEqual(store.getState().tasks,[{...task,award:3,finish:2,total:5,state:0}]);
assert.equal(task.award,'3');
const before=store.getState();
for(const field of ['award','finish','total','state']){
for(const value of ['', ' ', 'abc', 'Infinity', null, true, [], {}, undefined]){
assert.throws(()=>store.applyTaskList({tasks:[{...task,[field]:value}]}),new RegExp(field));
assert.equal(store.getState(),before);
}
}
assert.throws(()=>store.applyTaskList({tasks:[{...task,state:'1.5'}]}),/state/);
});
test('prepare and exitRoom use the first-slice builders with authoritative config and room state', () => {
const { commands, sent } = makeCommands(makeStore('inside', 0));
@@ -42,7 +42,7 @@ test('applyLoginSuccess commits the complete login once and notifies once', () =
sex: 0,
bean: 0,
ip: '127.0.0.1',
roomcard: 3, taskstate: 1, score: 0, sign: null,
roomcard: 3, taskstate: 1, advanced: 0, score: 0, sign: null,
});
assert.deepEqual(state.room, { kind: 'outside' });
});
@@ -310,6 +310,39 @@ function makeRuntime(config: RuntimeConfig = runtimeConfig(), entry = makeGameEn
return { runtime, wire, scene, clock, factoryConfigs, config };
}
test('VIP wire query response reaches canonical state and clears on login replacement',async()=>{
const {runtime,wire}=makeRuntime();await runtime.start();
wire.emit({type:'open',server:'ws://agent'});runtime.login(ACCOUNT);
const login=()=>wire.emit({type:'message',message:{route:'agent',rpc:'player_login',data:fixture('player-login-success.json')}});
login();runtime.getVipRooms(['123456']);
wire.emit({type:'message',message:{route:'agent',rpc:'getInfoByShortCode',data:{roomInfo:[{shortCode:123456,owner:'房主',playerList:[]}]}}});
assert.equal(runtime.state.vipRooms?.[0].shortCode,123456);
wire.emit({type:'message',message:{route:'agent',rpc:'getInfoByShortCode',data:{roomInfo:[]}}});
assert.deepEqual(runtime.state.vipRooms,[]);
login();assert.equal(runtime.state.vipRooms,undefined);runtime.stop();
});
test('task wire responses reach the store and claim does not invent reward balances',async()=>{
const {runtime,wire}=makeRuntime();await runtime.start();
wire.emit({type:'open',server:'ws://agent'});runtime.login(ACCOUNT);
wire.emit({type:'message',message:{route:'agent',rpc:'player_login',data:fixture('player-login-success.json')}});
runtime.getPlayerTasks();
wire.emit({type:'message',message:{route:'agent',rpc:'get_player_task',data:{tasks:[{taskid:7,title:'任务',award:'3',finish:'1',total:'1',state:'1'}]}}});
assert.equal(runtime.state.tasks?.[0].award,3);
const balance=runtime.state.players.entities[430511].roomcard;
runtime.claimTask(7);
wire.emit({type:'message',message:{route:'agent',rpc:'get_task_award',data:{taskid:'7',taskstate:0}}});
assert.equal(runtime.state.tasks?.[0].state,2);
assert.equal(runtime.state.players.entities[430511].taskstate,0);
assert.equal(runtime.state.players.entities[430511].roomcard,balance);
wire.emit({type:'message',message:{route:'agent',rpc:'update_roomcard',data:{roomcard:25}}});
assert.equal(runtime.state.players.entities[430511].roomcard,25);
wire.emit({type:'message',message:{route:'agent',rpc:'player_finish_task',data:{state:1}}});
assert.equal(runtime.state.players.entities[430511].taskstate,1);
wire.emit({type:'slow'});assert.throws(()=>runtime.getPlayerTasks(),/logged-in/);
runtime.stop();
});
test('UI observes canonical commits and unsubscribe is idempotent', async () => {
const { runtime, wire } = makeRuntime();
const seen: unknown[] = [];