mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
- Saving the security settings threw: the form's values were named security, hiding the Security object the new token goes to. A save from None to None no longer reconnects, as the device closes nobody then. - Each sync redraws the lists and reloads the settings, so a reconnect shows what changed meanwhile; closing the config panel drops its feeds even while the socket is down. - A 401 on backup or an upload opens the login, as a refused socket does (Security.tokenRefused); a failed firmware upload says so. - The general and security panels fill in what the device leaves out at its default, so a save never writes back what the form showed before. The hostname holds 31 characters, as the device does. - The views (settings with defaults, a group's shades, frames, scans, the network and radio panels, op errors) move to data-src/js/views.js, with tests. runOp is the one op path with the busy retry; repeatOf, commandArgs, gone(); dead update*List and editedRepeaters removed. - Tests for ProtoClient.off and a listener removing itself mid-event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
4 KiB
JavaScript
82 lines
4 KiB
JavaScript
// What the web UI's panels bind (data-src/js/views.js), from protocol 2's
|
|
// shapes.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { ProtoViews } = require('../../data-src/js/views.js');
|
|
const { ProtoModel } = require('../../data-src/js/model.js');
|
|
const { recordedSchema, deviceSections } = require('./helpers.js');
|
|
|
|
const schema = recordedSchema();
|
|
function synced() {
|
|
const model = new ProtoModel(schema);
|
|
model.beginSync();
|
|
for (const d of deviceSections(schema)) model.applySync(d);
|
|
model.endSync();
|
|
return model;
|
|
}
|
|
|
|
test('a settings section has every field, defaults where the device left them out', () => {
|
|
// ssdpBroadcast off and no NTP server are at their defaults, so not sent.
|
|
assert.deepEqual(ProtoViews.settings(schema, 'general', { hostname: 'Somfy' }),
|
|
{ hostname: 'Somfy', ssdpBroadcast: false, ntpServer: '', posixZone: '' });
|
|
assert.deepEqual(ProtoViews.settings(schema, 'security', {}), { securityType: 0, username: '', hasPassword: false, hasPin: false });
|
|
});
|
|
|
|
test('the settings sections are what the device sends', () => {
|
|
for (const section of Object.keys(ProtoViews.SETTINGS))
|
|
for (const name of ProtoViews.SETTINGS[section]) assert.ok(schema.keys[name], `${section}.${name}`);
|
|
});
|
|
|
|
test('a group shows its shades whole', () => {
|
|
const m = synced();
|
|
const group = m.groupList()[0];
|
|
const view = ProtoViews.group(m, group);
|
|
assert.equal(view.linkedShades.length, group.linkedShades.length);
|
|
assert.equal(view.linkedShades[0].name, m.shades.get(group.linkedShades[0]).name);
|
|
// The model's item is left as it is.
|
|
assert.equal(typeof group.linkedShades[0], 'number');
|
|
// A shade the model hasn't got yet still shows.
|
|
assert.equal(ProtoViews.group(m, Object.assign({}, group, { linkedShades: [250] })).linkedShades[0].shadeId, 250);
|
|
assert.equal(ProtoViews.group(m, undefined), undefined);
|
|
});
|
|
|
|
test('a frame and a scan under the names the UI reads', () => {
|
|
const f = ProtoViews.frame(schema, { remoteAddress: 5, lastRollingCode: 9, command: 'Up', rssi: -60, valid: true, pulses: [1, 2] });
|
|
assert.equal(f.address, 5);
|
|
assert.equal(f.rcode, 9);
|
|
assert.equal(f.encKey, 0);
|
|
assert.deepEqual(f.pulses, [1, 2]);
|
|
assert.equal(f.stepSize, undefined);
|
|
const s = ProtoViews.scan(schema, { scanning: true, testFreq: 433.1, testRssi: -80 });
|
|
assert.deepEqual(s, { scanning: true, testFreq: 433.1, testRSSI: -80, frequency: 0, RSSI: 0 });
|
|
});
|
|
|
|
test('the network section as the panel binds it', () => {
|
|
const v = ProtoViews.network(schema, { connMode: 1, ssid: 'home', hasPassphrase: true, dhcp: true, PWRPin: -1 });
|
|
assert.equal(v.connType, 1);
|
|
assert.equal(v.hasEthernet, false);
|
|
assert.deepEqual(v.wifi, { ssid: 'home', hasPassphrase: true, roaming: false, hidden: false });
|
|
assert.equal(v.ethernet.PWRPin, -1);
|
|
assert.deepEqual(v.ip, { dhcp: true, ip: '', gateway: '', subnet: '', dns1: '', dns2: '' });
|
|
});
|
|
|
|
test('the radio section as the panel binds it, and back', () => {
|
|
const config = ProtoViews.radio(schema, { bitLength: 80, frequency: 433.42, enabled: true, radioInit: true });
|
|
assert.equal(config.type, 80);
|
|
assert.equal(config.bitLength, undefined);
|
|
assert.equal(config.TXPin, 0);
|
|
const save = ProtoViews.radioSave(config);
|
|
assert.equal(save.section, 'radio');
|
|
assert.equal(save.bitLength, 80);
|
|
assert.equal(save.frequency, 433.42);
|
|
// Only shown, never saved.
|
|
assert.equal('radioInit' in save, false);
|
|
assert.equal('type' in save, false);
|
|
});
|
|
|
|
test('an op refusal for the error box, with a shade validation code', () => {
|
|
assert.deepEqual(ProtoViews.opError('saveShade', { code: 'badRequest', desc: 'Pin in use', data: { validation: -10 } }),
|
|
{ htmlError: 'badRequest', service: 'saveShade', desc: 'Pin in use', code: -10 });
|
|
assert.deepEqual(ProtoViews.opError('reboot', { code: 'closed', desc: 'The connection closed' }),
|
|
{ htmlError: 'closed', service: 'reboot', desc: 'The connection closed' });
|
|
});
|