MatchMaster/tools/test-shop-popup-rewards.cjs
2026-09-29 18:57:56 +08:00

173 lines
9.9 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const { loadMembers, loadModule } = require('./new-player-ab-test-support.cjs');
function fixture(sceneName, shopState = 'open') {
const info = { coin: 100, userPowerTime: 0, hp: 5, hp_Max: 5,
hammerAmount: 0, freezeAmount: 0, magicAmount: 0, iosStarterOrder: 'starter-order' };
const events = [], timers = [], pending = {};
const snapshot = kind => events.push({ kind, coin: info.coin, power: info.userPowerTime,
hp: info.hp, hammer: info.hammerAmount, freeze: info.freezeAmount, magic: info.magicAmount });
class JiaZai {}
const home = Object.assign(new JiaZai(), {
startStarter_pack() {}, updateCoin() { snapshot('home-coin'); },
updatePower() { snapshot('home-power'); },
setHealthInfo() { snapshot('home-health'); }, startTimeCutDown() {},
});
const cc = { isValid: value => !!value && !value.destroyed, warn: (...args) => assert.fail(args.join(' ')),
fx: { GameConfig: { GM_INFO: info }, StorageMessage: { setStorage() {} }, GameTool: {
shopBuy(id) {
if (id === 'starter_pack') {
info.coin += 3000; info.userPowerTime += 1800;
info.hammerAmount += 3; info.freezeAmount += 3; info.magicAmount += 3;
} else {
assert.equal(id, 'month_Card'); info.coin += 6000;
}
snapshot('grant');
},
getMonthlyCardValidityDays: () => Promise.resolve({ days: 30, time: '2026-09-29T00:00:00Z' }),
checkExpiration: () => true, setUserHealth: (_, done) => done({}), getHealth() {},
getPassCheckPorp(id, count) {
const fields = { 1001: 'coin', 2001: 'freezeAmount', 2002: 'hammerAmount',
2003: 'magicAmount', 3004: 'userPowerTime' };
assert.ok(fields[id], `unexpected reward ${id}`);
info[fields[id]] += count; snapshot('grant');
},
} },
};
const MapConroler = { _instance: { node: {},
updateCoin() { snapshot('game-coin'); }, setPropNum() { snapshot('game-props'); } } };
const SceneManager = loadMembers('assets/Script/SceneManager.ts',
['refreshJungleRewardDisplay', 'updateCoin'], { cc, MapConroler });
const game = new SceneManager();
const NumberToImage = {
numberToImageNodes() {},
numberToImageNodesCoin(value) { snapshot('shop-coin'); assert.equal(value, info.coin); },
};
const Shop = loadMembers('assets/shop/script/shop.ts', ['refreshJungleRewardDisplay'], { cc, NumberToImage });
const shop = Object.assign(new Shop(), {
node: { activeInHierarchy: shopState === 'open' }, coin: {}, currentCoin: 100, coinAnimating: true,
refreshJungleEntryLayout() {}, refreshActivityEntries() {}, updateIcon() {},
openShop() { snapshot('shop-open'); }, updatePower() { snapshot('shop-power'); },
});
const shopNode = { activeInHierarchy: shopState === 'open', getComponent: name => name === 'shop' ? shop : null };
const canvas = {
children: shopState === 'absent' ? [] : [shopNode],
getChildByName: name => name === 'shop' && shopState !== 'absent' ? shopNode : null,
getComponent(type) {
if ((type === JiaZai || type === 'JiaZai') && sceneName === 'HomeScene') return home;
if ((type === SceneManager || type === 'SceneManager') && sceneName === 'GameScene') return game;
return null;
},
};
cc.find = name => name === 'Canvas' ? canvas : name === 'Canvas/shop' && shopState !== 'absent' ? shopNode : null;
const globals = { cc, JiaZai, SceneManager, NumberToImage,
console: { log() {} }, MiniGameSdk: { API: { yinli_Pay() {}, showToast() {} } },
setTimeout: (callback, delay) => { timers.push({ callback, delay }); return timers.length; },
Utils: { setMonthlyCard: (_, callback, order) => { pending.monthly = callback; pending.order = order; } },
};
const Gift = loadMembers('assets/action_bundle/script/NewbieGift.ts', ['completeOrder', 'buyStarter_pack'], { ...globals });
const gift = Object.assign(new Gift(), { logPayment() {}, closeLoad() {},
closeStarter_pack() { snapshot('gift-close'); } });
const Monthly = loadMembers('assets/Script/monthlyCard.ts', ['buyMonthCard', 'setReward'], { ...globals });
const monthly = Object.assign(new Monthly(), { closeLoad() {}, refreshCardState() {}, monthCardTime: {} });
const Jungle = loadModule('assets/jungle_treasure/script/JungleRewardService.ts', {}, { cc }).default;
return { info, events, timers, pending, gift, monthly, shop, jungle: new Jungle(), snapshot };
}
const kinds = f => f.events.map(event => event.kind);
function assertShopShowsLatest(f) {
const coin = f.events.find(event => event.kind === 'shop-coin');
const power = f.events.find(event => event.kind === 'shop-power');
assert.ok(coin, 'shop coin digits refreshed');
assert.ok(power, 'shop stamina refreshed');
assert.equal(coin.coin, f.info.coin);
assert.equal(power.power, f.info.userPowerTime);
assert.equal(power.hp, f.info.hp);
assert.equal(f.shop.currentCoin, f.info.coin);
assert.equal(f.shop.coinAnimating, false, 'old coin animation cannot overwrite the balance');
}
for (const scene of ['HomeScene', 'GameScene']) {
test(`${scene}: starter purchase refreshes the visible shop after coin and stamina arrive`, () => {
const f = fixture(scene);
f.gift.completeOrder('starter-order');
assert.equal(f.info.coin, 3100); assert.equal(f.info.userPowerTime, 1800);
assert.equal(f.info.starterPackPurchased, true); assert.equal(f.info.iosStarterOrder, '');
assertShopShowsLatest(f);
const sceneCoin = f.events.find(event => event.kind === (scene === 'HomeScene' ? 'home-coin' : 'game-coin'));
assert.equal(sceneCoin.coin, 3100);
assert.ok(kinds(f).indexOf('grant') < kinds(f).indexOf('shop-coin'));
assert.equal(kinds(f).at(-1), 'gift-close');
if (scene === 'GameScene') assert.equal(f.events.find(event => event.kind === 'game-props').hammer, 3);
});
for (const shopState of ['closed', 'absent']) {
test(`${scene}: starter confirmation with ${shopState} shop still refreshes the scene safely`, () => {
const f = fixture(scene, shopState);
assert.doesNotThrow(() => f.gift.completeOrder('starter-order'));
assert.equal(f.info.coin, 3100);
assert.ok(!kinds(f).includes('shop-coin'));
assert.ok(kinds(f).includes(scene === 'HomeScene' ? 'home-coin' : 'game-coin'));
});
}
test(`${scene}: monthly rewards and refresh wait for server success`, async () => {
const f = fixture(scene);
f.monthly.buyMonthCard('month_Card', 'monthly-order');
assert.equal(f.pending.order, 'monthly-order');
assert.equal(f.info.coin, 100); assert.equal(f.events.length, 0);
assert.equal(f.timers.length, 0);
f.pending.monthly({ code: 1 });
assert.equal(f.info.coin, 6100); assert.equal(f.info.hp, 7);
assertShopShowsLatest(f);
assert.ok(kinds(f).indexOf('grant') < kinds(f).indexOf('shop-coin'));
assert.ok(kinds(f).indexOf('shop-open') < kinds(f).indexOf('shop-coin'), 'preserve original shop open flow');
if (scene === 'HomeScene') {
assert.ok(!kinds(f).includes('home-coin'), 'home retains its existing delayed refresh');
assert.equal(f.timers.length, 1); assert.equal(f.timers[0].delay, 300);
f.timers[0].callback();
assert.equal(f.events.find(event => event.kind === 'home-coin').coin, 6100);
assert.equal(f.events.find(event => event.kind === 'home-health').hp, 7);
} else {
assert.equal(f.events.find(event => event.kind === 'game-coin').coin, 6100);
}
await Promise.resolve();
});
test(`${scene}: unsuccessful monthly activation cannot grant or redraw reward balances`, () => {
const f = fixture(scene);
f.monthly.buyMonthCard('month_Card', 'monthly-order');
f.pending.monthly({ code: 0 });
assert.equal(f.info.coin, 100); assert.equal(f.events.length, 0); assert.equal(f.timers.length, 0);
});
test(`${scene}: monthly activation tolerates an absent shop`, async () => {
const f = fixture(scene, 'absent');
f.monthly.buyMonthCard('month_Card', 'monthly-order');
assert.doesNotThrow(() => f.pending.monthly({ code: 1 }));
f.timers.forEach(timer => timer.callback());
assert.equal(f.info.coin, 6100); assert.ok(!kinds(f).includes('shop-coin'));
assert.equal(f.events.find(event => event.kind === (scene === 'HomeScene' ? 'home-coin' : 'game-coin')).coin, 6100);
await Promise.resolve();
});
test(`${scene}: Jungle grants all reward types before refreshing shop and scene`, () => {
const f = fixture(scene);
const rewards = [{ type: 'coin', count: 1500 }, { type: 'hammer', count: 2 },
{ type: 'freeze', count: 3 }, { type: 'magic_wand', count: 4 }, { type: 'infinite_health', count: 1800 }];
f.jungle.grant({ emit: name => { assert.equal(name, 'jungle-reward-granted'); f.snapshot('jungle-event'); } }, rewards);
assert.equal(f.info.coin, 1600); assert.equal(f.info.userPowerTime, 1800);
assertShopShowsLatest(f);
assert.deepEqual(kinds(f).slice(0, rewards.length), Array(rewards.length).fill('grant'));
const sceneCoin = f.events.find(event => event.kind === (scene === 'HomeScene' ? 'home-coin' : 'game-coin'));
assert.equal(sceneCoin.coin, 1600);
if (scene === 'HomeScene') assert.equal(f.events.find(event => event.kind === 'home-power').power, 1800);
else {
const props = f.events.find(event => event.kind === 'game-props');
assert.deepEqual([props.hammer, props.freeze, props.magic], [2, 3, 4]);
}
assert.equal(kinds(f).at(-1), 'jungle-event');
});
}