Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 1 addition & 6 deletions code/addons/docs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,6 @@ export const resolvedReact = async (existing: any) => ({
mdx: existing?.mdx ?? dirname(require.resolve('@mdx-js/react')),
});

const optimizeViteDeps = [
'@mdx-js/react',
'@storybook/addon-docs > acorn-jsx',
'@storybook/addon-docs',
'markdown-to-jsx',
];
const optimizeViteDeps = ['@mdx-js/react', '@storybook/addon-docs', 'markdown-to-jsx'];

export { webpackX as webpack, docsX as docs, optimizeViteDeps };
6 changes: 3 additions & 3 deletions code/addons/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
"@types/micromatch": "^4.0.0",
"@types/node": "^22.0.0",
"@types/semver": "^7",
"@vitest/browser": "^3.1.1",
"@vitest/runner": "^3.1.1",
"@vitest/browser": "^3.2.0",
"@vitest/runner": "^3.2.0",
"boxen": "^8.0.1",
"es-toolkit": "^1.36.0",
"execa": "^8.0.1",
Expand All @@ -119,7 +119,7 @@
"tree-kill": "^1.2.2",
"ts-dedent": "^2.2.0",
"typescript": "^5.8.3",
"vitest": "^3.1.1"
"vitest": "^3.2.0"
},
"peerDependencies": {
"@vitest/browser": "^3.0.0",
Expand Down
180 changes: 96 additions & 84 deletions code/addons/vitest/src/postinstall.ts

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions code/addons/vitest/src/updateVitestFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,135 @@ describe('updateConfigFile', () => {
expect(updated).toBe(false);
});

it('adds projects property to test config', async () => {
const source = babel.babelParse(
await loadTemplate('vitest.config.3.2.template.ts', {
CONFIG_DIR: '.storybook',
BROWSER_CONFIG: "{ provider: 'playwright' }",
SETUP_FILE: '../.storybook/vitest.setup.ts',
})
);
const target = babel.babelParse(`
/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
},
})
`);

const updated = updateConfigFile(source, target);
expect(updated).toBe(true);

const { code } = babel.generate(target);
expect(code).toMatchInlineSnapshot(`
"/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vite.dev/config/
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));

// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
plugins: [react()],
test: {
globals: true,
projects: [{
extends: true,
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({
configDir: path.join(dirname, '.storybook')
})],
test: {
name: 'storybook',
browser: {
provider: 'playwright'
},
setupFiles: ['../.storybook/vitest.setup.ts']
}
}]
}
});"
`);
});

it('edits projects property of test config', async () => {
const source = babel.babelParse(
await loadTemplate('vitest.config.3.2.template.ts', {
CONFIG_DIR: '.storybook',
BROWSER_CONFIG: "{ provider: 'playwright' }",
SETUP_FILE: '../.storybook/vitest.setup.ts',
})
);
const target = babel.babelParse(`
/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
projects: ['packages/*', {some: 'config'}]
}
})
`);

const updated = updateConfigFile(source, target);
expect(updated).toBe(true);

const { code } = babel.generate(target);
expect(code).toMatchInlineSnapshot(`
"/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vite.dev/config/
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));

// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
plugins: [react()],
test: {
globals: true,
projects: ['packages/*', {
some: 'config'
}, {
extends: true,
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({
configDir: path.join(dirname, '.storybook')
})],
test: {
name: 'storybook',
browser: {
provider: 'playwright'
},
setupFiles: ['../.storybook/vitest.setup.ts']
}
}]
}
});"
`);
});

it('adds workspace property to test config', async () => {
const source = babel.babelParse(
await loadTemplate('vitest.config.template.ts', {
Expand Down
25 changes: 25 additions & 0 deletions code/addons/vitest/src/updateVitestFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ const mergeProperties = (
}
};

/**
* Merges a source Vitest configuration AST into a target configuration AST.
*
* This function intelligently combines configuration elements from a source file (typically a
* template) into an existing target configuration file, avoiding duplicates and preserving the
* structure of both files.
*
* The function performs the following operations:
*
* 1. **Import Merging**: Adds new import statements from source that don't exist in target (determined
* by local specifier name). Imports are inserted after existing imports.
* 2. **Variable Declaration Merging**: Copies variable declarations from source to target if they
* don't already exist (determined by variable name). Variables are inserted after imports.
* 3. **Configuration Object Merging**: Merges the configuration object properties from source into
* target's default export. Supports both direct object exports and function-wrapped exports
* (e.g., `defineConfig({})`). The merging is recursive:
*
* - Nested objects are merged deeply
* - Arrays are concatenated (shallow merge)
* - Primitive values are overwritten
*
* @param source - The source Babel AST (template configuration to merge from)
* @param target - The target Babel AST (existing configuration to merge into)
* @returns {boolean} - True if the target was modified, false otherwise
*/
export const updateConfigFile = (source: BabelFile['ast'], target: BabelFile['ast']) => {
let updated = false;
for (const sourceNode of source.program.body) {
Expand Down
5 changes: 5 additions & 0 deletions code/addons/vitest/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import sirv from 'sirv';
import { dedent } from 'ts-dedent';

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

Expand Down Expand Up @@ -151,6 +152,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
staticDirs,
previewLevelTags,
core,
extraOptimizeDeps,
] = await Promise.all([
getStoryGlobsAndFiles(presets, directories),
presets.apply('framework', undefined),
Expand All @@ -159,6 +161,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
presets.apply('staticDirs', []),
extractTagsFromPreview(finalOptions.configDir),
presets.apply('core'),
presets.apply('optimizeViteDeps', []),
]);

const pluginsToIgnore = [
Expand Down Expand Up @@ -326,6 +329,8 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>

optimizeDeps: {
include: [
...extraOptimizeDeps,
...INCLUDE_CANDIDATES,
'@storybook/addon-vitest/internal/setup-file',
'@storybook/addon-vitest/internal/global-setup',
'@storybook/addon-vitest/internal/test-utils',
Expand Down
30 changes: 30 additions & 0 deletions code/addons/vitest/templates/vitest.config.3.2.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { defineConfig } from 'vitest/config';

import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';

const dirname =
typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));

// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
test: {
projects: [
{
extends: true,
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({ configDir: path.join(dirname, 'CONFIG_DIR') }),
],
test: {
name: 'storybook',
browser: BROWSER_CONFIG,
setupFiles: ['SETUP_FILE'],
},
},
],
},
});
Loading
Loading