From 47b748c66f1d3c8044c9148bbc80241ecc3145db Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 10 Apr 2026 11:06:01 +0200 Subject: [PATCH 1/6] ise vitest's provide+inject API instead of browser commands to inject configuration into the browser environment. allow any run config overrides. store all reports on currentRun, not just a11yReports --- code/addons/vitest/src/constants.ts | 4 +- .../vitest/src/node/test-manager.test.ts | 96 +++++++++++++++++-- code/addons/vitest/src/node/test-manager.ts | 32 ++++--- code/addons/vitest/src/node/vitest-manager.ts | 20 +++- code/addons/vitest/src/types.ts | 13 ++- code/addons/vitest/src/vitest-plugin/index.ts | 32 ++----- .../vitest/src/vitest-plugin/test-utils.ts | 42 +++++--- .../vitest/src/vitest-provided-context.d.ts | 8 ++ 8 files changed, 178 insertions(+), 69 deletions(-) create mode 100644 code/addons/vitest/src/vitest-provided-context.d.ts diff --git a/code/addons/vitest/src/constants.ts b/code/addons/vitest/src/constants.ts index 81113de40931..76cc765705eb 100644 --- a/code/addons/vitest/src/constants.ts +++ b/code/addons/vitest/src/constants.ts @@ -38,7 +38,7 @@ export const storeOptions = { }, componentTestStatuses: [], a11yStatuses: [], - a11yReports: {}, + reports: {}, componentTestCount: { success: 0, error: 0, @@ -75,7 +75,7 @@ export type TriggerTestRunRequestPayload = { requestId: string; actor: string; storyIds?: string[]; - config?: Partial; + config?: Record; }; export type TestRunResult = CurrentRun; diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index 12e884fb000d..07401419d6c4 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -22,6 +22,7 @@ const vitest = vi.hoisted(() => ({ init: vi.fn(), close: vi.fn(), onCancel: vi.fn(), + provide: vi.fn(), runTestSpecifications: vi.fn(), cancelCurrentRun: vi.fn(), globTestSpecifications: vi.fn(), @@ -154,11 +155,15 @@ const mockTestProviderStore: TestProviderStoreById = { const tests = [ { - project: { config: { env: { __STORYBOOK_URL__: 'http://localhost:6006' } } }, + project: { + config: { env: { __STORYBOOK_URL__: 'http://localhost:6006' } }, + }, moduleId: path.join(process.cwd(), 'path/to/file'), }, { - project: { config: { env: { __STORYBOOK_URL__: 'http://localhost:6006' } } }, + project: { + config: { env: { __STORYBOOK_URL__: 'http://localhost:6006' } }, + }, moduleId: path.join(process.cwd(), 'path/to/another/file'), }, ]; @@ -211,9 +216,76 @@ describe('TestManager', () => { }, }); expect(createVitest).toHaveBeenCalledTimes(1); + expect(vitest.provide).toHaveBeenCalledWith('sb-config', { + coverage: false, + a11y: false, + }); expect(vitest.runTestSpecifications).toHaveBeenCalledWith(tests, true); }); + it('should provide merged config override before running tests', async () => { + vitest.globTestSpecifications.mockImplementation(() => tests); + const testManager = await TestManager.start(options); + + await testManager.handleTriggerRunEvent({ + type: 'TRIGGER_RUN', + payload: { + triggeredBy: 'external:actor', + configOverride: { + coverage: false, + a11y: true, + customFlag: 'custom-value', + }, + }, + }); + + expect(vitest.provide).toHaveBeenLastCalledWith('sb-config', { + coverage: false, + a11y: true, + customFlag: 'custom-value', + }); + }); + + it('should persist all reports in currentRun', async () => { + const testManager = await TestManager.start(options); + const passedResult = { + state: 'passed', + errors: [], + } as unknown as TestResult; + + await testManager.runTestsWithState({ + storyIds: ['story--one'], + triggeredBy: 'global', + callback: async () => { + testManager.onTestCaseResult({ + storyId: 'story--one', + testResult: passedResult, + reports: [ + { + type: 'a11y', + status: 'passed', + result: { id: 'a11y-report' } as any, + } as any, + { + type: 'custom', + status: 'passed', + result: { id: 'custom-report' } as any, + } as any, + ], + }); + testManager.onTestRunEnd({ + totalTestCount: 1, + unhandledErrors: [], + }); + }, + }); + + expect(mockStore.getState().currentRun.reports['story--one']).toEqual([ + { type: 'a11y', status: 'passed', result: { id: 'a11y-report' } }, + { type: 'custom', status: 'passed', result: { id: 'custom-report' } }, + ]); + }); + it('should filter tests', async () => { vitest.globTestSpecifications.mockImplementation(() => tests); const testManager = await TestManager.start(options); @@ -325,7 +397,10 @@ describe('TestManager', () => { it('should ignore non-requested same-name story results after run', async () => { const testManager = await TestManager.start(options); - const passedResult = { state: 'passed', errors: [] } as unknown as TestResult; + const passedResult = { + state: 'passed', + errors: [], + } as unknown as TestResult; await testManager.runTestsWithState({ storyIds: ['story--one', 'another--two'], @@ -357,7 +432,10 @@ describe('TestManager', () => { it('should keep child test results when parent story is requested', async () => { const testManager = await TestManager.start(options); - const passedResult = { state: 'passed', errors: [] } as unknown as TestResult; + const passedResult = { + state: 'passed', + errors: [], + } as unknown as TestResult; await testManager.runTestsWithState({ storyIds: ['parent--story'], @@ -385,7 +463,10 @@ describe('TestManager', () => { expect(createVitest).toHaveBeenCalledTimes(1); createVitest.mockClear(); - mockStore.setState((s) => ({ ...s, config: { coverage: true, a11y: false } })); + mockStore.setState((s) => ({ + ...s, + config: { coverage: true, a11y: false }, + })); await testManager.handleTriggerRunEvent({ type: 'TRIGGER_RUN', @@ -408,7 +489,10 @@ describe('TestManager', () => { expect(createVitest).toHaveBeenCalledTimes(1); createVitest.mockClear(); - mockStore.setState((s) => ({ ...s, config: { coverage: true, a11y: false } })); + mockStore.setState((s) => ({ + ...s, + config: { coverage: true, a11y: false }, + })); await testManager.handleTriggerRunEvent({ type: 'TRIGGER_RUN', diff --git a/code/addons/vitest/src/node/test-manager.ts b/code/addons/vitest/src/node/test-manager.ts index d795ab1ecea6..f7402c448f3e 100644 --- a/code/addons/vitest/src/node/test-manager.ts +++ b/code/addons/vitest/src/node/test-manager.ts @@ -8,14 +8,13 @@ import type { TestProviderStoreById, } from 'storybook/internal/types'; -import type { A11yReport } from '@storybook/addon-a11y'; - import { throttle } from 'es-toolkit/function'; import type { Report } from 'storybook/preview-api'; import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants.ts'; import type { CurrentRun, + RunConfig, RunTrigger, StoreEvent, StoreState, @@ -79,7 +78,9 @@ export class TestManager { this.store .untilReady() .then(() => { - return this.vitestManager.startVitest({ coverage: this.store.getState().config.coverage }); + return this.vitestManager.startVitest({ + coverage: this.store.getState().config.coverage, + }); }) .then(() => this.onReady?.()) .catch((e) => { @@ -129,7 +130,7 @@ export class TestManager { }: { storyIds?: string[]; triggeredBy: RunTrigger; - configOverride?: StoreState['config']; + configOverride?: RunConfig; callback: () => Promise; }) { this.componentTestStatusStore.unset(storyIds); @@ -147,10 +148,6 @@ export class TestManager { config: runConfig, }, })); - // set the config at the start of a test run, - // so that changing the config during the test run does not affect the currently running test run - process.env.VITEST_STORYBOOK_CONFIG = JSON.stringify(runConfig); - await this.testProviderStore.runWithState(async () => { await callback(); this.store.send({ @@ -228,15 +225,18 @@ export class TestManager { this.componentTestStatusStore.set(componentTestStatuses); - const a11yReportsByStoryId: CurrentRun['a11yReports'] = {}; + const reportsByStoryId: CurrentRun['reports'] = {}; const a11yStatuses: typeof componentTestStatuses = []; for (const { storyId, reports } of testCaseResultsToFlush) { + if (reports?.length) { + reportsByStoryId[storyId] = reports; + } + const storyA11yReports = reports?.filter((r) => r.type === 'a11y'); if (!storyA11yReports?.length) { continue; } - a11yReportsByStoryId[storyId] = storyA11yReports.map((r) => r.result) as A11yReport[]; for (const a11yReport of storyA11yReports) { a11yStatuses.push({ storyId, @@ -281,12 +281,16 @@ export class TestManager { currentRun: { ...s.currentRun, componentTestCount: { success: ctSuccess, error: ctError }, - a11yCount: { success: a11ySuccess, warning: a11yWarning, error: a11yError }, + a11yCount: { + success: a11ySuccess, + warning: a11yWarning, + error: a11yError, + }, componentTestStatuses: s.currentRun.componentTestStatuses.concat(componentTestStatuses), a11yStatuses: s.currentRun.a11yStatuses.concat(a11yStatuses), - a11yReports: { - ...s.currentRun.a11yReports, - ...a11yReportsByStoryId, + reports: { + ...s.currentRun.reports, + ...reportsByStoryId, }, // in some cases successes and errors can exceed the anticipated totalTestCount // e.g. when testing more tests than the stories we know about upfront diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index ca1ed83f9f9a..823058d6e147 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -102,7 +102,10 @@ export class VitestManager { for (const location of potentialConfigFileLocations) { for (const file of configFiles) { - const maybe = find.any([file], { cwd: location, last: getProjectRoot() }); + const maybe = find.any([file], { + cwd: location, + last: getProjectRoot(), + }); if (maybe && existsSync(maybe)) { firstVitestConfig ??= dirname(maybe); const content = readFileSync(maybe, 'utf8'); @@ -363,10 +366,19 @@ export class VitestManager { return { filteredTestSpecifications, filteredStoryIds }; } + private getCurrentRunConfig() { + return this.testManager.store.getState().currentRun.config; + } + + private provideRunConfig() { + this.vitest?.provide('sb-config', this.getCurrentRunConfig()); + } + async runTests(runPayload: TriggerRunEvent['payload']) { - const { watching, config } = this.testManager.store.getState(); + const { watching } = this.testManager.store.getState(); + const runConfig = this.getCurrentRunConfig(); const coverageShouldBeEnabled = - config.coverage && !watching && (runPayload?.storyIds?.length ?? 0) === 0; + !!runConfig.coverage && !watching && (runPayload?.storyIds?.length ?? 0) === 0; const currentCoverage = this.vitest?.config.coverage?.enabled; if (!this.vitest) { @@ -377,6 +389,8 @@ export class VitestManager { await this.vitestRestartPromise; } + this.provideRunConfig(); + this.resetGlobalTestNamePattern(); await this.cancelCurrentRun(); diff --git a/code/addons/vitest/src/types.ts b/code/addons/vitest/src/types.ts index e1f4d64ccdd7..f5afdc367230 100644 --- a/code/addons/vitest/src/types.ts +++ b/code/addons/vitest/src/types.ts @@ -1,10 +1,7 @@ import type { experimental_UniversalStore } from 'storybook/internal/core-server'; import type { PreviewAnnotation, Status, StoryId, StoryIndex } from 'storybook/internal/types'; import type { API_HashEntry } from 'storybook/internal/types'; - -// import type { A11yReport } from '@storybook/addon-a11y'; -// TODO: There's a type error in axe-core that makes this error during production builds -type A11yReport = any; +import type { Report } from 'storybook/preview-api'; export interface VitestError extends Error { VITEST_TEST_PATH?: string; @@ -24,6 +21,8 @@ export type ErrorLike = { cause?: ErrorLike; }; +export type RunConfig = Record; + export type RunTrigger = | 'run-all' | 'global' @@ -33,7 +32,7 @@ export type RunTrigger = export type CurrentRun = { triggeredBy: RunTrigger | undefined; - config: StoreState['config']; + config: RunConfig; componentTestStatuses: Status[]; a11yStatuses: Status[]; componentTestCount: { @@ -45,7 +44,7 @@ export type CurrentRun = { warning: number; error: number; }; - a11yReports: Record; + reports: Record; totalTestCount: number | undefined; storyIds: StoryId[] | undefined; startedAt: number | undefined; @@ -84,7 +83,7 @@ export type TriggerRunEvent = { payload: { storyIds?: string[] | undefined; triggeredBy: RunTrigger; - configOverride?: StoreState['config']; + configOverride?: RunConfig; }; }; diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 17a42dd20882..181a4b745d38 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -7,7 +7,6 @@ import type { ViteUserConfig } from 'vitest/config'; import { DEFAULT_FILES_PATTERN, getInterpretedFile, - loadPreviewOrConfigFile, normalizeStories, optionalEnvToBoolean, resolvePathInStorybookCache, @@ -19,12 +18,7 @@ import { experimental_loadStorybook, mapStaticDir, } from 'storybook/internal/core-server'; -import { - componentTransform, - isCsfFactoryPreview, - readConfig, - vitestTransform, -} from 'storybook/internal/csf-tools'; +import { componentTransform, readConfig, vitestTransform } from 'storybook/internal/csf-tools'; import { MainFileMissingError } from 'storybook/internal/server-errors'; import { telemetry } from 'storybook/internal/telemetry'; import { oneWayHash } from 'storybook/internal/telemetry'; @@ -356,6 +350,11 @@ export const storybookTest = async (options?: UserOptions): Promise => __VITEST_SKIP_TAGS__: finalOptions.tags.skip.join(','), }, + provide: { + 'sb-config': {}, + 'sb-ghost-stories': !!process.env.STORYBOOK_COMPONENT_PATHS, + }, + include: [...includeStories, ...getComponentTestPaths()], exclude: [ ...(nonMutableInputConfig.test?.exclude ?? []), @@ -375,25 +374,6 @@ export const storybookTest = async (options?: UserOptions): Promise => : {}), browser: { - commands: { - getInitialGlobals: () => { - const envConfig = JSON.parse(process.env.VITEST_STORYBOOK_CONFIG ?? '{}'); - - const shouldRunA11yTests = isVitestStorybook ? (envConfig.a11y ?? false) : true; - const globals: Record = {}; - globals.a11y = { - manual: !shouldRunA11yTests, - }; - - if (process.env.STORYBOOK_COMPONENT_PATHS) { - globals.ghostStories = { - enabled: true, - }; - } - - return globals; - }, - }, // if there is a test.browser config AND test.browser.screenshotFailures is not explicitly set, we set it to false ...(nonMutableInputConfig.test?.browser && nonMutableInputConfig.test.browser.screenshotFailures === undefined diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 876ced8f055c..71542189d11b 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -1,21 +1,12 @@ -import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; +import { inject, type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; import { type Meta, type Story, getStoryChildren, isStory } from 'storybook/internal/csf'; import type { ComponentAnnotations, ComposedStoryFn, Renderer } from 'storybook/internal/types'; -import { server } from '@vitest/browser/context'; import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/preview-api'; import { setViewport } from './viewports.ts'; -declare module 'vitest/browser' { - interface BrowserCommands { - getInitialGlobals: () => Promise>; - } -} - -const { getInitialGlobals } = server.commands; - /** * Converts a file URL to a file path, handling URL encoding * @@ -60,10 +51,39 @@ export const testStory = ({ const storyAnnotations = test ? test.input : annotations.story; + let runConfig: Record = { a11y: true }; + try { + runConfig = inject('sb-config'); + } catch { + // Standalone Vitest runs might not provide Storybook run config. + } + + let ghostStoriesEnabled = false; + try { + ghostStoriesEnabled = inject('sb-ghost-stories'); + } catch { + // Standalone Vitest runs might not provide Storybook ghost stories config. + } + + const shouldRunA11yTests = !!runConfig.a11y; + const initialGlobals = { + sbConfig: runConfig, + ...(ghostStoriesEnabled + ? { + ghostStories: { + enabled: true, + }, + } + : {}), + a11y: { + manual: !shouldRunA11yTests, + }, + }; + const composedStory = composeStory( storyAnnotations, annotations.meta!, - { initialGlobals: (await getInitialGlobals?.()) ?? {} }, + { initialGlobals }, annotations.preview ?? globalThis.globalProjectAnnotations, exportName ); diff --git a/code/addons/vitest/src/vitest-provided-context.d.ts b/code/addons/vitest/src/vitest-provided-context.d.ts new file mode 100644 index 000000000000..556b4658578c --- /dev/null +++ b/code/addons/vitest/src/vitest-provided-context.d.ts @@ -0,0 +1,8 @@ +import 'vitest'; + +declare module 'vitest' { + interface ProvidedContext { + 'sb-config': Record; + 'sb-ghost-stories': boolean; + } +} From 7e3dc40d62c3a283bec3aa96b3c12e02103b942f Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 10 Apr 2026 12:42:03 +0200 Subject: [PATCH 2/6] fix a11y toggle --- code/addons/vitest/src/vitest-plugin/index.ts | 1 - code/addons/vitest/src/vitest-plugin/test-utils.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index 181a4b745d38..d23611842ade 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -351,7 +351,6 @@ export const storybookTest = async (options?: UserOptions): Promise => }, provide: { - 'sb-config': {}, 'sb-ghost-stories': !!process.env.STORYBOOK_COMPONENT_PATHS, }, diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 71542189d11b..63a574511588 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -53,14 +53,14 @@ export const testStory = ({ let runConfig: Record = { a11y: true }; try { - runConfig = inject('sb-config'); + runConfig = inject('sb-config') ?? { a11y: true }; } catch { // Standalone Vitest runs might not provide Storybook run config. } let ghostStoriesEnabled = false; try { - ghostStoriesEnabled = inject('sb-ghost-stories'); + ghostStoriesEnabled = inject('sb-ghost-stories') ?? false; } catch { // Standalone Vitest runs might not provide Storybook ghost stories config. } From 0cbdc2fc8fd991b19a3744a30272b1e32bf69dba Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 22 Apr 2026 11:20:01 +0200 Subject: [PATCH 3/6] make provided keys more generic Co-authored-by: Copilot --- code/addons/vitest/src/constants.ts | 3 +++ .../vitest/src/node/test-manager.test.ts | 22 ++++++++++++------- code/addons/vitest/src/node/test-manager.ts | 10 +++++++++ code/addons/vitest/src/node/vitest-manager.ts | 4 ++-- code/addons/vitest/src/types.ts | 4 ++++ code/addons/vitest/src/vitest-plugin/index.ts | 3 ++- .../vitest/src/vitest-plugin/test-utils.ts | 8 +++++-- .../vitest/src/vitest-provided-context.d.ts | 9 ++++++-- 8 files changed, 48 insertions(+), 15 deletions(-) diff --git a/code/addons/vitest/src/constants.ts b/code/addons/vitest/src/constants.ts index 76cc765705eb..4d980f3c0c25 100644 --- a/code/addons/vitest/src/constants.ts +++ b/code/addons/vitest/src/constants.ts @@ -38,6 +38,7 @@ export const storeOptions = { }, componentTestStatuses: [], a11yStatuses: [], + a11yReports: {}, reports: {}, componentTestCount: { success: 0, @@ -66,6 +67,8 @@ export const TEST_PROVIDER_STORE_CHANNEL_EVENT_NAME = 'UNIVERSAL_STORE:storybook export const STATUS_TYPE_ID_COMPONENT_TEST = 'storybook/component-test'; export const STATUS_TYPE_ID_A11Y = 'storybook/a11y'; +export const STORYBOOK_TEST_CONFIG_PROVIDE_KEY = 'storybook/test-config'; +export const STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY = 'storybook/core-ghost-stories'; // Channel event names for programmatic test triggering export const TRIGGER_TEST_RUN_REQUEST = `${ADDON_ID}/trigger-test-run-request`; diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index 07401419d6c4..58c7fc908f45 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -10,8 +10,14 @@ import type { } from 'storybook/internal/types'; import path from 'pathe'; - -import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants.ts'; +import type { Report } from 'storybook/preview-api'; + +import { + STATUS_TYPE_ID_A11Y, + STATUS_TYPE_ID_COMPONENT_TEST, + STORYBOOK_TEST_CONFIG_PROVIDE_KEY, + storeOptions, +} from '../constants.ts'; import type { StoreEvent, StoreState } from '../types.ts'; import { TestManager, type TestManagerOptions } from './test-manager.ts'; import { DOUBLE_SPACES } from './vitest-manager.ts'; @@ -216,7 +222,7 @@ describe('TestManager', () => { }, }); expect(createVitest).toHaveBeenCalledTimes(1); - expect(vitest.provide).toHaveBeenCalledWith('sb-config', { + expect(vitest.provide).toHaveBeenCalledWith(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, { coverage: false, a11y: false, }); @@ -239,7 +245,7 @@ describe('TestManager', () => { }, }); - expect(vitest.provide).toHaveBeenLastCalledWith('sb-config', { + expect(vitest.provide).toHaveBeenLastCalledWith(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, { coverage: false, a11y: true, customFlag: 'custom-value', @@ -264,13 +270,13 @@ describe('TestManager', () => { { type: 'a11y', status: 'passed', - result: { id: 'a11y-report' } as any, - } as any, + result: { id: 'a11y-report' }, + } as Report, { type: 'custom', status: 'passed', - result: { id: 'custom-report' } as any, - } as any, + result: { id: 'custom-report' }, + } as Report, ], }); testManager.onTestRunEnd({ diff --git a/code/addons/vitest/src/node/test-manager.ts b/code/addons/vitest/src/node/test-manager.ts index f7402c448f3e..83d945b780ec 100644 --- a/code/addons/vitest/src/node/test-manager.ts +++ b/code/addons/vitest/src/node/test-manager.ts @@ -225,6 +225,7 @@ export class TestManager { this.componentTestStatusStore.set(componentTestStatuses); + const a11yReportsByStoryId: CurrentRun['a11yReports'] = {}; const reportsByStoryId: CurrentRun['reports'] = {}; const a11yStatuses: typeof componentTestStatuses = []; @@ -237,6 +238,7 @@ export class TestManager { if (!storyA11yReports?.length) { continue; } + a11yReportsByStoryId[storyId] = storyA11yReports.map((report) => report.result); for (const a11yReport of storyA11yReports) { a11yStatuses.push({ storyId, @@ -288,6 +290,14 @@ export class TestManager { }, componentTestStatuses: s.currentRun.componentTestStatuses.concat(componentTestStatuses), a11yStatuses: s.currentRun.a11yStatuses.concat(a11yStatuses), + /* + TODO: a11yReports is just here for backwards compatibility with older versions of addon-mcp. + They are also part of the more generic reports property, so we can remove this in a future major release when we can break compatibility. + */ + a11yReports: { + ...s.currentRun.a11yReports, + ...a11yReportsByStoryId, + }, reports: { ...s.currentRun.reports, ...reportsByStoryId, diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 823058d6e147..775c376a24dd 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -19,7 +19,7 @@ import path, { dirname, join, normalize, resolve } from 'pathe'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import { COVERAGE_DIRECTORY } from '../constants.ts'; +import { COVERAGE_DIRECTORY, STORYBOOK_TEST_CONFIG_PROVIDE_KEY } from '../constants.ts'; import { log } from '../logger.ts'; import type { TriggerRunEvent } from '../types.ts'; import type { StorybookCoverageReporterOptions } from './coverage-reporter.ts'; @@ -371,7 +371,7 @@ export class VitestManager { } private provideRunConfig() { - this.vitest?.provide('sb-config', this.getCurrentRunConfig()); + this.vitest?.provide(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, this.getCurrentRunConfig()); } async runTests(runPayload: TriggerRunEvent['payload']) { diff --git a/code/addons/vitest/src/types.ts b/code/addons/vitest/src/types.ts index f5afdc367230..362cb515f595 100644 --- a/code/addons/vitest/src/types.ts +++ b/code/addons/vitest/src/types.ts @@ -30,6 +30,8 @@ export type RunTrigger = | Extract | `external:${string}`; +export type A11yRunReport = Report['result']; + export type CurrentRun = { triggeredBy: RunTrigger | undefined; config: RunConfig; @@ -44,6 +46,8 @@ export type CurrentRun = { warning: number; error: number; }; + // Backwards compatibility for consumers that still read the legacy a11y-only shape. + a11yReports: Record; reports: Record; totalTestCount: number | undefined; storyIds: StoryId[] | undefined; diff --git a/code/addons/vitest/src/vitest-plugin/index.ts b/code/addons/vitest/src/vitest-plugin/index.ts index d23611842ade..119ba600b351 100644 --- a/code/addons/vitest/src/vitest-plugin/index.ts +++ b/code/addons/vitest/src/vitest-plugin/index.ts @@ -34,6 +34,7 @@ import type { PluginOption } from 'vite'; // Shared plugins from builder-vite (relative import to prebundle without adding a package dependency) import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins.ts'; +import { STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY } from '../constants.ts'; import type { InternalOptions, UserOptions } from './types.ts'; import { requiresProjectAnnotations } from './utils.ts'; @@ -351,7 +352,7 @@ export const storybookTest = async (options?: UserOptions): Promise => }, provide: { - 'sb-ghost-stories': !!process.env.STORYBOOK_COMPONENT_PATHS, + [STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY]: !!process.env.STORYBOOK_COMPONENT_PATHS, }, include: [...includeStories, ...getComponentTestPaths()], diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 63a574511588..084a0dd70fed 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -5,6 +5,10 @@ import type { ComponentAnnotations, ComposedStoryFn, Renderer } from 'storybook/ import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/preview-api'; +import { + STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, + STORYBOOK_TEST_CONFIG_PROVIDE_KEY, +} from '../constants.ts'; import { setViewport } from './viewports.ts'; /** @@ -53,14 +57,14 @@ export const testStory = ({ let runConfig: Record = { a11y: true }; try { - runConfig = inject('sb-config') ?? { a11y: true }; + runConfig = inject(STORYBOOK_TEST_CONFIG_PROVIDE_KEY) ?? { a11y: true }; } catch { // Standalone Vitest runs might not provide Storybook run config. } let ghostStoriesEnabled = false; try { - ghostStoriesEnabled = inject('sb-ghost-stories') ?? false; + ghostStoriesEnabled = inject(STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY) ?? false; } catch { // Standalone Vitest runs might not provide Storybook ghost stories config. } diff --git a/code/addons/vitest/src/vitest-provided-context.d.ts b/code/addons/vitest/src/vitest-provided-context.d.ts index 556b4658578c..52670c7d1e8d 100644 --- a/code/addons/vitest/src/vitest-provided-context.d.ts +++ b/code/addons/vitest/src/vitest-provided-context.d.ts @@ -1,8 +1,13 @@ import 'vitest'; +import type { + STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, + STORYBOOK_TEST_CONFIG_PROVIDE_KEY, +} from './constants.ts'; + declare module 'vitest' { interface ProvidedContext { - 'sb-config': Record; - 'sb-ghost-stories': boolean; + [STORYBOOK_TEST_CONFIG_PROVIDE_KEY]: Record; + [STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY]: boolean; } } From 41b56a9fc3b3d2cda634d567f40c854b9f22b6e8 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 22 Apr 2026 11:31:26 +0200 Subject: [PATCH 4/6] use better provide name --- code/addons/vitest/src/constants.ts | 2 +- code/addons/vitest/src/node/test-manager.test.ts | 6 +++--- code/addons/vitest/src/node/vitest-manager.ts | 4 ++-- code/addons/vitest/src/vitest-plugin/test-utils.ts | 6 +++--- code/addons/vitest/src/vitest-provided-context.d.ts | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/code/addons/vitest/src/constants.ts b/code/addons/vitest/src/constants.ts index 4d980f3c0c25..15a2ed166aed 100644 --- a/code/addons/vitest/src/constants.ts +++ b/code/addons/vitest/src/constants.ts @@ -67,7 +67,7 @@ export const TEST_PROVIDER_STORE_CHANNEL_EVENT_NAME = 'UNIVERSAL_STORE:storybook export const STATUS_TYPE_ID_COMPONENT_TEST = 'storybook/component-test'; export const STATUS_TYPE_ID_A11Y = 'storybook/a11y'; -export const STORYBOOK_TEST_CONFIG_PROVIDE_KEY = 'storybook/test-config'; +export const STORYBOOK_TEST_PROVIDE_KEY = 'storybook/test-provided'; export const STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY = 'storybook/core-ghost-stories'; // Channel event names for programmatic test triggering diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index 58c7fc908f45..8f74627b9dab 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -15,7 +15,7 @@ import type { Report } from 'storybook/preview-api'; import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, - STORYBOOK_TEST_CONFIG_PROVIDE_KEY, + STORYBOOK_TEST_PROVIDE_KEY, storeOptions, } from '../constants.ts'; import type { StoreEvent, StoreState } from '../types.ts'; @@ -222,7 +222,7 @@ describe('TestManager', () => { }, }); expect(createVitest).toHaveBeenCalledTimes(1); - expect(vitest.provide).toHaveBeenCalledWith(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, { + expect(vitest.provide).toHaveBeenCalledWith(STORYBOOK_TEST_PROVIDE_KEY, { coverage: false, a11y: false, }); @@ -245,7 +245,7 @@ describe('TestManager', () => { }, }); - expect(vitest.provide).toHaveBeenLastCalledWith(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, { + expect(vitest.provide).toHaveBeenLastCalledWith(STORYBOOK_TEST_PROVIDE_KEY, { coverage: false, a11y: true, customFlag: 'custom-value', diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 775c376a24dd..1e4f41f048b4 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -19,7 +19,7 @@ import path, { dirname, join, normalize, resolve } from 'pathe'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; -import { COVERAGE_DIRECTORY, STORYBOOK_TEST_CONFIG_PROVIDE_KEY } from '../constants.ts'; +import { COVERAGE_DIRECTORY, STORYBOOK_TEST_PROVIDE_KEY } from '../constants.ts'; import { log } from '../logger.ts'; import type { TriggerRunEvent } from '../types.ts'; import type { StorybookCoverageReporterOptions } from './coverage-reporter.ts'; @@ -371,7 +371,7 @@ export class VitestManager { } private provideRunConfig() { - this.vitest?.provide(STORYBOOK_TEST_CONFIG_PROVIDE_KEY, this.getCurrentRunConfig()); + this.vitest?.provide(STORYBOOK_TEST_PROVIDE_KEY, this.getCurrentRunConfig()); } async runTests(runPayload: TriggerRunEvent['payload']) { diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 084a0dd70fed..123fd1cf3cb4 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -7,7 +7,7 @@ import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/p import { STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, - STORYBOOK_TEST_CONFIG_PROVIDE_KEY, + STORYBOOK_TEST_PROVIDE_KEY, } from '../constants.ts'; import { setViewport } from './viewports.ts'; @@ -57,7 +57,7 @@ export const testStory = ({ let runConfig: Record = { a11y: true }; try { - runConfig = inject(STORYBOOK_TEST_CONFIG_PROVIDE_KEY) ?? { a11y: true }; + runConfig = inject(STORYBOOK_TEST_PROVIDE_KEY) ?? { a11y: true }; } catch { // Standalone Vitest runs might not provide Storybook run config. } @@ -71,7 +71,7 @@ export const testStory = ({ const shouldRunA11yTests = !!runConfig.a11y; const initialGlobals = { - sbConfig: runConfig, + [STORYBOOK_TEST_PROVIDE_KEY]: runConfig, ...(ghostStoriesEnabled ? { ghostStories: { diff --git a/code/addons/vitest/src/vitest-provided-context.d.ts b/code/addons/vitest/src/vitest-provided-context.d.ts index 52670c7d1e8d..2e595e518d68 100644 --- a/code/addons/vitest/src/vitest-provided-context.d.ts +++ b/code/addons/vitest/src/vitest-provided-context.d.ts @@ -2,12 +2,12 @@ import 'vitest'; import type { STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, - STORYBOOK_TEST_CONFIG_PROVIDE_KEY, + STORYBOOK_TEST_PROVIDE_KEY, } from './constants.ts'; declare module 'vitest' { interface ProvidedContext { - [STORYBOOK_TEST_CONFIG_PROVIDE_KEY]: Record; + [STORYBOOK_TEST_PROVIDE_KEY]: Record; [STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY]: boolean; } } From 05667eaf03bdbc68d4c963bfe107e459e06abe8d Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 29 Apr 2026 15:38:35 +0200 Subject: [PATCH 5/6] provide config in watch mode too --- .../vitest/src/node/test-manager.test.ts | 58 +++++++++++++++++++ code/addons/vitest/src/node/vitest-manager.ts | 1 + 2 files changed, 59 insertions(+) diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index 8f74627b9dab..26ca2ecea936 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -28,6 +28,9 @@ const vitest = vi.hoisted(() => ({ init: vi.fn(), close: vi.fn(), onCancel: vi.fn(), + logger: { + clearHighlightCache: vi.fn(), + }, provide: vi.fn(), runTestSpecifications: vi.fn(), cancelCurrentRun: vi.fn(), @@ -59,6 +62,13 @@ vi.mock('vitest/node', () => ({ const createVitest = mockCreateVitest; beforeEach(() => { + vi.clearAllMocks(); + mockStore.setState(() => ({ + ...storeOptions.initialState, + index: mockIndex, + })); + vitest.projects = [{}]; + vitest.config.coverage.enabled = false; createVitest.mockResolvedValue(vitest); }); @@ -252,6 +262,54 @@ describe('TestManager', () => { }); }); + it('should refresh provided config before watch-triggered reruns', async () => { + vitest.globTestSpecifications.mockImplementation(() => tests); + vitest.projects = [ + { + config: { + env: { __STORYBOOK_URL__: 'http://localhost:6006' }, + root: process.cwd(), + setupFiles: [], + }, + matchesTestGlob: vi.fn(), + vite: { + moduleGraph: { + getModuleById: vi.fn(), + getModulesByFile: vi.fn(() => []), + invalidateModule: vi.fn(), + }, + transformRequest: vi.fn(), + }, + }, + ] as any; + + const testManager = await TestManager.start(options); + + await testManager.handleTriggerRunEvent({ + type: 'TRIGGER_RUN', + payload: { + triggeredBy: 'global', + }, + }); + + vitest.provide.mockClear(); + mockStore.setState((s) => ({ + ...s, + watching: true, + config: { coverage: false, a11y: true }, + })); + + vi.spyOn(testManager.vitestManager as any, 'getTestDependencies').mockResolvedValue(new Set()); + + await testManager.vitestManager.runAffectedTestsAfterChange(tests[0].moduleId, 'change'); + + expect(vitest.provide).toHaveBeenCalledWith(STORYBOOK_TEST_PROVIDE_KEY, { + coverage: false, + a11y: true, + }); + expect(vitest.runTestSpecifications).toHaveBeenLastCalledWith(tests.slice(0, 1), false); + }); + it('should persist all reports in currentRun', async () => { const testManager = await TestManager.start(options); const passedResult = { diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 1e4f41f048b4..b5a6a9eb56d4 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -533,6 +533,7 @@ export class VitestManager { })); await this.vitest!.cancelCurrentRun('keyboard-input'); await this.runningPromise; + this.provideRunConfig(); await this.vitest!.runTestSpecifications(filteredTestSpecifications, false); }, }); From 4a90f0ac20589d6e3ec13bae041c3f8bdee1d7b0 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 30 Apr 2026 12:40:36 +0200 Subject: [PATCH 6/6] migrate render analysis to provide pattern --- .external/addon-svelte-csf | 1 + .external/addon-webpack5-compiler-babel | 1 + .external/addon-webpack5-compiler-swc | 1 + .rollout-repos/addon-coverage | 1 + .rollout-repos/addon-designs | 1 + .rollout-repos/addon-kit | 1 + .rollout-repos/addon-styling-webpack | 1 + .rollout-repos/addon-visual-tests | 1 + .rollout-repos/addon-webpack5-compiler-babel | 1 + .rollout-repos/addon-webpack5-compiler-swc | 1 + .rollout-repos/icons | 1 + .rollout-repos/telejson | 1 + .rollout-repos/test-runner | 1 + .rollout-repos/vite-plugin-storybook-nextjs | 1 + code/addons/vitest/src/constants.ts | 1 + .../addons/vitest/src/vitest-plugin/test-utils.ts | 15 +++++++++++++++ .../vitest/src/vitest-provided-context.d.ts | 2 ++ 17 files changed, 32 insertions(+) create mode 160000 .external/addon-svelte-csf create mode 160000 .external/addon-webpack5-compiler-babel create mode 160000 .external/addon-webpack5-compiler-swc create mode 160000 .rollout-repos/addon-coverage create mode 160000 .rollout-repos/addon-designs create mode 160000 .rollout-repos/addon-kit create mode 160000 .rollout-repos/addon-styling-webpack create mode 160000 .rollout-repos/addon-visual-tests create mode 160000 .rollout-repos/addon-webpack5-compiler-babel create mode 160000 .rollout-repos/addon-webpack5-compiler-swc create mode 160000 .rollout-repos/icons create mode 160000 .rollout-repos/telejson create mode 160000 .rollout-repos/test-runner create mode 160000 .rollout-repos/vite-plugin-storybook-nextjs diff --git a/.external/addon-svelte-csf b/.external/addon-svelte-csf new file mode 160000 index 000000000000..0ff845efcd43 --- /dev/null +++ b/.external/addon-svelte-csf @@ -0,0 +1 @@ +Subproject commit 0ff845efcd43373e5418b70ee83fb7325d33e940 diff --git a/.external/addon-webpack5-compiler-babel b/.external/addon-webpack5-compiler-babel new file mode 160000 index 000000000000..ae95d5f16854 --- /dev/null +++ b/.external/addon-webpack5-compiler-babel @@ -0,0 +1 @@ +Subproject commit ae95d5f168548237319071fe67e6cf51bd3981cf diff --git a/.external/addon-webpack5-compiler-swc b/.external/addon-webpack5-compiler-swc new file mode 160000 index 000000000000..3d64e2bf92ea --- /dev/null +++ b/.external/addon-webpack5-compiler-swc @@ -0,0 +1 @@ +Subproject commit 3d64e2bf92eae064f528ce5e45bdd4ad159ee6a4 diff --git a/.rollout-repos/addon-coverage b/.rollout-repos/addon-coverage new file mode 160000 index 000000000000..0279cd63a671 --- /dev/null +++ b/.rollout-repos/addon-coverage @@ -0,0 +1 @@ +Subproject commit 0279cd63a671d1c35e3c45b4ae35d2f6e0a39ab6 diff --git a/.rollout-repos/addon-designs b/.rollout-repos/addon-designs new file mode 160000 index 000000000000..50824a3e12c5 --- /dev/null +++ b/.rollout-repos/addon-designs @@ -0,0 +1 @@ +Subproject commit 50824a3e12c5004443695f3d58955c884876458e diff --git a/.rollout-repos/addon-kit b/.rollout-repos/addon-kit new file mode 160000 index 000000000000..e17a0f15cab7 --- /dev/null +++ b/.rollout-repos/addon-kit @@ -0,0 +1 @@ +Subproject commit e17a0f15cab7c0ea6fd9e9c5bc0cd6a8f24941ca diff --git a/.rollout-repos/addon-styling-webpack b/.rollout-repos/addon-styling-webpack new file mode 160000 index 000000000000..7df09eb6d759 --- /dev/null +++ b/.rollout-repos/addon-styling-webpack @@ -0,0 +1 @@ +Subproject commit 7df09eb6d7596f03d07aefda1252c707ba488d65 diff --git a/.rollout-repos/addon-visual-tests b/.rollout-repos/addon-visual-tests new file mode 160000 index 000000000000..be1829a2403b --- /dev/null +++ b/.rollout-repos/addon-visual-tests @@ -0,0 +1 @@ +Subproject commit be1829a2403b2bd55ddc694db00fd0f6ade3007d diff --git a/.rollout-repos/addon-webpack5-compiler-babel b/.rollout-repos/addon-webpack5-compiler-babel new file mode 160000 index 000000000000..5d9ade95a91d --- /dev/null +++ b/.rollout-repos/addon-webpack5-compiler-babel @@ -0,0 +1 @@ +Subproject commit 5d9ade95a91dd90e023bc471ce82c1eba2c19988 diff --git a/.rollout-repos/addon-webpack5-compiler-swc b/.rollout-repos/addon-webpack5-compiler-swc new file mode 160000 index 000000000000..5af31673f84b --- /dev/null +++ b/.rollout-repos/addon-webpack5-compiler-swc @@ -0,0 +1 @@ +Subproject commit 5af31673f84b656f19de0ee799ce691ec1ee2d49 diff --git a/.rollout-repos/icons b/.rollout-repos/icons new file mode 160000 index 000000000000..70f13df022f8 --- /dev/null +++ b/.rollout-repos/icons @@ -0,0 +1 @@ +Subproject commit 70f13df022f8a0a8ccc15ad358df967ddc2c2e5d diff --git a/.rollout-repos/telejson b/.rollout-repos/telejson new file mode 160000 index 000000000000..78136d94add2 --- /dev/null +++ b/.rollout-repos/telejson @@ -0,0 +1 @@ +Subproject commit 78136d94add2f441c2321cebd95ff9a793893828 diff --git a/.rollout-repos/test-runner b/.rollout-repos/test-runner new file mode 160000 index 000000000000..c1be8e0ebcb2 --- /dev/null +++ b/.rollout-repos/test-runner @@ -0,0 +1 @@ +Subproject commit c1be8e0ebcb2ba1e3e7374f14a2e89c120339fb2 diff --git a/.rollout-repos/vite-plugin-storybook-nextjs b/.rollout-repos/vite-plugin-storybook-nextjs new file mode 160000 index 000000000000..bbbb87a985dd --- /dev/null +++ b/.rollout-repos/vite-plugin-storybook-nextjs @@ -0,0 +1 @@ +Subproject commit bbbb87a985dd72f5c1f5a18dc3b3e73f650e8b6a diff --git a/code/addons/vitest/src/constants.ts b/code/addons/vitest/src/constants.ts index 15a2ed166aed..96e06983f104 100644 --- a/code/addons/vitest/src/constants.ts +++ b/code/addons/vitest/src/constants.ts @@ -69,6 +69,7 @@ export const STATUS_TYPE_ID_COMPONENT_TEST = 'storybook/component-test'; export const STATUS_TYPE_ID_A11Y = 'storybook/a11y'; export const STORYBOOK_TEST_PROVIDE_KEY = 'storybook/test-provided'; export const STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY = 'storybook/core-ghost-stories'; +export const STORYBOOK_CORE_RENDER_ANALYSIS_PROVIDE_KEY = 'storybook/core-render-analysis'; // Channel event names for programmatic test triggering export const TRIGGER_TEST_RUN_REQUEST = `${ADDON_ID}/trigger-test-run-request`; diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 123fd1cf3cb4..5ca0ebca8fd7 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -7,6 +7,7 @@ import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/p import { STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, + STORYBOOK_CORE_RENDER_ANALYSIS_PROVIDE_KEY, STORYBOOK_TEST_PROVIDE_KEY, } from '../constants.ts'; import { setViewport } from './viewports.ts'; @@ -69,6 +70,13 @@ export const testStory = ({ // Standalone Vitest runs might not provide Storybook ghost stories config. } + let renderAnalysisEnabled = false; + try { + renderAnalysisEnabled = inject(STORYBOOK_CORE_RENDER_ANALYSIS_PROVIDE_KEY) ?? false; + } catch { + // Standalone Vitest runs might not provide Storybook render analysis config. + } + const shouldRunA11yTests = !!runConfig.a11y; const initialGlobals = { [STORYBOOK_TEST_PROVIDE_KEY]: runConfig, @@ -79,6 +87,13 @@ export const testStory = ({ }, } : {}), + ...(renderAnalysisEnabled + ? { + renderAnalysis: { + enabled: true, + }, + } + : {}), a11y: { manual: !shouldRunA11yTests, }, diff --git a/code/addons/vitest/src/vitest-provided-context.d.ts b/code/addons/vitest/src/vitest-provided-context.d.ts index 2e595e518d68..fd5071d82e18 100644 --- a/code/addons/vitest/src/vitest-provided-context.d.ts +++ b/code/addons/vitest/src/vitest-provided-context.d.ts @@ -2,6 +2,7 @@ import 'vitest'; import type { STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY, + STORYBOOK_CORE_RENDER_ANALYSIS_PROVIDE_KEY, STORYBOOK_TEST_PROVIDE_KEY, } from './constants.ts'; @@ -9,5 +10,6 @@ declare module 'vitest' { interface ProvidedContext { [STORYBOOK_TEST_PROVIDE_KEY]: Record; [STORYBOOK_CORE_GHOST_STORIES_PROVIDE_KEY]: boolean; + [STORYBOOK_CORE_RENDER_ANALYSIS_PROVIDE_KEY]: boolean; } }