Files
meshcore-analyzer/test-payload-labels-namespace.js
b74a64ccfa fix(ui): canonical payload label map across packets/live/packet-filter (#1799) (#1804)
## Summary

Replaces the three drifted per-surface payload-type label vocabularies
with a single canonical map keyed by firmware enum name.

Per the locked triage comment on #1799
([comment-4823975431](https://github.com/Kpa-clawbot/CoreScope/issues/1799#issuecomment-4823975431)):

> Create `public/payload-labels.js` exporting `{GRP_DATA: {short:'Group
Data', long:'Group data packet', enumId:6}, ...}`. Migrate `packets.js
typeMap`, `packet-filter.js FW_PAYLOAD_TYPES`, `live.js TYPE_COLORS
legend` to consume it. E2E that scrapes each surface and asserts label
equality.

## Changes

- **`public/payload-labels.js`** (new) — canonical map exposed as
`window.PayloadLabels` and `window.PayloadLabelsApi`. Keys are firmware
enum names; values carry `{short, long, enumId}` plus derived
`SHORT_BY_ID` / `FW_PAYLOAD_TYPES` / `TYPE_ALIASES` for legacy callers.
- **`public/packets.js`** — `TYPE_NAMES` + `typeMap` now read from
`PayloadLabelsApi.SHORT_BY_ID`. Literal kept only as a defensive
fallback for the case where the script tag fails to load.
- **`public/packet-filter.js`** — `FW_PAYLOAD_TYPES` + `TYPE_ALIASES`
now sourced from `PayloadLabelsApi`. Literal fallback retained so `node
test-packet-filter.js` still works headlessly.
- **`public/live.js`** — legend `<li>` rows are now generated from
`window.PayloadLabels` in stable order, killing the third-vocabulary
`Message — Group text` / `Direct — Direct message` drift the #1797
review surfaced.
- **`public/index.html`** — `<script src="payload-labels.js">` loaded
before `roles.js` / `packet-filter.js` / `packets.js`.
- **`test-issue-1799-label-vocab-e2e.js`** (new) — Playwright E2E.
Scrapes `#liveLegend` rows and the `/packets` type-filter checklist,
asserts each label matches `window.PayloadLabels[ENUM].short` for
`TXT_MSG`, `GRP_TXT`, `GRP_DATA`. Also verifies `window.PacketFilter`
still recognises the enum names.
- **`.github/workflows/deploy.yml`** — wired the new E2E into the
existing Playwright block.

## TDD trail

- Red commit `eb392d4` — adds the failing E2E only (asserts
`window.PayloadLabels` exists and labels match; both fail).
- Green commit `44e902a` — introduces the canonical map and migrates the
three surfaces.

## Verification

- `node test-packet-filter.js` — 92/92 pass with the new fallback
wiring.
- Preflight: `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh
origin/master` — clean.

Browser verified: E2E `test-issue-1799-label-vocab-e2e.js` exercises
`/live` legend + `/packets` type filter against a Playwright headless
Chromium; CI's Playwright block runs it on every push.

E2E assertion added: `test-issue-1799-label-vocab-e2e.js:139` —
`assert(fromLegend === canon, ...)` and `assert(fromPackets === canon,
...)` per enum.

Fixes #1799

---------

Co-authored-by: mc-bot <bot@corescope>
Co-authored-by: openclaw-bot <bot@openclaw.local>
Co-authored-by: clawbot <clawbot@kpa.com>
Co-authored-by: clawbot <bot@clawbot.local>
2026-06-30 05:48:47 -07:00

112 lines
4.7 KiB
JavaScript

/* Unit test for the PayloadLabels namespace restructure (PR #1804 r1
* items 8 & 9).
*
* Item 8: canonical enum map at window.PayloadLabels.enums[ENUM] and
* helpers/derived at window.PayloadLabels.api — direct-prop
* collisions (e.g. an enum named BY_ID or ORDER) become
* impossible.
* Item 9: payload-labels.js is loaded synchronously before its
* consumers in index.html. Drop the inline fallbacks in
* packets.js / packet-filter.js / live.js and add a top-of-file
* guard so a missing module is a loud crash, not silent stale
* data.
*/
'use strict';
const fs = require('fs');
const vm = require('vm');
const labelsSrc = fs.readFileSync('public/payload-labels.js', 'utf8');
const packetsSrc = fs.readFileSync('public/packets.js', 'utf8');
const filterSrc = fs.readFileSync('public/packet-filter.js', 'utf8');
const liveSrc = fs.readFileSync('public/live.js', 'utf8');
const ctx = { window: {}, console };
vm.createContext(ctx);
vm.runInContext(labelsSrc, ctx);
const PL = ctx.window.PayloadLabels;
let pass = 0, fail = 0;
function test(name, fn) {
try { fn(); pass++; console.log(' \u2713 ' + name); }
catch (e) { fail++; console.log(' \u2717 ' + name + ' — ' + e.message); }
}
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
const EXPECTED_ENUMS = [
'REQ','RESPONSE','TXT_MSG','ACK','ADVERT','GRP_TXT','GRP_DATA',
'ANON_REQ','PATH','TRACE','MULTIPART','CONTROL','RAW_CUSTOM'
];
console.log('=== PayloadLabels namespace (item 8) ===');
test('window.PayloadLabels exposes .enums namespace', () => {
assert(PL && typeof PL.enums === 'object', 'PayloadLabels.enums missing');
for (const k of EXPECTED_ENUMS) {
assert(PL.enums[k], 'PayloadLabels.enums.' + k + ' missing');
assert(PL.enums[k].short, 'PayloadLabels.enums.' + k + '.short missing');
assert(PL.enums[k].enumId !== undefined, 'PayloadLabels.enums.' + k + '.enumId missing');
}
});
test('window.PayloadLabels exposes .api namespace with helpers', () => {
assert(PL.api && typeof PL.api === 'object', 'PayloadLabels.api missing');
assert(typeof PL.api.shortById === 'function', 'PayloadLabels.api.shortById missing');
assert(typeof PL.api.longById === 'function', 'PayloadLabels.api.longById missing');
assert(typeof PL.api.enumNameById === 'function', 'PayloadLabels.api.enumNameById missing');
assert(PL.api.SHORT_BY_ID, 'PayloadLabels.api.SHORT_BY_ID missing');
assert(PL.api.FW_PAYLOAD_TYPES, 'PayloadLabels.api.FW_PAYLOAD_TYPES missing');
assert(PL.api.TYPE_ALIASES, 'PayloadLabels.api.TYPE_ALIASES missing');
assert(Array.isArray(PL.api.ORDER), 'PayloadLabels.api.ORDER missing');
});
test('legacy direct-prop access still works (back-compat)', () => {
// Existing callers (live.js, customize*.js, the E2E that pins literals)
// read pl[ENUM].short directly. Keep that working — restructure adds
// .enums/.api, doesn't break the direct shape.
for (const k of EXPECTED_ENUMS) {
assert(PL[k] && PL[k].short, 'legacy PayloadLabels.' + k + '.short broken');
assert(PL[k] === PL.enums[k], 'legacy ' + k + ' should alias .enums.' + k);
}
// Helpers still reachable at root for back-compat too.
assert(PL.SHORT_BY_ID, 'legacy PayloadLabels.SHORT_BY_ID missing');
assert(PL.ORDER, 'legacy PayloadLabels.ORDER missing');
});
console.log('=== inline-fallback maps (item 9) ===');
test('packets.js: DEFAULT_TYPE_NAMES inline fallback is gone', () => {
assert(!/DEFAULT_TYPE_NAMES\s*=\s*\{/.test(packetsSrc),
'packets.js still declares DEFAULT_TYPE_NAMES fallback map');
});
test('packets.js: hard guard for missing PayloadLabels', () => {
// Must throw, not silently fall back.
assert(/!\s*window\.PayloadLabels[\s\S]{0,80}throw/.test(packetsSrc),
'packets.js missing `!window.PayloadLabels → throw` guard');
});
test('packet-filter.js: _FALLBACK_FW + _FALLBACK_ALIASES gone', () => {
assert(!/_FALLBACK_FW\s*=\s*\{/.test(filterSrc),
'packet-filter.js still declares _FALLBACK_FW');
assert(!/_FALLBACK_ALIASES\s*=\s*\{/.test(filterSrc),
'packet-filter.js still declares _FALLBACK_ALIASES');
});
test('packet-filter.js: hard guard for missing PayloadLabels', () => {
assert(/!\s*window\.PayloadLabels[\s\S]{0,120}throw/.test(filterSrc),
'packet-filter.js missing throw-guard');
});
test('live.js buildLegendHtml: INLINE_LABELS fallback is gone', () => {
assert(!/INLINE_LABELS\s*=\s*\{/.test(liveSrc),
'live.js still declares INLINE_LABELS fallback inside buildLegendHtml');
});
test('live.js: hard guard for missing PayloadLabels at top of file', () => {
assert(/!\s*window\.PayloadLabels[\s\S]{0,120}throw/.test(liveSrc),
'live.js missing throw-guard');
});
console.log('=== ' + pass + ' passed, ' + fail + ' failed ===');
process.exit(fail === 0 ? 0 : 1);