Update dashboard controls and add command argument completion

This commit is contained in:
CAtlas0784 committed 2026-10-30 15:02:49 +07:00
1 parent b11121e27e
commit 6bb1b18d50
11 files changed
+1798 -54

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
// Regression check for the actual server envelopes and the independent selector.
const { readFileSync } = require('node:fs');
const { join } = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const source = readFileSync(join(__dirname, '..', 'js', 'app.js'), 'utf8');
const start = source.indexOf('async function loadAhaHelpSettings()');
const end = source.indexOf('// Render Ex-Rank Cards', start);
assert(start >= 0 && end > start, 'settings function boundaries');
async function run(persistent, helpFails = false) {
const select = { disabled: true, value: '', options: [], replaceChildren(...options) { this.options = options; }, add(option) { this.options.push(option); } };
const save = { disabled: true };
const status = { dataset: {} };
const statusText = { textContent: '' };
const el = { toggleSwGlobal: {}, toggleCastoriceGlobal: {}, toggleAhaGlobal: {}, persistentMcSelect: {}, persistentMarchSelect: {} };
const calls = [];
const context = vm.createContext({
state: { serverMode: 'gac', isConnected: true }, el,
document: { getElementById: id => ({
'aha-help-level': select,
'btn-save-aha-help': save,
'aha-help-status': status,
'aha-help-status-text': statusText
})[id] || null },
Option: function(text, value) { this.text = text; this.value = value; },
t: (key, vars = {}) => ({
ahaHelpLoading: 'Loading levels…', ahaHelpLegacyTitle: 'Use FreeSR setting',
ahaHelpOffTitle: 'Off', ahaHelpNoLevels: 'No levels', ahaHelpConnect: 'Connect to GaC',
ahaHelpStatusSaved: 'Saved', ahaHelpLoadError: 'Could not load levels: {error}',
enabled: 'enabled', disabled: 'disabled'
}[key] || key).replace(/\{(\w+)\}/g, (_, name) => vars[name] ?? `{${name}}`),
console: { warn() {} }, savePersistentCache() {},
apiCall: async endpoint => {
calls.push(endpoint);
if (endpoint === '/api/aha-help') {
if (helpFails) throw Error('HTTP 404');
return { status: 200, level: 0, levels: [{ BuffLevel: 1 }, { BuffLevel: 2 }, { BuffLevel: 3 }] };
}
return persistent;
}
});
vm.runInContext(source.slice(start, end), context);
await vm.runInContext('loadPersistentSettings()', context);
return { select, save, el, calls };
}
(async () => {
const result = await run({ status: 200, persistent: { enable_aha_global: false } });
assert.equal(result.select.disabled, false);
assert.equal(result.save.disabled, false);
assert.equal(result.select.value, '0');
assert.equal(result.select.options.length, 5);
assert.equal(result.el.toggleAhaGlobal.checked, false);
const withoutCharacterData = await run({ status: 200 });
assert.equal(withoutCharacterData.select.disabled, false, 'Aha loader must not depend on character cache');
const failed = await run({ status: 200, persistent: {} }, true);
assert.equal(failed.save.disabled, true);
assert.match(failed.select.options[0].text, /HTTP 404/);
console.log('Aha Help: server envelope, empty character cache, explicit off and API failure checks passed.');
})().catch(error => { console.error(error); process.exitCode = 1; });
+18
View File
@@ -0,0 +1,18 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { COMMANDS, inspectCommand, completeCommand } from '../js/command-assistant.mjs';
const catalogs={avatars:[{value:'1001',label:'March 7th'},{value:'1308',label:'Acheron'}],items:[{value:'101',label:'Item 101'}],planes:[{value:'20',label:'Plane 20'}],floors:[{value:'201',plane:20},{value:'301',plane:30}],cw_roles:[{value:'500',star:1},{value:'500',star:2},{value:'600',star:3}]};
test('every supported command has preview and help',()=>{
for(const cmd of COMMANDS){const value=cmd.path.join(' ')+' ';const m=inspectCommand(value,value.length,catalogs);assert.ok(m.command);assert.equal(m.command,cmd);assert.ok(m.preview.includes(cmd.path[0]));assert.ok(m.hint);}
});
test('all roots are browsable, no six-row truncation',()=>{const m=inspectCommand('/',1);assert.ok(m.suggestions.length>6);assert.ok(m.suggestions.some(s=>s.value==='/faketime'));assert.ok(m.suggestions.some(s=>s.value==='/cw'));});
test('Tab completion advances through command, avatar, level, rank without running',()=>{
let text='/ava';let m=inspectCommand(text,text.length,catalogs);let r=completeCommand(text,m,m.suggestions[0]);assert.equal(r.value,'/avatar ');
m=inspectCommand(r.value,r.caret,catalogs);assert.equal(m.active.name,'id');r=completeCommand(r.value,m,m.suggestions[0]);assert.equal(r.value,'/avatar 1001 ');
m=inspectCommand(r.value,r.caret,catalogs);assert.equal(m.active.name,'level');r=completeCommand(r.value,m,m.suggestions.find(x=>x.value==='80'));
m=inspectCommand(r.value,r.caret,catalogs);assert.equal(m.active.name,'rank');assert.equal(m.suggestions.length,7);
});
test('find by entity name, replace only token at caret, preserve suffix',()=>{const text='/avatar ach 80 6';const m=inspectCommand(text,11,catalogs);assert.equal(m.suggestions[0].value,'1308');const r=completeCommand(text,m,m.suggestions[0]);assert.equal(r.value,'/avatar 1308 80 6');});
test('CW nested options and resource-backed stars',()=>{let m=inspectCommand('/cw ',4,catalogs);assert.ok(m.suggestions.some(s=>s.value==='gold'));m=inspectCommand('/cw role 500 ',13,catalogs);assert.deepEqual(m.suggestions.map(s=>s.value),['1','2']);});
test('floors belong to selected plane and required arguments remain visible',()=>{let m=inspectCommand('/teleport 20 ',13,catalogs);assert.deepEqual(m.suggestions.map(s=>s.value),['201']);m=inspectCommand('/give ',6,catalogs);assert.deepEqual(m.missing,['item_id']);assert.equal(m.active.name,'item_id');});
test('faketime optional modes and unknown commands do not fabricate matches',()=>{let m=inspectCommand('/faketime ',10,catalogs);assert.ok(m.suggestions.some(s=>s.value==='off'));m=inspectCommand('/nope',5,catalogs);assert.equal(m.command,null);assert.equal(m.suggestions.length,0);});