mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 18:27:58 +00:00
## Summary Adds **Firmware** and **Client** columns to the observers table (`#/observers`). Both values already come back from `/api/observers` (`firmware`, `client_version`) — they were just never rendered. Fleet operators have been asking to sort/scan firmware versions to coordinate upgrades. Closes #1789. ## Changes - `public/observers.js` - Two new `<th data-priority="4" data-sort-key="...">` headers (Firmware, Client). Priority 4 matches Clock Offset / Uptime so `TableResponsive` hides them first on narrow viewports. - Two new `<td class="mono">` cells with `data-value="${escapeHtml(raw)}"` for sort and the rendered text escape-wrapped. - `truncateBuildSuffix()` helper trims the long `" Build: ..."` tail from firmware in the displayed text; the full string is preserved in `title=` for hover. - `test-issue-1789-observer-firmware-cols.js` — TDD red→green static-source regression test (same pattern as `test-observers-headings.js`). - `test-observers-headings.js` — updated expected heading list with the two new columns (existing #1039 invariant test). - `test-all.sh` — wires the new test into CI. ## TDD evidence - Red: `c6c4e594c1084d664730666ba069871ac6d9755c` — test commit fails on assertions (not import errors): 5/6 cases fail because the headers/cells/title attr don't yet exist; the column-count invariant still passes because both thead and tbody are unmodified. - Green: `02a0246a185950c903828ac1225d6795f5a3b2f4` — implementation; all 6 cases pass. ## Browser verified To be verified post-deploy on staging (`http://analyzer-stg.00id.net/#/observers`). No backend changes — purely additive frontend render of fields that are already on the wire. ## Perf No new API calls, no extra fetches, two extra template-literal cells per observer row (~10s of observers in prod). O(n) render unchanged. --------- Co-authored-by: clawbot <bot@meshcore.local>
69 lines
2.6 KiB
JavaScript
69 lines
2.6 KiB
JavaScript
/* test-observers-headings.js — Issue #1039 regression test.
|
|
* Asserts observer table thead column count matches tbody row column count.
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const assert = require('assert');
|
|
|
|
const src = fs.readFileSync(path.join(__dirname, 'public', 'observers.js'), 'utf8');
|
|
|
|
let passed = 0, failed = 0;
|
|
function test(name, fn) {
|
|
try { fn(); passed++; console.log(` ✓ ${name}`); }
|
|
catch (e) { failed++; console.log(` ✗ ${name}\n ${e.message}`); }
|
|
}
|
|
|
|
function extractBlock(s, openRe, closeRe) {
|
|
const m = s.match(openRe);
|
|
if (!m) throw new Error('open marker not found');
|
|
const start = m.index + m[0].length;
|
|
const rest = s.slice(start);
|
|
const cm = rest.match(closeRe);
|
|
if (!cm) throw new Error('close marker not found');
|
|
return rest.slice(0, cm.index);
|
|
}
|
|
|
|
console.log('── Observers table headings (#1039) ──');
|
|
|
|
test('thead column count equals tbody row column count', () => {
|
|
const thead = extractBlock(src, /<thead><tr>/, /<\/tr><\/thead>/);
|
|
const thCount = (thead.match(/<th\b/g) || []).length;
|
|
|
|
// tbody row template lives inside a backtick-template `<tr ...>...</tr>`.
|
|
// Grab from the first `<tr ` after `tbody>` up to the first `</tr>`.
|
|
const tbodyStart = src.indexOf('<tbody>');
|
|
assert.ok(tbodyStart > 0, '<tbody> not found in observers.js');
|
|
const after = src.slice(tbodyStart);
|
|
const trOpen = after.search(/`<tr\b/);
|
|
assert.ok(trOpen > 0, 'row template `<tr` not found');
|
|
const rowStart = trOpen;
|
|
const rowEnd = after.indexOf('</tr>', rowStart);
|
|
assert.ok(rowEnd > rowStart, '</tr> not found in row template');
|
|
const row = after.slice(rowStart, rowEnd);
|
|
const tdCount = (row.match(/<td\b/g) || []).length;
|
|
|
|
assert.strictEqual(
|
|
tdCount, thCount,
|
|
`Observer table column mismatch: ${thCount} <th> headings vs ${tdCount} <td> cells per row. ` +
|
|
`Headings drift after "Last Packet" — see issue #1039.`
|
|
);
|
|
});
|
|
|
|
test('expected headings present and ordered', () => {
|
|
const thead = extractBlock(src, /<thead><tr>/, /<\/tr><\/thead>/);
|
|
const labels = [];
|
|
const re = /<th[^>]*>([^<]+)<\/th>/g;
|
|
let m;
|
|
while ((m = re.exec(thead)) !== null) labels.push(m[1].trim());
|
|
const expected = ['Status', 'Name', 'Region', 'Last Status', 'Last Packet',
|
|
'Packet Health', 'Total Packets', 'Packets/Hour', 'Clock Offset', 'Uptime',
|
|
'Firmware', 'Client'];
|
|
assert.deepStrictEqual(labels, expected,
|
|
`Headings out of sync.\nGot: ${JSON.stringify(labels)}\nExpected: ${JSON.stringify(expected)}`);
|
|
});
|
|
|
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
process.exit(failed === 0 ? 0 : 1);
|