MatchMaster/tools/test-wucai-entry.cjs

183 lines
8.9 KiB
JavaScript

// Run: node --test tools/test-wucai-entry.cjs
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const ts = require('typescript');
const root = path.resolve(__dirname, '..');
function loadClass(file, members, globals = {}) {
let text = fs.readFileSync(path.join(root, file), 'utf8');
// Isolate the changed adapter from an unrelated pre-existing invalid character earlier in Utils.
if (file.endsWith('/Utils.ts')) text = 'class Utils {\n' + text.slice(text.indexOf(' static wucaiTransfer('));
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
const declaration = source.statements.find(ts.isClassDeclaration);
const body = members ? members.map(name => {
const member = declaration.members.find(node => node.name?.getText(source) === name);
assert.ok(member, name);
return member.getText(source);
}).join('\n') : declaration.members.map(node => node.getText(source)).join('\n');
const name = declaration.name.text;
return vm.runInNewContext(ts.transpileModule(`class ${name} { ${body} }\n${name};`, {
compilerOptions: { target: ts.ScriptTarget.ES2017 },
}).outputText, globals);
}
const entry = () => loadClass('assets/Script/module/WucaiEntry.ts');
const flush = async () => { for (let i = 0; i < 8; i++) await Promise.resolve(); };
test('cold launch and its paired onShow create only one opportunity', () => {
const Entry = entry();
const options = { query: { from: 'wucai' }, scene: 1037 };
Entry.captureLaunch(options);
const first = Entry.loginEntryId;
assert.ok(first);
assert.equal(Entry.captureShow(options), false);
assert.equal(Entry.loginEntryId, first);
assert.equal(Entry.pendingEntryId, '');
});
test('each later external jump gets a new opportunity; ordinary resume does not', () => {
const Entry = entry();
Entry.captureLaunch({ query: {} });
Entry.onHide();
assert.equal(Entry.captureShow({ query: { from: 'wucai' }, scene: 1037 }), true);
const first = Entry.pendingEntryId;
Entry.onHide();
assert.equal(Entry.captureShow({ query: { from: 'wucai' }, scene: 1089 }), false);
assert.equal(Entry.pendingEntryId, first, 'a pending game-scene entry survives ordinary resume');
Entry.onHide();
assert.equal(Entry.captureShow({ query: { from: 'wucai' }, scene: 1037 }), true);
assert.notEqual(Entry.pendingEntryId, first);
});
test('internal restart reuses the entry, and the post-choice restart is an ordinary login', () => {
const Entry = entry();
const query = Object.fromEntries(new URLSearchParams(Entry.restartPath('entry-1')));
Entry.captureLaunch({ query });
assert.equal(Entry.loginEntryId, 'entry-1');
Entry.onHide();
assert.equal(Entry.captureShow({ query, scene: 1037 }), false);
Entry.captureLaunch({ query: Object.fromEntries(new URLSearchParams(Entry.restartPath())) });
assert.equal(Entry.loginEntryId, '');
assert.equal(Entry.pendingEntryId, '');
});
function homeFixture() {
const state = { alive: true, preparingGame: false, reboots: [], cancelled: 0, loading: false, toasts: [] };
let resolve, reject;
const saving = new Promise((yes, no) => { resolve = yes; reject = no; });
const Entry = entry();
Entry.pendingEntryId = 'jump-1';
const Utils = { prepareWucaiTransfer: () => { state.prepares = (state.prepares || 0) + 1; return saving; },
cancelWucaiTransferPreparation: () => state.cancelled++ };
class Node {
setContentSize() {}
addComponent() {}
destroy() { this.destroyed = true; }
}
const cc = { Node, BlockInputEvents: {}, macro: { MAX_ZINDEX: 999 }, isValid: node => !node.destroyed,
fx: { GameConfig: { isGameplayEntryPreparing: () => state.preparingGame } } };
const MiniGameSdk = { API: { reboot: (path, fail) => state.reboots.push({ path, fail }), showToast: msg => state.toasts.push(msg) } };
const Home = loadClass('assets/Script/JiaZai.ts', ['preparingWucaiTransfer', 'deferredWucaiEntryId', 'hasPendingWucaiEntry', 'resumeWucaiEntry'], {
cc, Utils, WucaiEntry: Entry, MiniGameSdk,
});
const home = new Home();
home.node = { getContentSize: () => ({}), addChild: node => { state.blocker = node; } };
home.isHomeRuntimeAlive = () => state.alive;
home.openLoad = () => { state.loading = true; };
home.closeLoad = () => { state.loading = false; };
home.resumeDeferredHomePopups = () => { state.popupsResumed = true; };
home.flushHomeAutomaticTasks = () => { state.tasksResumed = true; };
return { state, home, Entry, resolve, reject };
}
test('home entry blocks input and waits for saves before restarting with the same opportunity', async () => {
const { state, home, resolve } = homeFixture();
home.resumeWucaiEntry();
home.resumeWucaiEntry();
assert.equal(state.prepares, 1);
assert.ok(state.blocker);
assert.equal(state.loading, true);
assert.equal(state.reboots.length, 0);
resolve(); await flush();
assert.equal(state.reboots.length, 1);
assert.equal(state.reboots[0].path, '?from=wucai&wucai_entry_id=jump-1');
});
test('a jump in gameplay waits for HomeScene without touching saves or restarting the round', async () => {
const { state, home, Entry, resolve } = homeFixture();
state.alive = false;
home.resumeWucaiEntry();
assert.equal(state.prepares, undefined);
assert.equal(Entry.pendingEntryId, 'jump-1');
state.alive = true;
home.resumeWucaiEntry();
resolve(); await flush();
assert.equal(state.reboots.length, 1);
});
test('save failure or restart failure releases input, retains the entry, and reports failure', async () => {
for (const kind of ['save', 'restart']) {
const { state, home, Entry, resolve, reject } = homeFixture();
home.resumeWucaiEntry();
if (kind === 'save') reject({ msg: '保存结果未确认' });
else { resolve(); await flush(); state.reboots[0].fail(); }
await flush();
assert.equal(state.cancelled, 1);
assert.equal(state.blocker.destroyed, true);
assert.equal(state.loading, false);
assert.equal(home.preparingWucaiTransfer, false);
assert.equal(Entry.pendingEntryId, 'jump-1');
assert.equal(home.hasPendingWucaiEntry(), false, 'failed preparation must allow normal play and saves to recover');
assert.equal(state.popupsResumed, true);
assert.equal(state.tasksResumed, true);
assert.equal(state.toasts.length, 1);
if (kind === 'save') assert.equal(state.toasts[0], '保存结果未确认');
}
});
test('lifecycle processes a hot jump only at home, and later normal resumes retain deferred jumps', () => {
const Entry = entry();
Entry.captureLaunch({ query: {} });
const state = { scene: 'GameScene', resumes: 0 };
const cc = { fx: { GameConfig: { GM_INFO: {} } }, audioEngine: { resumeAll() {} }, game: { resume() {} },
director: { getScene: () => ({ name: state.scene }) },
find: () => ({ getComponent: () => ({ resumeWucaiEntry: () => state.resumes++ }) }) };
const Manager = loadClass('assets/Script/GameManager.ts', ['onShow', 'updateWucaiEntry'], {
cc, WucaiEntry: Entry, MiniProgramBenefitsBridge: { instance: { capture() {} } },
});
const manager = new Manager();
Entry.onHide();
manager.onShow({ query: { from: 'wucai' }, scene: 1037 });
assert.equal(state.resumes, 0);
assert.ok(Entry.pendingEntryId);
state.scene = 'HomeScene';
Entry.onHide();
manager.onShow({ query: {}, scene: 1089 });
assert.equal(state.resumes, 1);
});
test('choice submission carries the round id and reloads without opening another round', () => {
const state = { reboots: [], saved: [] };
const gm = { uid: 'u1', wucai_type: 1, wucai_entry_id: 'round-2', wucai_preview: { levelAmount: 4 },
wucai_current_preview: { levelAmount: 7 }, coin: 99 };
const MiniGameSdk = { API: { reboot: path => state.reboots.push(path), showToast() {},
shushu_TrackWucaiTransfer() {}, shushu_userSetTransferredFromWucai() {} } };
const Utils = loadClass('assets/Script/module/Pay/Utils.ts', ['wucaiTransfer'], {
cc: { fx: { GameConfig: { GM_INFO: gm }, StorageMessage: { setStorage: (...args) => state.saved.push(args) } } },
MiniGameSdk,
});
Utils.POST = (path, body, callback) => {
assert.equal(path, 'wucaiTransfer');
assert.equal(body.wucai_entry_id, 'round-2');
callback({ code: 1, data: { wucai_type: 2, wucai_entry_id: 'round-2',
wucai_reward_granted: true, wucai_reward_popup_pending: false } });
};
Utils.wucaiTransfer('choose_wucai');
assert.equal(gm.wucai_reward_granted, true);
assert.equal(gm.wucai_reward_popup_pending, false);
assert.equal(gm.coin, 99, 'client does not locally award another migration gift');
assert.deepEqual(state.reboots, ['?wucai_resume=1']);
});