Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vitest: Add plugins from viteFinal #30105

Open
wants to merge 17 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
- [Added source code panel to docs](#added-source-code-panel-to-docs)
- [Addon-a11y: Component test integration](#addon-a11y-component-test-integration)
- [Addon-a11y: Deprecated `parameters.a11y.manual`](#addon-a11y-deprecated-parametersa11ymanual)
- [Indexing behavior of @storybook/experimental-addon-test is changed](#indexing-behavior-of-storybookexperimental-addon-test-is-changed)
- [Addon-test: You should no longer copy the content of `viteFinal` to your configuration](#addon-test-you-should-no-longer-copy-the-content-of-vitefinal-to-your-configuration)
- [Addon-test: Indexing behavior of @storybook/experimental-addon-test is changed](#addon-test-indexing-behavior-of-storybookexperimental-addon-test-is-changed)
- [From version 8.2.x to 8.3.x](#from-version-82x-to-83x)
- [Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types](#removed-experimental_sidebar_bottom-and-deprecated-experimental_sidebar_top-addon-types)
- [New parameters format for addon backgrounds](#new-parameters-format-for-addon-backgrounds)
Expand Down Expand Up @@ -468,7 +469,13 @@ beforeAll(annotations.beforeAll);

We have deprecated `parameters.a11y.manual` in 8.5. Please use `globals.a11y.manual` instead.

### Indexing behavior of @storybook/experimental-addon-test is changed
### Addon-test: You should no longer copy the content of `viteFinal` to your configuration

In version 8.4 of `@storybook/experimental-addon-test`, it was required to copy any custom configuration you had in `viteFinal` in `main.ts`, to the Vitest Storybook project. This is no longer necessary, as the Storybook Test plugin will automatically include your `viteFinal` configuration. You should remove any configurations you might already have in `viteFinal` to remove duplicates.

This is especially the case for any plugins you might have, as they could now end up being loaded twice, which is likely to cause errors when running tests. In 8.4 we documented and automatically added some Vite plugins from Storybook frameworks like `@storybook/experimental-nextjs-vite` and `@storybook/sveltekit` - **these needs to be removed as well**.

### Addon-test: Indexing behavior of @storybook/experimental-addon-test is changed

The Storybook test addon used to index stories based on the `test.include` field in the Vitest config file. This caused indexing issues with Storybook, because stories could have been indexed by Storybook and not Vitest, and vice versa. Starting in Storybook 8.5.0-alpha.18, we changed the indexing behavior so that it always uses the globs defined in the `stories` field in `.storybook/main.js` for a more consistent experience. It is now discouraged to use `test.include`, please remove it.

Expand Down
49 changes: 4 additions & 45 deletions code/addons/test/src/postinstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,6 @@ export default async function postInstall(options: PostinstallOptions) {
}
}

const vitestInfo = getVitestPluginInfo(info.frameworkPackageName);

if (info.frameworkPackageName === '@storybook/nextjs') {
printInfo(
'🍿 Just so you know...',
Expand Down Expand Up @@ -414,7 +412,7 @@ export default async function postInstall(options: PostinstallOptions) {
browserWorkspaceFile,
dedent`
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport}
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';

// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineWorkspace([
Expand All @@ -424,7 +422,7 @@ export default async function postInstall(options: PostinstallOptions) {
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
storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
storybookTest({ configDir: '${options.configDir}' }),
],
test: {
name: 'storybook',
Expand Down Expand Up @@ -454,14 +452,14 @@ export default async function postInstall(options: PostinstallOptions) {
newVitestConfigFile,
dedent`
import { defineConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport}
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';

// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
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
storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
storybookTest({ configDir: '${options.configDir}' }),
],
test: {
name: 'storybook',
Expand Down Expand Up @@ -496,45 +494,6 @@ export default async function postInstall(options: PostinstallOptions) {
logger.line(1);
}

const getVitestPluginInfo = (framework: string) => {
let frameworkPluginImport = '';
let frameworkPluginCall = '';
let frameworkPluginDocs = '';

if (framework === '@storybook/nextjs' || framework === '@storybook/experimental-nextjs-vite') {
frameworkPluginImport =
"import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';";
frameworkPluginDocs =
'// More info at: https://github.com/storybookjs/vite-plugin-storybook-nextjs';
frameworkPluginCall = 'storybookNextJsPlugin()';
}

if (framework === '@storybook/sveltekit') {
frameworkPluginImport =
"import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite-plugin';";
frameworkPluginCall = 'storybookSveltekitPlugin()';
}

if (framework === '@storybook/vue3-vite') {
frameworkPluginImport =
"import { storybookVuePlugin } from '@storybook/vue3-vite/vite-plugin';";
frameworkPluginCall = 'storybookVuePlugin()';
}

if (framework === '@storybook/react-native-web-vite') {
frameworkPluginImport =
"import { storybookReactNativeWeb } from '@storybook/react-native-web-vite/vite-plugin';";
frameworkPluginCall = 'storybookReactNativeWeb()';
}

// spaces for file indentation
frameworkPluginImport = `\n${frameworkPluginImport}`;
frameworkPluginDocs = frameworkPluginDocs ? `\n ${frameworkPluginDocs}` : '';
frameworkPluginCall = frameworkPluginCall ? `\n ${frameworkPluginCall},` : '';

return { frameworkPluginImport, frameworkPluginCall, frameworkPluginDocs };
};

async function getStorybookInfo({ configDir, packageManager: pkgMgr }: PostinstallOptions) {
const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr });
const packageJson = await packageManager.retrievePackageJson();
Expand Down
38 changes: 29 additions & 9 deletions code/addons/test/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import picocolors from 'picocolors';
import sirv from 'sirv';
import { convertPathToPattern } from 'tinyglobby';
import { dedent } from 'ts-dedent';
import type { PluginOption } from 'vite';

// ! Relative import to prebundle it without needing to depend on the Vite builder
import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins';
import type { InternalOptions, UserOptions } from './types';

const WORKING_DIR = process.cwd();
Expand Down Expand Up @@ -64,9 +67,9 @@ const getStoryGlobsAndFiles = async (
};
};

const packageDir = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));
const PACKAGE_DIR = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));

export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> => {
const finalOptions = {
...defaultOptions,
...options,
Expand Down Expand Up @@ -109,14 +112,27 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
getStoryGlobsAndFiles(presets, directories),
presets.apply('framework', undefined),
presets.apply('env', {}),
presets.apply('viteFinal', {}),
presets.apply<{ plugins?: Plugin[] }>('viteFinal', {}),
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
presets.apply('staticDirs', []),
extractTagsFromPreview(finalOptions.configDir),
]);

return {
// filter out plugins that we know are unnecesary for tests, eg. docgen plugins
const plugins = (await withoutVitePlugins(
(viteConfigFromStorybook.plugins as unknown as PluginOption[]) ?? [],
[
'storybook:package-deduplication', // addon-docs
'storybook:mdx-plugin', // addon-docs
'storybook:react-docgen-plugin',
'vite:react-docgen-typescript', // aka @joshwooding/vite-plugin-react-docgen-typescript
'storybook:svelte-docgen-plugin',
'storybook:vue-component-meta-plugin',
'storybook:vue-docgen-plugin',
]
)) as unknown as Plugin[];
Comment on lines +120 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: filtering out docgen plugins could miss important plugins needed by some frameworks - needs thorough testing across all frameworks


plugins.push({
name: 'vite-plugin-storybook-test',
enforce: 'pre',
async transformIndexHtml(html) {
const [headHtmlSnippet, bodyHtmlSnippet] = await Promise.all([
presets.apply('previewHead'),
Expand Down Expand Up @@ -153,7 +169,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
const baseConfig: Omit<ViteUserConfig, 'plugins'> = {
test: {
setupFiles: [
join(packageDir, 'dist/vitest-plugin/setup-file.mjs'),
join(PACKAGE_DIR, 'dist/vitest-plugin/setup-file.mjs'),
// if the existing setupFiles is a string, we have to include it otherwise we're overwriting it
typeof inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test
?.setupFiles === 'string' &&
Expand All @@ -162,7 +178,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {

...(finalOptions.storybookScript
? {
globalSetup: [join(packageDir, 'dist/vitest-plugin/global-setup.mjs')],
globalSetup: [join(PACKAGE_DIR, 'dist/vitest-plugin/global-setup.mjs')],
}
: {}),

Expand Down Expand Up @@ -244,7 +260,9 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {

optimizeDeps: {
include: [
'@storybook/experimental-addon-test/**',
'@storybook/experimental-addon-test/internal/setup-file',
'@storybook/experimental-addon-test/internal/global-setup',
'@storybook/experimental-addon-test/internal/test-utils',
...(frameworkName?.includes('react') || frameworkName?.includes('nextjs')
? ['react-dom/test-utils']
: []),
Expand Down Expand Up @@ -317,7 +335,9 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
});
}
},
};
});

return plugins;
};

export default storybookTest;
6 changes: 0 additions & 6 deletions docs/_snippets/vitest-plugin-vitest-config.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
```ts filename="vitest.config.ts" renderer="react"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// 👇 If you're using Next.js, apply this framework plugin as well
// import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';

import viteConfig from './vite.config';

Expand All @@ -15,7 +13,6 @@ export default mergeConfig(
// The --ci flag will skip prompts and not open a browser
storybookScript: 'yarn storybook --ci',
}),
// storybookNextJsPlugin(),
],
test: {
// Enable browser mode
Expand Down Expand Up @@ -68,8 +65,6 @@ export default mergeConfig(
```ts filename="vitest.config.ts" renderer="svelte"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// 👇 If you're using Sveltekit, apply this framework plugin as well
// import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite-plugin';

import viteConfig from './vite.config';

Expand All @@ -82,7 +77,6 @@ export default mergeConfig(
// The --ci flag will skip prompts and not open a browser
storybookScript: 'yarn storybook --ci',
}),
// storybookSveltekitPlugin(),
],
test: {
// Enable browser mode
Expand Down
8 changes: 0 additions & 8 deletions docs/_snippets/vitest-plugin-vitest-workspace.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
```ts filename="vitest.workspace.ts" renderer="react"
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// 👇 If you're using Next.js, apply this framework plugin as well
// import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';

export default defineWorkspace([
// This is the path to your existing Vitest config file
Expand All @@ -16,7 +14,6 @@ export default defineWorkspace([
// The --ci flag will skip prompts and not open a browser
storybookScript: 'yarn storybook --ci',
}),
// storybookNextJsPlugin(),
],
test: {
name: 'storybook',
Expand All @@ -37,7 +34,6 @@ export default defineWorkspace([
```ts filename="vitest.config.ts" renderer="vue"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import { storybookVuePlugin } from '@storybook/vue3-vite/vite-plugin';

import viteConfig from './vite.config';

Expand All @@ -53,7 +49,6 @@ export default defineWorkspace([
// The --ci flag will skip prompts and not open a browser
storybookScript: 'yarn storybook --ci',
}),
storybookVuePlugin(),
],
test: {
name: 'storybook',
Expand All @@ -74,8 +69,6 @@ export default defineWorkspace([
```ts filename="vitest.config.ts" renderer="svelte"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// 👇 If you're using Sveltekit, apply this framework plugin as well
// import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite-plugin';

import viteConfig from './vite.config';

Expand All @@ -91,7 +84,6 @@ export default defineWorkspace([
// The --ci flag will skip prompts and not open a browser
storybookScript: 'yarn storybook --ci',
}),
// storybookSveltekitPlugin(),
],
test: {
name: 'storybook',
Expand Down
85 changes: 1 addition & 84 deletions docs/writing-tests/test-addon.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,90 +68,7 @@ For some project setups, the `add` command may be unable to automate the addon a
1. Configure Vitest to use [browser mode](https://vitest.dev/guide/browser/).
1. Install the addon, `@storybook/experimental-addon-test`, in your project and [register it in your Storybook configuration](http://storybook.js.org/docs/addons/install-addons#manual-installation).
1. Create a test setup file, `.storybook/vitest.setup.ts`. You can use the [example setup file](#example-vitest-setup) as a guide.
1. Adjust your Vitest configuration to include the plugin(s) and reference the setup file. You can use the [example configuration files](#example-configuration-files) as a guide.

#### Framework plugins

Some Storybook frameworks require additional setup to enable the framework's features to work with Vitest. Each of those frameworks exports a Vite plugin that you can use to configure your project correctly:

<If renderer="react">
If you're using Next.js, first install the `@storybook/experimental-nextjs-vite` package:

{/* prettier-ignore-start */}

<CodeSnippets path="nextjs-vite-install.md" />

{/* prettier-ignore-end */}

Then apply the plugin from `@storybook/experimental-nextjs-vite/vite-plugin`:

```js title="vitest.config.ts"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';

import viteConfig from './vite.config';

export default mergeConfig(
viteConfig,
defineConfig({
plugins: [
storybookTest(),
storybookNextJsPlugin(), // 👈 Apply the framework plugin here
],
// ...
})
);
```
</If>

<If renderer="vue">
Vue projects should apply the plugin from `@storybook/vue3-vite/vite-plugin`:

```js title="vitest.config.ts"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import { storybookVuePlugin } from '@storybook/vue3-vite/vite-plugin';

import viteConfig from './vite.config'

export default mergeConfig(
viteConfig,
defineConfig({
plugins: [
storybookTest(),
storybookVuePlugin(), // 👈 Apply the framework plugin here
],
// ...
})
);
```
</If>

<If renderer="svelte">
If you're using SvelteKit, apply the plugin from `@storybook/sveltekit/vite-plugin`:

```js title="vitest.config.ts"
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite-plugin';

import viteConfig from './vite.config';

export default mergeConfig(
viteConfig,
defineConfig({
plugins: [
storybookTest(),
storybookSveltekitPlugin(), // 👈 Apply the framework plugin here
],
// ...
})
);
```
</If>

The above example uses the framework's plugin in a Vitest configuration file. You can also use it in a Vitest workspace file, if that is how your project is configured.
1. Adjust your Vitest configuration to include the plugin and reference the setup file. You can use the [example configuration files](#example-configuration-files) as a guide.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the How do I apply custom Vite configuration? FAQ will also need to be either rewritten to emphasize that we automatically pull everything from viteFinal or removed outright.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if that's correct and you'd prefer I do it. Happy to help!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds about right to me. Honestly, I'd love if you could do that, either in this PR or a follow-up, up to you.


### Example configuration files

Expand Down
Loading
Loading