Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 71 additions & 14 deletions test-issue-1122-details-row-clamp-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,26 @@ const VIEWPORTS = [
{ w: 375, h: 812, name: 'mobile-375' }, // repo mobile convention (#1668 M6)
];

async function gotoPackets(page) {
await page.goto(BASE + '/#/packets', { waitUntil: 'domcontentloaded' });
// #244: open with the widest selectable time window, not the default 15 min.
// makeColumnsResizable() (app.js) sizes the columns ONCE, from the rows of the
// first render, and later time-window changes keep those widths. With the
// default window those rows depend on the fixture age: in CI the window holds
// only a few packets ~13 min after freshen-fixture.sh, and none after 15 min;
// the expand column then gets ~43% of the table and Details ~5% (64px at
// 1200px), and advert names wrap onto the clamped-away 2nd line. The newest
// rows of a 24 h window (3 h at <=1024px, where longer windows are disabled)
// are the same ones for the whole e2e job (150 min timeout), so the measured
// layout no longer depends on the fixture age.
const pinnedWindowMin = (vp) => (vp.w > 1024 ? 1440 : 180);
// A fixture advert with a long name ("KN6PLV-BrkOxfLA-Yebes", 21 chars) that
// is wider than its Details clip at every viewport here; it renders near the
// top of the list.
const PINNED_ADVERT_ROW = 'e8b09a35ac87fa5c';
// Enough of a name to read and click: about two characters.
const MIN_VISIBLE_LINK_PX = 12;

async function gotoPackets(page, vp) {
await page.goto(BASE + '/#/packets?timeWindow=' + pinnedWindowMin(vp), { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#packetFilterInput', { state: 'attached', timeout: 8000 });
await page.waitForFunction(() => !!document.querySelector('#filterUxBar'), { timeout: 8000 });
// All time, so the fixture's long channel messages are rendered (same as #1122).
Expand Down Expand Up @@ -109,7 +127,7 @@ function measureRows() {

let rows = [];
await step(`[${vp.name}] navigate to /packets`, async () => {
await gotoPackets(page);
await gotoPackets(page, vp);
rows = await page.evaluate(measureRows);
assert(rows.length > 0, 'no packet rows rendered');
});
Expand All @@ -136,20 +154,59 @@ function measureRows() {
});

await step(`[${vp.name}] advert links in Details stay visible and clickable`, async () => {
// Every advert link in a Details summary that is on screen: the part of
// the link inside the one-line clip (its first line fragment) must be
// visible and be what a click there hits. A long name that does not fit
// wraps; the clamp hides its 2nd+ lines, so the link's bounding box (the
// union of its fragments) reaches into hidden lines and the next row --
// probing the box centre (#244) tested a hidden spot, not the link.
const res = await page.evaluate(() => {
const links = [...document.querySelectorAll('#pktBody td.col-details .col-details-clip a.hop-link')];
for (const a of links) {
const r = a.getBoundingClientRect();
if (r.width < 2 || r.height < 2 || r.top < 0 || r.bottom > innerHeight) continue;
const td = a.closest('td').getBoundingClientRect();
const x = Math.min(r.left + 4, td.right - 2), y = r.top + r.height / 2;
const hit = document.elementFromPoint(x, y);
return { found: true, hitIsLink: !!(hit && (hit === a || a.contains(hit))), text: a.textContent };
const desc = (el) => el ? {
tag: el.tagName,
cls: String(el.className && el.className.baseVal != null ? el.className.baseVal : el.className).slice(0, 60),
text: (el.textContent || '').trim().slice(0, 40),
row: el.closest('tr') ? el.closest('tr').getAttribute('data-hash') : undefined,
} : null;
const round = (r) => ({ left: Math.round(r.left), top: Math.round(r.top), right: Math.round(r.right), bottom: Math.round(r.bottom) });
const checked = [];
for (const a of document.querySelectorAll('#pktBody td.col-details .col-details-clip a.hop-link')) {
const clip = a.closest('.col-details-clip').getBoundingClientRect();
// Only rows fully on screen (elementFromPoint needs the viewport).
if (clip.height < 2 || clip.top < 0 || clip.bottom > innerHeight) continue;
const frags = [...a.getClientRects()].filter(r => r.width >= 1 && r.height >= 1);
const out = (r) => r.bottom > clip.bottom + 0.5 || r.right > clip.right + 0.5;
const item = { row: a.closest('tr').getAttribute('data-hash'), text: a.textContent, clip: round(clip),
frags: frags.map(round), truncated: frags.some(out) };
// Visible part of the first fragment that intersects the clip.
for (const r of frags) {
const vis = { left: Math.max(r.left, clip.left), right: Math.min(r.right, clip.right),
top: Math.max(r.top, clip.top), bottom: Math.min(r.bottom, clip.bottom) };
if (vis.right - vis.left < 1 || vis.bottom - vis.top < 1) continue;
item.visibleW = Math.round(vis.right - vis.left);
item.point = { x: vis.left + Math.min(4, (vis.right - vis.left) / 2), y: (vis.top + vis.bottom) / 2 };
const hit = document.elementFromPoint(item.point.x, item.point.y);
item.hitIsLink = !!(hit && (hit === a || a.contains(hit)));
if (!item.hitIsLink) item.hit = desc(hit);
break;
}
checked.push(item);
}
return { found: false, count: links.length };
return checked;
});
assert(res.found, 'no visible advert link in Details to check (' + JSON.stringify(res) + ')');
assert(res.hitIsLink, 'advert link in Details is not hit-testable: ' + JSON.stringify(res));
assert(res.length > 0, 'no on-screen advert link in Details to check');
// The pinned long-name row must be among them and be cut by the clip,
// so the test cannot pass without exercising a long advert name.
const pinned = res.find(r => r.row === PINNED_ADVERT_ROW);
assert(pinned, `pinned advert row ${PINNED_ADVERT_ROW} is not on screen; checked: ` +
JSON.stringify(res.map(r => [r.row, r.text])));
assert(pinned.truncated,
'pinned advert name fits its Details clip -- long names are not exercised: ' + JSON.stringify(pinned));
const hidden = res.filter(r => !(r.visibleW >= MIN_VISIBLE_LINK_PX));
assert(hidden.length === 0, `${hidden.length}/${res.length} advert links show < ${MIN_VISIBLE_LINK_PX}px ` +
'of their name in the one-line Details clip: ' + JSON.stringify(hidden.slice(0, 3)));
const missed = res.filter(r => !r.hitIsLink);
assert(missed.length === 0, `${missed.length}/${res.length} advert links in Details are not hit-testable ` +
'(hit = element under the visible part of the link): ' + JSON.stringify(missed.slice(0, 3)));
});

await step(`[${vp.name}] full message is shown when a long row is selected`, async () => {
Expand Down
Loading