183 lines
8.9 KiB
JavaScript
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']);
|
|
});
|