100 lines
6.7 KiB
JavaScript
100 lines
6.7 KiB
JavaScript
// GitHub #173 「网页版游戏文件未加载」: the page runs but its game data never arrives (every /data/*.json request fails —
|
|
// a blocked request, a proxy, a server without its data folder). useData counts a file that failed ('missing') as
|
|
// settled like a loaded one, so 干员调配 drew an empty roster (「没有符合条件的干员」) and the 导入 of a loadout said the data
|
|
// had no loadout usable in this version; the 干员持有 / 自选编队 imports, sanitised against the absent data, stored an
|
|
// empty list over the saved one. Now the tab says that the game data did not load (which files, what to try) and every
|
|
// import is refused with the same words, before anything is parsed or applied — the saved settings stay as they are.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
// the reporter's case: every data request of the page fails (the screen reads the browser data store singleton)
|
|
globalThis.fetch = async () => ({ ok: false, status: 404, json: async () => ({}) });
|
|
const warn = console.warn;
|
|
console.warn = (...a) => { if (!String(a[0]).startsWith('[data]')) warn(...a); };
|
|
|
|
const { data } = await import('../../public/js/data.js');
|
|
const { missingGameData, importRefusal, DataMissing } = await import('../../public/js/screens/loadout.js');
|
|
const { setLang, setMessages } = await import('../../shared/i18n.js');
|
|
|
|
const TABS = ['loadout', 'ownership', 'diy'];
|
|
const statusOf = (map, dflt = 'ready') => (name) => map[name] ?? dflt;
|
|
const NOT_LOADED = '导入失败:游戏数据没有载入,未做任何改动。请刷新页面;仍不行时,请检查广告拦截插件和网络';
|
|
const LOADING = '干员数据仍在载入,请稍候再导入';
|
|
|
|
function* walk(v) {
|
|
if (Array.isArray(v)) { for (const x of v) yield* walk(x); return; }
|
|
if (!v || typeof v !== 'object') return;
|
|
yield v;
|
|
yield* walk(v.props?.children);
|
|
}
|
|
const textOf = (v) => [...walk(v)].flatMap((n) => (Array.isArray(n.props?.children) ? n.props.children : [n.props?.children]))
|
|
.filter((x) => typeof x === 'string').map((s) => s.trim()).filter(Boolean).join(' | ');
|
|
|
|
test('a data file that never arrived is settled like a loaded one: the tab names the files it cannot work without', async () => {
|
|
await data.loadAll('chess', 'bonds', 'assets', 'local', 'backups'); // the screen's useData(...) files
|
|
for (const name of ['chess', 'bonds', 'assets', 'local', 'backups']) assert.equal(data.status(name), 'missing', name);
|
|
// (default: the browser store) chess.json for every tab, backups.json too for 自选编队 (its slots and picks' forms)
|
|
assert.deepEqual(missingGameData('loadout'), ['chess']);
|
|
assert.deepEqual(missingGameData('ownership'), ['chess']);
|
|
assert.deepEqual(missingGameData('diy'), ['chess', 'backups']);
|
|
for (const tab of TABS) {
|
|
assert.deepEqual(missingGameData(tab, statusOf({})), [], `${tab}: loaded`);
|
|
assert.deepEqual(missingGameData(tab, statusOf({}, 'loading')), [], `${tab}: still loading (the spinner, not this message)`);
|
|
}
|
|
// only the art / bond files missing: every tab still works
|
|
for (const tab of TABS) assert.deepEqual(missingGameData(tab, statusOf({ bonds: 'missing', assets: 'missing', local: 'missing' })), []);
|
|
const noBackups = statusOf({ backups: 'missing' });
|
|
assert.deepEqual(TABS.map((tab) => missingGameData(tab, noBackups)), [[], [], ['backups']]);
|
|
});
|
|
|
|
test('every import is refused while the files load or when the game data did not load — no other reason', () => {
|
|
const missing = statusOf({ chess: 'missing', backups: 'missing' });
|
|
for (const kind of TABS) {
|
|
assert.deepEqual(importRefusal(kind, false, statusOf({})), { text: LOADING, tone: 'warn' }, `${kind}: loading`);
|
|
assert.deepEqual(importRefusal(kind, true, missing), { text: NOT_LOADED, tone: 'error' }, `${kind}: not loaded`);
|
|
assert.deepEqual(importRefusal(kind, true), { text: NOT_LOADED, tone: 'error' }, `${kind}: the browser store`);
|
|
assert.equal(importRefusal(kind, true, statusOf({})), null, `${kind}: loaded`);
|
|
}
|
|
const noBackups = statusOf({ backups: 'missing' });
|
|
assert.equal(importRefusal('loadout', true, noBackups), null);
|
|
assert.equal(importRefusal('ownership', true, noBackups), null);
|
|
assert.deepEqual(importRefusal('diy', true, noBackups), { text: NOT_LOADED, tone: 'error' });
|
|
});
|
|
|
|
test('the screen: the import asks importRefusal before parsing or applying anything; a tab without its data shows DataMissing', () => {
|
|
const src = readFileSync(path.join(ROOT, 'public/js/screens/loadout.js'), 'utf8');
|
|
const at = src.indexOf('const ioApply = () => {');
|
|
assert.ok(at > 0, 'ioApply');
|
|
const body = src.slice(at, src.indexOf('\n };\n', at));
|
|
const guard = body.indexOf('const refused = importRefusal(io.kind, ready);');
|
|
assert.ok(guard > 0, 'the guard');
|
|
assert.match(body.slice(guard), /^const refused = importRefusal\(io\.kind, ready\);\s*if \(refused\) \{ toast\(refused\.text, refused\.tone\); return; \}/);
|
|
for (const call of ['parseDiyImport(', 'applyDiyImport(', 'parseOwnershipImport(', 'applyOwnershipImport(', 'parseImport(', 'applyLoadoutEntries(']) {
|
|
assert.ok(body.indexOf(call) > guard, `${call} only after the guard`);
|
|
}
|
|
assert.ok(!/if \(!ready\)/.test(body), 'one guard for both cases');
|
|
assert.match(src, /const lost = ready \? missingGameData\(tab\) : \[\];/);
|
|
assert.match(src, /: lost\.length \? html`<\$\{DataMissing\} files=\$\{lost\} \/>` : tab === 'diy'/);
|
|
});
|
|
|
|
test('the message names the files and what to try, in Chinese and in English', () => {
|
|
const one = DataMissing({ files: ['chess'] });
|
|
assert.equal(one.props.role, 'alert');
|
|
assert.equal(textOf(one), '游戏数据没有载入 | /data/chess.json 没有下载成功,这一页无法显示;已保存的设置不受影响。请刷新页面;仍不行时,请检查广告拦截插件和网络。');
|
|
assert.match(textOf(DataMissing({ files: ['chess', 'backups'] })), /\/data\/chess\.json、\/data\/backups\.json 没有下载成功/);
|
|
const en = JSON.parse(readFileSync(path.join(ROOT, 'public/i18n/en.json'), 'utf8'));
|
|
setMessages('en', en);
|
|
setLang('en');
|
|
try {
|
|
assert.equal(textOf(DataMissing({ files: ['chess', 'backups'] })), 'The game data did not load | /data/chess.json, /data/backups.json could not be downloaded, '
|
|
+ 'so this page cannot be shown. Your saved settings are unaffected. Reload the page; if that does not help, check your ad blocker and your network.');
|
|
assert.equal(importRefusal('loadout', true).text, 'Import failed: the game data did not load. Nothing was changed. Reload the page; if that does not help, check your ad blocker and your network');
|
|
} finally {
|
|
setLang('zh');
|
|
}
|
|
});
|