Files
ark-stronghold-protocol/test/ui/localArt.test.js
T
sganggs 01a44cd288 卫戍协议:盟约 web remake — initial import
Online co-op browser remake of Arknights' Stronghold Protocol: Alliance
(client-simulated combat, official waves/bonds/items/strategies, 3D board,
facing wheel, operator loadout, official emotes, deployment scripts).
2026-09-29 16:21:31 +08:00

77 lines
3.9 KiB
JavaScript

// Local-client art is optional (DESIGN §13): the emote art, the 玩法说明 pages and the official tier chips resolve
// from data/local-assets.json when present and fall back (text / tips / CSS) when the manifest or an entry is missing.
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { EMOTES, emoteArtPath, emoteArtGroup, emoteInfo } from '../../shared/constants.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const manifest = {
version: 1,
groups: {
'emoticon/basic': { pic_happy_battle: { path: '/assets/local/emoticon/basic/pic_happy_battle.png', w: 120, h: 120 } },
guide: Object.fromEntries(['autochess_home_1', 'autochess_home_2', 'autochess_shop_1', 'autochess_handbook_4']
.map((k) => [k, { path: `/assets/local/guide/${k}.png`, w: 1024, h: 1024 }])),
'ui/battle': { img_chess_level_3: { path: '/assets/local/ui/battle/img_chess_level_3.png', w: 45, h: 45 } },
},
};
let mode = 'ok';
globalThis.fetch = async (url) => {
if (String(url).endsWith('/local-assets.json') && mode === 'ok') return { ok: true, status: 200, json: async () => manifest };
return { ok: false, status: 404, json: async () => ({}) };
};
describe('local-client art fallbacks', () => {
test('manifest present: listed entries resolve, unlisted ones stay null', async () => {
const { data, localAsset } = await import('../../public/js/data.js');
await data.load('local');
const { emoteArtUrl } = await import('../../public/js/ui/emotes.js');
const { guidePages, GUIDE_CHAPTERS } = await import('../../public/js/ui/guide.js');
const { tierChipUrl } = await import('../../public/js/ui/components.js');
assert.equal(emoteArtUrl('autochess_battle_happy'), emoteArtPath('autochess_battle_happy'));
assert.equal(emoteArtUrl('autochess_battle_sad'), null, 'unlisted emote art is never requested');
assert.equal(emoteArtUrl('nope'), null);
const pages = guidePages();
assert.deepEqual(pages.map((p) => p.key), ['autochess_home_1', 'autochess_home_2', 'autochess_shop_1', 'autochess_handbook_4'], 'reading order, missing pages skipped');
assert.deepEqual(pages.map((p) => p.chapter), [0, 0, 1, 2]);
assert.equal(GUIDE_CHAPTERS.reduce((n, c) => n + c.pages.length, 0), 19, 'the 19 official tutorial pages');
assert.equal(tierChipUrl(3), '/assets/local/ui/battle/img_chess_level_3.png');
assert.equal(tierChipUrl(4), null, 'CSS fallback');
assert.equal(localAsset('guide', 'autochess_home_9'), null);
assert.equal(localAsset('nope', 'x'), null);
});
test('manifest missing: everything falls back', async () => {
mode = 'missing';
const { data } = await import('../../public/js/data.js');
const orig = console.warn; console.warn = () => {};
try { await data.invalidate('local'); } finally { console.warn = orig; }
assert.equal(data.status('local'), 'missing');
const { emoteArtUrl } = await import('../../public/js/ui/emotes.js');
const { guidePages } = await import('../../public/js/ui/guide.js');
const { tierChipUrl } = await import('../../public/js/ui/components.js');
for (const id of EMOTES) assert.equal(emoteArtUrl(id), null);
assert.deepEqual(guidePages(), []);
assert.equal(tierChipUrl(1), null);
});
test('the real manifest (when extracted) lists all 36 emotes and all 19 guide pages', { skip: !safeRead() && 'no data/local-assets.json' }, () => {
const real = JSON.parse(safeRead());
for (const id of EMOTES) {
const entry = real.groups[emoteArtGroup(id)]?.[emoteInfo(id).picId];
assert.ok(entry, `emote ${id}`);
assert.equal(entry.path, emoteArtPath(id));
}
assert.equal(Object.keys(real.groups.guide || {}).length, 19);
});
});
function safeRead() {
try { return readFileSync(path.join(ROOT, 'data/local-assets.json'), 'utf8'); } catch { return null; }
}