#301: stop /settings scrolling horizontally at 320px #309
@ -120,7 +120,8 @@ async function expectNoHorizontalScroll(page: Page, label: string): Promise<void
|
||||
const report = await page.evaluate(() => {
|
||||
const doc = document.documentElement;
|
||||
const limit = doc.clientWidth;
|
||||
const describe = (el: HTMLElement): string => {
|
||||
|
||||
const describe = (el: Element): string => {
|
||||
const cls =
|
||||
el.className && typeof el.className === 'string'
|
||||
? `.${el.className.trim().split(/\s+/).join('.')}`
|
||||
@ -128,58 +129,41 @@ async function expectNoHorizontalScroll(page: Page, label: string): Promise<void
|
||||
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 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 && !containedByFittingScroller(el)) {
|
||||
offenders.push(
|
||||
`${describe(el)} (right=${Math.round(rect.right)}, width=${Math.round(rect.width)})`,
|
||||
// Every element whose own content is wider than its box. One of these is
|
||||
// the source; the ones that scroll it away on purpose are marked.
|
||||
const overflowing: string[] = [];
|
||||
for (const el of Array.from(document.querySelectorAll('*'))) {
|
||||
if (el.scrollWidth > el.clientWidth + 1 && el.clientWidth > 0) {
|
||||
const overflowX = getComputedStyle(el).overflowX;
|
||||
overflowing.push(
|
||||
`${describe(el)} client=${el.clientWidth} scroll=${el.scrollWidth} overflow-x=${overflowX}`,
|
||||
);
|
||||
if (!widest || rect.width > widest.getBoundingClientRect().width) widest = el;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)}`);
|
||||
// Raw: every box reaching past the viewport, no filtering at all.
|
||||
const past: string[] = [];
|
||||
for (const el of Array.from(document.querySelectorAll('body *'))) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.right > limit + 1) {
|
||||
past.push(`${describe(el)} right=${Math.round(rect.right)} w=${Math.round(rect.width)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
scrollWidth: doc.scrollWidth,
|
||||
clientWidth: limit,
|
||||
offenders: offenders.slice(0, 20),
|
||||
chain,
|
||||
overflowBy: doc.scrollWidth - limit,
|
||||
viewport: `html client=${limit} scroll=${doc.scrollWidth} | body client=${document.body.clientWidth} scroll=${document.body.scrollWidth} rect=${Math.round(document.body.getBoundingClientRect().width)}`,
|
||||
overflowing: overflowing.slice(0, 15),
|
||||
past: past.slice(0, 15),
|
||||
};
|
||||
});
|
||||
expect(
|
||||
{
|
||||
overflowBy: report.scrollWidth - report.clientWidth,
|
||||
offenders: report.offenders,
|
||||
chain: report.chain,
|
||||
},
|
||||
const diagnosis = [
|
||||
`${label}: horizontaler Überlauf bei 320 px`,
|
||||
).toEqual({ overflowBy: 0, offenders: [], chain: [] });
|
||||
report.viewport,
|
||||
`eigener Inhaltsüberlauf: ${JSON.stringify(report.overflowing, null, 1)}`,
|
||||
`Boxen über dem Rand: ${JSON.stringify(report.past, null, 1)}`,
|
||||
].join('\n');
|
||||
expect({ overflowBy: report.overflowBy }, diagnosis).toEqual({ overflowBy: 0 });
|
||||
}
|
||||
|
||||
test.describe('reflow at 320px', () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user