mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 18:09:00 +00:00
## 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>
165 lines
6.9 KiB
JavaScript
165 lines
6.9 KiB
JavaScript
/**
|
|
* #1380 — Colorblind a11y stretch goal: Brettel/Vienot SVG simulation overlay.
|
|
*
|
|
* Deferred from #1361 / PR #1378. This test enforces the overlay wiring:
|
|
* - public/index.html contains inline SVG defs with filter ids
|
|
* cb-deut, cb-prot, cb-trit, cb-achromat (Brettel/Vienot 1997
|
|
* dichromatic matrices via <feColorMatrix>).
|
|
* - A CSS rule selects body[data-cb-sim="<class>"] and applies
|
|
* filter:url(#cb-<class>) to a top-level wrapper (body or #app).
|
|
* - public/customize-v2.js renders an "off/deut/prot/trit/achromat"
|
|
* radio selector marked with data-cv2-cb-sim and the change handler
|
|
* toggles body[data-cb-sim].
|
|
*
|
|
* Pure-string + vm.createContext assertions, mirrors test-issue-1361.
|
|
* Persistence is intentionally NOT asserted (preview-only per spec).
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
|
|
let passed = 0, failed = 0;
|
|
function assert(cond, msg) {
|
|
if (cond) { passed++; console.log(' \u2713 ' + msg); }
|
|
else { failed++; console.error(' \u2717 ' + msg); }
|
|
}
|
|
|
|
const indexSrc = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
|
|
const customSrc = fs.readFileSync(path.join(__dirname, 'public', 'customize-v2.js'), 'utf8');
|
|
const labelsSrc = fs.readFileSync(path.join(__dirname, 'public', 'payload-labels.js'), 'utf8');
|
|
|
|
console.log('\n=== #1380 A: index.html has inline SVG filters for the 4 sim classes ===');
|
|
['cb-deut', 'cb-prot', 'cb-trit', 'cb-achromat'].forEach(function (id) {
|
|
var re = new RegExp('<filter[^>]*id=["\']' + id + '["\']', 'i');
|
|
assert(re.test(indexSrc), 'index.html contains <filter id="' + id + '">');
|
|
});
|
|
assert(/<feColorMatrix[^>]*type=["']matrix["']/i.test(indexSrc),
|
|
'index.html SVG defs use <feColorMatrix type="matrix"> (Brettel/Vienot 1997 form)');
|
|
|
|
console.log('\n=== #1380 B: CSS rule wires body[data-cb-sim] to filter:url(#cb-*) ===');
|
|
['deut', 'prot', 'trit', 'achromat'].forEach(function (cls) {
|
|
var re = new RegExp('body\\[data-cb-sim=["\']' + cls + '["\']\\][^{]*\\{[^}]*filter:\\s*url\\(#cb-' + cls + '\\)', 'i');
|
|
assert(re.test(indexSrc),
|
|
'body[data-cb-sim="' + cls + '"] rule applies filter:url(#cb-' + cls + ')');
|
|
});
|
|
|
|
console.log('\n=== #1380 C: customize-v2.js renders the cv2-cb-sim radio group ===');
|
|
assert(/data-cv2-cb-sim/.test(customSrc),
|
|
'customize-v2.js exposes a data-cv2-cb-sim hook for the sim radio group');
|
|
assert(/name=["']cv2-cb-sim["']/.test(customSrc),
|
|
'customize-v2.js radio inputs use name="cv2-cb-sim"');
|
|
['', 'deut', 'prot', 'trit', 'achromat'].forEach(function (val) {
|
|
var re = new RegExp('value=["\']' + val + '["\']', 'i');
|
|
// The empty value is the "off" radio; assert it explicitly.
|
|
if (val === '') {
|
|
assert(/data-cv2-cb-sim[^>]*value=["']["']/.test(customSrc) ||
|
|
/value=["']["'][^>]*data-cv2-cb-sim/.test(customSrc),
|
|
'customize-v2.js radio has an "off" option (value="")');
|
|
} else {
|
|
assert(re.test(customSrc),
|
|
'customize-v2.js radio has value="' + val + '"');
|
|
}
|
|
});
|
|
|
|
console.log('\n=== #1380 D: change handler toggles body[data-cb-sim] attribute ===');
|
|
assert(/setAttribute\(\s*['"]data-cb-sim['"]/.test(customSrc),
|
|
'customize-v2.js change handler calls setAttribute("data-cb-sim", ...)');
|
|
assert(/removeAttribute\(\s*['"]data-cb-sim['"]/.test(customSrc),
|
|
'customize-v2.js change handler can clear body[data-cb-sim] when "off"');
|
|
|
|
console.log('\n=== #1380 E: vm.createContext — selecting deut applies the attribute ===');
|
|
// Build a minimal sandbox, eval customize-v2.js, drive the radio change.
|
|
function makeSandbox() {
|
|
const stored = {};
|
|
const ls = {
|
|
getItem(k) { return Object.prototype.hasOwnProperty.call(stored, k) ? stored[k] : null; },
|
|
setItem(k, v) { stored[k] = String(v); },
|
|
removeItem(k) { delete stored[k]; },
|
|
};
|
|
const body = {
|
|
_attrs: {},
|
|
setAttribute(k, v) { this._attrs[k] = v; },
|
|
getAttribute(k) { return Object.prototype.hasOwnProperty.call(this._attrs, k) ? this._attrs[k] : null; },
|
|
removeAttribute(k) { delete this._attrs[k]; },
|
|
dataset: {},
|
|
};
|
|
const handlers = {};
|
|
const sandbox = {
|
|
window: null,
|
|
document: {
|
|
readyState: 'complete',
|
|
body: body,
|
|
documentElement: {
|
|
style: { setProperty() {}, removeProperty() {}, getPropertyValue() { return ''; } },
|
|
dataset: { theme: 'light' },
|
|
getAttribute() { return 'light'; },
|
|
setAttribute() {},
|
|
},
|
|
head: { appendChild() {} },
|
|
getElementById() { return null; },
|
|
createElement() {
|
|
return {
|
|
id: '', textContent: '', innerHTML: '', className: '',
|
|
setAttribute() {}, appendChild() {}, style: {},
|
|
addEventListener() {}, querySelectorAll() { return []; }, querySelector() { return null; },
|
|
};
|
|
},
|
|
addEventListener(ev, cb) { (handlers[ev] = handlers[ev] || []).push(cb); },
|
|
querySelectorAll() { return []; },
|
|
querySelector() { return null; },
|
|
},
|
|
localStorage: ls,
|
|
console: console,
|
|
setTimeout(fn) { try { fn(); } catch (e) {} },
|
|
clearTimeout() {},
|
|
MutationObserver: class { observe() {} },
|
|
HashChangeEvent: class {},
|
|
CustomEvent: class { constructor(t, o) { this.type = t; this.detail = o && o.detail; } },
|
|
Event: class { constructor(t) { this.type = t; } },
|
|
getComputedStyle() { return { getPropertyValue() { return ''; } }; },
|
|
};
|
|
sandbox.window = {
|
|
addEventListener(ev, cb) { (handlers[ev] = handlers[ev] || []).push(cb); },
|
|
dispatchEvent(ev) { (handlers[ev.type] || []).forEach(function (cb) { try { cb(ev); } catch (_) {} }); return true; },
|
|
localStorage: ls,
|
|
SITE_CONFIG: {},
|
|
location: { hash: '', pathname: '/' },
|
|
CustomEvent: sandbox.CustomEvent,
|
|
Event: sandbox.Event,
|
|
matchMedia() { return { matches: false, addEventListener() {} }; },
|
|
};
|
|
sandbox.self = sandbox.window;
|
|
return { sandbox, body, ls };
|
|
}
|
|
|
|
let envOK = false, env, exposed;
|
|
try {
|
|
env = makeSandbox();
|
|
vm.createContext(env.sandbox);
|
|
vm.runInContext(labelsSrc, env.sandbox, { filename: 'payload-labels.js' });
|
|
vm.runInContext(customSrc, env.sandbox, { filename: 'customize-v2.js' });
|
|
exposed = env.sandbox.window._customizerV2;
|
|
envOK = !!exposed;
|
|
} catch (e) {
|
|
console.error(' ! customize-v2.js failed to load in vm sandbox: ' + e.message);
|
|
}
|
|
assert(envOK, 'customize-v2.js loads in vm sandbox and exposes window._customizerV2');
|
|
|
|
// Drive the handler directly — exposed for tests.
|
|
if (envOK && typeof exposed.applyCbSim === 'function') {
|
|
exposed.applyCbSim('deut');
|
|
assert(env.body.getAttribute('data-cb-sim') === 'deut',
|
|
'applyCbSim("deut") sets body[data-cb-sim="deut"]');
|
|
exposed.applyCbSim('');
|
|
assert(env.body.getAttribute('data-cb-sim') === null,
|
|
'applyCbSim("") clears body[data-cb-sim]');
|
|
} else {
|
|
assert(false, 'customize-v2.js exposes applyCbSim() helper for test-driven toggling');
|
|
}
|
|
|
|
console.log('\n=== #1380 summary ===');
|
|
console.log(' passed: ' + passed + ' failed: ' + failed);
|
|
if (failed > 0) process.exit(1);
|