Merge pull request #1968 from modelcontextprotocol/v2/chore/1898-drop-setprojectannotations

chore(storybook): drop the redundant setProjectAnnotations setup file
This commit is contained in:
Cliff Hall 2026-08-11 11:19:01 -04:00 • committed by GitHub
commit 4b9d84d4f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 102 additions and 8 deletions

View file

@ -1,7 +0,0 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from "@storybook/react-vite";
import * as projectAnnotations from "./preview";
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);

View file

@ -0,0 +1,93 @@
import { Button } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, within } from "storybook/test";
import { theme } from "../theme/theme";
// Guard for #1898.
//
// `.storybook/vitest.setup.ts` used to call `setProjectAnnotations([...])` to
// hand the preview annotations to the Storybook vitest project. Since Storybook
// 10.3 `@storybook/addon-vitest` applies them automatically — and *skips* doing
// so when it finds such a setup file — so the file was removed and the
// `setupFiles` entry dropped from the `storybook` project in `vite.config.ts`.
//
// A green suite is not evidence that the automatic path works. Two things the
// setup file used to provision are silently losable:
//
// • `./preview`'s decorator, which wraps every story in `MantineProvider`
// with the project theme, and its `import "../src/App.css"`, which defines
// the `--inspector-*` tokens. Without them stories render unthemed and
// unstyled — and would very likely still pass their play functions.
// • `@storybook/addon-a11y/preview`, which runs axe after each play function.
// Without it the `a11y: { test: "error" }` parameter is inert and every
// story passes its accessibility check vacuously.
//
// The story below closes the first gap: it asserts the Mantine theme variables
// and the `App.css` tokens are actually present in the rendered document, so an
// unthemed render fails loudly rather than passing. It also asserts the preview
// `parameters` reached the story, which is the same channel the a11y parameter
// arrives on. The axe *runner* itself can't be introspected from a play
// function; it was verified out-of-band by temporarily giving a story a real
// violation and confirming the suite went red (see the PR for #1898).
// Mantine's own default when a theme pins no `primaryShade` — the value it
// falls back to when deriving `--mantine-primary-color-filled`.
const MANTINE_DEFAULT_LIGHT_PRIMARY_SHADE = 6;
// The shade Mantine derives `--mantine-primary-color-filled` from. Everything
// here is read from the theme rather than restated, so re-pinning
// `primaryShade` or repainting the palette moves the expectation with it — a
// guard that had to be edited alongside a legitimate theme change would just
// train people to edit it, which is the opposite of what it's for.
//
// The light branch is the one that matters: `./preview` renders every story
// with `defaultColorScheme="light"` and an `initialGlobals.colorScheme` of
// `"light"`. `primaryShade` is `number | { light, dark }` in Mantine's types,
// so both forms are handled.
function lightPrimaryShade(): number {
const shade = theme.primaryShade;
if (typeof shade === "number") return shade;
return shade?.light ?? MANTINE_DEFAULT_LIGHT_PRIMARY_SHADE;
}
function expectedPrimaryColor(): string {
const palette = theme.colors?.[theme.primaryColor ?? ""];
const color = palette?.[lightPrimaryShade()];
if (!color) throw new Error("theme is missing its primary color palette");
return color;
}
const meta: Meta<typeof Button> = {
title: "Meta/Preview Annotations",
component: Button,
args: { children: "Themed" },
};
export default meta;
type Story = StoryObj<typeof Button>;
export const ProjectAnnotationsApplied: Story = {
play: async ({ canvasElement, parameters }) => {
const canvas = within(canvasElement);
expect(canvas.getByRole("button", { name: "Themed" })).toBeInTheDocument();
const root = getComputedStyle(document.documentElement);
// `MantineProvider` (the `./preview` decorator) injects the theme's CSS
// variables onto `:root`. No provider, no variables.
expect(root.getPropertyValue("--mantine-primary-color-filled").trim()).toBe(
expectedPrimaryColor(),
);
// `--inspector-brand-primary` is defined in `App.css`, which only reaches
// the story through `./preview`'s stylesheet import — and it resolves
// *through* the Mantine variable above, so this covers both layers.
expect(root.getPropertyValue("--inspector-brand-primary").trim()).toBe(
expectedPrimaryColor(),
);
// The preview `parameters` merged into the story context — the same channel
// that carries `a11y: { test: "error" }` to the a11y addon.
expect(parameters.a11y).toMatchObject({ test: "error" });
},
};

View file

@ -379,7 +379,15 @@ export default defineConfig(({ command }) => {
},
],
},
setupFiles: [".storybook/vitest.setup.ts"],
// No `setupFiles`: since Storybook 10.3 `@storybook/addon-vitest`
// provisions the preview annotations (`.storybook/preview.tsx` plus
// `@storybook/addon-a11y/preview`) itself, and *skips* doing so when
// it finds a setup file calling `setProjectAnnotations` — so the old
// `.storybook/vitest.setup.ts` was both redundant and actively
// opting out of the automatic path (#1898). A green suite doesn't
// prove the automatic provisioning works (stories rendered without
// the Mantine decorator would very likely still pass), so
// `src/test/PreviewAnnotations.stories.tsx` asserts it directly.
},
},
],