From 23d7a089e228862478972895279d4caeb6c591db Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 8 Oct 2026 11:15:14 +0100 Subject: [PATCH] fix(website): keep plugin carousels from widening the mobile page (#6367) Anchor the absolutely positioned screen reader install label to its count. Without a containing block inside the carousel, the invisible label escapes the scroll container and expands the document. --- packages/website/e2e/plugins.spec.ts | 35 +++++++++++++++++++ .../website/src/plugins/install-count.tsx | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/website/e2e/plugins.spec.ts b/packages/website/e2e/plugins.spec.ts index 26c0d9619a..93da12f78f 100644 --- a/packages/website/e2e/plugins.spec.ts +++ b/packages/website/e2e/plugins.spec.ts @@ -15,6 +15,41 @@ function pluginCards(page: Page, section: string): Locator { .filter({ hasNotText: /^See all$/ }); } +test("scrolls mobile plugin carousels without widening the page", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await openPlugins(page); + await expectPageWithinViewport(page); + await scrollPluginCarousel(page, "Featured"); + await scrollPluginCarousel(page, "What’s new"); + await expectPageWithinViewport(page); +}); + +async function expectPageWithinViewport(page: Page) { + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual( + await page.evaluate(() => document.documentElement.clientWidth), + ); + await page.evaluate(() => window.scrollTo({ left: 1000, behavior: "instant" })); + expect(await page.evaluate(() => window.scrollX)).toBe(0); +} + +async function scrollPluginCarousel(page: Page, section: string) { + const cards = pluginCards(page, section); + // The cards share a non-semantic scrolling container. + const strip = cards.first().locator(".."); + await strip.evaluate((element) => + element.scrollIntoView({ block: "center", behavior: "instant" }), + ); + expect(await strip.evaluate((element) => element.scrollWidth)).toBeGreaterThan( + await strip.evaluate((element) => element.clientWidth), + ); + await expect(cards.last()).not.toBeInViewport(); + await strip.evaluate((element) => + element.scrollTo({ left: element.scrollWidth, behavior: "instant" }), + ); + await expect(cards.last()).toBeInViewport({ ratio: 1 }); + await expect(cards.first()).not.toBeInViewport(); +} + test("browses from the directory into a category, a plugin, and its author", async ({ page, context, diff --git a/packages/website/src/plugins/install-count.tsx b/packages/website/src/plugins/install-count.tsx index 4867415596..198905ddea 100644 --- a/packages/website/src/plugins/install-count.tsx +++ b/packages/website/src/plugins/install-count.tsx @@ -4,7 +4,7 @@ import { formatInstalls } from "./registry"; /** An install count as a download icon and the number; inherits the surrounding text style. */ export function InstallCount({ count }: { count: number }) { return ( - + {formatInstalls(count)} installs