mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-04 12:01:19 +00:00
b2d654bf61
## Closes #1415 — packets cross-viewport jank ## Closes #1458 — Tufte mobile-packets P0 findings (folded into same branch) Single PR covers both issues — they touch the same files (`public/packets.js`, `public/style.css`) and a split would invite merge thrash. ### #1415 — column priority + chrome compaction Locked column-priority tiers (operator spec): | Tier | Viewport | Columns | |---|---|---| | 1 | always (mobile through desktop) | expand · time · type · details | | 2 | tablet+ (>768px) | path | | 3 | desktop only (>1024px) | hash · observer · rpt | Enforced via existing `data-priority` system in `TableResponsive.apply` (priorities 3 → hide ≤1024, 5 → hide ≤768). CSS: - `.col-expand` pinned to `width/min-width/max-width: 32px` at every viewport — kills the 50–180px dead column that pushed every data column right. - `.col-details` capped at `max-width: 480px` so wide viewports stop wasting hundreds of px on the last column. - `@media (max-width: 480px)` hides page-header BYOP, shrinks the h2, and tightens row padding → pre-table chrome drops from ~280px to ~140px. ### #1458 — Tufte mobile P0 findings **P0-A: semantic-first detail panel.** Was: `"Packet Byte Breakdown (134 bytes)"` title + giant neon hex grid above the meaningful fields. Now: type badge + decoded summary + hop count + `src → dst` lead the panel, followed by the existing `.detail-meta` dl (reordered: Payload Type → Path → Timestamp → Observer). **P0-B: raw-bytes disclosure.** Hex legend / hex dump / field table wrapped in `<details class="detail-technical">`. Disclosure copy reads "Show raw bytes". Collapsed by default on phones (`window.innerWidth ≤ 480`), expanded on tablet+. **P0-C: mobile filter-zone collapse.** The always-on filter-expression input above `.filter-bar` is now wrapped with `.pkt-filter-expr` and hidden under the `@media (max-width: 480px)` block. Reveals when the existing "Filters ▾" toggle adds `.filters-expanded` to the sibling `.filter-bar` (CSS `:has()` selector — one tap reveals both chrome rows together). ### TDD `test-issue-1415-packets-layout.js` — pure source-grep, no browser: - col-expand class on first `<th>` + `<td>` + CSS 32px pin - locked column-priority tier values per column - `.col-details` max-width ≤ 480px - mobile @media block: hides BYOP, hides `.pkt-filter-expr` (revealed by `.filters-expanded`) - detail-meta order: Payload Type before Observer - `<details class="detail-technical">` wrapper exists with "Show raw bytes" summary - detail-title leads with a type badge; `.detail-srcdst` emitted - old "Packet Byte Breakdown (N bytes)" title literal removed Red commit `d4372d82` (8 assertion failures, no compile errors), green commit `4fab9dbd` (#1415 work), follow-up commit `a5218035` (#1458 work) keeps everything green. 26 assertions, 0 failed. --------- Co-authored-by: openclaw-bot <bot@openclaw>
135 lines
6.1 KiB
JavaScript
135 lines
6.1 KiB
JavaScript
/**
|
|
* E2E test (#1188): observer IATA must render alongside observer name
|
|
* on packets rows and in the detail pane. Plus, the wireshark-style
|
|
* filter grammar must accept `observer_iata` / `iata` expressions.
|
|
*
|
|
* Runs against the e2e fixture (see test-fixtures/e2e-fixture.db).
|
|
* Observers in the fixture carry IATA codes (e.g. SJC, OAK, MRY),
|
|
* so once the UI changes land, at least one rendered packet row must
|
|
* carry one of those codes next to its observer name.
|
|
*
|
|
* Usage: BASE_URL=http://localhost:13581 node test-observer-iata-1188-e2e.js
|
|
*/
|
|
const { chromium } = require('playwright');
|
|
|
|
const BASE = process.env.BASE_URL || 'http://localhost:3000';
|
|
|
|
async function test(name, fn) {
|
|
try {
|
|
await fn();
|
|
console.log(` \u2705 ${name}`);
|
|
} catch (err) {
|
|
console.log(` \u274c ${name}: ${err.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function assert(cond, msg) {
|
|
if (!cond) throw new Error(msg || 'Assertion failed');
|
|
}
|
|
|
|
async function run() {
|
|
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: 1400, height: 900 } });
|
|
const page = await ctx.newPage();
|
|
page.setDefaultTimeout(15000);
|
|
|
|
console.log(`\nRunning observer-IATA E2E tests against ${BASE}\n`);
|
|
|
|
await test('Packets table renders an IATA badge in an observer cell', async () => {
|
|
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
|
// Wide time window so fixture rows are in scope
|
|
await page.evaluate(() => localStorage.setItem('meshcore-time-window', '525600'));
|
|
await page.reload({ waitUntil: 'load' });
|
|
await page.waitForSelector('[data-loaded="true"]', { timeout: 20000 });
|
|
await page.waitForSelector('table tbody tr:not([id^=vscroll])', { timeout: 15000 });
|
|
|
|
// Cells in the Observer column should contain a `.badge-iata` element
|
|
// for at least one row that has a known IATA.
|
|
const iataBadges = await page.$$('td.col-observer .badge-iata');
|
|
assert(iataBadges.length > 0,
|
|
`expected at least one .badge-iata inside a .col-observer cell; got ${iataBadges.length}`);
|
|
|
|
// The badge text should be a recognizable IATA code (3 uppercase letters)
|
|
const text = (await iataBadges[0].textContent() || '').trim();
|
|
assert(/^[A-Z]{3}$/.test(text), `expected 3-letter IATA in badge, got "${text}"`);
|
|
});
|
|
|
|
await test('Filter grammar: observer_iata == "<code>" narrows the table', async () => {
|
|
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('[data-loaded="true"]', { timeout: 20000 });
|
|
await page.waitForSelector('table tbody tr:not([id^=vscroll])', { timeout: 15000 });
|
|
|
|
// Pick the first IATA shown in any badge in the table
|
|
const firstBadge = await page.$('td.col-observer .badge-iata');
|
|
assert(firstBadge, 'no .badge-iata found to pick a filter value from');
|
|
const iata = (await firstBadge.textContent() || '').trim();
|
|
assert(/^[A-Z]{3}$/.test(iata), `expected IATA, got "${iata}"`);
|
|
|
|
// Apply the filter — `iata == "XXX"` should leave rows visible
|
|
const input = await page.$('#packetFilterInput');
|
|
assert(input, 'packet filter input not found');
|
|
await input.fill(`iata == "${iata}"`);
|
|
await page.waitForTimeout(500); // debounce
|
|
|
|
const rowsAfter = await page.$$('table tbody tr:not([id^=vscroll])');
|
|
assert(rowsAfter.length > 0, `expected matching rows for iata == "${iata}"`);
|
|
|
|
// Every remaining observer cell should carry the same IATA
|
|
const badges = await page.$$('td.col-observer .badge-iata');
|
|
for (const b of badges) {
|
|
const t = (await b.textContent() || '').trim();
|
|
assert(t === iata, `unexpected IATA ${t} in row when filter is iata == "${iata}"`);
|
|
}
|
|
});
|
|
|
|
await test('Mobile viewport (375px): observer column drops from row, but IATA badge still appears in expanded detail panel (#1415 locked spec)', async () => {
|
|
// #1415 locked column-priority spec: col-observer is tier-3 (desktop only,
|
|
// hidden ≤1024px). The iron rule: anything hidden from the row at the
|
|
// current viewport MUST appear in the expanded Details pane. So at 375px:
|
|
// (a) `td.col-observer` is hidden (display:none via .col-hidden)
|
|
// (b) tapping a row opens the panel whose .detail-meta carries the
|
|
// Observer row + .badge-iata next to the observer name.
|
|
const mobile = await browser.newContext({ viewport: { width: 375, height: 812 } });
|
|
const mpage = await mobile.newPage();
|
|
mpage.setDefaultTimeout(15000);
|
|
await mpage.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
|
await mpage.evaluate(() => localStorage.setItem('meshcore-time-window', '525600'));
|
|
await mpage.reload({ waitUntil: 'load' });
|
|
await mpage.waitForSelector('[data-loaded="true"]', { timeout: 20000 });
|
|
await mpage.waitForSelector('table tbody tr:not([id^=vscroll])', { timeout: 15000 });
|
|
|
|
// (a) observer column hidden in the row at 375px
|
|
const rowObserverVisible = await mpage.$eval(
|
|
'td.col-observer',
|
|
el => window.getComputedStyle(el).display !== 'none'
|
|
).catch(() => false);
|
|
assert(!rowObserverVisible,
|
|
'observer column should be hidden in rows at 375px (tier-3, desktop-only per #1415 spec)');
|
|
|
|
// (b) tap first row → detail panel renders observer + IATA badge
|
|
const firstRow = await mpage.$('table tbody tr[data-hash]');
|
|
assert(firstRow, 'no packet row found to tap');
|
|
await firstRow.click();
|
|
await mpage.waitForSelector('.detail-meta', { timeout: 10000 });
|
|
const detailIata = await mpage.$('.detail-meta .badge-iata');
|
|
assert(detailIata, '.badge-iata must appear in .detail-meta after tapping a row at 375px');
|
|
const box = await detailIata.boundingBox();
|
|
assert(box && box.width > 0 && box.height > 0,
|
|
`.detail-meta .badge-iata has zero/no dimensions: ${box && (box.width + 'x' + box.height)}`);
|
|
await mobile.close();
|
|
});
|
|
|
|
await browser.close();
|
|
console.log(`\nAll observer-IATA E2E tests passed.\n`);
|
|
}
|
|
|
|
run().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|