Files
MatchMaster/tools/test-pyramid-entry.cjs
T
2026-10-12 15:00:49 +08:00

51 lines
3.8 KiB
JavaScript

const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'),ts=require('typescript');
function fixture(){
let period=null,state=null,valid=true,now=1000,closed=0;
const cc={_decorator:{ccclass:c=>c},Component:class{},Sprite:class{},fx:{GameConfig:{GM_INFO:{level:50}}}};
const service={data:()=>state,now:()=>now,prefab:()=>Promise.resolve()};
const schedule={active:()=>period,hasSchedule:()=>valid,now:()=>now};
const context={exports:{},cc,require:name=>({default:name.endsWith('PyramidService')?service:name.endsWith('ActivityScheduleService')?schedule:{}})};
vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/PyramidView.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017,experimentalDecorators:true}}).outputText,context);
const view=new context.exports.default();view.entry={active:false,getComponent:()=>({}),getChildByName:()=>null};view.node={getComponent:()=>null};
view.closeOverlay=()=>closed++;
return {view,cc,service,setPeriod:p=>period=p,setState:s=>state=s,setValid:v=>valid=v,setNow:n=>now=n,get closed(){return closed;}};
}
test('home updates do not poll pyramid; entering or returning requests one recovery check',()=>{
const f=fixture();let checks=0;
f.view.syncPaymentLoading=()=>{};f.view.check=()=>{checks++;};
f.view.update();assert.equal(checks,1);
for(let i=0;i<120;i++){f.setNow(1000+i*30000);f.view.update();}
assert.equal(checks,1);
f.view.wake();f.service.busy=true;f.view.update();assert.equal(checks,1);
f.service.busy=false;f.view.update();assert.equal(checks,2);
f.view.update();assert.equal(checks,2);
});
test('entry visibility comes only from shared schedule and the completed-level threshold',()=>{
const f=fixture();f.setState({status:'active',endAt:5000,completed:false});f.view.render();assert.equal(f.view.entry.active,false);
f.setPeriod({unlockLevel:50});f.setState(null);f.cc.fx.GameConfig.GM_INFO.level=49;f.view.render();assert.equal(f.view.entry.active,false);
f.cc.fx.GameConfig.GM_INFO.level=50;f.view.render();assert.equal(f.view.entry.active,true);
f.setPeriod(null);f.view.render();assert.equal(f.view.entry.active,false);
});
test('private completed state hides the same period without closing the last reward animation; expiry closes it',()=>{
const f=fixture();f.setPeriod({periodId:'p',unlockLevel:50});f.setState({periodId:'p',status:'active',endAt:5000,completed:true});f.view.render();assert.equal(f.view.entry.active,false);assert.equal(f.closed,0);
f.setPeriod({periodId:'next',unlockLevel:50});f.view.render();assert.equal(f.view.entry.active,true);
f.setNow(5000);f.view.render();assert.equal(f.closed,1);
});
test('in-flight schedule invalidation does not close an active panel',()=>{
const f=fixture();f.setState({status:'active',endAt:5000});f.setValid(false);f.view.render();assert.equal(f.closed,0);
});
test('badge follows eligible undelivered rewards in the current period only',()=>{
const f=fixture(),badge={active:false};
f.view.entry.getChildByName=name=>name==='red'?badge:null;
f.setPeriod({periodId:'p',unlockLevel:50});
const state={periodId:'p',status:'active',endAt:5000,completed:false,pendingGrants:[],levels:[{rewards:[{gain:false,deliveryStatus:'not_eligible'}]}]};
f.setState(state);f.view.render();assert.equal(badge.active,false);
state.levels[0].rewards[0].deliveryStatus='unclaimed';f.view.render();assert.equal(badge.active,true);
state.levels[0].rewards[0].deliveryStatus='pending';f.view.render();assert.equal(badge.active,true);
state.levels[0].rewards[0].deliveryStatus='delivered';f.view.render();assert.equal(badge.active,false);
state.pendingGrants=[{grantId:'g'}];f.view.render();assert.equal(badge.active,true);
f.setPeriod({periodId:'next',unlockLevel:50});f.view.render();assert.equal(badge.active,false);
f.setPeriod(null);f.view.render();assert.equal(badge.active,false);
});