159 lines
7.7 KiB
JavaScript
159 lines
7.7 KiB
JavaScript
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 file = path.join(__dirname, '../assets/Script/JiaZai.ts');
|
|
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true);
|
|
const declaration = source.statements.find(ts.isClassDeclaration);
|
|
const methods = ['isHomeRuntimeAlive', 'openDailyQuests'].map(name => {
|
|
const method = declaration.members.find(item => item.name?.getText(source) === name);
|
|
assert.ok(method, name);
|
|
return method.getText(source);
|
|
}).join('\n');
|
|
|
|
function fixture(cached = false) {
|
|
const state = { scene: 'HomeScene', preparing: false, requests: 0, bundleLoads: 0, prefabLoads: 0,
|
|
created: 0, attached: 0, initialized: [], closed: 0, opened: 0, errors: [], toasts: [], canPresent: true, shown: 0 };
|
|
const pending = {};
|
|
const prefab = {};
|
|
const task = { daily: 'fixture' };
|
|
const response = { code: 1, data: { task } };
|
|
const cc = {
|
|
isValid: (value, strict) => !!value && !value.destroyed && !(strict && value.destroyPending),
|
|
director: { getScene: () => ({ name: state.scene }) }, Prefab: class {},
|
|
error: error => state.errors.push(error),
|
|
instantiate(asset) {
|
|
assert.equal(asset, prefab);
|
|
state.created++;
|
|
return { active: true, getComponent: () => ({ init: data => state.initialized.push(data) }) };
|
|
},
|
|
fx: { GameConfig: { GM_INFO: { level: 40 }, isNewPlayerB: () => false,
|
|
isGameplayEntryPreparing: () => state.preparing },
|
|
GameTool: { shushu_Track() {}, getDailyQuestsInfo(callback) { state.requests++; pending.response = callback; } } },
|
|
};
|
|
const Home = vm.runInNewContext(ts.transpileModule(`class JiaZai { ${methods} }; JiaZai;`, {
|
|
compilerOptions: { target: ts.ScriptTarget.ES2018 },
|
|
}).outputText, { cc, console: { log() {} }, MiniGameSdk: { API: { showToast: text => state.toasts.push(text) } },
|
|
HomePopupQueue: { get(node) {
|
|
assert.ok(cc.isValid(node, true), 'queue cannot be accessed through a destroyed node');
|
|
return { canPresent: () => state.canPresent, markShown: () => state.shown++ };
|
|
} } });
|
|
if (cached) Home.cachedDailyQuestsPrefab = prefab;
|
|
const home = new Home();
|
|
home.node = { addChild() { state.attached++; } };
|
|
home.vibrateButtonClick = () => {};
|
|
home.openLoad2 = () => state.opened++;
|
|
home.closeLoad = () => state.closed++;
|
|
home.loadHomeBundleWithDependencies = (_name, _priority, callback) => { state.bundleLoads++; pending.bundle = callback; };
|
|
const bundle = { load(_path, _type, callback) { state.prefabLoads++; pending.prefab = callback; } };
|
|
const finish = stage => {
|
|
if (stage === 'response') pending.response(response);
|
|
if (stage === 'bundle') pending.bundle(null, bundle);
|
|
if (stage === 'prefab') pending.prefab(null, prefab);
|
|
};
|
|
return { home, Home, state, pending, prefab, task, finish };
|
|
}
|
|
|
|
const invalidations = {
|
|
'node cleared': f => { f.home.node = null; },
|
|
'node destroyed': f => { f.home.node.destroyed = true; },
|
|
'node pending destruction': f => { f.home.node.destroyPending = true; },
|
|
'component destroyed': f => { f.home.destroyed = true; },
|
|
'component pending destruction': f => { f.home.destroyPending = true; },
|
|
'scene changed': f => { f.state.scene = 'GameScene'; },
|
|
'entering gameplay': f => { f.state.preparing = true; },
|
|
};
|
|
for (const stage of ['entry', 'response', 'bundle', 'prefab']) {
|
|
for (const [name, invalidate] of Object.entries(invalidations)) test(`${stage}: ${name} cannot open daily quests`, () => {
|
|
const f = fixture();
|
|
if (stage !== 'entry') f.home.openDailyQuests();
|
|
if (stage === 'bundle' || stage === 'prefab') f.finish('response');
|
|
if (stage === 'prefab') f.finish('bundle');
|
|
const closed = f.state.closed;
|
|
invalidate(f);
|
|
assert.doesNotThrow(() => stage === 'entry' ? f.home.openDailyQuests() : f.finish(stage));
|
|
assert.equal(f.state.created, 0);
|
|
assert.equal(f.state.attached, 0);
|
|
assert.equal(f.state.closed, closed, 'stale callbacks cannot touch loading UI');
|
|
if (stage === 'entry') assert.equal(f.state.requests, 0);
|
|
if (stage === 'response') assert.equal(f.state.bundleLoads, 0);
|
|
if (stage === 'bundle') assert.equal(f.state.prefabLoads, 0);
|
|
if (stage === 'prefab') assert.equal(f.Home.cachedDailyQuestsPrefab, f.prefab, 'completed resources remain reusable');
|
|
});
|
|
}
|
|
|
|
for (const cached of [true, false]) test(`live daily quests opens and reuses its node, cached=${cached}`, () => {
|
|
const f = fixture(cached);
|
|
f.home.openDailyQuests();
|
|
f.home.openDailyQuests();
|
|
assert.equal(f.state.requests, 1, 'pending API request is not duplicated');
|
|
f.finish('response');
|
|
if (!cached) { f.finish('bundle'); f.finish('prefab'); }
|
|
assert.equal(f.state.created, 1);
|
|
assert.equal(f.state.attached, 1);
|
|
assert.equal(f.state.initialized[0], f.task);
|
|
f.home.dailyQuestsNode.active = false;
|
|
f.home.openDailyQuests(); f.finish('response');
|
|
assert.equal(f.home.dailyQuestsNode.active, true);
|
|
assert.equal(f.state.created, 1);
|
|
assert.equal(f.state.initialized.length, 2);
|
|
});
|
|
|
|
for (const stage of ['response', 'bundle', 'prefab']) test(`${stage} failure allows a later retry`, () => {
|
|
const f = fixture();
|
|
f.home.openDailyQuests();
|
|
if (stage !== 'response') f.finish('response');
|
|
if (stage === 'prefab') f.finish('bundle');
|
|
if (stage === 'response') f.pending.response({ code: 0, data: null });
|
|
else f.pending[stage](new Error('offline'));
|
|
assert.equal(f.state.created, 0);
|
|
assert.equal(f.state.errors.length + f.state.toasts.length, 1);
|
|
f.home.openDailyQuests(); f.finish('response'); f.finish('bundle'); f.finish('prefab');
|
|
assert.equal(f.state.created, 1);
|
|
assert.equal(f.state.attached, 1);
|
|
});
|
|
|
|
for (const stage of ['entry', 'response', 'bundle', 'prefab']) {
|
|
for (const [name, invalidate] of Object.entries({ ...invalidations,
|
|
'queue no longer permits presentation': f => { f.state.canPresent = false; },
|
|
})) test(`automatic ${stage}: ${name} releases queue without marking shown`, () => {
|
|
const f = fixture();
|
|
let done = 0;
|
|
const open = () => f.home.openDailyQuests(undefined, undefined, undefined, () => done++);
|
|
if (stage !== 'entry') open();
|
|
if (stage === 'bundle' || stage === 'prefab') f.finish('response');
|
|
if (stage === 'prefab') f.finish('bundle');
|
|
invalidate(f);
|
|
if (stage === 'entry') open(); else f.finish(stage);
|
|
assert.equal(done, 1);
|
|
assert.equal(f.state.shown, 0);
|
|
assert.equal(f.state.created, 0);
|
|
});
|
|
}
|
|
|
|
for (const cached of [false, true]) test(`automatic success marks shown and releases queue, cached=${cached}`, () => {
|
|
const f = fixture(cached);
|
|
let done = 0;
|
|
f.home.openDailyQuests(undefined, undefined, undefined, () => done++);
|
|
f.finish('response');
|
|
if (!cached) { assert.equal(done, 0); f.finish('bundle'); f.finish('prefab'); }
|
|
assert.equal(f.state.attached, 1);
|
|
assert.equal(f.state.shown, 1);
|
|
assert.equal(done, 1);
|
|
});
|
|
|
|
for (const stage of ['response', 'bundle', 'prefab']) test(`automatic ${stage} failure releases queue`, () => {
|
|
const f = fixture();
|
|
let done = 0;
|
|
f.home.openDailyQuests(undefined, undefined, undefined, () => done++);
|
|
if (stage !== 'response') f.finish('response');
|
|
if (stage === 'prefab') f.finish('bundle');
|
|
if (stage === 'response') f.pending.response({ code: 0, data: null });
|
|
else f.pending[stage](new Error('offline'));
|
|
assert.equal(done, 1);
|
|
assert.equal(f.state.shown, 0);
|
|
});
|