Files
meshcore-analyzer/tests/e2e/test-issue-1413-nav-overlap-e2e.js
T
Alex B 893773338e chore(tests): move root test-*.js into tests/unit and tests/e2e (#2036)
Moves 290 root test-*.js into tests/unit (177, listed in test-all.sh) and tests/e2e (113, classified in scripts/non-unit-tests.json), per #1981 and PR-D of #1385. Root goes from 348 entries to 48. test-all.sh and test-fixtures/ stay put. The inventory guard now fails if a test reappears in the root or sits in the wrong folder.

Verified independently of the diff: the invoked sets are unchanged (test-all.sh 177 before and after, deploy.yml 96 before and after, both identical as sets), and a full local run of test-all.sh on master and on the branch produced 4702 output lines each whose only differences are absolute paths, stack-trace line numbers shifted by the REPO_ROOT line, the inventory wording and two perf ratios. The guard was mutation-checked: a test back in the root, a unit suite in tests/e2e, and a suite dropped from test-all.sh each make it exit 1. CI run 35246304316 ran 97 suites from tests/e2e and is green.

Follow-up 9335c51d finished the instruction files: no bare root test command is left in AGENTS.md, the squad charters, .github or docs, and every tests/ path they name resolves.

Merged by the interim maintainer without a second human reviewer: CI and the local runs above are the independent checks.

Known and deliberately out of scope: 18 of the 113 files in tests/e2e are invoked by no runner at all, and one of them cannot run anywhere because it requires jsdom, which is not a declared dependency. Tracked separately.
2026-09-17 19:06:07 +02:00

135 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
/* Issue #1413 — More button overlaps nav-stats badge at vw~1200px.
*
* Symptom: at viewport ~1101..1599px on a non-mobile page (e.g.
* /#/packets), the ".nav-more-btn" (in .nav-left) and ".nav-stats"
* (in .nav-right) overlap horizontally. CDP-confirmed: at vw=1200,
* .nav-more-btn rect (x=499..556) sat on top of .nav-stats (x=502..961),
* a ~54px x-axis overlap. Visually the stats badge number rendered on
* top of the "More" text and the chevron.
*
* Acceptance (from issue #1413):
* - At vw=1101..1920 (sample step), .nav-more-btn.right + GAP <=
* .nav-stats.left, where GAP >= 8px.
* - At vw <= 1100, .nav-stats is display:none (no change).
* - Nav doesn't horizontally scroll at any viewport.
*
* Root cause: .top-nav uses display:flex with justify-content:
* space-between, but .nav-left had no flex-grow and .nav-links had no
* flex-grow either, so .nav-left only consumed its content's intrinsic
* width. .nav-right (flex-shrink:0) then sat at its natural position
* computed from total content — and the JS Priority+ fits() check
* succeeded based on intrinsic widths that under-reported the real
* collision because .top-nav has overflow:hidden masking it.
*
* Fix (verified via CDP at vw 1101..1920): `.nav-links { flex: 1 1
* auto; min-width: 0 }` + `.top-nav { column-gap: 16px }`. Reverting
* either part of the fix reintroduces overlap at vw=1200.
*
* Mutation guard: revert the CSS fix → this test fails at vw=1200.
*/
'use strict';
const assert = require('node:assert');
const { chromium } = require('playwright');
const BASE = process.env.BASE_URL || 'http://localhost:13581';
const WIDTHS = [1101, 1200, 1366, 1440, 1600, 1920];
const HEIGHT = 800;
const MIN_GAP_PX = 8;
async function main() {
let browser;
try {
browser = await chromium.launch({
headless: true,
executablePath: process.env.CHROMIUM_PATH || undefined,
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
});
} catch (err) {
if (process.env.CHROMIUM_REQUIRE === '1') {
console.error(`test-issue-1413-nav-overlap-e2e.js: FAIL — Chromium required but unavailable: ${err.message}`);
process.exit(1);
}
console.log(`test-issue-1413-nav-overlap-e2e.js: SKIP (Chromium unavailable: ${err.message.split('\n')[0]})`);
process.exit(0);
}
let failures = 0;
let passes = 0;
const ctx = await browser.newContext();
const page = await ctx.newPage();
page.setDefaultTimeout(15000);
for (const w of WIDTHS) {
await page.setViewportSize({ width: w, height: HEIGHT });
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.top-nav .nav-links');
await page.evaluate(() => document.fonts && document.fonts.ready ? document.fonts.ready : null);
// Settle layout: two consecutive frames identical for nav-right.
await page.waitForFunction(() => {
const el = document.querySelector('.top-nav .nav-right');
if (!el) return false;
const r1 = el.getBoundingClientRect();
return new Promise((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(() => {
const r2 = el.getBoundingClientRect();
resolve(r1.right === r2.right && r1.left === r2.left);
}));
});
}, null, { timeout: 5000 });
await page.evaluate(() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))));
const data = await page.evaluate(() => {
const more = document.querySelector('.nav-more-btn');
const stats = document.querySelector('.nav-stats');
const moreVisible = more && getComputedStyle(more).display !== 'none' &&
getComputedStyle(more.parentElement).display !== 'none' &&
!more.parentElement.classList.contains('is-hidden');
const statsVisible = stats && getComputedStyle(stats).display !== 'none';
const mb = more ? more.getBoundingClientRect() : null;
const sb = stats ? stats.getBoundingClientRect() : null;
const topNav = document.querySelector('.top-nav');
const tnScrollW = topNav ? topNav.scrollWidth : 0;
const tnClientW = topNav ? topNav.clientWidth : 0;
return {
moreVisible, statsVisible,
more: mb ? { x: mb.x, right: mb.right, w: mb.width } : null,
stats: sb ? { x: sb.x, right: sb.right, w: sb.width } : null,
tnScrollW, tnClientW,
};
});
let status = 'PASS';
const reasons = [];
// Acceptance: if both visible, more.right + 8 <= stats.left.
if (data.moreVisible && data.statsVisible && data.more && data.stats) {
const gap = data.stats.x - data.more.right;
if (gap < MIN_GAP_PX) {
status = 'FAIL';
reasons.push(`overlap: more.right=${data.more.right.toFixed(1)} stats.left=${data.stats.x.toFixed(1)} gap=${gap.toFixed(1)} (need >= ${MIN_GAP_PX})`);
}
}
// No horizontal scroll in nav.
if (data.tnScrollW > data.tnClientW + 1) {
status = 'FAIL';
reasons.push(`top-nav h-scroll: scrollW=${data.tnScrollW} clientW=${data.tnClientW}`);
}
if (status === 'FAIL') {
failures++;
console.error(`vw=${w} #/packets ${status}: ${reasons.join('; ')}`);
} else {
passes++;
console.log(`vw=${w} #/packets PASS (more.right=${data.more && data.more.right.toFixed(1)} stats.left=${data.stats && data.stats.x.toFixed(1)})`);
}
}
await browser.close();
console.log(`\ntest-issue-1413-nav-overlap-e2e.js: ${passes} pass, ${failures} fail`);
process.exit(failures > 0 ? 1 : 0);
}
main().catch((err) => { console.error('test-issue-1413-nav-overlap-e2e.js: ERROR', err); process.exit(1); });