Files
MatchMaster/tools/test-activity-resource-integration.cjs
T
2026-10-16 15:02:14 +08:00

93 lines
6.8 KiB
JavaScript

const test = require('node:test'), assert = require('node:assert/strict');
const fs = require('node:fs'), vm = require('node:vm'), ts = require('typescript');
const flush = () => new Promise(resolve => setImmediate(resolve));
function load(file, cc, imports) {
const exports = {};
vm.runInNewContext(ts.transpileModule(fs.readFileSync(file, 'utf8'), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017, experimentalDecorators: true }
}).outputText, { exports, cc, require: name => imports(name) });
return exports.default;
}
function miner() {
const requests = [], draws = [], calls = { acquire: 0, release: 0, initialize: 0 };
let resolveLease, rejectLease;
const lease = new Promise((resolve, reject) => { resolveLease = resolve; rejectLease = reject; });
const bundle = { load(path, type, done) { requests.push({ path, done }); }, releaseAll() { throw Error('host cannot release a managed bundle'); } };
const cc = { Component: class {}, _decorator: { ccclass: c => c }, Prefab: class {}, warn() {},
fx: { GameConfig: { GM_INFO: { uid: 'player' } } }, macro: { MAX_ZINDEX: 100 }, isValid: n => !!n,
director: { once: (_event, done) => draws.push(done) }, Director: { EVENT_AFTER_DRAW: 1 },
instantiate: () => ({ zIndex: 0, destroy() {}, removeFromParent() {}, getComponent: () => ({ initialize() { calls.initialize++; } }) }) };
const Host = load('assets/Script/coin_madness/CoinMadnessHost.ts', cc, name => {
if (name.includes('ActivityBundleLoader')) return { default: { acquireBundle() { calls.acquire++; return lease; } } };
if (name.includes('Utils')) return { default: { serverUrl: () => 'test' } };
return { default: {}, MiniGameSdk: { API: { showToast() {} } } };
});
const host = new Host();
host.node = { getComponent: () => null, addChild() {} };
host.service = { readForDisplay: async () => ({ state: {} }), refresh: () => new Promise(() => {}), stopPolling() {}, settlementNotice: 0 };
host.refreshCoinDisplay = host.clearHelp = host.updateEntry = () => {};
return { host, calls, requests, draws, rejectLease,
async acquired() { resolveLease({ bundle, release() { calls.release++; } }); await flush(); },
async finish(index = 0, error = null) { requests[index].done(error, {}); await flush(); },
draw() { draws.splice(0).forEach(done => done()); } };
}
test('miner only acquires at its original open trigger and retains resources until the final draw', async () => {
const f = miner(); assert.equal(f.calls.acquire, 0);
f.host.open(); f.host.open(); assert.equal(f.calls.acquire, 1);
await f.acquired(); assert.deepEqual(f.requests.map(x => x.path), ['prefab/CoinMadness']);
await f.finish(); assert.equal(f.calls.initialize, 1); assert.equal(f.calls.release, 0);
f.host.close(); f.host.close(); assert.equal(f.calls.release, 0);
f.draw(); assert.equal(f.calls.release, 1);
});
test('miner close during acquisition or prefab loading releases the late lease exactly once', async () => {
for (const phase of ['bundle', 'prefab']) {
const f = miner(); f.host.open();
if (phase === 'prefab') await f.acquired();
f.host.close(); f.draw(); assert.equal(f.calls.release, 0);
if (phase === 'bundle') await f.acquired(); else await f.finish();
assert.equal(f.calls.initialize, 0); f.draw(); assert.equal(f.calls.release, 1);
if (phase === 'bundle') assert.equal(f.requests.length, 0);
}
});
test('miner retains outstanding help and settlement prefab loads after the panel closes', async () => {
for (const page of ['help', 'settlement']) {
const f = miner(); f.host.open(); await f.acquired(); await f.finish();
if (page === 'help') f.host.openHelp(); else f.host.openSettlement({ state: {} });
assert.equal(f.requests.length, 2); f.host.close(); f.draw();
assert.equal(f.calls.release, 0, 'a pending prefab still owns its dependencies');
await f.finish(1); f.draw(); assert.equal(f.calls.release, 1);
assert.equal(f.calls.initialize, 1, 'late callbacks cannot mount another panel');
}
});
test('miner late callbacks release their lease even after Cocos clears destroyed component fields', async () => {
const f = miner(); f.host.open(); await f.acquired();
f.host.alive = false; f.host.close(); f.host.resourceState = null; f.host.node = null;
await f.finish(); f.draw(); assert.equal(f.calls.release, 1); assert.equal(f.calls.initialize, 0);
});
test('miner keeps direct-settlement routing and releases on failed or destroyed loads', async () => {
const notice = miner(); notice.host.service.settlementNotice = 100; notice.host.open(); await notice.acquired();
assert.deepEqual(notice.requests.map(x => x.path), ['prefab/CoinMadnessSettlement']);
await notice.finish(0, Error('missing')); notice.draw(); assert.equal(notice.calls.release, 1);
const failed = miner(); failed.host.open(); failed.rejectLease(Error('offline')); await flush();
assert.equal(failed.calls.release, 0); assert.equal(failed.host.isPopupActive(), false);
const next = miner(); next.host.open(); await next.acquired(); await next.finish(); next.host.close(); next.draw();
assert.equal(next.calls.release, 1, 'failed acquisition cannot leave the static occupied flag set');
});
test('Cloud Rise page cache follows Bundle identity and holds the whole group through async prefab loading', async () => {
let holds = 0, loads = 0, pending, bundle = { load(_path, _type, done) { loads++; pending = done; } };
const requested = [];
const loader = { retainGroup() { holds++; return () => holds--; }, prepareGroup: async () => {},
ensureBundle: async name => { requested.push(name); return name === 'art' ? {} : bundle; } };
const cc = { Prefab: class {}, assetManager: { loadBundle(name, _options, done) { requested.push(name); done(null, {}); } } };
const Service = load('assets/Script/module/Config/CloudRiseService.ts', cc, name => ({ default: name.includes('ActivityBundleLoader') ? loader : {} }));
const a = Service.pageFragment('stage', 2), b = Service.pageFragment('stage', 2); await flush();
assert.equal(loads, 1); assert.equal(holds, 2);
const first = {}; pending(null, first); assert.equal(await a, first); assert.equal(await b, first); assert.equal(holds, 0);
assert.equal(await Service.pageFragment('stage', 2), first); assert.equal(loads, 1);
const replacement = {}; bundle = { load(_path, _type, done) { loads++; done(null, replacement); } };
assert.equal(await Service.pageFragment('stage', 2), replacement); assert.equal(loads, 2);
bundle = { load(_path, _type, done) { done(Error('missing')); } };
await assert.rejects(Service.pageFragment('stage', 2)); assert.equal(holds, 0);
assert(!requested.includes('stage1')); assert(!requested.includes('stage3')); assert(!requested.includes('matching'));
});