429 lines
29 KiB
JavaScript
429 lines
29 KiB
JavaScript
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const fs = require('node:fs');
|
||
const path = require('node:path');
|
||
const vm = require('node:vm');
|
||
const { stripTypeScriptTypes } = require('node:module');
|
||
const root = path.resolve(__dirname, '..');
|
||
const prefab = JSON.parse(fs.readFileSync(path.join(root, 'assets/action_bundle/prefab/dailyQuests.prefab'), 'utf8'));
|
||
const source = fs.readFileSync(path.join(root, 'assets/action_bundle/script/DailyQuests.ts'), 'utf8')
|
||
.replace(/^import .*;\r?$/gm, '').replace(/^\s*@(?:ccclass|property[^\r\n]*)\r?$/gm, '')
|
||
.replace('export default class DailyQuests', 'class DailyQuests');
|
||
const trackerSource = fs.readFileSync(path.join(root, 'assets/Script/module/Task/DailyTaskProgressTracker.ts'), 'utf8')
|
||
.replace(/^export type /gm, 'type ').replace('export default class', 'class');
|
||
const code = stripTypeScriptTypes(trackerSource + '\n' + source) + '\nglobalThis.DailyQuests = DailyQuests;';
|
||
class Node {
|
||
constructor(o) {
|
||
this.name = o._name; this.active = o._active; this.width = o._contentSize.width; this.height = o._contentSize.height;
|
||
this.x = o._trs.array[0]; this.y = o._trs.array[1]; this.scaleX = o._trs.array[7]; this.scaleY = o._trs.array[8];
|
||
this.angle = o._eulerAngles.z; this.anchorX = o._anchorPoint.x; this.anchorY = o._anchorPoint.y;
|
||
this.children = []; this.components = {};
|
||
}
|
||
set scale(value) { this.scaleX=value; this.scaleY=value; }
|
||
get scale() {return this.scaleX;}
|
||
get activeInHierarchy() { return !this.destroyed && this.active && (!this.parent || this.parent.activeInHierarchy); }
|
||
on(type,handler,target,capture) { (this.listeners ||= []).push({type,handler,target,capture}); }
|
||
off(type,handler,target,capture) {this.listeners=this.listeners.filter(item=>item.handler!==handler || item.capture!==capture);}
|
||
setContentSize(width,height) {this.width=width;this.height=height;}
|
||
getBoundingBoxToWorld() {
|
||
let x=this.x,y=this.y,parent=this.parent;
|
||
while(parent){x=x*parent.scaleX+parent.x;y=y*(parent.scaleY || 1)+parent.y;parent=parent.parent;}
|
||
return {contains:point=>Math.abs(point.x-x)<=this.width*this.scaleX/2 && Math.abs(point.y-y)<=this.height*(this.scaleY||1)/2};
|
||
}
|
||
getChildByName(name) { return this.children.find(node => node.name === name); }
|
||
getComponent(type) { return this.components[type] || null; }
|
||
addChild(node) { assert.ok(node, 'addChild must receive a node'); this.children.push(node); }
|
||
setSiblingIndex() {}
|
||
setPosition(x, y) { this.x = x; this.y = y; }
|
||
}
|
||
function build(id) {
|
||
const o = prefab[id], node = new Node(o);
|
||
for (const ref of o._components) {
|
||
const component=structuredClone(prefab[ref.__id__]);
|
||
if(component.clickEvents) component.clickEvents=component.clickEvents.map(event=>event.__id__ == null ? event : structuredClone(prefab[event.__id__]));
|
||
node.components[component.__type__]=component;
|
||
}
|
||
if (node.components['cc.Layout']) node.components['cc.Layout'].updateLayout = () => {
|
||
const active=node.children.filter(child=>child.active);
|
||
node.width=active.reduce((sum,child)=>sum+child.width*child.scaleX,0)+Math.max(0,active.length-1)*(node.components['cc.Layout'].spacingX ?? node.components['cc.Layout']['_N$spacingX']);
|
||
};
|
||
node.children = o._children.map(ref => build(ref.__id__)); node.children.forEach(child=>child.parent=node); return node;
|
||
}
|
||
function clone(node) {
|
||
const copy = Object.assign(Object.create(Node.prototype), node);
|
||
copy.components = Object.fromEntries(Object.entries(node.components).map(([key, value]) => [key, { ...value }])); copy.children = node.children.map(clone); return copy;
|
||
}
|
||
function setup(stars = 15) {
|
||
const requests = [], toasts = [], tweens = [];
|
||
// Advance animation time deterministically, including the gap between loops.
|
||
// Only started tweens run; stopping one prevents any later property changes.
|
||
class Tween {
|
||
constructor(target) { this.target = target; this.steps = []; this.index = 0; this.elapsed = 0; this.cancelled = false; }
|
||
to(duration, properties, options = {}) { this.steps.push({ duration, properties, options }); return this; }
|
||
delay(duration) { this.steps.push({ duration }); return this; }
|
||
repeatForever(tween) { this.steps.push(...tween.steps); this.repeating = true; return this; }
|
||
start() { tweens.push(this); return this; }
|
||
stop() { this.cancelled = true; return this; }
|
||
advance(seconds) {
|
||
while (seconds > 1e-9 && !this.cancelled && this.index < this.steps.length) {
|
||
const step = this.steps[this.index];
|
||
this.initial ||= Object.fromEntries(Object.keys(step.properties || {}).map(key => [key, this.target[key]]));
|
||
const consumed = Math.min(seconds, step.duration - this.elapsed);
|
||
this.elapsed += consumed; seconds -= consumed;
|
||
const progress = Math.min(1, this.elapsed / step.duration);
|
||
const eased = step.options?.easing === 'sineInOut' ? (1 - Math.cos(Math.PI * progress)) / 2 : progress;
|
||
for (const [key, end] of Object.entries(step.properties || {})) this.target[key] = this.initial[key] + (end - this.initial[key]) * eased;
|
||
if (this.elapsed + 1e-9 < step.duration) return;
|
||
if (step.properties) Object.assign(this.target, step.properties);
|
||
this.index++; this.elapsed = 0; this.initial = null;
|
||
if (this.repeating && this.index === this.steps.length) this.index = 0;
|
||
}
|
||
}
|
||
static stopAllByTarget(target) { tweens.filter(tween => tween.target === target).forEach(tween => tween.stop()); }
|
||
}
|
||
const cc = { Component: class {}, Node, Tween, tween: target => new Tween(target), Label: {toString:()=> 'cc.Label',Overflow:{SHRINK:2}}, RichText: 'cc.RichText', Sprite: {toString:()=> 'cc.Sprite',Type:{SLICED:1},SizeMode:{CUSTOM:0,TRIMMED:1}},
|
||
ProgressBar: 'cc.ProgressBar', Button: 'cc.Button', Layout: 'cc.Layout', _decorator: {}, Camera:{findCamera:()=>null},
|
||
isValid: node => !!node && !node.destroyed, instantiate: clone, color: (r,g,b) => ({r,g,b}), error: message => { throw Error(message); },
|
||
fx: { GameConfig: { GM_INFO: {} } } };
|
||
cc.Node.EventType={TOUCH_START:'touchstart'};
|
||
cc.find=()=>null;
|
||
const context = { cc, Date, console, setTimeout, Utils: { getDailyQuestReward: (id, callback, scope) => requests.push([id, response => {
|
||
if (response.code === 1) cc.fx.GameConfig.GM_INFO.tasks = response.data.task;
|
||
callback(response);
|
||
}, scope]) },
|
||
MiniGameSdk: { API: { showToast: text => toasts.push(text) } } };
|
||
cc.fx.GameTool = {getDailyTaskServerTime: () => Date.now(), claimDailyQuestReward: context.Utils.getDailyQuestReward};
|
||
cc.fx.GameTool.getCachedDailyQuests = () => null;
|
||
vm.runInNewContext(code, context);
|
||
const daily = new context.DailyQuests(); daily.node = build(1);
|
||
daily.content = daily.node.getChildByName('bg').getChildByName('content'); daily.touchStart = true;
|
||
daily.rewardIcons = ['xinCoin', 'ice', 'hammer', 'magic', 'skyLine'].map(name => {
|
||
const meta = JSON.parse(fs.readFileSync(path.join(root, `assets/res/public/${name}.png.meta`), 'utf8')).subMetas[name];
|
||
return { getRect: () => ({ width: meta.width, height: meta.height }) };
|
||
});
|
||
daily.openLoad = () => {}; daily.closeLoad = () => {};
|
||
// Reward presentation phases are covered by test-daily-reward-animation.cjs.
|
||
daily.playRewardAnimation = (rewards, complete) => complete();
|
||
daily.scheduleOnce = callback => { daily.timeout = callback; };
|
||
daily.unschedule = callback => { if (daily.timeout === callback) daily.timeout = null; };
|
||
const data = { version: 2, weeklyStars: stars, dailyTasks: [], weeklyRewards: [35,70,105,140].map(target => ({
|
||
rewardId: `weekly_${target}`, target, state: stars >= target ? 1 : 0,
|
||
rewards: target === 140 ? [{type:'infinite_rocket',count:900},{type:'infinite_hourglass',count:900},{type:'coin',count:1500}]
|
||
: [{type:'hammer',count:1},{type:'infinite_health',count:900}]
|
||
})) };
|
||
cc.fx.GameConfig.GM_INFO.tasks = data;
|
||
const weekly = daily.node.getChildByName('bg').getChildByName('weeklyRewards');
|
||
const preview = weekly.getChildByName('preview');
|
||
daily.onLoad();daily.renderWeeklyRewards(data);
|
||
return {daily,data,weekly,preview,requests,toasts,cc,tweens,
|
||
advance(seconds) { tweens.forEach(tween => tween.advance(seconds)); } };
|
||
}
|
||
|
||
test('weekly chests pivot at top center without changing their resting visual bounds', () => {
|
||
const {weekly}=setup(0),sizes=[78,90,100,110];
|
||
weekly.getChildByName('tiers').children.forEach((tier,index)=>{
|
||
const chest=tier.getChildByName('chest');
|
||
assert.equal(chest.anchorX,0.5);assert.equal(chest.anchorY,1);assert.equal(chest.angle,0);
|
||
assert.equal(chest.width,sizes[index]);assert.equal(chest.height,sizes[index]);
|
||
assert.equal(chest.scaleX,1);assert.equal(chest.scaleY,1);
|
||
// Before moving the anchor, each centered icon was positioned at (0, 10).
|
||
const left=chest.x-chest.anchorX*chest.width*chest.scaleX;
|
||
const bottom=chest.y-chest.anchorY*chest.height*chest.scaleY;
|
||
assert.equal(left,-sizes[index]/2);assert.equal(bottom,10-sizes[index]/2);
|
||
assert.equal(bottom+chest.height*chest.scaleY,10+sizes[index]/2);
|
||
});
|
||
});
|
||
|
||
test('only ready chest icons swing gently left and right, with two seconds at rest between loops', () => {
|
||
const h=setup(0),tiers=h.weekly.getChildByName('tiers').children;
|
||
h.data.weeklyRewards.forEach((reward,index)=>reward.state=[0,1,2,1][index]);
|
||
h.daily.renderWeeklyRewards(h.data);
|
||
const chests=tiers.map(tier=>tier.getChildByName('chest'));
|
||
const stationary=tiers.flatMap(tier=>[tier,tier.getChildByName('target'),tier.getChildByName('claimable'),tier.getChildByName('claimed')]);
|
||
const transforms=stationary.map(node=>[node.x,node.y,node.angle,node.scaleX,node.scaleY]);
|
||
assert.deepEqual(h.tweens.map(tween=>tween.target),[chests[1],chests[3]]);
|
||
for(let loop=0;loop<3;loop++){
|
||
h.advance(0.06);assert.ok(chests[1].angle>0 && chests[1].angle<6);
|
||
h.advance(0.06);assert.equal(chests[1].angle,6);
|
||
h.advance(0.24);assert.equal(chests[1].angle,-6);
|
||
h.advance(0.12);assert.equal(chests[1].angle,0);
|
||
h.advance(1);assert.equal(chests[1].angle,0);
|
||
h.advance(0.99);assert.equal(chests[1].angle,0);
|
||
h.advance(0.01);assert.equal(chests[1].angle,0);
|
||
assert.equal(chests[0].angle,0);assert.equal(chests[2].angle,0);
|
||
assert.deepEqual(stationary.map(node=>[node.x,node.y,node.angle,node.scaleX,node.scaleY]),transforms);
|
||
}
|
||
h.advance(0.12);assert.equal(chests[1].angle,6);assert.equal(chests[3].angle,6);
|
||
assert.equal(h.tweens.length,2,'loops reuse the original tweens');
|
||
assert.deepEqual(h.data.weeklyRewards.map(reward=>reward.state),[0,1,2,1]);
|
||
});
|
||
|
||
test('repeated rendering keeps the current swing and pause instead of restarting or stacking tweens', () => {
|
||
const h=setup(35),chest=h.weekly.getChildByName('tiers').children[0].getChildByName('chest');
|
||
const tween=h.tweens[0];
|
||
h.advance(0.06);const angle=chest.angle;
|
||
for(let i=0;i<3;i++) h.daily.renderWeeklyRewards(h.data);
|
||
assert.equal(h.tweens.length,1);assert.equal(h.tweens[0],tween);assert.equal(chest.angle,angle);
|
||
h.advance(0.06);assert.equal(chest.angle,6);
|
||
h.advance(0.36);assert.equal(chest.angle,0);
|
||
h.advance(1);h.daily.renderWeeklyRewards(h.data);
|
||
h.advance(0.99);assert.equal(chest.angle,0);
|
||
h.advance(0.07);assert.ok(chest.angle>0 && chest.angle<6);
|
||
assert.equal(h.tweens.length,1);
|
||
});
|
||
|
||
for(const state of [0,2]) test(`changing a ready reward to state ${state} stops its swing and restores the authored angle`, () => {
|
||
const h=setup(0),chest=h.weekly.getChildByName('tiers').children[0].getChildByName('chest');
|
||
chest.angle=11;h.data.weeklyRewards[0].state=1;h.daily.renderWeeklyRewards(h.data);
|
||
h.advance(0.12);assert.equal(chest.angle,17);
|
||
h.data.weeklyRewards[0].state=state;h.daily.renderWeeklyRewards(h.data);
|
||
assert.equal(chest.angle,11);assert.equal(h.tweens[0].cancelled,true);
|
||
h.advance(10);assert.equal(chest.angle,11);
|
||
assert.equal(h.daily.weeklyChestAnimations.length,0);
|
||
h.data.weeklyRewards[0].state=1;h.daily.renderWeeklyRewards(h.data);
|
||
h.advance(0.12);assert.equal(chest.angle,17);assert.equal(h.tweens.length,2);
|
||
});
|
||
|
||
test('removing one reward stops only its icon, and empty weekly data stops the remaining loops', () => {
|
||
const h=setup(140),tiers=h.weekly.getChildByName('tiers').children;
|
||
const chests=tiers.map(tier=>tier.getChildByName('chest'));
|
||
h.advance(0.12);h.data.weeklyRewards.shift();h.daily.renderWeeklyRewards(h.data);
|
||
assert.equal(tiers[0].active,false);assert.equal(chests[0].angle,0);assert.equal(h.tweens[0].cancelled,true);
|
||
assert.equal(h.daily.weeklyChestAnimations.length,3);
|
||
h.advance(0.24);assert.equal(chests[0].angle,0);assert.equal(chests[1].angle,-6);
|
||
h.data.weeklyRewards=[];h.daily.renderWeeklyRewards(h.data);
|
||
assert.equal(h.weekly.active,false);assert.equal(h.daily.weeklyChestAnimations.length,0);
|
||
assert.ok(h.tweens.every(tween=>tween.cancelled));
|
||
h.advance(10);assert.ok(chests.every(chest=>chest.angle===0));
|
||
});
|
||
|
||
test('a removed weekly section releases all running chest animations', () => {
|
||
const h=setup(140),chests=h.weekly.getChildByName('tiers').children.map(tier=>tier.getChildByName('chest'));
|
||
h.advance(0.12);
|
||
h.weekly.parent.children=h.weekly.parent.children.filter(node=>node!==h.weekly);
|
||
h.daily.renderWeeklyRewards(h.data);
|
||
assert.ok(h.tweens.every(tween=>tween.cancelled));assert.equal(h.daily.weeklyChestAnimations.length,0);
|
||
h.advance(10);assert.ok(chests.every(chest=>chest.angle===0));
|
||
});
|
||
|
||
test('disabling a panel restores its icons, inactive rendering stays stopped, and reopening starts fresh loops', () => {
|
||
const h=setup(140),chests=h.weekly.getChildByName('tiers').children.map(tier=>tier.getChildByName('chest'));
|
||
h.advance(0.12);h.daily.node.active=false;h.daily.onDisable();h.daily.onDisable();
|
||
assert.ok(h.tweens.every(tween=>tween.cancelled));assert.equal(h.daily.weeklyChestAnimations.length,0);
|
||
h.daily.renderWeeklyRewards(h.data);h.advance(10);
|
||
assert.equal(h.tweens.length,4);assert.ok(chests.every(chest=>chest.angle===0));
|
||
h.daily.node.active=true;h.daily.renderWeeklyRewards(h.data);h.daily.renderWeeklyRewards(h.data);
|
||
assert.equal(h.tweens.length,8);assert.equal(h.daily.weeklyChestAnimations.length,4);
|
||
h.advance(0.12);assert.ok(chests.every(chest=>chest.angle===6));
|
||
});
|
||
|
||
test('destroying a panel stops its loops without cancelling unrelated tweens on the same icon', () => {
|
||
const h=setup(35),chest=h.weekly.getChildByName('tiers').children[0].getChildByName('chest');
|
||
const unrelated=h.cc.tween(chest).to(1,{opacity:100}).start();
|
||
h.advance(0.12);h.daily.onDestroy();h.daily.onDestroy();
|
||
assert.equal(h.tweens[0].cancelled,true);assert.equal(unrelated.cancelled,false);
|
||
assert.equal(h.daily.weeklyChestAnimations.length,0);assert.equal(chest.angle,0);
|
||
h.advance(1);assert.equal(chest.angle,0);assert.equal(chest.opacity,100);
|
||
});
|
||
test('weekly prefab references, nine-slice background and check assets are valid', () => {
|
||
function walk(value) {
|
||
if (!value || typeof value !== 'object') return;
|
||
if ('__id__' in value) assert.ok(prefab[value.__id__]);
|
||
Object.values(value).forEach(walk);
|
||
}
|
||
walk(prefab);
|
||
const {weekly,preview}=setup();
|
||
assert.equal(preview.getComponent('cc.Sprite')._type,1);
|
||
const pop=JSON.parse(fs.readFileSync(path.join(root,'assets/action_bundle/img/dailyQuest/pop.png.meta'),'utf8'));
|
||
for (const key of ['borderLeft','borderRight','borderTop','borderBottom']) assert.equal(pop.subMetas.pop[key],28);
|
||
assert.equal(preview.getChildByName('close'),undefined);
|
||
assert.equal(preview.getChildByName('btnClaim'),undefined);
|
||
const meta=JSON.parse(fs.readFileSync(path.join(root,'assets/action_bundle/img/dailyQuest/dagou.png.meta'),'utf8'));
|
||
for(const tier of weekly.getChildByName('tiers').children){
|
||
const check=tier.getChildByName('claimed');assert.ok(check.width>0 && check.width<=tier.getChildByName('chest').width);assert.ok(check.height>0 && check.height<=tier.getChildByName('chest').height);
|
||
assert.equal(check.getComponent('cc.Sprite')._spriteFrame.__uuid__,meta.subMetas.dagou.uuid);
|
||
assert.equal(tier.getComponent('cc.Button').clickEvents[0].handler,'selectWeeklyReward');
|
||
}
|
||
});
|
||
test('weekly progress and total do not create rows in the daily list',()=>{
|
||
const {daily,weekly,preview}=setup(15);
|
||
assert.equal(weekly.getChildByName('progress').getComponent('cc.ProgressBar').progress,15/140);
|
||
assert.equal(weekly.getChildByName('current').getChildByName('count').getComponent('cc.Label').string,'15');
|
||
assert.equal(daily.content.children.length,1);assert.equal(preview.active,false);
|
||
});
|
||
test('clicking an unreached chest only shows its rewards',()=>{
|
||
const {daily,weekly,preview,requests}=setup(15);daily.selectWeeklyReward(null,'weekly_35');
|
||
assert.equal(preview.active,true);assert.equal(weekly.getChildByName('previewDismiss'),undefined);
|
||
const items=preview.getChildByName('rewards').children.filter(node=>node.active);
|
||
assert.equal(items.length,2);assert.ok(items.every(node=>!node.getChildByName('stars').active));
|
||
assert.equal(items[1].getChildByName('count').getComponent('cc.Label').string,'15分钟');
|
||
assert.equal(requests.length,0);
|
||
});
|
||
test('clicking a ready chest claims immediately and adds the check only after success',()=>{
|
||
const {daily,data,weekly,preview,requests,cc,tweens,advance}=setup(105);
|
||
const tier=weekly.getChildByName('tiers').getChildByName('weekly_105');
|
||
const check=tier.getChildByName('claimed'),chest=tier.getChildByName('chest');
|
||
const click=tier.getComponent('cc.Button').clickEvents[0];
|
||
advance(0.12);assert.equal(chest.angle,6);assert.equal(tier.angle,0);assert.equal(check.angle,0);
|
||
let granted=0;
|
||
daily.openReward=(id,rewards)=>{assert.equal(id,'weekly_105');assert.equal(rewards.length,2);granted++;};
|
||
daily[click.handler](null,click.customEventData);daily[click.handler](null,click.customEventData);
|
||
assert.equal(requests.length,1);assert.equal(requests[0][0],'weekly_105');assert.equal(requests[0][2],'weekly');
|
||
assert.equal(preview.active,false);assert.equal(check.active,false);
|
||
const result=structuredClone(data);result.weeklyRewards[2].state=2;
|
||
requests[0][1]({code:1,data:{rewards:result.weeklyRewards[2].rewards,task:result}});
|
||
assert.equal(granted,1);assert.equal(check.active,true);assert.equal(daily.touchStart,true);
|
||
assert.equal(chest.angle,0);assert.equal(tweens.find(tween=>tween.target===chest).cancelled,true);
|
||
advance(2.6);assert.equal(chest.angle,0);assert.equal(check.angle,0);
|
||
assert.equal(daily.weeklyChestAnimations.length,2,'other claimable rewards keep swinging');
|
||
daily.selectWeeklyReward(null,'weekly_105');assert.equal(requests.length,1);assert.equal(preview.active,true);
|
||
});
|
||
test('clicking an already claimed chest only previews rewards',()=>{
|
||
const {daily,data,weekly,preview,requests}=setup(140);data.weeklyRewards[3].state=2;daily.renderWeeklyRewards(data);
|
||
daily.selectWeeklyReward(null,'weekly_140');
|
||
assert.equal(preview.active,true);assert.equal(requests.length,0);
|
||
assert.equal(weekly.getChildByName('tiers').getChildByName('weekly_140').getChildByName('claimed').active,true);
|
||
assert.equal(preview.getChildByName('rewards').children.filter(node=>node.active).length,3);
|
||
});
|
||
test('capture listener closes outside touches without a dismiss overlay and preserves inside touches',()=>{
|
||
const {daily,weekly,preview}=setup(0);daily.selectWeeklyReward(null,'weekly_35');
|
||
const listener=daily.node.listeners[0];assert.equal(listener.capture,true);assert.equal(listener.type,'touchstart');
|
||
let stopped=0;
|
||
const inside={getLocation:()=>({x:daily.node.x+preview.x,y:daily.node.y+weekly.y+preview.y}),stopPropagation:()=>stopped++};
|
||
listener.handler.call(listener.target,inside);assert.equal(preview.active,true);assert.equal(stopped,0);
|
||
const outside={getLocation:()=>({x:daily.node.x-500,y:daily.node.y+600}),stopPropagation:()=>stopped++};
|
||
listener.handler.call(listener.target,outside);assert.equal(preview.active,false);assert.equal(stopped,1);
|
||
daily.selectWeeklyReward(null,'weekly_70');assert.equal(preview.active,true);
|
||
daily.onDestroy();assert.equal(daily.node.listeners.length,0);
|
||
});
|
||
|
||
test('failed requests allow retry and slow requests do not mark a chest claimed',()=>{
|
||
const {daily,weekly,requests,toasts,tweens,advance}=setup(35);daily.selectWeeklyReward(null,'weekly_35');
|
||
requests[0][1]({code:0,msg:'领取失败'});
|
||
const check=weekly.getChildByName('tiers').getChildByName('weekly_35').getChildByName('claimed');
|
||
assert.equal(check.active,false);assert.equal(daily.touchStart,true);assert.deepEqual(toasts,['领取失败']);
|
||
daily.selectWeeklyReward(null,'weekly_35');assert.equal(requests.length,2);daily.timeout();
|
||
assert.equal(daily.touchStart,true);assert.equal(check.active,false);
|
||
assert.equal(tweens.length,1);assert.equal(tweens[0].cancelled,false);
|
||
advance(0.12);assert.equal(weekly.getChildByName('tiers').getChildByName('weekly_35').getChildByName('chest').angle,6);
|
||
});
|
||
test('edge previews stay inside the panel and reuse editor-created reward nodes',()=>{
|
||
const {daily,weekly,preview,cc}=setup(0);const items=preview.getChildByName('rewards').children,original=items.slice();
|
||
cc.instantiate=()=>{throw Error('weekly rewards must not instantiate nodes');};
|
||
const tier=weekly.getChildByName('tiers').children[0];tier.x=-120;daily.renderWeeklyRewards(cc.fx.GameConfig.GM_INFO.tasks);assert.equal(tier.x,-120);
|
||
daily.selectWeeklyReward(null,'weekly_140');assert.equal(preview.width,508);assert.equal(preview.x,136);assert.equal(preview.getChildByName('arrow').x,204);
|
||
assert.equal(items.filter(node=>node.active).length,3);
|
||
daily.selectWeeklyReward(null,'weekly_35');assert.equal(items.filter(node=>node.active).length,2);
|
||
items.forEach((node,index)=>assert.equal(node,original[index]));
|
||
});
|
||
test('progress remains bounded and empty weekly data hides the section',()=>{
|
||
const {daily,data,weekly}=setup(0);assert.equal(weekly.getChildByName('progress').getComponent('cc.ProgressBar').progress,0);
|
||
data.weeklyStars=999;daily.renderWeeklyRewards(data);assert.equal(weekly.getChildByName('progress').getComponent('cc.ProgressBar').progress,1);
|
||
data.weeklyRewards=[];daily.renderWeeklyRewards(data);assert.equal(weekly.active,false);
|
||
});
|
||
|
||
test('five weekly rewards reuse editor slots in centered rows with a fixed arrow tip',()=>{
|
||
const {daily,data,preview,cc}=setup(0);
|
||
const rewards=[{type:'coin',count:2000},{type:'infinite_health',count:3600},{type:'freeze',count:1},{type:'magic_wand',count:1},{type:'hammer',count:1}];
|
||
data.weeklyRewards[3].rewards=rewards;
|
||
cc.instantiate=()=>{throw Error('preview must not create reward nodes');};
|
||
daily.selectWeeklyReward(null,'weekly_140');
|
||
const list=preview.getChildByName('rewards'),slots=list.children.filter(n=>n.active);
|
||
assert.equal(slots.length,5);assert.equal(preview.width,508);assert.equal(preview.height,360);
|
||
assert.deepEqual(slots.map(n=>[n.x,n.y]),[[-164,90],[0,90],[164,90],[-82,-90],[82,-90]]);
|
||
assert.equal(preview.y+preview.getChildByName('arrow').y,25);
|
||
assert.deepEqual(slots.map(n=>n.getChildByName('count').getComponent('cc.Label').string),['2000','60分钟','×1','×1','×1']);
|
||
daily.selectWeeklyReward(null,'weekly_35');
|
||
assert.equal(preview.height,180);assert.equal(preview.y,124);assert.equal(list.children.filter(n=>n.active).length,2);
|
||
});
|
||
|
||
test('daily reward icons match the coin height without stretching or resizing the fish can',()=>{
|
||
const {daily,cc}=setup(0);
|
||
const list=daily.content.getChildByName('taskItem').getChildByName('rewards');
|
||
const stars=list.children[0].getChildByName('stars');
|
||
const originalStars=[stars.width,stars.height,stars.scaleX,stars.scaleY];
|
||
const coinRect=daily.rewardIcons[0].getRect();
|
||
for(const [index,type] of ['coin','freeze','hammer','magic_wand','infinite_health'].entries()){
|
||
// Reuse the same node, including after the much larger heart/wand assets.
|
||
daily.renderTaskRewards(list,{stars:2,rewards:[{type,count:1}]});
|
||
const icon=list.children[1].getChildByName('icon'),rect=daily.rewardIcons[index].getRect();
|
||
assert.ok(Math.abs(icon.height*icon.scaleY-coinRect.height)<1e-8,`${type} matches the outer coin height`);
|
||
assert.equal(icon.width/icon.height,rect.width/rect.height,`${type} retains its trimmed aspect ratio`);
|
||
assert.equal(icon.scaleX,icon.scaleY);
|
||
assert.equal(icon.getComponent('cc.Sprite').sizeMode,cc.Sprite.SizeMode.TRIMMED);
|
||
assert.equal(icon.getComponent('cc.Sprite').trim,true);
|
||
assert.deepEqual([stars.width,stars.height,stars.scaleX,stars.scaleY],originalStars);
|
||
if(type==='coin') assert.equal(icon.scale,1,'the existing coin size is unchanged');
|
||
}
|
||
daily.renderTaskRewards(list,{stars:2,rewards:[{type:'rocket',count:1}]});
|
||
const icon=list.children[1].getChildByName('icon');
|
||
assert.equal(icon.getComponent('cc.Sprite').spriteFrame,null);
|
||
assert.equal(icon.scale,1,'missing assets do not retain a previous reward scale');
|
||
daily.renderTaskRewards(list,{stars:2,rewards:[{type:'coin',count:100}]});
|
||
assert.equal(icon.scale,1);
|
||
assert.equal(icon.height,coinRect.height);
|
||
});
|
||
|
||
test('preview icons match the outer coin height and retain count labels without action buttons',()=>{
|
||
const {daily,preview}=setup(0);daily.selectWeeklyReward(null,'weekly_35');
|
||
const rewards=preview.getChildByName('rewards');assert.equal(rewards.scaleX,1);
|
||
assert.equal(preview.width,344);assert.equal(preview.height,180);
|
||
for(const slot of rewards.children){
|
||
assert.equal(slot.scaleX,1);
|
||
const icon=slot.getChildByName('icon');
|
||
assert.equal(icon.scaleX,icon.scaleY);
|
||
const frame=icon.getComponent('cc.Sprite').spriteFrame;
|
||
if(frame){
|
||
assert.ok(Math.abs(icon.height*icon.scaleY-daily.rewardIcons[0].getRect().height)<1e-8);
|
||
assert.equal(icon.width/icon.height,frame.getRect().width/frame.getRect().height);
|
||
}
|
||
assert.equal(slot.getChildByName('count').getComponent('cc.Label').fontSize,40);
|
||
assert.equal(slot.getChildByName('plus').active,false);
|
||
}
|
||
assert.equal(preview.getChildByName('btnClaim'),undefined);assert.equal(preview.getChildByName('close'),undefined);
|
||
});
|
||
test('missing reward images keep empty icon slots and quantities without text substitutes',()=>{
|
||
const {daily,preview}=setup(0);
|
||
daily.selectWeeklyReward(null,'weekly_35');
|
||
daily.selectWeeklyReward(null,'weekly_140');
|
||
const items=preview.getChildByName('rewards').children;
|
||
for(const slot of items.slice(0,2)){
|
||
assert.equal(slot.active,true);
|
||
assert.equal(slot.getChildByName('icon').active,true);
|
||
assert.equal(slot.getChildByName('icon').getComponent('cc.Sprite').spriteFrame,null);
|
||
assert.equal(slot.getChildByName('rewardName').active,false);
|
||
assert.equal(slot.getChildByName('rewardName').getComponent('cc.Label').string,'');
|
||
assert.equal(slot.getChildByName('count').getComponent('cc.Label').string,'15分钟');
|
||
}
|
||
daily.rewardIcons[8]={getRect:()=>({width:100,height:200})};
|
||
daily.selectWeeklyReward(null,'weekly_140');
|
||
assert.equal(items[0].getChildByName('icon').getComponent('cc.Sprite').spriteFrame,daily.rewardIcons[8]);
|
||
});
|
||
|
||
test('runtime layout repairs the old 0.82 x 0.8 scaling without creating nodes',()=>{
|
||
const {daily,preview,cc}=setup(0),rewards=preview.getChildByName('rewards');
|
||
rewards.scale=0.82;
|
||
rewards.children.forEach(slot=>{slot.scale=0.8;slot.getChildByName('icon').setContentSize(90,94);});
|
||
cc.instantiate=()=>{throw Error('no runtime nodes');};
|
||
daily.selectWeeklyReward(null,'weekly_35');
|
||
assert.equal(rewards.scale,1);assert.equal(preview.width,344);
|
||
for(const slot of rewards.children){
|
||
assert.equal(slot.scale,1);
|
||
const icon=slot.getChildByName('icon');
|
||
if(icon.getComponent('cc.Sprite').spriteFrame) assert.ok(Math.abs(icon.height*icon.scaleY-daily.rewardIcons[0].getRect().height)<1e-8);
|
||
}
|
||
daily.selectWeeklyReward(null,'weekly_140');assert.equal(preview.width,508);
|
||
assert.equal(preview.getComponent('cc.Sprite').type,1);
|
||
});
|
||
|
||
test('outside hit testing converts camera screen coordinates before checking the preview',()=>{
|
||
const {daily,weekly,preview,cc}=setup(0);daily.selectWeeklyReward(null,'weekly_35');
|
||
const insideWorld={x:daily.node.x+preview.x,y:daily.node.y+weekly.y+preview.y};
|
||
cc.Camera.findCamera=()=>({getScreenToWorldPoint:(point,out)=>{out.x=point.x+700;out.y=point.y-300;return out;}});
|
||
let stopped=0;
|
||
daily.onWeeklyPreviewOutsideTouch({getLocation:()=>({x:insideWorld.x-700,y:insideWorld.y+300}),stopPropagation:()=>stopped++});
|
||
assert.equal(preview.active,true);assert.equal(stopped,0);
|
||
daily.onWeeklyPreviewOutsideTouch({getLocation:()=>({x:-1000,y:-1000}),stopPropagation:()=>stopped++});
|
||
assert.equal(preview.active,false);assert.equal(stopped,1);
|
||
});
|