"Projects / work / / Conversations / init" had no missing label.
Every project crumb link had min-width:44px, so a short name such as
"work" was padded with blank space after its text, and the separators
around it read as an empty crumb. The 44px target width now comes
from an ::after overlay centred on the link, so the layout box fits
the text on every project route.
The new browser test measures text-to-separator gaps and hit width
on the overview, conversations and conversation routes at 1440 and
390px. It fails on 6ca7f3b65 and passes here.
47 lines
3.3 KiB
JavaScript
47 lines
3.3 KiB
JavaScript
// Walkthrough defect 2 (Jason, 7350): the conversation breadcrumb read "Projects / work / / Conversations / init".
|
|
// No label was missing. Every project crumb link had min-width:44px, so a short project name such as "work" was
|
|
// padded with blank space after its text and the run of separators looked like an empty crumb.
|
|
// The crumbs must be evenly spaced whatever the name length, and each link keeps its 44px target.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { fixture } from './fixture.mjs';
|
|
import { browser } from './browser.mjs';
|
|
import { P, T, stub } from './helpers/projects-review-fixture.mjs';
|
|
|
|
const ROUTES = [[`/projects/${P}`, 'Overview'], [`/projects/${P}/conversations`, 'Conversations'], [`/projects/threads/${T}`, 'Build offline timer']];
|
|
|
|
// For each crumb link: the space from its text to the next visible separator's text (null when the phone layout
|
|
// hides what follows), and how many pixels wide its hit area is along the text's centre line.
|
|
const MEASURE = `(() => {
|
|
const box = document.querySelector('#breadcrumbs');
|
|
const text = e => { const n = document.createTreeWalker(e, NodeFilter.SHOW_TEXT).nextNode(), r = document.createRange(); r.selectNodeContents(n); return r.getBoundingClientRect(); };
|
|
return [...box.querySelectorAll(':scope > a')].map(a => {
|
|
let sep = a.nextElementSibling; if (sep?.classList.contains('project-crumb-current')) sep = sep.firstElementChild;
|
|
const t = text(a), next = sep && text(sep), y = t.top + t.height / 2;
|
|
let target = 0; for (let x = Math.floor(t.left) - 30; x <= t.right + 30; x++) if (document.elementFromPoint(x, y)?.closest('a') === a) target++;
|
|
return { label: a.textContent.trim(), gap: next?.width ? Math.round(next.left - t.right) : null, target };
|
|
});
|
|
})()`;
|
|
|
|
for (const [width, height] of [[1440, 900], [390, 844]]) {
|
|
test(`project crumbs are evenly spaced for a short project name at ${width}px`, { timeout: 60000 }, async t => {
|
|
const f = await fixture(), b = await browser({ machine: false });
|
|
t.after(async () => { await b.close(); await f.close(); });
|
|
await b.call('Page.addScriptToEvaluateOnNewDocument', { source: stub + `;fixtureProjects[0].name='work';` });
|
|
await b.viewport(width, height);
|
|
let n = 0;
|
|
for (const [route, leaf] of ROUTES) {
|
|
await b.navigate(`${f.base}/?r=${n++}#${route}`);
|
|
const ready = `document.querySelector('#breadcrumbs .project-crumb-current [aria-current="page"]')?.textContent === ${JSON.stringify(leaf)}`;
|
|
await b.evaluate(`(async () => { for (let i = 0; i < 300; i++) { if (${ready}) return document.fonts.ready; await new Promise(r => setTimeout(r, 30)); } throw Error('Timed out: crumbs for ' + ${JSON.stringify(route)} + ': ' + document.querySelector('#breadcrumbs')?.innerText); })()`);
|
|
const crumbs = await b.evaluate(MEASURE);
|
|
assert.equal(crumbs[1]?.label, 'work', `${route}: the project crumb names the project`);
|
|
const reference = crumbs[0].gap;
|
|
for (const crumb of crumbs) {
|
|
if (crumb.gap !== null) assert.ok(Math.abs(crumb.gap - reference) <= 1, `${route}: "${crumb.label}" is followed by ${crumb.gap}px, "Projects" by ${reference}px`);
|
|
assert.ok(crumb.target >= 44, `${route}: "${crumb.label}" has a ${crumb.target}px target, not 44px`);
|
|
}
|
|
}
|
|
});
|
|
}
|