93 lines
6.8 KiB
JavaScript
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'));
|
|
});
|