// 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`); } } }); }