#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 report = await page.evaluate(() => {
|
||||||
const doc = document.documentElement;
|
const doc = document.documentElement;
|
||||||
const limit = doc.clientWidth;
|
const limit = doc.clientWidth;
|
||||||
/** Content inside its own scroll container is allowed to be wider than
|
const describe = (el: HTMLElement): string => {
|
||||||
* the viewport — that is the prescribed remedy, not the defect. Only
|
const cls =
|
||||||
* what pushes the PAGE is a finding. */
|
el.className && typeof el.className === 'string'
|
||||||
const insideScroller = (el: HTMLElement): boolean => {
|
? `.${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) {
|
for (let node = el.parentElement; node && node !== doc; node = node.parentElement) {
|
||||||
const overflowX = getComputedStyle(node).overflowX;
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const offenders: string[] = [];
|
const offenders: string[] = [];
|
||||||
|
let widest: HTMLElement | null = null;
|
||||||
for (const el of Array.from(document.querySelectorAll<HTMLElement>('body *'))) {
|
for (const el of Array.from(document.querySelectorAll<HTMLElement>('body *'))) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
// 1 px Toleranz gegen Subpixel-Rundung.
|
// 1 px Toleranz gegen Subpixel-Rundung.
|
||||||
if (rect.width > 0 && rect.right > limit + 1 && !insideScroller(el)) {
|
if (rect.width > 0 && rect.right > limit + 1 && !containedByFittingScroller(el)) {
|
||||||
const cls =
|
|
||||||
el.className && typeof el.className === 'string'
|
|
||||||
? `.${el.className.trim().split(/\s+/).join('.')}`
|
|
||||||
: '';
|
|
||||||
offenders.push(
|
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(
|
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`,
|
`${label}: horizontaler Überlauf bei 320 px`,
|
||||||
).toEqual({ overflowBy: 0, offenders: [] });
|
).toEqual({ overflowBy: 0, offenders: [], chain: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('reflow at 320px', () => {
|
test.describe('reflow at 320px', () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user