Files
meshcore-analyzer/test-channel-modal-ux.js
T
Kpa-clawbot 282074b19d feat(#1034): wire QR generate + scan into channel modal (PR 3/3) (#1081)
## Summary

**PR 3/3 of #1034** — wires the existing `window.ChannelQR` module (PR2
#1035) into the existing channel modal placeholders (PR1 #1037).

### Changes

**`public/channels.js`**
- **Generate handler** (`#chGenerateBtn`): replaced the "QR coming in
next update" placeholder text with a real call to
`window.ChannelQR.generate(label || channelName, keyHex, qrOut)`.
Renders QR canvas + `meshcore://channel/add?...` URL + Copy Key inline
into `#qr-output`.
- **Scan handler** (`#scan-qr-btn`): removed `disabled` attribute,
refreshed title, and added a click handler that calls
`window.ChannelQR.scan()`. On success it populates `#chPskKey` (from
`result.secret`) and `#chPskName` (from `result.name`); on cancel it's a
no-op; on error it surfaces the message via `#chPskError`.

The Share button on sidebar entries was already wired to
`ChannelQR.generate` in PR1 (no change needed).

### TDD

1. **Red commit** (`178020b`): `test-channel-qr-wiring.js` — 12
assertions, 7 failed against the placeholder code (Generate handler
still printed "coming in next update", scan button still disabled).
2. **Green commit** (`e708f3f`): wiring added → all 12 assertions pass.

### E2E (rule 18)

`test-e2e-playwright.js` gains 3 Playwright tests (run against the live
Go server with fixture DB in CI):

- Generate → asserts `#qr-output canvas` and the
`meshcore://channel/add` URL appear after the click.
- Scan button is enabled (no `disabled` attribute).
- Stubs `ChannelQR.scan` to return `{name, secret}`, clicks the button,
asserts `#chPskKey` + `#chPskName` are populated.

### CI registration

Added `node test-channel-qr-wiring.js` and `node
test-channel-modal-ux.js` to the JS unit-test step in
`.github/workflows/deploy.yml` (and `test-all.sh`).

### Closes

Closes #1034 (final PR in the redesign series).

---------

Co-authored-by: OpenClaw Bot <bot@openclaw.local>
2026-05-05 01:59:17 -07:00

124 lines
5.3 KiB
JavaScript

/**
* Tests for #1034 — Channel UX redesign PR1: Modal + sectioned sidebar.
*
* Pattern follows test-channel-psk-ux.js: string-contract assertions over
* public/channels.js + DOM render harness via vm sandbox.
*
* - [+ Add Channel] button in sidebar (replaces inline form)
* - Modal overlay with three labeled sections:
* Generate PSK Channel | Add Private Channel (PSK) | Monitor Hashtag Channel
* - QR placeholders (#qr-output, #scan-qr-btn[disabled])
* - Privacy footer text
* - Sectioned sidebar render: My Channels / Network / Encrypted (N)
* - "No key" checkbox is gone
* - Three modal action handlers wired
*
* Runs in Node.js — no browser.
*/
'use strict';
const fs = require('fs');
const path = require('path');
let passed = 0;
let failed = 0;
function assert(cond, msg) {
if (cond) { passed++; console.log(' ✓ ' + msg); }
else { failed++; console.error(' ✗ ' + msg); }
}
const chSrc = fs.readFileSync(path.join(__dirname, 'public/channels.js'), 'utf8');
const cssSrc = fs.readFileSync(path.join(__dirname, 'public/style.css'), 'utf8');
console.log('\n=== #1034 PR1: [+ Add Channel] sidebar button ===');
assert(/id="chAddChannelBtn"/.test(chSrc),
'sidebar exposes #chAddChannelBtn (replaces inline form)');
assert(/\+ Add Channel/.test(chSrc) || /Add Channel/.test(chSrc),
'[+ Add Channel] button label present');
// Old "No key" toggle must be GONE.
assert(!/No key/.test(chSrc),
'old "No key" checkbox removed from sidebar');
assert(!/id="chShowEncrypted"/.test(chSrc),
'old #chShowEncrypted toggle removed');
console.log('\n=== #1034 PR1: Modal markup ===');
assert(/id="chAddChannelModal"/.test(chSrc),
'modal element #chAddChannelModal exists');
assert(/modal-overlay|ch-modal-overlay/.test(chSrc),
'modal uses overlay pattern (matches existing modal-overlay class)');
assert(/data-action="ch-modal-close"/.test(chSrc) || /id="chModalClose"/.test(chSrc),
'modal has close affordance (data-action ch-modal-close or #chModalClose)');
console.log('\n=== #1034 PR1: Three sections by label ===');
assert(/Generate PSK Channel/.test(chSrc),
'section 1 label: "Generate PSK Channel"');
assert(/Add Private Channel \(PSK\)/.test(chSrc),
'section 2 label: "Add Private Channel (PSK)"');
assert(/Monitor Hashtag Channel/.test(chSrc),
'section 3 label: "Monitor Hashtag Channel"');
console.log('\n=== #1034 PR1: Section 1 — Generate PSK ===');
assert(/id="chGenerateName"/.test(chSrc),
'generate section has #chGenerateName input');
assert(/id="chGenerateBtn"/.test(chSrc),
'generate section has #chGenerateBtn');
assert(/Generate &amp; Show QR|Generate & Show QR/.test(chSrc),
'[Generate & Show QR] button label present');
assert(/id="qr-output"/.test(chSrc),
'#qr-output placeholder div present (QR code render is PR #2)');
console.log('\n=== #1034 PR1: Section 2 — Add PSK ===');
assert(/id="chPskKey"/.test(chSrc),
'PSK section has #chPskKey input (32-hex)');
assert(/id="chPskName"/.test(chSrc),
'PSK section has optional #chPskName input');
assert(/id="chPskAddBtn"/.test(chSrc),
'PSK section has #chPskAddBtn');
assert(/id="scan-qr-btn"/.test(chSrc),
'#scan-qr-btn present (wired in PR3 — see test-channel-qr-wiring.js)');
assert(/\[0-9a-fA-F\]\{32\}|isHexKey/.test(chSrc),
'PSK section validates 32-hex format');
console.log('\n=== #1034 PR1: Section 3 — Monitor Hashtag ===');
assert(/id="chHashtagName"/.test(chSrc),
'hashtag section has #chHashtagName input');
assert(/id="chHashtagBtn"/.test(chSrc),
'hashtag section has #chHashtagBtn');
assert(/Case-sensitive|case-sensitive/.test(chSrc),
'hashtag section shows case-sensitivity warning');
console.log('\n=== #1034 PR1: Privacy footer ===');
assert(/Keys stay in your browser/.test(chSrc),
'privacy footer "Keys stay in your browser" present');
assert(/passive observer/.test(chSrc),
'privacy footer mentions "passive observer"');
console.log('\n=== #1034 PR1: Sectioned sidebar ===');
assert(/ch-section-mychannels|My Channels/.test(chSrc),
'sidebar renders "My Channels" section');
assert(/ch-section-network|>Network</.test(chSrc),
'sidebar renders "Network" section');
assert(/ch-section-encrypted|Encrypted \(/.test(chSrc),
'sidebar renders "Encrypted (N)" section');
assert(/data-encrypted-collapsed|chEncryptedCollapsed|encrypted-collapsed/.test(chSrc),
'Encrypted section is collapsible (collapsed by default)');
console.log('\n=== #1034 PR1: Modal action wiring ===');
assert(/chGenerateBtn[\s\S]{0,400}addEventListener|onGenerate|generatePsk/.test(chSrc),
'#chGenerateBtn has a click handler wired');
assert(/chPskAddBtn[\s\S]{0,400}addEventListener|onPskAdd/.test(chSrc),
'#chPskAddBtn has a click handler wired');
assert(/chHashtagBtn[\s\S]{0,400}addEventListener|onHashtag/.test(chSrc),
'#chHashtagBtn has a click handler wired');
// Generate uses crypto.getRandomValues(16)
assert(/getRandomValues\(\s*new Uint8Array\(\s*16\s*\)|getRandomValues\([^)]*16/.test(chSrc),
'generate handler uses crypto.getRandomValues(16) for the key');
console.log('\n=== #1034 PR1: CSS for modal ===');
assert(/ch-modal|ch-add-modal|chAddChannelModal/.test(cssSrc) || /\.modal-overlay/.test(cssSrc),
'modal CSS present (ch-modal-* or reuses .modal-overlay)');
console.log('\n=== Results ===');
console.log('Passed: ' + passed + ', Failed: ' + failed);
process.exit(failed > 0 ? 1 : 0);