change icon stats, new plana, redesign some ui, fix JSON managment
Deploy GaC-Srtool to GitHub Pages / deploy (push) Waiting to run
Deploy GaC-Srtool to GitHub Pages / deploy (push) Waiting to run
This commit is contained in:
1 parent
083848ee6e
commit
45d8414e60
19 files changed
+911
-222
No files matched your search
@@ -2,6 +2,7 @@ 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');
|
||||
|
||||
test('4.6 equipment metadata and bundled artwork are available', async () => {
|
||||
const { LIGHTCONE_METADATA, RELIC_SET_METADATA, getRelicSetInfo } = await import('../js/equipment-data.js');
|
||||
@@ -15,10 +16,58 @@ test('4.6 equipment metadata and bundled artwork are available', async () => {
|
||||
assert.equal(getRelicSetInfo(id, 'en').name, name);
|
||||
assert.ok(getRelicSetInfo(id, 'en').desc2);
|
||||
assert.notEqual(relic.creationAvailable, false);
|
||||
assert.ok(fs.existsSync(path.resolve(__dirname, '..', relic.setIcon)));
|
||||
for (const slot of [5, 6]) assert.ok(fs.existsSync(path.resolve(__dirname, '..', relic.images[slot])));
|
||||
}
|
||||
});
|
||||
|
||||
test('Aha recommends Dreamlit Actor and the captured God\'s Moment of Joy set', () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '../js/app.js'), 'utf8');
|
||||
const start = source.indexOf('const CHARACTER_OPTIMAL_BUILDS =');
|
||||
const end = source.indexOf('// Recommended Relic Builds by Path', start);
|
||||
assert.ok(start >= 0 && end > start);
|
||||
const context = vm.createContext({});
|
||||
vm.runInContext(source.slice(start, end) + '\nthis.build = getOptimalBuildForAvatar(1511);', context);
|
||||
assert.equal(context.build.cavern, 133);
|
||||
assert.equal(context.build.planar, 329);
|
||||
const capture = require('../assets/relic-client-capture.json');
|
||||
for (const [slot, id] of [[5, context.build.sphere], [6, context.build.rope]]) {
|
||||
const piece = capture.relics.find(row => row.SetID === context.build.planar && row.Type === slot);
|
||||
assert.ok(piece && capture.main_affixes.some(row => row.GroupID === piece.MainAffixGroup && row.AffixID === id));
|
||||
}
|
||||
});
|
||||
|
||||
test('all six relic slots use bundled game artwork and localized badge labels', async () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '../js/app.js'), 'utf8');
|
||||
const start = source.indexOf('const SLOT_NAMES_FULL =');
|
||||
const end = source.indexOf('function getRelicMainAffixName', start);
|
||||
assert.ok(start >= 0 && end > start);
|
||||
const context = vm.createContext({ t: key => key });
|
||||
vm.runInContext(source.slice(start, end) + '\nthis.slots = SLOT_NAMES_FULL;', context);
|
||||
const icons = new Set();
|
||||
for (let slot = 1; slot <= 6; slot++) {
|
||||
const icon = context.slots[slot].icon;
|
||||
assert.ok(fs.existsSync(path.resolve(__dirname, '..', icon)));
|
||||
icons.add(icon);
|
||||
}
|
||||
assert.equal(icons.size, 6);
|
||||
assert.match(source, /relic-slot-type-icon/);
|
||||
assert.match(source, /relic-slot-empty-icon/);
|
||||
const { TRANSLATIONS } = await import('../js/i18n.js');
|
||||
for (const language of ['th', 'en']) assert.match(TRANSLATIONS[language].relicSlotBadgeLabel, /\{slot\}/);
|
||||
});
|
||||
|
||||
test('equipment and trace labels do not embed emoji in any supported translation', async () => {
|
||||
const { TRANSLATIONS } = await import('../js/i18n.js');
|
||||
const keys = ['tabEquipment', 'modalAddLcTitle', 'modalAddRelicTitle', 'eqLcTitle', 'eqRelicTitle', 'overviewEquippedLc', 'overviewEquippedRelics', 'activeSetEffects', 'traceA2', 'traceA4', 'traceA6'];
|
||||
for (const [language, translations] of Object.entries(TRANSLATIONS)) {
|
||||
for (const key of keys) {
|
||||
if (translations[key]) assert.doesNotMatch(translations[key], /^[🌟✨🗡🛡]/u, `${language}: ${key}`);
|
||||
}
|
||||
}
|
||||
for (const language of ['th', 'en']) assert.match(TRANSLATIONS[language].relicSetPieces, /\{count\}/);
|
||||
});
|
||||
|
||||
test('captured relic templates reference matching client affix properties', () => {
|
||||
const capture = require('../assets/relic-client-capture.json');
|
||||
assert.deepEqual(capture.relics.map(row => row.ID), [63295, 63296, 63305, 63306]);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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');
|
||||
|
||||
function createEditor(apiCall, connected = true) {
|
||||
const source = fs.readFileSync(path.join(__dirname, '../js/app.js'), 'utf8');
|
||||
const start = source.indexOf('let jsonLoadGeneration =');
|
||||
const end = source.indexOf('// Save JSON Tab Data', start);
|
||||
assert.ok(start >= 0 && end > start);
|
||||
const el = {
|
||||
btnSelectFreesr: {}, btnSelectPersistent: {}, btnSelectDatagame: {},
|
||||
jsonEditor: { value: '' }, jsonStatusInfo: {}, btnJsonSave: {}
|
||||
};
|
||||
const state = { isConnected: connected, fullFreesrData: null, persistentData: null, dataGameData: null };
|
||||
const context = vm.createContext({
|
||||
el, state, apiCall, t: (key, vars = {}) => `${key}:${vars.file || ''}`,
|
||||
savePersistentCache() {}, saveFreesrCache() {}, saveDataGameCache() {},
|
||||
localStorage: { getItem() { return null; } }, DEFAULT_DATA_GAME: {}, DEFAULT_LINEUP: {}
|
||||
});
|
||||
vm.runInContext(source.slice(start, end) + '\nthis.loadJsonData = loadJsonData;', context);
|
||||
return { el, state, load: context.loadJsonData };
|
||||
}
|
||||
|
||||
test('switching JSON files reads the persistent response and the correct endpoint', async () => {
|
||||
const paths = [];
|
||||
const editor = createEditor(async url => {
|
||||
paths.push(url);
|
||||
return url === '/api/freesr' ? { data: { avatars: { 1: {} } } } : { persistent: { scene: 42, ex_ranks_unlocked: { 1: false } } };
|
||||
});
|
||||
await editor.load('freesr');
|
||||
assert.ok(JSON.parse(editor.el.jsonEditor.value).avatars);
|
||||
await editor.load('persistent');
|
||||
assert.deepEqual(JSON.parse(editor.el.jsonEditor.value), { scene: 42, ex_ranks_unlocked: { 1: false } });
|
||||
assert.deepEqual(paths, ['/api/freesr', '/api/persistent']);
|
||||
assert.equal(editor.el.btnJsonSave.disabled, false);
|
||||
});
|
||||
|
||||
test('unavailable persistent data clears previous file and prevents saving it', async () => {
|
||||
const editor = createEditor(async url => url === '/api/freesr' ? { data: { avatars: {} } } : { status: 500, error: 'unavailable' });
|
||||
await editor.load('freesr');
|
||||
await editor.load('persistent');
|
||||
assert.equal(editor.el.jsonEditor.value, '');
|
||||
assert.equal(editor.el.btnJsonSave.disabled, true);
|
||||
assert.match(editor.el.jsonStatusInfo.textContent, /jsonDataUnavailable:persistent/);
|
||||
});
|
||||
|
||||
test('late response cannot replace the newly selected file', async () => {
|
||||
let resolvePersistent;
|
||||
const editor = createEditor(url => url === '/api/persistent'
|
||||
? new Promise(resolve => { resolvePersistent = resolve; })
|
||||
: Promise.resolve({ data: { avatars: { current: {} } } }));
|
||||
const first = editor.load('persistent');
|
||||
await editor.load('freesr');
|
||||
resolvePersistent({ persistent: { scene: 42 } });
|
||||
await first;
|
||||
assert.ok(JSON.parse(editor.el.jsonEditor.value).avatars.current);
|
||||
assert.equal(editor.state.currentJsonMode, 'freesr');
|
||||
assert.equal(editor.el.btnJsonSave.disabled, false);
|
||||
});
|
||||
Reference in new issue
Block a user