const assert = require('node:assert/strict'); const fs = require('node:fs'); const ts = require('typescript'); const code = ts.transpileModule(fs.readFileSync('assets/Script/coin_madness/CoinMadnessHost.ts', 'utf8'), { compilerOptions: { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.CommonJS, experimentalDecorators: true } }).outputText; let now = 100, calls = 0; const service = { info: { periodId: 'p1', startsAt: 50, endsAt: 200 }, get serverNow() { return now; }, settlementNotice: 0, refreshNeedsRetry: false, async readForDisplay() { return {}; }, async refresh() { calls++; return {}; }, async retryRecovery() { calls++; return {}; } }; class Component { constructor() { this.jobs = new Map(); } schedule(fn, seconds) { this.jobs.set(fn, seconds); } scheduleOnce(fn, seconds) { this.jobs.set(fn, seconds); } unschedule(fn) { this.jobs.delete(fn); } } const cc = { isValid: () => true, Label: class {}, Button: class {}, Component, _decorator: { ccclass: value => value }, game: { on() {}, off() {} }, warn() {} }; const exportsHost = {}; new Function('require', 'exports', 'cc', code)(name => { if (name.includes('GoldMinerService')) return { default: { current: () => service } }; return {}; }, exportsHost, cc); const flush = () => new Promise(resolve => setImmediate(resolve)); (async () => { const host = new exportsHost.default(); host.updateEntry = host.updateTestEntry = host.refreshCoinDisplay = () => {}; host.start(); await flush(); assert.equal(calls, 1); assert.equal([...host.jobs.values()].includes(30), false, 'no recurring network poll'); for (let i = 0; i < 100; i++) host.refreshEntryBadge(); assert.equal(calls, 1, 'local ticks do not request'); now = 201; host.refreshEntryBadge(); await flush(); assert.equal(calls, 2, 'expiry requests once'); host.refreshEntryBadge(); await flush(); assert.equal(calls, 2); service.refreshNeedsRetry = true; host.resumeSync(); await flush(); for (const delay of [2, 3, 5, 7, 10, 15, 25, 30, 50]) { assert.equal(host.jobs.get(host.retrySync), delay); host.retrySync(); await flush(); } assert.equal(host.jobs.has(host.retrySync), false, 'retry budget exhausted'); let opened = 0, switched = 0; host.open = () => opened++; host.openSettlement = () => switched++; service.settlementNotice = 100; host.showSettlementNotice(); assert.equal(opened, 1, 'ended before login can show notice'); host.panel = { getComponent: name => name === 'CoinMadnessPanel' ? {} : null }; host.showSettlementNotice(); assert.equal(switched, 1, 'open activity switches to settlement'); host.panel = { getComponent: () => null }; host.showSettlementNotice(); assert.equal(switched, 1, 'existing settlement is not replaced'); host.showSettlementNotice = () => {}; service.refreshNeedsRetry = false; let animated = 0; host.panel = { getComponent: () => ({ rewardSaved: () => animated++ }) }; service.request = async (action, body, saved) => { saved(100); return {code:1,data:{granted:100}}; }; await host.requestActivity(service,'claim',{}); assert.equal(animated, 1); assert(host.isCoinDisplayHeld(), 'successful credit waits for animation'); host.rewardArrived(); assert.equal(host.isCoinDisplayHeld(),false); service.request = async () => ({code:0}); await host.requestActivity(service,'claim',{}); assert.equal(host.isCoinDisplayHeld(),false,'failed claim does not hold the balance'); let loads = 0; host.node = { getComponent: () => ({ openLoad: () => loads++, closeLoad: () => loads-- }) }; host.setEntryLoading(true); host.setEntryLoading(true); assert.equal(loads, 1); host.setEntryLoading(false); host.setEntryLoading(false); assert.equal(loads, 0); const label = { string: '' }, time = { active: true, getComponent: () => label }, bg = { active: true }; host.entry = { getComponent: () => null, getChildByName: name => name === 'time' ? time : name === 'timeBg' ? bg : null }; host.syncing = true; service.info.endsAt = now + 90061000; // Fixture serverNow and endsAt use milliseconds. host.refreshEntryBadge(); assert.equal(label.string, '1天1时'); service.info.endsAt = now + 3661000; host.refreshEntryBadge(); assert.equal(label.string, '01:01:01'); service.info.endsAt = now; host.refreshEntryBadge(); assert.equal(label.string, ''); assert.equal(bg.active, false); console.log('PASS: local countdown, expiry sync, bounded retries and settlement display'); })().catch(error => { console.error(error); process.exitCode = 1; });