From f52a7f9c849b1cb0255403445a90575e37371c33 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 23 Dec 2024 14:57:03 +0100 Subject: [PATCH 1/9] Automigration: Improve addon-a11y-addon-test --- code/addons/test/src/constants.ts | 7 ++++ code/addons/test/src/postinstall.ts | 12 ++----- .../fixes/addon-a11y-addon-test.test.ts | 36 +++++++++++++++++-- .../fixes/addon-a11y-addon-test.ts | 21 ++++++++++- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/code/addons/test/src/constants.ts b/code/addons/test/src/constants.ts index d57ff1b1da8a..58463326dcd7 100644 --- a/code/addons/test/src/constants.ts +++ b/code/addons/test/src/constants.ts @@ -12,6 +12,13 @@ export const DOCUMENTATION_FATAL_ERROR_LINK = `${DOCUMENTATION_LINK}#what-happen export const COVERAGE_DIRECTORY = 'coverage'; +export const SUPPORTED_FRAMEWORKS = [ + '@storybook/nextjs', + '@storybook/experimental-nextjs-vite', + '@storybook/sveltekit', +]; + +export const SUPPORTED_RENDERERS = ['@storybook/react', '@storybook/svelte', '@storybook/vue3']; export interface Config { coverage: boolean; a11y: boolean; diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index bb22c7517127..4e7fcc6a69d2 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -25,6 +25,7 @@ import { coerce, satisfies } from 'semver'; import { dedent } from 'ts-dedent'; import { type PostinstallOptions } from '../../../lib/cli-storybook/src/add'; +import { SUPPORTED_FRAMEWORKS, SUPPORTED_RENDERERS } from './constants'; import { printError, printInfo, printSuccess, step } from './postinstall-logger'; import { getAddonNames } from './utils'; @@ -106,18 +107,11 @@ export default async function postInstall(options: PostinstallOptions) { } } - const annotationsImport = [ - '@storybook/nextjs', - '@storybook/experimental-nextjs-vite', - '@storybook/sveltekit', - ].includes(info.frameworkPackageName) + const annotationsImport = SUPPORTED_FRAMEWORKS.includes(info.frameworkPackageName) ? info.frameworkPackageName === '@storybook/nextjs' ? '@storybook/experimental-nextjs-vite' : info.frameworkPackageName - : info.rendererPackageName && - ['@storybook/react', '@storybook/svelte', '@storybook/vue3'].includes( - info.rendererPackageName - ) + : info.rendererPackageName && SUPPORTED_RENDERERS.includes(info.rendererPackageName) ? info.rendererPackageName : null; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 7bd831d082b7..87da579183cb 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -7,7 +7,13 @@ import dedent from 'ts-dedent'; import { getAddonNames } from '../helpers/mainConfigFile'; import { addonA11yAddonTest, transformSetupFile } from './addon-a11y-addon-test'; -vi.mock('../helpers/mainConfigFile'); +vi.mock('../helpers/mainConfigFile', async (importOriginal) => { + const mod = (await importOriginal()) as any; + return { + ...mod, + getAddonNames: vi.fn(), + }; +}); // mock fs.existsSync vi.mock('fs', async (importOriginal) => { @@ -46,6 +52,20 @@ describe('addonA11yAddonTest', () => { expect(result).toBeNull(); }); + it('should return null if provided framework is not supported', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/angular', + }, + configDir: '', + } as any); + expect(result).toBeNull(); + }); + it('should return setupFile and transformedSetupCode if vitest.setup file exists', async () => { vi.mocked(getAddonNames).mockReturnValue([ '@storybook/addon-a11y', @@ -54,7 +74,12 @@ describe('addonA11yAddonTest', () => { vi.mocked(existsSync).mockReturnValue(true); vi.mocked(readFileSync).mockReturnValue('const annotations = setProjectAnnotations([]);'); - const result = await addonA11yAddonTest.check({ mainConfig, configDir } as any); + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/react-vite', + }, + configDir, + } as any); expect(result).toEqual({ setupFile: path.join(configDir, 'vitest.setup.js'), transformedSetupCode: expect.any(String), @@ -71,7 +96,12 @@ describe('addonA11yAddonTest', () => { throw new Error('Test error'); }); - const result = await addonA11yAddonTest.check({ mainConfig, configDir } as any); + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/sveltekit', + }, + configDir, + } as any); expect(result).toEqual({ setupFile: path.join(configDir, 'vitest.setup.js'), transformedSetupCode: null, diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 6b85146407f6..544f423b338a 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,10 +1,17 @@ +import { rendererPackages } from '@storybook/core/common'; + import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; import path from 'path'; import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; -import { getAddonNames } from '../helpers/mainConfigFile'; +// Relative path import to avoid dependency to @storybook/test +import { + SUPPORTED_FRAMEWORKS, + SUPPORTED_RENDERERS, +} from '../../../../../addons/test/src/constants'; +import { getAddonNames, getFrameworkPackageName, getRendererName } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; export const vitestFileExtensions = ['.js', '.ts', '.cts', '.mts', '.cjs', '.mjs'] as const; @@ -35,11 +42,23 @@ export const addonA11yAddonTest: Fix = { async check({ mainConfig, configDir }) { const addons = getAddonNames(mainConfig); + const frameworkPackageName = getFrameworkPackageName(mainConfig); + const rendererPackageName = getRendererName(mainConfig); + const hasA11yAddon = !!addons.find((addon) => addon.includes('@storybook/addon-a11y')); const hasTestAddon = !!addons.find((addon) => addon.includes('@storybook/experimental-addon-test') ); + if ( + !SUPPORTED_FRAMEWORKS.find((framework) => frameworkPackageName?.includes(framework)) && + !SUPPORTED_RENDERERS.find((renderer) => + rendererPackageName?.includes(rendererPackages[renderer]) + ) + ) { + return null; + } + if (!hasA11yAddon || !hasTestAddon || !configDir) { return null; } From 2b512d65b4d3041e3ab3a7197ba8ab3d4d211061 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 23 Dec 2024 15:12:38 +0100 Subject: [PATCH 2/9] Addon Test: Fix documentation links --- code/addons/test/src/postinstall.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index bb22c7517127..5731d797d0ee 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -177,14 +177,14 @@ export default async function postInstall(options: PostinstallOptions) { reasons.push( dedent` Please check the documentation for more information about its requirements and installation: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon`)} ` ); } else { reasons.push( dedent` Fear not, however, you can follow the manual installation process instead at: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)} ` ); } @@ -308,7 +308,7 @@ export default async function postInstall(options: PostinstallOptions) { ${colors.gray(vitestSetupFile)} Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)} ` ); logger.line(1); @@ -368,7 +368,7 @@ export default async function postInstall(options: PostinstallOptions) { your existing workspace file automatically, you must do it yourself. This was the last step. Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)} ` ); logger.line(1); @@ -390,7 +390,7 @@ export default async function postInstall(options: PostinstallOptions) { your existing workspace file automatically, you must do it yourself. This was the last step. Please refer to the documentation to complete the setup manually: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)} ` ); logger.line(1); @@ -416,14 +416,14 @@ export default async function postInstall(options: PostinstallOptions) { import { defineWorkspace } from 'vitest/config'; import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport} - // More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin + // More info at: https://storybook.js.org/docs/writing-tests/test-addon export default defineWorkspace([ '${relative(dirname(browserWorkspaceFile), rootConfig)}', { extends: '${viteConfigFile ? relative(dirname(browserWorkspaceFile), viteConfigFile) : ''}', plugins: [ // The plugin will run tests for the stories defined in your Storybook config - // See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest + // See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ], test: { @@ -456,11 +456,11 @@ export default async function postInstall(options: PostinstallOptions) { import { defineConfig } from 'vitest/config'; import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport} - // More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin + // More info at: https://storybook.js.org/docs/writing-tests/test-addon export default defineConfig({ plugins: [ // The plugin will run tests for the stories defined in your Storybook config - // See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest + // See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ], test: { @@ -490,7 +490,7 @@ export default async function postInstall(options: PostinstallOptions) { • When using the Vitest extension in your editor, all of your stories will be shown as tests! Check the documentation for more information about its features and options at: - ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin`)} + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon`)} ` ); logger.line(1); From cb69dbf6b3fc73ab53e5c88ab6cb4878a3bf7890 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 23 Dec 2024 15:26:55 +0100 Subject: [PATCH 3/9] fix: update import path for rendererPackages in addon-a11y-addon-test --- .../src/automigrate/fixes/addon-a11y-addon-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 544f423b338a..a8cd25cdf371 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,4 +1,4 @@ -import { rendererPackages } from '@storybook/core/common'; +import { rendererPackages } from 'storybook/internal/common'; import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; From f6e7b7cb5e444961e4d594040d5732fc5424330d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 24 Dec 2024 09:58:05 +0100 Subject: [PATCH 4/9] Addon Test: Enable strict mode --- .../test/src/components/Interaction.tsx | 7 ++-- .../test/src/components/MatcherResult.tsx | 4 +-- .../addons/test/src/components/MethodCall.tsx | 6 ++-- code/addons/test/src/components/Panel.tsx | 25 +++++++------- .../test/src/components/RelativeTime.tsx | 2 +- .../test/src/components/StatusBadge.tsx | 4 +-- code/addons/test/src/components/Subnav.tsx | 2 +- .../src/components/TestDiscrepancyMessage.tsx | 7 ++-- .../src/components/TestProviderRender.tsx | 14 +++++--- code/addons/test/src/manager.tsx | 9 +++-- code/addons/test/src/node/boot-test-runner.ts | 4 +-- code/addons/test/src/node/reporter.ts | 4 +-- .../addons/test/src/node/test-manager.test.ts | 3 +- code/addons/test/src/node/vitest-manager.ts | 6 ++-- .../test/src/vitest-plugin/global-setup.ts | 18 +++++----- code/addons/test/src/vitest-plugin/index.ts | 33 +++++++++++-------- .../test/src/vitest-plugin/viewports.ts | 2 +- code/addons/test/tsconfig.json | 2 +- 18 files changed, 84 insertions(+), 68 deletions(-) diff --git a/code/addons/test/src/components/Interaction.tsx b/code/addons/test/src/components/Interaction.tsx index 4ceef384d02a..75797c65c5a3 100644 --- a/code/addons/test/src/components/Interaction.tsx +++ b/code/addons/test/src/components/Interaction.tsx @@ -23,7 +23,7 @@ const MethodCallWrapper = styled.div(() => ({ const RowContainer = styled('div', { shouldForwardProp: (prop) => !['call', 'pausedAt'].includes(prop.toString()), -})<{ call: Call; pausedAt: Call['id'] }>( +})<{ call: Call; pausedAt: Call['id'] | undefined }>( ({ theme, call }) => ({ position: 'relative', display: 'flex', @@ -117,6 +117,9 @@ const RowMessage = styled('div')(({ theme }) => ({ export const Exception = ({ exception }: { exception: Call['exception'] }) => { const filter = useAnsiToHtmlFilter(); + if (!exception) { + return null; + } if (isJestError(exception)) { return ; } @@ -187,7 +190,7 @@ export const Interaction = ({ - {childCallIds?.length > 0 && ( + {(childCallIds?.length ?? 0) > 0 && ( } diff --git a/code/addons/test/src/components/MatcherResult.tsx b/code/addons/test/src/components/MatcherResult.tsx index 46b5e540ad8d..fa01b73548ec 100644 --- a/code/addons/test/src/components/MatcherResult.tsx +++ b/code/addons/test/src/components/MatcherResult.tsx @@ -74,10 +74,10 @@ export const MatcherResult = ({ {lines.flatMap((line: string, index: number) => { if (line.startsWith('expect(')) { const received = getParams(line, 7); - const remainderIndex = received && 7 + received.length; + const remainderIndex = received ? 7 + received.length : 0; const matcher = received && line.slice(remainderIndex).match(/\.(to|last|nth)[A-Z]\w+\(/); if (matcher) { - const expectedIndex = remainderIndex + matcher.index + matcher[0].length; + const expectedIndex = remainderIndex + (matcher.index ?? 0) + matcher[0].length; const expected = getParams(line, expectedIndex); if (expected) { return [ diff --git a/code/addons/test/src/components/MethodCall.tsx b/code/addons/test/src/components/MethodCall.tsx index 59b907d13daa..34d1e6bb6f58 100644 --- a/code/addons/test/src/components/MethodCall.tsx +++ b/code/addons/test/src/components/MethodCall.tsx @@ -139,7 +139,7 @@ export const Node = ({ case Object.prototype.hasOwnProperty.call(value, '__class__'): return ; case Object.prototype.hasOwnProperty.call(value, '__callId__'): - return ; + return ; /* eslint-enable no-underscore-dangle */ case Object.prototype.toString.call(value) === '[object Object]': @@ -418,7 +418,7 @@ export const MethodCall = ({ callsById, }: { call?: Call; - callsById: Map; + callsById?: Map; }) => { // Call might be undefined during initial render, can be safely ignored. if (!call) { @@ -434,7 +434,7 @@ export const MethodCall = ({ const callId = (elem as CallRef).__callId__; return [ callId ? ( - + ) : ( {elem as any} ), diff --git a/code/addons/test/src/components/Panel.tsx b/code/addons/test/src/components/Panel.tsx index cc2eaf233356..d6ea74843151 100644 --- a/code/addons/test/src/components/Panel.tsx +++ b/code/addons/test/src/components/Panel.tsx @@ -22,14 +22,6 @@ import type { API_StatusValue } from '@storybook/types'; import { ADDON_ID, STORYBOOK_ADDON_TEST_CHANNEL, TEST_PROVIDER_ID } from '../constants'; import { InteractionsPanel } from './InteractionsPanel'; -interface Interaction extends Call { - status: Call['status']; - childCallIds: Call['id'][]; - isHidden: boolean; - isCollapsed: boolean; - toggleCollapsed: () => void; -} - const INITIAL_CONTROL_STATES = { start: false, back: false, @@ -60,7 +52,7 @@ export const getInteractions = ({ const childCallMap = new Map(); return log - .map(({ callId, ancestors, status }) => { + .map(({ callId, ancestors, status }) => { let isHidden = false; ancestors.forEach((ancestor) => { if (collapsed.has(ancestor)) { @@ -68,11 +60,12 @@ export const getInteractions = ({ } childCallMap.set(ancestor, (childCallMap.get(ancestor) || []).concat(callId)); }); - return { ...calls.get(callId), status, isHidden }; + return { ...calls.get(callId)!, status, isHidden }; }) - .map((call) => { + .map((call) => { const status = call.status === CallStates.ERROR && + call.ancestors && callsById.get(call.ancestors.slice(-1)[0])?.status === CallStates.ACTIVE ? CallStates.ACTIVE : call.status; @@ -131,7 +124,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId const calls = useRef>>(new Map()); const setCall = ({ status, ...call }: Call) => calls.current.set(call.id, call); - const endRef = useRef(); + const endRef = useRef(); useEffect(() => { let observer: IntersectionObserver; if (global.IntersectionObserver) { @@ -151,6 +144,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId { [EVENTS.CALL]: setCall, [EVENTS.SYNC]: (payload) => { + // @ts-expect-error TODO set((s) => { const list = getInteractions({ log: payload.logItems, @@ -214,6 +208,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId ); useEffect(() => { + // @ts-expect-error TODO set((s) => { const list = getInteractions({ log: log.current, @@ -250,16 +245,17 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId const hasException = !!caughtException || !!unhandledErrors || + // @ts-expect-error TODO interactions.some((v) => v.status === CallStates.ERROR); const storyStatus = storyStatuses[storyId]?.[TEST_PROVIDER_ID]; const storyTestStatus = storyStatus?.status; - const browserTestStatus = useMemo(() => { + const browserTestStatus = useMemo(() => { if (!isPlaying && (interactions.length > 0 || hasException)) { return hasException ? CallStates.ERROR : CallStates.DONE; } - return isPlaying ? CallStates.ACTIVE : null; + return isPlaying ? CallStates.ACTIVE : undefined; }, [isPlaying, interactions, hasException]); const { testRunId } = storyStatus?.data || {}; @@ -315,6 +311,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId unhandledErrors={unhandledErrors} isPlaying={isPlaying} pausedAt={pausedAt} + // @ts-expect-error TODO endRef={endRef} onScrollToEnd={scrollTarget && scrollToTarget} /> diff --git a/code/addons/test/src/components/RelativeTime.tsx b/code/addons/test/src/components/RelativeTime.tsx index 9cb1df1b1b66..4d4cf3c48693 100644 --- a/code/addons/test/src/components/RelativeTime.tsx +++ b/code/addons/test/src/components/RelativeTime.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; export const RelativeTime = ({ timestamp }: { timestamp?: number }) => { - const [timeAgo, setTimeAgo] = useState(null); + const [timeAgo, setTimeAgo] = useState(null); useEffect(() => { if (timestamp) { diff --git a/code/addons/test/src/components/StatusBadge.tsx b/code/addons/test/src/components/StatusBadge.tsx index d730b8ef985c..a906b501a939 100644 --- a/code/addons/test/src/components/StatusBadge.tsx +++ b/code/addons/test/src/components/StatusBadge.tsx @@ -14,7 +14,7 @@ const StyledBadge = styled.div(({ theme, status }) => { [CallStates.ERROR]: theme.color.negative, [CallStates.ACTIVE]: theme.color.warning, [CallStates.WAITING]: theme.color.warning, - }[status]; + }[status!]; return { padding: '4px 6px 4px 8px;', borderRadius: '4px', @@ -36,7 +36,7 @@ export const StatusBadge: React.FC = ({ status }) => { [CallStates.ERROR]: 'Fail', [CallStates.ACTIVE]: 'Runs', [CallStates.WAITING]: 'Runs', - }[status]; + }[status!]; return ( {badgeText} diff --git a/code/addons/test/src/components/Subnav.tsx b/code/addons/test/src/components/Subnav.tsx index bf9d8436cee0..88fcbd5c4522 100644 --- a/code/addons/test/src/components/Subnav.tsx +++ b/code/addons/test/src/components/Subnav.tsx @@ -109,7 +109,7 @@ const RerunButton = styled(StyledIconButton)< >(({ theme, animating, disabled }) => ({ opacity: disabled ? 0.5 : 1, svg: { - animation: animating && `${theme.animation.rotate360} 200ms ease-out`, + animation: animating ? `${theme.animation.rotate360} 200ms ease-out` : undefined, }, })); diff --git a/code/addons/test/src/components/TestDiscrepancyMessage.tsx b/code/addons/test/src/components/TestDiscrepancyMessage.tsx index b23af4a7be6a..2ff2e97c9f77 100644 --- a/code/addons/test/src/components/TestDiscrepancyMessage.tsx +++ b/code/addons/test/src/components/TestDiscrepancyMessage.tsx @@ -1,13 +1,12 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { Link } from 'storybook/internal/components'; import { useStorybookApi } from 'storybook/internal/manager-api'; import { styled } from 'storybook/internal/theming'; -import type { StoryId } from 'storybook/internal/types'; import { CallStates } from '@storybook/instrumenter'; -import { DOCUMENTATION_DISCREPANCY_LINK, STORYBOOK_ADDON_TEST_CHANNEL } from '../constants'; +import { DOCUMENTATION_DISCREPANCY_LINK } from '../constants'; const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({ textAlign: 'start', @@ -32,7 +31,7 @@ const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({ })); interface TestDiscrepancyMessageProps { - browserTestStatus: CallStates; + browserTestStatus?: CallStates; } export const TestDiscrepancyMessage = ({ browserTestStatus }: TestDiscrepancyMessageProps) => { diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index 29b0f950da8c..4b1f4c35bc4d 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -162,7 +162,7 @@ export const TestProviderRender: FC< : undefined; const a11ySkippedAmount = - state.running || !state?.details.config?.a11y || !state.config.a11y + state.running || !state?.details.config?.a11y || !state.config?.a11y ? null : a11yResults?.filter((result) => !result).length; @@ -304,9 +304,11 @@ export const TestProviderRender: FC< const firstNotPassed = results.find( (r) => r.status === 'failed' || r.status === 'warning' ); - openPanel(firstNotPassed.storyId, PANEL_ID); + if (firstNotPassed) { + openPanel(firstNotPassed.storyId, PANEL_ID); + } } - : null + : undefined } icon={ state.crashed ? ( @@ -359,9 +361,11 @@ export const TestProviderRender: FC< (report) => report.status === 'failed' || report.status === 'warning' ) ); - openPanel(firstNotPassed.storyId, A11y_ADDON_PANEL_ID); + if (firstNotPassed) { + openPanel(firstNotPassed.storyId, A11y_ADDON_PANEL_ID); + } } - : null + : undefined } icon={} right={isStoryEntry ? null : a11yNotPassedAmount || null} diff --git a/code/addons/test/src/manager.tsx b/code/addons/test/src/manager.tsx index 26a8c8f42cd7..7264e5fe49cc 100644 --- a/code/addons/test/src/manager.tsx +++ b/code/addons/test/src/manager.tsx @@ -38,6 +38,7 @@ addons.register(ADDON_ID, (api) => { runnable: true, watchable: true, name: 'Component tests', + // @ts-expect-error: TODO: Fix types render: (state) => { const [isModalOpen, setModalOpen] = useState(false); return ( @@ -55,6 +56,7 @@ addons.register(ADDON_ID, (api) => { ); }, + // @ts-expect-error: TODO: Fix types sidebarContextMenu: ({ context, state }) => { if (context.type === 'docs') { return null; @@ -72,6 +74,7 @@ addons.register(ADDON_ID, (api) => { ); }, + // @ts-expect-error: TODO: Fix types stateUpdater: (state, update) => { const updated = { ...state, @@ -89,6 +92,7 @@ addons.register(ADDON_ID, (api) => { await api.experimental_updateStatus( TEST_PROVIDER_ID, Object.fromEntries( + // @ts-expect-error: TODO: Fix types update.details.testResults.flatMap((testResult) => testResult.results .filter(({ storyId }) => storyId) @@ -113,6 +117,7 @@ addons.register(ADDON_ID, (api) => { await api.experimental_updateStatus( 'storybook/addon-a11y/test-provider', Object.fromEntries( + // @ts-expect-error: TODO: Fix types update.details.testResults.flatMap((testResult) => testResult.results .filter(({ storyId }) => storyId) @@ -143,7 +148,7 @@ addons.register(ADDON_ID, (api) => { return updated; }, - } as Addon_TestProviderType); + } satisfies Omit, 'id'>); } const filter = ({ state }: Combo) => { @@ -158,7 +163,7 @@ addons.register(ADDON_ID, (api) => { match: ({ viewMode }) => viewMode === 'story', render: ({ active }) => { return ( - + {({ storyId }) => } ); diff --git a/code/addons/test/src/node/boot-test-runner.ts b/code/addons/test/src/node/boot-test-runner.ts index 3f0329807e98..f1fe3ddc94f3 100644 --- a/code/addons/test/src/node/boot-test-runner.ts +++ b/code/addons/test/src/node/boot-test-runner.ts @@ -24,7 +24,7 @@ const MAX_START_TIME = 30000; const vitestModulePath = join(__dirname, 'node', 'vitest.mjs'); // Events that were triggered before Vitest was ready are queued up and resent once it's ready -const eventQueue: { type: string; args: any[] }[] = []; +const eventQueue: { type: string; args?: any[] }[] = []; let child: null | ChildProcess; let ready = false; @@ -87,7 +87,7 @@ const bootTestRunner = async (channel: Channel) => { if (result.type === 'ready') { // Resend events that triggered (during) the boot sequence, now that Vitest is ready while (eventQueue.length) { - const { type, args } = eventQueue.shift(); + const { type, args } = eventQueue.shift()!; child?.send({ type, args, from: 'server' }); } diff --git a/code/addons/test/src/node/reporter.ts b/code/addons/test/src/node/reporter.ts index ecda9ab4a672..1f71e1bf9670 100644 --- a/code/addons/test/src/node/reporter.ts +++ b/code/addons/test/src/node/reporter.ts @@ -220,7 +220,7 @@ export class StorybookReporter implements Reporter { (t) => t.status === 'failed' && t.results.length === 0 ); - const reducedTestSuiteFailures = new Set(); + const reducedTestSuiteFailures = new Set(); testSuiteFailures.forEach((t) => { reducedTestSuiteFailures.add(t.message); @@ -240,7 +240,7 @@ export class StorybookReporter implements Reporter { message: Array.from(reducedTestSuiteFailures).reduce( (acc, curr) => `${acc}\n${curr}`, '' - ), + )!, } : { name: `${unhandledErrors.length} unhandled error${unhandledErrors?.length > 1 ? 's' : ''}`, diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index 7056aee5666b..985f74c97595 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -22,10 +22,11 @@ const vitest = vi.hoisted(() => ({ configOverride: { actualTestNamePattern: undefined, get testNamePattern() { - return this.actualTestNamePattern; + return this.actualTestNamePattern!; }, set testNamePattern(value: string) { setTestNamePattern(value); + // @ts-expect-error Ignore for testing this.actualTestNamePattern = value; }, }, diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 1e405ea9c344..4145acf18a3f 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -88,7 +88,7 @@ export class VitestManager { try { await this.vitest.init(); - } catch (e) { + } catch (e: any) { let message = 'Failed to initialize Vitest'; const isV8 = e.message?.includes('@vitest/coverage-v8'); const isIstanbul = e.message?.includes('@vitest/coverage-istanbul'); @@ -148,7 +148,7 @@ export class VitestManager { ])) as StoryIndex; const storyIds = requestStoryIds || Object.keys(index.entries); return storyIds.map((id) => index.entries[id]).filter((story) => story.type === 'story'); - } catch (e) { + } catch (e: any) { log('Failed to fetch story index: ' + e.message); return []; } @@ -330,7 +330,7 @@ export class VitestManager { async registerVitestConfigListener() { this.vitest?.server?.watcher.on('change', async (file) => { file = normalize(file); - const isConfig = file === this.vitest.server.config.configFile; + const isConfig = file === this.vitest?.server.config.configFile; if (isConfig) { log('Restarting Vitest due to config change'); await this.closeVitest(); diff --git a/code/addons/test/src/vitest-plugin/global-setup.ts b/code/addons/test/src/vitest-plugin/global-setup.ts index ca287c105e2b..8526c48e245e 100644 --- a/code/addons/test/src/vitest-plugin/global-setup.ts +++ b/code/addons/test/src/vitest-plugin/global-setup.ts @@ -74,13 +74,15 @@ export const teardown = async () => { logger.verbose('Stopping Storybook process'); await new Promise((resolve, reject) => { // Storybook starts multiple child processes, so we need to kill the whole tree - treeKill(storybookProcess.pid, 'SIGTERM', (error) => { - if (error) { - logger.error('Failed to stop Storybook process:'); - reject(error); - return; - } - resolve(); - }); + if (storybookProcess?.pid) { + treeKill(storybookProcess.pid, 'SIGTERM', (error) => { + if (error) { + logger.error('Failed to stop Storybook process:'); + reject(error); + return; + } + resolve(); + }); + } }); }; diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index e73549ded6d9..180075f9f891 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -193,6 +193,7 @@ export const storybookTest = async (options?: UserOptions): Promise => { } : {}), + // @ts-expect-error: TODO browser: { ...inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.browser, commands: { @@ -266,9 +267,11 @@ export const storybookTest = async (options?: UserOptions): Promise => { // alert the user of problems if ( - inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include?.length > 0 + (inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.include?.length ?? + 0) > 0 ) { // remove the user's existing include, because we're replacing it with our own heuristic based on main.ts#stories + // @ts-expect-error: Ignore inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include = []; console.log( picocolors.yellow(dedent` @@ -285,19 +288,21 @@ export const storybookTest = async (options?: UserOptions): Promise => { return config; }, async configureServer(server) { - for (const staticDir of staticDirs) { - try { - const { staticPath, targetEndpoint } = mapStaticDir(staticDir, directories.configDir); - server.middlewares.use( - targetEndpoint, - sirv(staticPath, { - dev: true, - etag: true, - extensions: [], - }) - ); - } catch (e) { - console.warn(e); + if (staticDirs) { + for (const staticDir of staticDirs) { + try { + const { staticPath, targetEndpoint } = mapStaticDir(staticDir, directories.configDir); + server.middlewares.use( + targetEndpoint, + sirv(staticPath, { + dev: true, + etag: true, + extensions: [], + }) + ); + } catch (e) { + console.warn(e); + } } } }, diff --git a/code/addons/test/src/vitest-plugin/viewports.ts b/code/addons/test/src/vitest-plugin/viewports.ts index a8bcc90bc408..905ee44fc937 100644 --- a/code/addons/test/src/vitest-plugin/viewports.ts +++ b/code/addons/test/src/vitest-plugin/viewports.ts @@ -85,7 +85,7 @@ export const setViewport = async (parameters: Parameters = {}, globals: Globals let viewportWidth = DEFAULT_VIEWPORT_DIMENSIONS.width; let viewportHeight = DEFAULT_VIEWPORT_DIMENSIONS.height; - if (defaultViewport in viewports) { + if (defaultViewport && defaultViewport in viewports) { const styles = viewports[defaultViewport].styles as ViewportStyles; if (styles?.width && styles?.height) { const { width, height } = styles; diff --git a/code/addons/test/tsconfig.json b/code/addons/test/tsconfig.json index 060b5d432fc7..e8a15eafa0bd 100644 --- a/code/addons/test/tsconfig.json +++ b/code/addons/test/tsconfig.json @@ -5,7 +5,7 @@ "module": "Preserve", "moduleResolution": "Bundler", "types": ["vitest"], - "strict": false + "strict": true }, "include": ["src/**/*", "./typings.d.ts"] } From 3247e7067cca2a17fffffcf71ca51d32b31221f0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 24 Dec 2024 10:17:07 +0100 Subject: [PATCH 5/9] Fix story type --- code/addons/test/src/components/TestProviderRender.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/test/src/components/TestProviderRender.stories.tsx b/code/addons/test/src/components/TestProviderRender.stories.tsx index 1189248f3d53..5111c1507a49 100644 --- a/code/addons/test/src/components/TestProviderRender.stories.tsx +++ b/code/addons/test/src/components/TestProviderRender.stories.tsx @@ -43,7 +43,7 @@ const baseState: TestProviderState = { cancellable: true, cancelling: false, crashed: false, - error: null, + error: undefined, failed: false, running: false, watching: false, From 5a7af5786c1b2d761cf3a8538192431ddcde2812 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 24 Dec 2024 10:58:02 +0100 Subject: [PATCH 6/9] Add Vite alias for sb-original/image-context to internal Storybook config --- code/.storybook/main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts index 623d6f5c525e..9870741de29e 100644 --- a/code/.storybook/main.ts +++ b/code/.storybook/main.ts @@ -4,6 +4,7 @@ import type { StorybookConfig } from '../frameworks/react-vite'; const componentsPath = join(__dirname, '../core/src/components'); const managerApiPath = join(__dirname, '../core/src/manager-api'); +const imageContextPath = join(__dirname, '..//frameworks/nextjs/src/image-context.ts'); const config: StorybookConfig = { stories: [ @@ -146,6 +147,7 @@ const config: StorybookConfig = { 'storybook/internal/components': componentsPath, '@storybook/manager-api': managerApiPath, 'storybook/internal/manager-api': managerApiPath, + 'sb-original/image-context': imageContextPath, } : {}), }, From dcd49108c917daa707b519a2ff5a95f71fd36d1b Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 24 Dec 2024 11:14:43 +0100 Subject: [PATCH 7/9] Always use installed version of vitest --- code/addons/test/src/postinstall.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index ac91f8aff5c7..6418f30fe5e9 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -56,8 +56,7 @@ export default async function postInstall(options: PostinstallOptions) { const allDeps = await packageManager.getAllDependencies(); // only install these dependencies if they are not already installed const dependencies = ['vitest', '@vitest/browser', 'playwright'].filter((p) => !allDeps[p]); - const vitestVersionSpecifier = - allDeps.vitest || (await packageManager.getInstalledVersion('vitest')); + const vitestVersionSpecifier = await packageManager.getInstalledVersion('vitest'); const coercedVitestVersion = vitestVersionSpecifier ? coerce(vitestVersionSpecifier) : null; // if Vitest is installed, we use the same version to keep consistency across Vitest packages const vitestVersionToInstall = vitestVersionSpecifier ?? 'latest'; From 0f78a4ffe1bddc66076aa5fe50b2719d6514744e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 24 Dec 2024 11:34:12 +0100 Subject: [PATCH 8/9] Use correct vitest config file path --- code/addons/test/src/postinstall.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index ac91f8aff5c7..7e98296d0234 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -378,7 +378,7 @@ export default async function postInstall(options: PostinstallOptions) { '🚨 Oh no!', dedent` You seem to have an existing test configuration in your Vite config file: - ${colors.gray(vitestWorkspaceFile || '')} + ${colors.gray(viteConfigFile || '')} I was able to configure most of the addon but could not safely extend your existing workspace file automatically, you must do it yourself. This was the last step. From c3da2d42f726ea5b29e04730cb693eb73de89d38 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:57:49 +0000 Subject: [PATCH 9/9] Write changelog for 8.5.0-beta.6 [skip ci] --- CHANGELOG.prerelease.md | 7 +++++++ code/package.json | 3 ++- docs/versions/next.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 2a2eb7b7453d..2be90903a97a 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,10 @@ +## 8.5.0-beta.6 + +- Addon Test: Always use installed version of vitest - [#30134](https://github.com/storybookjs/storybook/pull/30134), thanks @kasperpeulen! +- Addon Test: Fix documentation links - [#30128](https://github.com/storybookjs/storybook/pull/30128), thanks @yannbf! +- Addon Test: Use correct vitest config file path - [#30135](https://github.com/storybookjs/storybook/pull/30135), thanks @kasperpeulen! +- Automigration: Improve addon-a11y-addon-test - [#30127](https://github.com/storybookjs/storybook/pull/30127), thanks @valentinpalkovic! + ## 8.5.0-beta.5 - Addon Test: Only reset story count on file change when watch mode is enabled - [#30121](https://github.com/storybookjs/storybook/pull/30121), thanks @ghengeveld! diff --git a/code/package.json b/code/package.json index b8531c49d34d..64fbd5522100 100644 --- a/code/package.json +++ b/code/package.json @@ -294,5 +294,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.5.0-beta.6" } diff --git a/docs/versions/next.json b/docs/versions/next.json index b620efbf34e0..0394414f9b7e 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.5.0-beta.5","info":{"plain":"- Addon Test: Only reset story count on file change when watch mode is enabled - [#30121](https://github.com/storybookjs/storybook/pull/30121), thanks @ghengeveld!\n- Build: Revert Downgrade to esbuild 0.24.0 - [#30120](https://github.com/storybookjs/storybook/pull/30120), thanks @yannbf!\n- Core: Fix `ERR_PACKAGE_PATH_NOT_EXPORTED` in `@storybook/node-logger` - [#30093](https://github.com/storybookjs/storybook/pull/30093), thanks @JReinhold!\n- React: Use Act wrapper in Storybook for component rendering - [#30037](https://github.com/storybookjs/storybook/pull/30037), thanks @valentinpalkovic!\n- Vite: Add extra entries to `optimizeDeps` - [#30117](https://github.com/storybookjs/storybook/pull/30117), thanks @ndelangen!"}} +{"version":"8.5.0-beta.6","info":{"plain":"- Addon Test: Always use installed version of vitest - [#30134](https://github.com/storybookjs/storybook/pull/30134), thanks @kasperpeulen!\n- Addon Test: Fix documentation links - [#30128](https://github.com/storybookjs/storybook/pull/30128), thanks @yannbf!\n- Addon Test: Use correct vitest config file path - [#30135](https://github.com/storybookjs/storybook/pull/30135), thanks @kasperpeulen!\n- Automigration: Improve addon-a11y-addon-test - [#30127](https://github.com/storybookjs/storybook/pull/30127), thanks @valentinpalkovic!"}}