From 1d064e077c283030b2df15bff851ba40b183be21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sun, 25 Oct 2026 20:54:37 -0300 Subject: [PATCH 1/2] chore: start #651 From c81164e59ee8a2a236cd1774540865082c469788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sun, 25 Oct 2026 20:56:29 -0300 Subject: [PATCH 2/2] refactor: VISUAL_ROUTES lives in route-impact.ts and the visual spec imports it Fail before: with the old spec the new test 'is the list the visual spec captures, not a copy of it' fails; pass after: 34 pass. Closes #651 --- packages/ui/tests/route-impact.test.ts | 23 +++++++++++++++++++++++ scripts/route-impact.ts | 14 ++++++++++++++ scripts/test-changed.ts | 15 +++------------ site/tests/pages.visual.spec.ts | 13 ++++--------- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/packages/ui/tests/route-impact.test.ts b/packages/ui/tests/route-impact.test.ts index fe2f1082..af4bb539 100644 --- a/packages/ui/tests/route-impact.test.ts +++ b/packages/ui/tests/route-impact.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; import { ATLAS_REPRESENTATIVES, buildImpact, @@ -6,6 +8,7 @@ import { lockReachesRuntime, ownRoutes, select, + VISUAL_ROUTES, } from "../../../scripts/route-impact"; import { TERMS, termSlug } from "../../../site/src/data/atlas/atlas"; @@ -283,3 +286,23 @@ describe("route-impact", () => { }); }); }); + +// The land gate rebuilds and retests the pages `pages.visual.spec.ts` captures from `VISUAL_ROUTES`. Two copies +// kept "in step" by a comment let a page get a baseline that no land ever rebuilt (#651): one list, imported. +describe("VISUAL_ROUTES", () => { + const spec = readFileSync( + join(import.meta.dir, "../../../site/tests/pages.visual.spec.ts"), + "utf8" + ); + it("is the list the visual spec captures, not a copy of it", () => { + expect(spec).toMatch( + /import \{[^}]*\bVISUAL_ROUTES\b[^}]*\} from "..\/..\/scripts\/route-impact"/ + ); + expect(spec).not.toMatch(/^\s*"\/templates\//m); + }); + it("lists real, distinct routes", () => { + expect(VISUAL_ROUTES.length).toBeGreaterThan(0); + expect(new Set(VISUAL_ROUTES).size).toBe(VISUAL_ROUTES.length); + for (const r of VISUAL_ROUTES) expect(r).toMatch(/^\/(.*\/)?$/); + }); +}); diff --git a/scripts/route-impact.ts b/scripts/route-impact.ts index 7c28bec0..b7517f94 100644 --- a/scripts/route-impact.ts +++ b/scripts/route-impact.ts @@ -227,6 +227,20 @@ export const GALLERY_REPRESENTATIVES = [ "page-auth-sign-in", ]; +/** + * The pages `site/tests/pages.visual.spec.ts` captures at the viewport: the landing, the templates and the theme + * editor. Every docs page is a stack of component demos that the component baselines already compare one by one, and + * the layout audit checks their geometry on every route. The land gate rebuilds exactly these when a diff reaches one + * (`test-changed.ts`), and the spec imports the same list (#651). + */ +export const VISUAL_ROUTES = [ + "/", + "/templates/", + "/templates/app/dashboard/", + "/templates/auth/sign-in/", + "/theme/", +]; + /** * `/atlas//` is one template (`pages/atlas/[slug].astro`) over every term of the atlas, so the land gate * audits the pages that stand for all of them, and so does the release sweep (`test:routes`, #613); the HTML checks read every one (#595): a term with sources and style diff --git a/scripts/test-changed.ts b/scripts/test-changed.ts index a4604221..679da916 100755 --- a/scripts/test-changed.ts +++ b/scripts/test-changed.ts @@ -36,7 +36,7 @@ import { spawn, spawnSync } from "node:child_process"; import { existsSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { distCovers, distStale, readScope } from "./dist-fresh.ts"; -import { lockReachesRuntime, ownRoutes, ROOT, select } from "./route-impact.ts"; +import { lockReachesRuntime, ownRoutes, ROOT, select, VISUAL_ROUTES } from "./route-impact.ts"; import { affected, buildGraph, @@ -57,15 +57,6 @@ const CANARIES = [ "/theme/", ]; -/** The routes `pages.visual.spec.ts` captures (keep in step with its PAGES). */ -const VISUAL_ROUTES = new Set([ - "/", - "/templates/", - "/templates/app/dashboard/", - "/templates/auth/sign-in/", - "/theme/", -]); - /** * A site spec that is not the route harness: it runs itself when it changed. `no-flash.spec.ts` is not one of * them: it paints real pages at 6x CPU on Slow 3G (~90 s for its cases), so it runs in `test:e2e` at release. @@ -121,14 +112,14 @@ const specs = changed.filter(ownSpec); const routes = sel.all ? [] : [...new Set([...sel.routes, ...(land ? CANARIES : [])])].sort(); const sweepAll = sel.all || (land && sharedFile !== undefined); const siteRun = sweepAll || routes.length > 0 || specs.length > 0; -const visualRun = sel.all || sel.routes.some((r) => VISUAL_ROUTES.has(r)); +const visualRun = sel.all || sel.routes.some((r) => VISUAL_ROUTES.includes(r)); const routeRun = sweepAll || routes.length > 0; // A narrow site run builds only the pages it audits (`CA_SITE_ROUTES`, site/src/lib/only-routes.ts): a behaviour spec // visits arbitrary pages and a sweep audits all of them, so those need the whole site. 915 pages are ~80 s of the // page generation, 80 pages ~11 s. const builtRoutes: string[] | undefined = !sweepAll && specs.length === 0 - ? [...new Set([...routes, ...(visualRun ? [...VISUAL_ROUTES] : [])])].sort() + ? [...new Set([...routes, ...(visualRun ? VISUAL_ROUTES : [])])].sort() : undefined; // Shared: everything that can reach every test runs, and screenshots are left to `test:vrt`. const shared = sel.all || sharedFile !== undefined; diff --git a/site/tests/pages.visual.spec.ts b/site/tests/pages.visual.spec.ts index ab426fdc..7630659d 100644 --- a/site/tests/pages.visual.spec.ts +++ b/site/tests/pages.visual.spec.ts @@ -1,17 +1,12 @@ +import { VISUAL_ROUTES } from "../../scripts/route-impact"; import { expect, settle, test, waitIslands } from "./fixtures"; // The pages whose own composition is what a screenshot can show: the landing, the templates and the theme // editor, at the visible viewport (the first screen a visitor sees). Every docs page is a stack of component // demos that the component baselines (packages/ui `*.vrt.test.ts`) already compare one by one, and the layout // audit (routes.spec.ts) checks their geometry on every route, so a tall full-page docs shot added only the -// slowest and flakiest copy of both. -const PAGES = [ - "/", - "/templates/", - "/templates/app/dashboard/", - "/templates/auth/sign-in/", - "/theme/", -]; +// slowest and flakiest copy of both. The list is `VISUAL_ROUTES` (scripts/route-impact.ts), which the land gate +// also reads to rebuild these pages. // Per-test timeout: a viewport shot is taken twice and compared (and written when updating); the // 5 s expect and 30 s test defaults are enough on a native machine, so only the shot itself is bounded. @@ -31,7 +26,7 @@ for (const scheme of ["dark", "light"] as const) { test.describe(`visual ${scheme}`, () => { test.use({ theme: { scheme }, colorScheme: scheme }); - for (const route of PAGES) { + for (const route of VISUAL_ROUTES) { test(route, async ({ page }) => { await prepare(page, route); // Visible viewport only: no `fullPage`, so no layout-height rounding between the two compared