MatchMaster/tools/test-home-activity-entry-layout.cjs
2026-09-24 19:33:11 +08:00

66 lines
4.6 KiB
JavaScript

const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'),ts=require('typescript');
const left=['shop','yicon','redeemBtn','passBtn','jungle','xinshou','chengxu','cloudRise'];
const right=['rank','gacha','day','hammer'];
function fixture(){
const exports={};
vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/HomeActivityEntryLayout.ts','utf8'),{
compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017}}).outputText,{exports});
const a=JSON.parse(fs.readFileSync('assets/Scene/HomeScene.fire')),nodes=new Map();let writes=0;
for(let i=0;i<a.length;i++)if(a[i].__type__==='cc.Node'){
const n=a[i];nodes.set(i,{name:n._name,active:n._active,x:n._trs.array[0],y:n._trs.array[1],
width:n._contentSize.width,height:n._contentSize.height,anchorY:n._anchorPoint.y,scaleY:n._trs.array[8],children:[],
getChildByName(name){return this.children.find(n=>n.name===name);},setPosition(x,y){this.x=x;this.y=y;writes++;}});
}
for(const [i,n]of nodes){n.children=a[i]._children.map(r=>nodes.get(r.__id__));n.parent=nodes.get(a[i]._parent?.__id__);}
const top=[...nodes.values()].find(n=>n.name==='Top'&&n.parent.name==='Load');
const gift=top.getChildByName('xinshou');
const frame=JSON.parse(fs.readFileSync('assets/UI/UI/start/startUI2.plist.meta')).subMetas['cloudRise.png'];
const cloud={...gift,name:'cloudRise',width:frame.rawWidth,height:frame.rawHeight,children:gift.children.filter(n=>['New Sprite','time'].includes(n.name))};
top.children.push(cloud);
const layout=exports.default;
function box(n){const b=layout.extent(n),scale=top.parent.scaleY;return {top:n.y+b.top*scale,bottom:n.y+b.bottom*scale};}
function check(){
const all=[...left,...right].map(n=>top.getChildByName(n)).filter(n=>n.active);
for(let i=0;i<all.length;i++)for(let j=i+1;j<all.length;j++){
const p=all[i],q=all[j];if(Math.abs(p.x-q.x)>300)continue;
const a=box(p),b=box(q);assert(a.bottom>=b.top+15.99||b.bottom>=a.top+15.99,`${p.name} overlaps ${q.name}`);
}
const footer=top.parent.getChildByName('shezhiBtn'),bound=footer.y+footer.height*(1-footer.anchorY)*top.parent.scaleY-top.y;
for(const n of all)assert(box(n).bottom>=bound+15.99,`${n.name} covers footer: ${box(n).bottom} < ${bound+16}`);
}
return {layout,top,check,get writes(){return writes;}};
}
test('every visibility combination fits without overlap, including both columns and bottom controls',()=>{
const f=fixture();
for(const scale of [1,0.85]){
f.top.parent.scaleY=scale;
for(let mask=0;mask<4096;mask++){
[...left,...right].forEach((name,i)=>f.top.getChildByName(name).active=!!(mask&(1<<i)));
f.layout.refresh(f.top);f.check();
}
}
});
test('benefits and cloudRise occupy distinct slots and async hide/show immediately compacts the layout',()=>{
const f=fixture();[...left,...right].forEach(name=>f.top.getChildByName(name).active=name!=='redeemBtn');
f.layout.refresh(f.top);f.check();
const benefit=f.top.getChildByName('chengxu'),cloud=f.top.getChildByName('cloudRise');
assert.notDeepEqual([benefit.x,benefit.y],[cloud.x,cloud.y]);
assert.equal(cloud.x,f.top.getChildByName('rank').x,'overflow uses the free right slot');
const old={x:benefit.x,y:benefit.y};benefit.active=false;f.layout.refresh(f.top);f.check();
// The countdown makes CloudRise taller than benefits; only reclaim a slot if the whole entry fits.
f.top.getChildByName('xinshou').active=false;f.layout.refresh(f.top);f.check();
assert.equal(cloud.x,old.x);assert(cloud.y>old.y);
const writes=f.writes;for(let i=0;i<20;i++)f.layout.refresh(f.top);assert.equal(f.writes,writes,'unchanged frames do not reposition buttons');
benefit.active=true;f.layout.refresh(f.top);f.check();
});
test('home lays out late in the frame after asynchronous entry visibility changes',()=>{
const source=fs.readFileSync('assets/Script/HomeActivitySchedule.ts','utf8');let arranged;
const exports={},cc={Component:class{},_decorator:{ccclass:c=>c}};
vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017,experimentalDecorators:true}}).outputText,
{exports,cc,require:name=>({default:name.endsWith('HomeActivityEntryLayout')?{refresh:top=>arranged=top}:{}})});
const f=fixture(),home=new exports.default();home.node={getChildByName:()=>f.top.parent};home.lateUpdate();assert.equal(arranged,f.top);
assert(!fs.readFileSync('assets/Script/CloudRiseView.ts','utf8').includes('private layoutEntry()'),'CloudRise no longer writes competing positions');
assert(!fs.readFileSync('assets/Script/JiaZai.ts','utf8').includes('benefitsEntryOffset'),'benefits no longer follows a separate layout');
});