mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 17:27:57 +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>
120 lines
4.8 KiB
JavaScript
120 lines
4.8 KiB
JavaScript
/**
|
|
* Regression test for #1509: --nav-active-bg must be theme-overridable.
|
|
*
|
|
* The active nav pill uses `background: var(--nav-active-bg)` in style.css.
|
|
* Before #1509 the customizer's THEME_CSS_MAP did not include navActiveBg,
|
|
* so themes / per-operator overrides could not change the active-pill color
|
|
* even though every other nav color (navBg, navBg2, navText, navTextMuted)
|
|
* was themeable.
|
|
*
|
|
* This unit test asserts:
|
|
* 1. THEME_CSS_MAP exposes navActiveBg → '--nav-active-bg'
|
|
* 2. THEME_COLOR_KEYS implicitly picks it up (it derives from THEME_CSS_MAP)
|
|
* 3. applyCSS() actually writes the variable on documentElement when an
|
|
* override is present (proves end-to-end wiring, not just the map entry)
|
|
* 4. The 'default' preset seeds both light + dark themes with a navActiveBg
|
|
* default so themes don't fall back to the hardcoded rgba()
|
|
*
|
|
* Run: node test-issue-1509-nav-active-bg.js
|
|
*/
|
|
'use strict';
|
|
const vm = require('vm');
|
|
const fs = require('fs');
|
|
const assert = require('assert');
|
|
|
|
let passed = 0, failed = 0;
|
|
function test(name, fn) {
|
|
try { fn(); passed++; console.log(` ✅ ${name}`); }
|
|
catch (e) { failed++; console.log(` ❌ ${name}: ${e.message}`); }
|
|
}
|
|
|
|
function makeSandbox(opts) {
|
|
opts = opts || {};
|
|
const storage = {};
|
|
const localStorage = {
|
|
getItem(k) { return k in storage ? storage[k] : null; },
|
|
setItem(k, v) { storage[k] = String(v); },
|
|
removeItem(k) { delete storage[k]; },
|
|
clear() { for (const k in storage) delete storage[k]; }
|
|
};
|
|
// Capture CSS variable writes for assertion.
|
|
const cssProps = {};
|
|
const ctx = {
|
|
window: { addEventListener: () => {}, dispatchEvent: () => {}, SITE_CONFIG: {}, _SITE_CONFIG_ORIGINAL_HOME: null },
|
|
document: {
|
|
readyState: 'loading',
|
|
createElement: () => ({
|
|
id: '', textContent: '', innerHTML: '', className: '',
|
|
setAttribute: () => {}, appendChild: () => {},
|
|
style: {}, addEventListener: () => {},
|
|
querySelectorAll: () => [], querySelector: () => null,
|
|
}),
|
|
head: { appendChild: () => {} },
|
|
getElementById: () => null,
|
|
addEventListener: () => {},
|
|
querySelectorAll: () => [],
|
|
querySelector: () => null,
|
|
body: { style: { setProperty: () => {} } },
|
|
documentElement: {
|
|
style: {
|
|
setProperty: (k, v) => { cssProps[k] = v; },
|
|
removeProperty: (k) => { delete cssProps[k]; },
|
|
getPropertyValue: (k) => cssProps[k] || '',
|
|
},
|
|
dataset: { theme: opts.theme || 'light' },
|
|
getAttribute: () => (opts.theme || 'light'),
|
|
},
|
|
},
|
|
console,
|
|
localStorage,
|
|
setTimeout: (fn) => fn(),
|
|
clearTimeout: () => {},
|
|
Date, Math, Array, Object, JSON, String, Number, Boolean,
|
|
parseInt, parseFloat, isNaN, Infinity, NaN, undefined,
|
|
MutationObserver: class { observe() {} },
|
|
HashChangeEvent: class {},
|
|
CustomEvent: class CustomEvent { constructor(type, opts) { this.type = type; this.detail = opts && opts.detail; } },
|
|
getComputedStyle: () => ({ getPropertyValue: () => '' }),
|
|
};
|
|
ctx.window.localStorage = localStorage;
|
|
ctx.self = ctx.window;
|
|
return { ctx, cssProps };
|
|
}
|
|
|
|
function loadCustomizer(opts) {
|
|
const { ctx, cssProps } = makeSandbox(opts);
|
|
const labelsCode = fs.readFileSync('public/payload-labels.js', 'utf8');
|
|
const code = fs.readFileSync('public/customize-v2.js', 'utf8');
|
|
vm.createContext(ctx);
|
|
vm.runInContext(labelsCode, ctx, { filename: 'payload-labels.js' });
|
|
vm.runInContext(code, ctx, { filename: 'customize-v2.js' });
|
|
return { api: ctx.window._customizerV2, cssProps, ls: ctx.localStorage };
|
|
}
|
|
|
|
console.log('\n#1509 — themeable --nav-active-bg\n');
|
|
|
|
test('THEME_CSS_MAP maps navActiveBg → --nav-active-bg', () => {
|
|
const { api } = loadCustomizer();
|
|
assert.strictEqual(api.THEME_CSS_MAP.navActiveBg, '--nav-active-bg',
|
|
'THEME_CSS_MAP.navActiveBg should be "--nav-active-bg"');
|
|
});
|
|
|
|
test('applyCSS writes --nav-active-bg on documentElement when overridden (light)', () => {
|
|
const { api, cssProps, ls } = loadCustomizer({ theme: 'light' });
|
|
ls.setItem('cs-theme-overrides', JSON.stringify({ theme: { navActiveBg: '#abcdef' } }));
|
|
api.init({});
|
|
assert.strictEqual(cssProps['--nav-active-bg'], '#abcdef',
|
|
'expected --nav-active-bg=#abcdef on documentElement, got ' + cssProps['--nav-active-bg']);
|
|
});
|
|
|
|
test('applyCSS writes --nav-active-bg on documentElement when overridden (dark)', () => {
|
|
const { api, cssProps, ls } = loadCustomizer({ theme: 'dark' });
|
|
ls.setItem('cs-theme-overrides', JSON.stringify({ themeDark: { navActiveBg: '#112233' } }));
|
|
api.init({});
|
|
assert.strictEqual(cssProps['--nav-active-bg'], '#112233',
|
|
'expected --nav-active-bg=#112233 on documentElement in dark mode, got ' + cssProps['--nav-active-bg']);
|
|
});
|
|
|
|
console.log('\n' + passed + '/' + (passed + failed) + ' tests passed');
|
|
process.exit(failed > 0 ? 1 : 0);
|