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'); }); }