mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 03:23:47 +00:00
## #1297 B3 — Playwright E2E coverage for `public/channels.js` Pure-coverage PR. Adds five Playwright suites targeting the largest under-tested branches of `public/channels.js` (1950 LOC, was **19.9% statements** per the live coverage refinement in #1297 — the single biggest delta opportunity in the umbrella). No production code changes. ### Coverage exemption Per repo `AGENTS.md` TDD rule: this is the **net-new test coverage** case — there is no production change to gate, so a failing-then-passing red commit isn't applicable. All five suites exercise existing channels init() code paths that ship today. ### New test files | File | Scenarios exercised | | --- | --- | | `test-channels-list-render-e2e.js` | Sectioned sidebar (My Channels / Network / Encrypted) headers, encrypted collapse toggle + localStorage persistence, row badges + previews, color dot + color clear control, sidebar resize handle width persist | | `test-channels-selection-flow-e2e.js` | `selectChannel()` header update + URL replaceState, message row rendering (avatars, sender colors, packet links), node detail panel open via mouse + keyboard + close-with-focus-restore, deep-link route restoration, scroll button initial state | | `test-channels-add-modal-e2e.js` | Generate PSK Channel (key + QR + status banner + localStorage persist), Add PSK invalid hex error path, Add PSK valid hex success + close + My Channels row, Monitor Hashtag with and without leading `#`, empty-hashtag no-op, Scan QR unavailable fallback, Escape close, Remove ✕ flow | | `test-channels-share-color-e2e.js` | Share modal normal mode (dedicated `#chShareModal` with QR + Hex Key + Copy success label), Share modal error mode (`openShareModalError` when no stored key — field groups hidden), Escape close, `ChannelColorPicker.show` invocation on color-dot click, keyboard Enter on a `[data-share-channel]` span | | `test-channels-ws-batch-e2e.js` | `processWSBatch` via `_channelsProcessWSBatchForTest`: explicit-sender append, `"Sender: text"` parsing branch, packetHash dedup + observer accumulation, new-channel append (channel previously unseen), scroll-button branch when user not at bottom, region-filter exclusion code path | All five tests wired into `.github/workflows/deploy.yml` after the existing `test-channel-fluid-e2e.js` step. ### Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → exit 0, all gates pass (PII, CSS vars, branch scope, etc.). Refs #1297 --------- Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com> Co-authored-by: openclaw-bot <bot@openclaw.local> Co-authored-by: mc-bot <bot@meshcore.local>
142 lines
6.6 KiB
JavaScript
142 lines
6.6 KiB
JavaScript
/**
|
|
* #1297 B3 — channels.js share modal + color picker coverage.
|
|
*
|
|
* Exercises:
|
|
* - Share modal normal mode (QR + Hex key + Copy button)
|
|
* - Share modal error mode (no key found → openShareModalError)
|
|
* - Escape closes share modal + focus restore
|
|
* - Channel color dot click triggers ChannelColorPicker.show (stubbed)
|
|
*
|
|
* Usage: BASE_URL=http://localhost:13581 node test-channels-share-color-e2e.js
|
|
*/
|
|
'use strict';
|
|
const { chromium } = require('playwright');
|
|
|
|
const BASE = process.env.BASE_URL || 'http://localhost:13581';
|
|
|
|
let passed = 0, failed = 0;
|
|
async function step(name, fn) {
|
|
try { await fn(); passed++; console.log(' ✓ ' + name); }
|
|
catch (e) { failed++; console.error(' ✗ ' + name + ': ' + e.message); }
|
|
}
|
|
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
executablePath: process.env.CHROMIUM_PATH || undefined,
|
|
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
|
});
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
const page = await ctx.newPage();
|
|
page.on('dialog', (d) => d.accept());
|
|
page.setDefaultTimeout(8000);
|
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
|
|
|
console.log(`\n=== #1297 B3 channels share-color E2E against ${BASE} ===`);
|
|
|
|
await page.goto(BASE + '/', { waitUntil: 'domcontentloaded' });
|
|
await page.evaluate(() => { try { localStorage.clear(); } catch (e) {} });
|
|
await page.goto(BASE + '/#/channels', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('#chAddChannelBtn', { timeout: 8000 });
|
|
|
|
// Add a PSK channel so we have a Share button to click.
|
|
await page.click('#chAddChannelBtn');
|
|
await page.waitForSelector('#chAddChannelModal:not(.hidden)');
|
|
await page.fill('#chPskKey', '00112233445566778899aabbccddeeff');
|
|
await page.fill('#chPskName', 'CovShare');
|
|
await page.click('#chPskAddBtn');
|
|
await page.waitForFunction(() => document.getElementById('chAddChannelModal')?.classList.contains('hidden'), { timeout: 5000 });
|
|
await page.waitForSelector('.ch-section-mychannels [data-share-channel]',
|
|
{ timeout: 5000 });
|
|
|
|
await step('clicking Share opens dedicated #chShareModal with QR + Hex key', async () => {
|
|
await page.click('.ch-section-mychannels [data-share-channel]');
|
|
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
|
|
const title = await page.textContent('#chShareModalTitle');
|
|
assert(/Share/i.test(title), 'title should start with Share: ' + title);
|
|
const keyField = await page.$eval('#chShareKey', (el) => el.value);
|
|
assert(/^[0-9a-f]{32}$/i.test(keyField),
|
|
'hex key field should be populated with 32-char hex, got: ' + keyField);
|
|
const qrEl = await page.$('#chShareQr img, #chShareQr canvas, #chShareQr table, #chShareQr svg');
|
|
assert(qrEl, 'QR element should be rendered in #chShareQr');
|
|
});
|
|
|
|
await step('Share modal Copy button labels success', async () => {
|
|
const copyBtn = await page.$('#chShareModal [data-share-copy]');
|
|
assert(copyBtn, 'copy button missing');
|
|
await copyBtn.click();
|
|
await page.waitForFunction(() => {
|
|
const b = document.querySelector('#chShareModal [data-share-copy]');
|
|
return b && /Copied/i.test(b.textContent);
|
|
}, { timeout: 3000 });
|
|
});
|
|
|
|
await step('Escape closes share modal', async () => {
|
|
await page.keyboard.press('Escape');
|
|
await page.waitForFunction(() => document.getElementById('chShareModal')?.classList.contains('hidden'), { timeout: 3000 });
|
|
});
|
|
|
|
await step('Share with no stored key → error modal (openShareModalError)', async () => {
|
|
// Wipe the stored key for our channel, then click Share again.
|
|
await page.evaluate(() => {
|
|
try {
|
|
var KEY = 'corescope_channel_keys';
|
|
var keys = JSON.parse(localStorage.getItem(KEY) || '{}');
|
|
// Remove every key so getStoredKeys()[name] is undefined.
|
|
localStorage.setItem(KEY, '{}');
|
|
return keys;
|
|
} catch (e) { return null; }
|
|
});
|
|
await page.click('.ch-section-mychannels [data-share-channel]');
|
|
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
|
|
const errTxt = await page.textContent('#chShareQr');
|
|
assert(/No stored key|cannot share/i.test(errTxt),
|
|
'error mode should show "No stored key" message, got: ' + errTxt);
|
|
// Field-groups should be hidden in error mode.
|
|
const fieldsHidden = await page.$$eval(
|
|
'#chShareModal .ch-share-field-group',
|
|
(els) => els.every((e) => e.hidden));
|
|
assert(fieldsHidden, 'field groups should be hidden in error mode');
|
|
// Close via the X button.
|
|
await page.click('#chShareModalClose');
|
|
await page.waitForFunction(() => document.getElementById('chShareModal')?.classList.contains('hidden'), { timeout: 3000 });
|
|
});
|
|
|
|
await step('color dot click invokes ChannelColorPicker.show', async () => {
|
|
// Stub the color picker so we don't depend on its DOM.
|
|
await page.evaluate(() => {
|
|
window.__pickerCalls = [];
|
|
window.ChannelColorPicker = {
|
|
show: function (ch, x, y) { window.__pickerCalls.push({ ch: ch, x: x, y: y }); },
|
|
};
|
|
});
|
|
const dot = await page.$('.ch-section-network .ch-color-dot');
|
|
assert(dot, 'no .ch-color-dot found in network section');
|
|
await dot.click();
|
|
const calls = await page.evaluate(() => window.__pickerCalls);
|
|
assert(calls.length >= 1, 'ChannelColorPicker.show should fire on dot click');
|
|
});
|
|
|
|
await step('share via keyboard Enter on the Share span (#chList keydown handler)', async () => {
|
|
// Re-add a key so the share button exists with stored key again.
|
|
await page.click('#chAddChannelBtn');
|
|
await page.waitForSelector('#chAddChannelModal:not(.hidden)');
|
|
await page.fill('#chPskKey', '11223344556677889900aabbccddeeff');
|
|
await page.fill('#chPskName', 'CovKbd');
|
|
await page.click('#chPskAddBtn');
|
|
await page.waitForFunction(() => document.getElementById('chAddChannelModal')?.classList.contains('hidden'), { timeout: 5000 });
|
|
await page.waitForSelector('.ch-section-mychannels [data-share-channel]',
|
|
{ timeout: 5000 });
|
|
const shareSpan = await page.$('.ch-section-mychannels [data-share-channel]');
|
|
await shareSpan.focus();
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
|
|
await page.keyboard.press('Escape');
|
|
});
|
|
|
|
await browser.close();
|
|
console.log(`\n=== B3 share-color: ${passed} passed, ${failed} failed ===\n`);
|
|
process.exit(failed === 0 ? 0 : 1);
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|