#301: make the reflow guard report the ancestor chain
The previous run came back with an empty offender list and an unchanged 417px overflow: the filter treated everything under a scroll container as innocent, including the container that was itself too wide. A scroller only absolves its children when the scroller fits. It now reports the chain from body down to the widest offender with each box's width, so the first element wider than the viewport is visible instead of inferred.
This commit is contained in:
parent
18c2ed0bfe
commit
9fce824a8e
@ -120,37 +120,66 @@ async function expectNoHorizontalScroll(page: Page, label: string): Promise<void
|
||||
const report = await page.evaluate(() => {
|
||||
const doc = document.documentElement;
|
||||
const limit = doc.clientWidth;
|
||||
/** Content inside its own scroll container is allowed to be wider than
|
||||
* the viewport — that is the prescribed remedy, not the defect. Only
|
||||
* what pushes the PAGE is a finding. */
|
||||
const insideScroller = (el: HTMLElement): boolean => {
|
||||
const describe = (el: HTMLElement): string => {
|
||||
const cls =
|
||||
el.className && typeof el.className === 'string'
|
||||
? `.${el.className.trim().split(/\s+/).join('.')}`
|
||||
: '';
|
||||
return `${el.tagName.toLowerCase()}${cls}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Content inside a scroll container may exceed the viewport — that is the
|
||||
* remedy, not the defect. But only if the CONTAINER itself fits: a
|
||||
* scroller that is wider than the viewport still pushes the page, and its
|
||||
* children are then symptoms, not causes.
|
||||
*/
|
||||
const containedByFittingScroller = (el: HTMLElement): boolean => {
|
||||
for (let node = el.parentElement; node && node !== doc; node = node.parentElement) {
|
||||
const overflowX = getComputedStyle(node).overflowX;
|
||||
if (overflowX === 'auto' || overflowX === 'scroll' || overflowX === 'hidden') return true;
|
||||
if (overflowX === 'auto' || overflowX === 'scroll' || overflowX === 'hidden') {
|
||||
return node.getBoundingClientRect().right <= limit + 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const offenders: string[] = [];
|
||||
let widest: HTMLElement | null = null;
|
||||
for (const el of Array.from(document.querySelectorAll<HTMLElement>('body *'))) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
// 1 px Toleranz gegen Subpixel-Rundung.
|
||||
if (rect.width > 0 && rect.right > limit + 1 && !insideScroller(el)) {
|
||||
const cls =
|
||||
el.className && typeof el.className === 'string'
|
||||
? `.${el.className.trim().split(/\s+/).join('.')}`
|
||||
: '';
|
||||
if (rect.width > 0 && rect.right > limit + 1 && !containedByFittingScroller(el)) {
|
||||
offenders.push(
|
||||
`${el.tagName.toLowerCase()}${cls} (right=${Math.round(rect.right)}, width=${Math.round(rect.width)})`,
|
||||
`${describe(el)} (right=${Math.round(rect.right)}, width=${Math.round(rect.width)})`,
|
||||
);
|
||||
if (!widest || rect.width > widest.getBoundingClientRect().width) widest = el;
|
||||
}
|
||||
}
|
||||
return { scrollWidth: doc.scrollWidth, clientWidth: limit, offenders: offenders.slice(0, 20) };
|
||||
|
||||
// Where does the inflation start? The chain from body down to the widest
|
||||
// offender, with each box's width — the first entry wider than the
|
||||
// viewport is the element that actually needs constraining.
|
||||
const chain: string[] = [];
|
||||
for (let node: HTMLElement | null = widest; node && node !== doc; node = node.parentElement) {
|
||||
chain.unshift(`${describe(node)} w=${Math.round(node.getBoundingClientRect().width)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
scrollWidth: doc.scrollWidth,
|
||||
clientWidth: limit,
|
||||
offenders: offenders.slice(0, 20),
|
||||
chain,
|
||||
};
|
||||
});
|
||||
expect(
|
||||
{ overflowBy: report.scrollWidth - report.clientWidth, offenders: report.offenders },
|
||||
{
|
||||
overflowBy: report.scrollWidth - report.clientWidth,
|
||||
offenders: report.offenders,
|
||||
chain: report.chain,
|
||||
},
|
||||
`${label}: horizontaler Überlauf bei 320 px`,
|
||||
).toEqual({ overflowBy: 0, offenders: [] });
|
||||
).toEqual({ overflowBy: 0, offenders: [], chain: [] });
|
||||
}
|
||||
|
||||
test.describe('reflow at 320px', () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user