mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 06:45:09 +00:00
* feat(web): Schedule and General become ES-module pages (stage 3) Schedule and General follow stage 2 (#727): no inline scripts or inline handlers in either partial. Their code moves to static/v3/js/pages/schedule.js and pages/general.js, started per swap-in by the page registry. - Schedule: both pickers are drawn from the saved config carried as JSON in data-* attributes. The forms' hx-on save handlers become one htmx:afterRequest listener on the page; the forms are marked data-reports-result, which app.js now treats like an hx-on after-request handler, so a save still shows one notification. - General: the timezone picker reads data-timezone. The Security section's forms and buttons are delegated data-actions; requests go through core/api.js, so the login redirect is quiet, and a change made just before a swap is still reported. - handleScheduleResponse, handleDimScheduleResponse and webLogin stay as deprecated aliases through window.LEDMatrix. - New DOM suites test_schedule_page.js and test_general_page.js; the web login unit suite imports the module; test_es_modules.py pins the pages, the aliases, and the schedule config's round trip through its attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(web): no unused catch bindings or computed writes in the stage 3 modules Codacy flagged two unused catch variables and dynamic-key writes in pages/schedule.js and boot.js. The schedule config is read with getAttribute, and the default days and the webLogin alias object are built with Object.fromEntries. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.7 KiB
JavaScript
95 lines
3.7 KiB
JavaScript
// Creating an API token on the General tab must leave its form clean.
|
|
//
|
|
// app.js marks a form data-dirty on any input in it and clears the mark only
|
|
// after a successful htmx request; its beforeunload handler then asks "Leave
|
|
// site?" while any visible form is still dirty. The token form posts with
|
|
// fetch (createToken in static/v3/js/pages/general.js), so after a token was
|
|
// created the form stayed dirty and reloading the page while the General tab
|
|
// was open prompted about changes that had been saved.
|
|
//
|
|
// Imports the shipped page module and runs createToken with a fake fetch and
|
|
// DOM -- no jsdom and no server needed.
|
|
|
|
const path = require('path');
|
|
const { pathToFileURL } = require('url');
|
|
|
|
const JS = path.resolve(__dirname, '../../../web_interface/static/v3/js');
|
|
const load = f => import(pathToFileURL(path.join(JS, f)).href);
|
|
|
|
let pass = 0, fail = 0;
|
|
const ok = (label, cond, extra) => cond
|
|
? (pass++, console.log(' ok ' + label))
|
|
: (fail++, console.log(' FAIL ' + label + (extra !== undefined ? ' ' + JSON.stringify(extra) : '')));
|
|
|
|
function el() {
|
|
const classes = new Set(['hidden']);
|
|
return {
|
|
textContent: '', dataset: {}, style: {}, className: '',
|
|
classList: { add: c => classes.add(c), remove: c => classes.delete(c), contains: c => classes.has(c) },
|
|
appendChild() {}, addEventListener() {}, querySelector: () => null,
|
|
};
|
|
}
|
|
|
|
async function setup(answer) {
|
|
const { createApi } = await load('core/api.js');
|
|
const general = await load('pages/general.js');
|
|
const elements = {
|
|
'#web-login-tokens': el(),
|
|
'#web-login-new-token-value': el(),
|
|
'#web-login-new-token': el(),
|
|
};
|
|
const notes = [];
|
|
const doc = { createElement: () => el(), defaultView: { confirm: () => true } };
|
|
const root = { ownerDocument: doc, querySelector: sel => elements[sel] || null, querySelectorAll: () => [] };
|
|
const fetch = () => Promise.resolve({
|
|
ok: answer.ok, status: answer.ok ? 200 : 400,
|
|
headers: { get: () => null },
|
|
text: () => Promise.resolve(JSON.stringify(answer.body)),
|
|
});
|
|
const ctx = {
|
|
root, state: {}, signal: { aborted: false },
|
|
api: createApi({ fetch }),
|
|
notify: (m, t) => notes.push([m, t]),
|
|
};
|
|
return { general, ctx, elements, notes };
|
|
}
|
|
|
|
function dirtyForm() {
|
|
const attrs = new Map([['data-dirty', '']]);
|
|
return {
|
|
querySelector: sel => (sel === '[name="name"]' ? { value: 'Home Assistant' } : null),
|
|
reset() {},
|
|
hasAttribute: name => attrs.has(name),
|
|
setAttribute: (name, value) => attrs.set(name, String(value)),
|
|
removeAttribute: name => attrs.delete(name),
|
|
};
|
|
}
|
|
|
|
(async () => {
|
|
console.log('\n── General tab: API token form ──');
|
|
|
|
{
|
|
const t = await setup({ ok: true, body: {
|
|
status: 'success', message: 'Token created',
|
|
data: { token: 'lmx_secret', record: { id: 't1', name: 'Home Assistant', prefix: 'lmx_sec' } },
|
|
} });
|
|
const form = dirtyForm();
|
|
await t.general.createToken(t.ctx, form);
|
|
ok('the new token is shown', t.elements['#web-login-new-token-value'].textContent === 'lmx_secret');
|
|
ok('a created token leaves the form clean (no "Leave site?" on reload)',
|
|
!form.hasAttribute('data-dirty'));
|
|
}
|
|
|
|
{
|
|
const t = await setup({ ok: false, body: { status: 'error', message: 'Name is required' } });
|
|
const form = dirtyForm();
|
|
await t.general.createToken(t.ctx, form);
|
|
ok('a refused request reports the error', t.notes.some(([m, type]) => type === 'error' && /Name is required/.test(m)),
|
|
t.notes);
|
|
ok('...and keeps the form dirty: nothing was saved', form.hasAttribute('data-dirty'));
|
|
}
|
|
|
|
console.log(`\n${pass} passed, ${fail} failed\n`);
|
|
process.exit(fail ? 1 : 0);
|
|
})().catch(e => { console.log('HARNESS ERROR: ' + e.stack); process.exit(1); });
|