-
-
Notifications
You must be signed in to change notification settings - Fork 32.6k
[test] Cover docs landing-page composites with Argos #48589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
LukasTy
wants to merge
11
commits into
mui:master
Choose a base branch
from
LukasTy:claude/cover-landing-composites-argos
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+487
−155
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
372fef5
[test] Cover docs landing-page composites with Argos
LukasTy e7691db
[test] Fix prettier formatting
LukasTy ec5d9f9
[test] Fix TypeScript errors surfaced by composite coverage
LukasTy 93d0d93
[test] Dedupe pnpm lockfile
LukasTy 46b6203
[test] Insulate test typecheck from branding theme augmentations
LukasTy 4ddf533
Merge remote-tracking branch 'upstream/master' into claude/cover-land…
LukasTy 3c05e15
[test] Narrow composite coverage to meaningful integrations
LukasTy 437d663
[test] Freeze the clock for date-dependent composites
LukasTy 29dab5a
[test] Align date freeze with base-ui's regression approach
LukasTy 7e01d9a
[test] Simplify composite route mapping to single-word products
LukasTy 0be52a0
[test] Move date freeze into a module, matching base-ui's structure
LukasTy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import * as React from 'react'; | ||
| import { ThemeProvider } from '@mui/material/styles'; | ||
| // Go through a local `.d.ts` shim rather than importing the docs branding | ||
| // theme directly. The theme source file declares `@mui/material/*` module | ||
| // augmentations that resolve fine in the package's own typecheck and in the | ||
| // Vite bundle, but fail under `test/tsconfig.json`'s `nodenext` resolution. | ||
| // The runtime side is in `brandingThemeShim.js`, excluded from the test | ||
| // typecheck. | ||
| import brandingLightTheme from './brandingThemeShim'; | ||
|
|
||
| // `docs/src/components/product*/*.tsx` composites are authored to run inside | ||
| // the Next.js docs site and read the docs branding theme | ||
| // (`palette.primaryDark`, `palette.gradients`, `applyDarkStyles`, ...). | ||
| // Supply it so they render in the regression fixture. Inlines the minimal | ||
| // light-mode subset of `BrandingProvider` (a thin `ThemeProvider` wrapper); | ||
| // composites that read `theme.palette.mode` get `'light'`, which matches | ||
| // the live `/material-ui` / `/x` pages' default. | ||
| export default function MarketingWrapper({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <ThemeProvider theme={brandingLightTheme}> | ||
| <div style={{ width: '100%' }}>{children}</div> | ||
| </ThemeProvider> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import type { Theme } from '@mui/material/styles'; | ||
|
|
||
| // Declaration-only shim for the docs branding theme. | ||
| // | ||
| // The runtime export lives in the matching `.js` file (excluded from the | ||
| // test typecheck). We declare the shape here so `MarketingWrapper.tsx` can | ||
| // consume it without TS transitively pulling in | ||
| // `packages-internal/core-docs/src/branding/brandingTheme.ts`. That source | ||
| // file declares augmentations to `@mui/material/styles` and friends, which | ||
| // resolve fine in the package's own `bundler`-resolution typecheck and in | ||
| // the Vite build, but fail under `test/tsconfig.json`'s `nodenext` | ||
| // resolution (`@mui/material/themeCssVarsAugmentation` can't be found in | ||
| // the test package's node_modules layout). | ||
| declare const brandingLightTheme: Theme; | ||
| export default brandingLightTheme; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // Runtime re-export of the docs branding theme. See `brandingThemeShim.d.ts` | ||
| // for the rationale — this file is excluded from `test/tsconfig.json` so TS | ||
| // uses the declaration; Vite resolves the import here at bundle time. | ||
| // eslint-disable-next-line import/no-relative-packages | ||
| import { brandingLightTheme } from '../../packages-internal/core-docs/src/branding/brandingTheme'; | ||
|
|
||
| export default brandingLightTheme; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,8 @@ | |
| * screenshots, one for axe — so editing one tool can never stomp on the | ||
| * other. Each list is evaluated last-match-wins (no inheritance: an override | ||
| * rule must restate every field it cares about) against the docs path | ||
| * `docs/data/material/components/{slug}/{Demo}`. | ||
| * `docs/data/material/components/{slug}/{Demo}` (component demos) or | ||
| * `docs/src/components/product{Product}/{Name}` (landing-page composites). | ||
| * | ||
| * Whole-slug exclusions where *no* tool wants anything live in the | ||
| * `index.jsx` glob — dropping them from the bundle entirely, not just from | ||
|
|
@@ -12,12 +13,17 @@ | |
|
|
||
| import { minimatch } from 'minimatch'; | ||
|
|
||
| /** Default playwright viewport when no `ScreenshotRule.viewport` matches. */ | ||
| export const DEFAULT_VIEWPORT = { width: 1000, height: 700 }; | ||
|
|
||
| export interface ScreenshotRule { | ||
| /** Minimatch glob against `docs/data/material/components/{slug}/{Demo}`. */ | ||
| /** Minimatch glob against the docs path (see file-level comment). */ | ||
| test: string; | ||
| enabled?: boolean; | ||
| /** Playwright waits for this selector after navigation, before axe + screenshot. */ | ||
| waitForSelector?: string; | ||
| /** Per-route playwright viewport override. Defaults to {@link DEFAULT_VIEWPORT}. */ | ||
| viewport?: { width: number; height: number }; | ||
| } | ||
|
|
||
| export interface A11yRule { | ||
|
|
@@ -98,6 +104,14 @@ export const SCREENSHOT_RULES: ScreenshotRule[] = [ | |
| test: 'docs/data/material/components/table/ReactVirtualizedTable', | ||
| waitForSelector: '[data-index="1"]', | ||
| }, // Wait for virtualized rows to render | ||
|
|
||
| // Landing-page composites under `docs/src/components/product*/` use | ||
| // desktop breakpoints (`md`+) and look clipped at the 1000×700 default. | ||
| { test: 'docs/src/components/product*/**', viewport: { width: 1280, height: 800 } }, | ||
| // X composites (large grids, dense charts) want a wider canvas to match | ||
| // their live-docs desktop layout. Last-match-wins so this overrides the | ||
| // broader product*/** viewport above. | ||
| { test: 'docs/src/components/productX/**', viewport: { width: 1440, height: 900 } }, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iirc playwright has a |
||
| ]; | ||
|
|
||
| /** | ||
|
|
@@ -117,19 +131,32 @@ export interface ParsedRoute { | |
| demo: string; | ||
| } | ||
|
|
||
| const ROUTE_REGEX = /^\/docs-components-([^/]+)\/(.+)$/; | ||
| const COMPONENT_ROUTE_REGEX = /^\/docs-components-([^/]+)\/(.+)$/; | ||
| const COMPOSITE_ROUTE_REGEX = /^\/docs-product-([^/]+)\/(.+)$/; | ||
|
|
||
| /** | ||
| * Map a VRT route to its docs path + slug + demo, or `null` for non-component | ||
| * routes (regression fixtures). | ||
| * | ||
| * Recognises two route shapes: | ||
| * - `/docs-components-{slug}/{Demo}` → `docs/data/material/components/{slug}/{Demo}` | ||
| * - `/docs-product-{product}/{Name}` → `docs/src/components/product{Product}/{Name}` | ||
| */ | ||
| export function parseRoute(route: string): ParsedRoute | null { | ||
| const match = route.match(ROUTE_REGEX); | ||
| if (!match) { | ||
| return null; | ||
| const componentMatch = route.match(COMPONENT_ROUTE_REGEX); | ||
| if (componentMatch) { | ||
| const [, slug, demo] = componentMatch; | ||
| return { path: `docs/data/material/components/${slug}/${demo}`, slug, demo }; | ||
| } | ||
| const compositeMatch = route.match(COMPOSITE_ROUTE_REGEX); | ||
| if (compositeMatch) { | ||
| const [, product, demo] = compositeMatch; | ||
| // Re-capitalize the single-word product segment from `index.jsx`'s glob | ||
| // (`material` → `Material`, `x` → `X`) to rebuild the directory name. | ||
| const dir = `product${product.charAt(0).toUpperCase()}${product.slice(1)}`; | ||
| return { path: `docs/src/components/${dir}/${demo}`, slug: product, demo }; | ||
| } | ||
| const [, slug, demo] = match; | ||
| return { path: `docs/data/material/components/${slug}/${demo}`, slug, demo }; | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Override `Date` so `new Date()` and `Date.now()` return a stable instant for | ||
| // Argos — otherwise the `today` in date-aware composites (XHero / | ||
| // XDateRangeDemo build picker defaults from `dayjs()`) shifts the baseline | ||
| // every day. | ||
| // | ||
| // This MUST be imported before any module that reads `Date` at module scope. | ||
| // `index.jsx` imports it on its first line, ahead of `./fixtures` (which hosts | ||
| // the eager `import.meta.glob` calls). Eager globs *prepend* their imports to | ||
| // the module that contains them rather than appending (see mui/base-ui#4370), | ||
| // so the globs live in a separate `./fixtures` module that is imported after | ||
| // this one — guaranteeing the demos' top-level `dayjs()` runs after the | ||
| // override is installed. | ||
| // | ||
| // `Reflect.construct` preserves the native `Date.prototype` (timezone-aware | ||
| // date libraries enumerate its own methods) and forwards `new.target` for | ||
| // correct subclass construction. Follows mui/base-ui#4337. The playwright page | ||
| // also pins `timezoneId: 'UTC'` (see `index.test.js`) so the frozen instant | ||
| // renders identically regardless of the machine's local timezone. | ||
|
|
||
| const fakeNow = new Date('2025-06-15T09:00:00.000Z').valueOf(); | ||
|
|
||
| const OriginalDate = Date; | ||
| const offset = fakeNow - OriginalDate.now(); | ||
|
|
||
| function FakeDate(...args: any[]): Date | string { | ||
| if (new.target) { | ||
| return args.length === 0 | ||
| ? Reflect.construct(OriginalDate, [OriginalDate.now() + offset], new.target) | ||
| : Reflect.construct(OriginalDate, args, new.target); | ||
| } | ||
| return new OriginalDate(OriginalDate.now() + offset).toString(); | ||
| } | ||
|
|
||
| FakeDate.prototype = OriginalDate.prototype; | ||
| // Static methods can't be assigned to a plain function type under `strict`, so | ||
| // copy them through `Object.assign` (base-ui's version relies on a looser | ||
| // tsconfig; this is the typecheck-clean equivalent). | ||
| Object.assign(FakeDate, { | ||
| parse: OriginalDate.parse, | ||
| UTC: OriginalDate.UTC, | ||
| now: () => OriginalDate.now() + offset, | ||
| }); | ||
|
|
||
| (globalThis as { Date: unknown }).Date = FakeDate; | ||
|
|
||
| export {}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, but no. This file shouldn't be necessary, if vitest can resolve like a bundler then we should configure typescript to resolve like a bundler.