const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const ts = require('typescript'); function fixture(hp, remaining) { let now = 1800000000000; const info = { hp, hp_Max: 5, userPowerTime: remaining ? now / 1000 + remaining : 0 }; const storage = new Map([['userPowerTime', info.userPowerTime]]); const timers = new Set(); const label = { string: '' }; const children = { skyLine: { active: remaining > 0, getChildByName: () => ({ getComponent: () => label }) }, man: { active: remaining === 0 && hp === 5 }, add: { active: remaining === 0 && hp < 5 }, time: { opacity: remaining > 0 ? 0 : 255 }, }; const cc = { Label: class {}, fx: { GameConfig: { GM_INFO: info }, StorageMessage: { getStorage: key => storage.get(key), setStorage: (key, value) => storage.set(key, value) }, }, find: () => ({ getComponent: () => home }) }; const context = vm.createContext({ cc, wx: {}, Date: { now: () => now }, Utils: { setUserPowerTime() {} }, console: { log() {} } }); function run(source) { vm.runInContext(ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS }, }).outputText, context); } function parse(path) { return ts.createSourceFile(path, fs.readFileSync(path, 'utf8'), ts.ScriptTarget.Latest, true); } const toolSource = parse('assets/Script/module/Tool/GameTool.ts'); let toolObject; function visit(node) { if (ts.isVariableDeclaration(node) && node.name.getText(toolSource) === 'GameTool') toolObject = node.initializer; ts.forEachChild(node, visit); } visit(toolSource); const toolMethods = toolObject.properties.filter(node => ['setUserPowerTime', 'getUserPowerTime', 'getTimeMargin2'].includes(node.name?.getText(toolSource))); run('cc.fx.GameTool = {' + toolMethods.map(node => node.getText(toolSource)).join(',\n') + '};'); cc.fx.GameTool.shushu_Track = () => {}; const homeSource = parse('assets/Script/JiaZai.ts'); const homeMethods = homeSource.statements.find(ts.isClassDeclaration).members.filter(node => ['updatePower', 'startPowerTime', 'stopPowerTime', 'refreshStaminaDisplay'].includes(node.name?.getText(homeSource))); run('globalThis.Home = class {' + homeMethods.map(node => node.getText(homeSource)).join('\n') + '};'); const home = new context.Home(); home.Stamina = { getChildByName: name => children[name] }; home.schedule = callback => timers.add(callback); home.unschedule = callback => timers.delete(callback); home.updateCoin = () => {}; if (remaining) home.updatePower(); context.exports = {}; context.require = () => ({ default: { getUid: () => 'test-user' } }); run(fs.readFileSync('assets/seven_day_gift/SevenDayGiftReward.ts', 'utf8')); const receipt = { claimId: 'day-one', rewards: [{ type: 'infinite_health', count: 900 }] }; return { info, children, label, timers, grant() { let completed = false; context.exports.default.grant(receipt, success => { assert.equal(success, true); completed = true; }); assert.equal(completed, true); }, tick() { now += 1000; [...timers].forEach(callback => callback()); } }; } for (const hp of [5, 2]) { for (const remaining of [0, 600]) { test(`day-one reward shows infinite stamina: hp=${hp}, remaining=${remaining}`, () => { const f = fixture(hp, remaining); f.grant(); assert.equal(f.children.skyLine.active, true, 'infinite stamina becomes visible after claiming'); assert.equal(f.children.man.active, false); assert.equal(f.children.add.active, false); assert.equal(f.children.time.opacity, 0); assert.equal(f.timers.size, 1, 'exactly one infinite stamina timer runs'); f.tick(); assert.equal(f.label.string, remaining ? '24:59' : '14:59'); const expiry = f.info.userPowerTime; f.grant(); assert.equal(f.info.userPowerTime, expiry, 'repeated receipt does not add time twice'); assert.equal(f.timers.size, 1); }); } }