| @@ -0,0 +1,56 @@ |
| | 1 | // An empty Toolkits grid must fit the viewport: with no toolkits in either |
| | 2 | // scope, both the Workspace and Personal add cards sit above the fold and the |
| | 3 | // grid does not scroll. Regression: each shelf reserved a fixed ~3-row |
| | 4 | // min-height, so two empty shelves stacked past the viewport and the page |
| | 5 | // scrolled with nothing to see. |
| | 6 | import { expect } from "@effect/vitest"; |
| | 7 | import { Effect } from "effect"; |
| | 8 | |
| | 9 | import { scenario } from "../src/scenario"; |
| | 10 | import { Browser, Target } from "../src/services"; |
| | 11 | import { visit } from "../src/surfaces/browser"; |
| | 12 | |
| | 13 | scenario( |
| | 14 | "Toolkits · empty grid fits the viewport without scrolling", |
| | 15 | { timeout: 120_000 }, |
| | 16 | Effect.gen(function* () { |
| | 17 | const target = yield* Target; |
| | 18 | const browser = yield* Browser; |
| | 19 | const identity = yield* target.newIdentity(); |
| | 20 | |
| | 21 | yield* browser.session(identity, async ({ page, step }) => { |
| | 22 | await step("Open the Toolkits page with no toolkits in either scope", async () => { |
| | 23 | await visit(page, "/default/toolkits/"); |
| | 24 | await page.getByRole("heading", { name: "Toolkits", level: 1 }).waitFor(); |
| | 25 | await page.getByRole("heading", { name: "Workspace" }).waitFor(); |
| | 26 | await page.getByRole("heading", { name: "Personal" }).waitFor(); |
| | 27 | await page.getByRole("button", { name: "Add workspace toolkit" }).waitFor(); |
| | 28 | await page.getByRole("button", { name: "Add personal toolkit" }).waitFor(); |
| | 29 | await page.locator('main [data-slot="skeleton"]').first().waitFor({ state: "detached" }); |
| | 30 | }); |
| | 31 | |
| | 32 | await step("The empty grid does not overflow its scroll container", async () => { |
| | 33 | // Walk up from the Personal add card to its nearest scrollable |
| | 34 | // ancestor; on an empty grid that ancestor must have nothing to |
| | 35 | // scroll. This is the user-visible contract — both add cards are |
| | 36 | // reachable without scrolling — measured at the scroll boundary. |
| | 37 | const overflow = await page.evaluate(() => { |
| | 38 | const addCard = [...document.querySelectorAll("button")].find( |
| | 39 | (button) => button.getAttribute("aria-label") === "Add personal toolkit", |
| | 40 | ); |
| | 41 | let node: HTMLElement | null = addCard ?? null; |
| | 42 | while (node && node.scrollHeight <= node.clientHeight + 1) { |
| | 43 | node = node.parentElement; |
| | 44 | } |
| | 45 | if (!node) return null; |
| | 46 | return { |
| | 47 | tag: node.tagName, |
| | 48 | scrollHeight: node.scrollHeight, |
| | 49 | clientHeight: node.clientHeight, |
| | 50 | }; |
| | 51 | }); |
| | 52 | expect(overflow, "no scrollable ancestor overflows for an empty grid").toBeNull(); |
| | 53 | }); |
| | 54 | }); |
| | 55 | }), |
| | 56 | ); |