From 1ee63d16baddae7bd9036c4c7fb7477d53bc7ac2 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 15 Oct 2024 18:39:22 +0200 Subject: [PATCH 1/8] Refactor test addon to include stories automatically --- code/.storybook/vitest.config.ts | 7 --- code/addons/test/src/postinstall.ts | 6 +- code/addons/test/src/vitest-plugin/index.ts | 62 +++++++++++++-------- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/code/.storybook/vitest.config.ts b/code/.storybook/vitest.config.ts index 05205f3e762a..2667035a4564 100644 --- a/code/.storybook/vitest.config.ts +++ b/code/.storybook/vitest.config.ts @@ -34,13 +34,6 @@ export default mergeConfig( ], test: { name: 'storybook-ui', - include: [ - '../addons/**/*.{story,stories}.?(c|m)[jt]s?(x)', - // '../core/template/stories/**/*.{story,stories}.?(c|m)[jt]s?(x)', - '../core/src/manager/**/*.{story,stories}.?(c|m)[jt]s?(x)', - '../core/src/preview-api/**/*.{story,stories}.?(c|m)[jt]s?(x)', - '../core/src/components/{brand,components}/**/*.{story,stories}.?(c|m)[jt]s?(x)', - ], exclude: [ ...defaultExclude, '../node_modules/**', diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index e179b42fa1d2..09604357bfb0 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -334,6 +334,7 @@ export default async function postInstall(options: PostinstallOptions) { { extends: '${viteConfigFile ? relative(dirname(browserWorkspaceFile), viteConfigFile) : ''}', plugins: [ + // The plugin will run tests in the stories defined in your Storybook config // See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest storybookTest(),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ], @@ -345,8 +346,6 @@ export default async function postInstall(options: PostinstallOptions) { name: 'chromium', provider: 'playwright', }, - // Make sure to adjust this pattern to match your stories files. - include: ['**/*.stories.?(m)[jt]s?(x)'], setupFiles: ['./.storybook/vitest.setup.ts'], }, }, @@ -370,6 +369,7 @@ export default async function postInstall(options: PostinstallOptions) { // More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin export default defineConfig({ plugins: [ + // The plugin will run tests in the stories defined in your Storybook config // See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest storybookTest(),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ], @@ -381,8 +381,6 @@ export default async function postInstall(options: PostinstallOptions) { name: 'chromium', provider: 'playwright', }, - // Make sure to adjust this pattern to match your stories files. - include: ['**/*.stories.?(m)[jt]s?(x)'], setupFiles: ['./.storybook/vitest.setup.ts'], }, }); diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index e71b54cefe81..ad5768adcf76 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -6,6 +6,7 @@ import type { Plugin } from 'vitest/config'; import { getInterpretedFile, loadAllPresets, + normalizeStories, validateConfigurationFiles, } from 'storybook/internal/common'; import { readConfig, vitestTransform } from 'storybook/internal/csf-tools'; @@ -20,6 +21,39 @@ const defaultOptions: UserOptions = { storybookUrl: 'http://localhost:6006', }; +async function extractStorybookData(finalOptions: InternalOptions) { + const configDir = finalOptions.configDir; + try { + await validateConfigurationFiles(configDir); + } catch (err) { + throw new MainFileMissingError({ + location: configDir, + source: 'vitest', + }); + } + const previewLevelTags = await extractTagsFromPreview(configDir); + + const presets = await loadAllPresets({ + configDir, + corePresets: [], + overridePresets: [], + packageJson: {}, + }); + const stories = await presets.apply('stories', []); + + const normalizedStories = normalizeStories(stories, { + configDir: finalOptions.configDir, + workingDir: process.cwd(), + }); + + const storiesGlobs = normalizedStories.map((entry) => `${entry.directory}/${entry.files}`); + // To discuss: Do we want to filter out mdx files? + // The vitest plugin ignores mdx files, but perhaps it might still give side effects based on user's config + // However if we do filter out mdx, how do we do it without affecting things like ./*.stories.@(js|jsx|ts|mdx|tsx)? + // .filter((entry) => !entry.includes('mdx')); + return { previewLevelTags, stories, storiesGlobs }; +} + const extractTagsFromPreview = async (configDir: string) => { const previewConfigPath = getInterpretedFile(join(resolve(configDir), 'preview')); @@ -64,30 +98,14 @@ export const storybookTest = (options?: UserOptions): Plugin => { return { name: 'vite-plugin-storybook-test', enforce: 'pre', - async buildStart() { - // evaluate main.js and preview.js so we can extract - // stories for autotitle support and tags for tags filtering support - const configDir = finalOptions.configDir; - try { - await validateConfigurationFiles(configDir); - } catch (err) { - throw new MainFileMissingError({ - location: configDir, - source: 'vitest', - }); - } + async config(config) { + const storybookData = await extractStorybookData(finalOptions); + stories = storybookData.stories; + previewLevelTags = storybookData.previewLevelTags; - const presets = await loadAllPresets({ - configDir, - corePresets: [], - overridePresets: [], - packageJson: {}, - }); + config.test.include ??= []; + config.test.include.push(...storybookData.storiesGlobs); - stories = await presets.apply('stories', []); - previewLevelTags = await extractTagsFromPreview(configDir); - }, - async config(config) { // If we end up needing to know if we are running in browser mode later // const isRunningInBrowserMode = config.plugins.find((plugin: Plugin) => // plugin.name?.startsWith('vitest:browser') From 4e7eb16b285d6f783dbe109b1f642215a254c570 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 3 Dec 2024 13:27:40 +0100 Subject: [PATCH 2/8] cleanup --- code/addons/test/src/vitest-plugin/index.ts | 37 --------------------- 1 file changed, 37 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 56df1c000a05..040b592d491a 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -22,39 +22,6 @@ const defaultOptions: UserOptions = { storybookUrl: 'http://localhost:6006', }; -async function extractStorybookData(finalOptions: InternalOptions) { - const configDir = finalOptions.configDir; - try { - await validateConfigurationFiles(configDir); - } catch (err) { - throw new MainFileMissingError({ - location: configDir, - source: 'vitest', - }); - } - const previewLevelTags = await extractTagsFromPreview(configDir); - - const presets = await loadAllPresets({ - configDir, - corePresets: [], - overridePresets: [], - packageJson: {}, - }); - const stories = await presets.apply('stories', []); - - const normalizedStories = normalizeStories(stories, { - configDir: finalOptions.configDir, - workingDir: process.cwd(), - }); - - const storiesGlobs = normalizedStories.map((entry) => `${entry.directory}/${entry.files}`); - // To discuss: Do we want to filter out mdx files? - // The vitest plugin ignores mdx files, but perhaps it might still give side effects based on user's config - // However if we do filter out mdx, how do we do it without affecting things like ./*.stories.@(js|jsx|ts|mdx|tsx)? - // .filter((entry) => !entry.includes('mdx')); - return { previewLevelTags, stories, storiesGlobs }; -} - const extractTagsFromPreview = async (configDir: string) => { const previewConfigPath = getInterpretedFile(join(resolve(configDir), 'preview')); @@ -137,8 +104,6 @@ export const storybookTest = (options?: UserOptions): Plugin => { const storyFiles = generator.storyFileNames(); - // console.log({ storyFiles }); - previewLevelTags = await extractTagsFromPreview(configDir); const framework = await presets.apply('framework', undefined); @@ -156,8 +121,6 @@ export const storybookTest = (options?: UserOptions): Plugin => { config.test.exclude ??= []; config.test.exclude.push('**/*.mdx'); - // console.log(config.test); - config.test.env ??= {}; config.test.env = { ...config.test.env, From c34a62e109a81b0fb8d744330800b87cf9b63f82 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 3 Dec 2024 13:28:40 +0100 Subject: [PATCH 3/8] add extra whilespace --- code/addons/test/src/vitest-plugin/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 040b592d491a..8713f7a9291d 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -113,6 +113,7 @@ export const storybookTest = (options?: UserOptions): Plugin => { // const isRunningInBrowserMode = config.plugins.find((plugin: Plugin) => // plugin.name?.startsWith('vitest:browser') // ) + config.test ??= {}; config.test.include ??= []; From 15139be55f4a64cd277aa22e59ffa7b5f5bcadee Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 3 Dec 2024 13:35:36 +0100 Subject: [PATCH 4/8] improve transform file condition --- code/addons/test/src/vitest-plugin/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 8713f7a9291d..df567d4b002b 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -61,6 +61,7 @@ export const storybookTest = (options?: UserOptions): Plugin => { let previewLevelTags: string[]; let storiesGlobs: StoriesEntry[]; + let storiesFiles: string[]; return { name: 'vite-plugin-storybook-test', @@ -102,7 +103,7 @@ export const storybookTest = (options?: UserOptions): Plugin => { await generator.initialize(); - const storyFiles = generator.storyFileNames(); + storiesFiles = generator.storyFileNames(); previewLevelTags = await extractTagsFromPreview(configDir); @@ -117,7 +118,7 @@ export const storybookTest = (options?: UserOptions): Plugin => { config.test ??= {}; config.test.include ??= []; - config.test.include.push(...storyFiles); + config.test.include.push(...storiesFiles); config.test.exclude ??= []; config.test.exclude.push('**/*.mdx'); @@ -191,7 +192,7 @@ export const storybookTest = (options?: UserOptions): Plugin => { return code; } - if (id.match(/(story|stories)\.[cm]?[jt]sx?$/)) { + if (storiesFiles.includes(id)) { return vitestTransform({ code, fileName: id, From 0247332a1cdaad4e0fdd5563b89fc9c859de90cc Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 3 Dec 2024 16:46:26 +0100 Subject: [PATCH 5/8] remove include list in sandbox generation, add exclude for svelte stories for now --- scripts/tasks/sandbox-parts.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 27b73c36de4e..920b31857a8b 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -498,14 +498,11 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio test: { name: "storybook", pool: "threads", - include: [ - "src/**/*.{story,stories}.?(c|m)[jt]s?(x)", - "template-stories/**/*.{story,stories}.?(c|m)[jt]s?(x)", - ], exclude: [ ...defaultExclude, // TODO: investigate TypeError: Cannot read properties of null (reading 'useContext') "**/*argtypes*", + ${template.expected.renderer === '@storybook/svelte' ? '"**/*.stories.svelte",' : ''} ], /** * TODO: Either fix or acknowledge limitation of: From 3985f97ce1473430d6a4895b53803f5ca1332881 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Dec 2024 09:06:04 +0100 Subject: [PATCH 6/8] cleanup [skip-ci] --- code/.storybook/vitest.config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/code/.storybook/vitest.config.ts b/code/.storybook/vitest.config.ts index 1d76c3708bf2..4b177f409281 100644 --- a/code/.storybook/vitest.config.ts +++ b/code/.storybook/vitest.config.ts @@ -39,10 +39,8 @@ export default mergeConfig( '../node_modules/**', '**/__mockdata__/**', '../**/__mockdata__/**', - // expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED - '**/Zoom.stories.tsx', - // @yannbf what's wrong with these stories, that they do not work - '**/lib/blocks/src/**', + '**/Zoom.stories.tsx', // expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED + '**/lib/blocks/src/**', // won't work because of https://github.com/storybookjs/storybook/issues/29783 ], // TODO: bring this back once portable stories support @storybook/core/preview-api hooks // @ts-expect-error this type does not exist but the property does! From e25e33d67eb5291f971e00e13cf60c86ee50272b Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 4 Dec 2024 11:50:44 +0100 Subject: [PATCH 7/8] Escape stories paths before passing them as globs --- code/addons/test/package.json | 1 + code/addons/test/src/vitest-plugin/index.ts | 8 +++++++- code/yarn.lock | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/code/addons/test/package.json b/code/addons/test/package.json index 55babe10d98a..9be90b1c5199 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -103,6 +103,7 @@ "execa": "^8.0.1", "find-up": "^7.0.0", "formik": "^2.2.9", + "glob": "^10.0.0", "istanbul-lib-report": "^3.0.1", "pathe": "^1.1.2", "picocolors": "^1.1.0", diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index df567d4b002b..177b2e6ca745 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -12,6 +12,8 @@ import { readConfig, vitestTransform } from 'storybook/internal/csf-tools'; import { MainFileMissingError } from 'storybook/internal/server-errors'; import type { DocsOptions, StoriesEntry } from 'storybook/internal/types'; +// eslint-disable-next-line depend/ban-dependencies +import { escape } from 'glob'; import { join, resolve } from 'pathe'; import type { InternalOptions, UserOptions } from './types'; @@ -118,7 +120,11 @@ export const storybookTest = (options?: UserOptions): Plugin => { config.test ??= {}; config.test.include ??= []; - config.test.include.push(...storiesFiles); + config.test.include.push( + // Escape magic characters in paths because they shouldn't be treated as glob patterns + // Paths are resolved using `pathe` to convert Windows paths to POSIX paths first + ...storiesFiles.map((path) => escape(resolve(path))) + ); config.test.exclude ??= []; config.test.exclude.push('**/*.mdx'); diff --git a/code/yarn.lock b/code/yarn.lock index 1bada105b2f1..71d84f6cdb7c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6623,6 +6623,7 @@ __metadata: execa: "npm:^8.0.1" find-up: "npm:^7.0.0" formik: "npm:^2.2.9" + glob: "npm:^10.0.0" istanbul-lib-report: "npm:^3.0.1" pathe: "npm:^1.1.2" picocolors: "npm:^1.1.0" From 3b9f33ef331efd8dc04d876b0ea0dc54aab5ec37 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 4 Dec 2024 12:55:22 +0100 Subject: [PATCH 8/8] Apply suggestions from code review --- code/addons/test/src/postinstall.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index 38beabc9e060..3ea45bd01bf7 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -353,7 +353,7 @@ export default async function postInstall(options: PostinstallOptions) { { extends: '${viteConfigFile ? relative(dirname(browserWorkspaceFile), viteConfigFile) : ''}', plugins: [ - // The plugin will run tests in the stories defined in your Storybook config + // 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 storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ], @@ -390,7 +390,7 @@ export default async function postInstall(options: PostinstallOptions) { // More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin export default defineConfig({ plugins: [ - // The plugin will run tests in the stories defined in your Storybook config + // 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 storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall} ],