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
89 changes: 89 additions & 0 deletions code/addons/vitest/src/updateVitestFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,95 @@ describe('updateConfigFile', () => {
`);
});

it('appends storybook project to existing test.projects array (no double nesting)', 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(`
import { mergeConfig, defineConfig } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(
viteConfig,
defineConfig({
test: {
expect: { requireAssertions: true },
projects: [
{
extends: "./vite.config.ts",
test: { name: "client" },
},
{
extends: "./vite.config.ts",
test: { name: "server" },
},
],
},
})
)
`);

const before = babel.generate(target).code;
const updated = updateConfigFile(source, target);
expect(updated).toBe(true);

const after = babel.generate(target).code;

// check if the code was updated at all
expect(after).not.toBe(before);

// check if the code was updated correctly (storybook project appended to existing projects, no double nesting)
expect(getDiff(before, after)).toMatchInlineSnapshot(`
" import { mergeConfig, defineConfig } from 'vitest/config';
import viteConfig from './vite.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 mergeConfig(viteConfig, defineConfig({
test: {
expect: {
requireAssertions: true
...
test: {
name: "server"
}

+ }, {
+ 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: {
+ enabled: true,
+ headless: true,
+ provider: 'playwright',
+ instances: [{
+ browser: 'chromium'
+ }]
+ },
+ setupFiles: ['../.storybook/vitest.setup.ts']
+ }
+
}]
}
}));"
`);
});

it('extracts coverage config and keeps it at top level when using workspace', async () => {
const source = babel.babelParse(
await loadTemplate('vitest.config.template.ts', {
Expand Down
60 changes: 58 additions & 2 deletions code/addons/vitest/src/updateVitestFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,64 @@ export const updateConfigFile = (source: BabelFile['ast'], target: BabelFile['as
p.type === 'ObjectProperty' && p.key.type === 'Identifier' && p.key.name === 'test'
) as t.ObjectProperty | undefined;

if (templateTestProp && templateTestProp.value.type === 'ObjectExpression') {
// Find the workspace/projects array in the template
const hasProjectsProp = (
p: t.ObjectMethod | t.ObjectProperty | t.SpreadElement
): p is t.ObjectProperty =>
p.type === 'ObjectProperty' &&
p.key.type === 'Identifier' &&
p.key.name === 'projects' &&
p.value.type === 'ArrayExpression';

// Check if the existing config already uses a projects array (multi-project setup).
// If so, we must append the storybook project to that array instead of wrapping
// the entire test config as a single project (which would cause double nesting).
const existingProjectsProp = existingTestProp.value.properties.find(hasProjectsProp);

if (existingProjectsProp) {
// Existing config already has test.projects: append storybook project(s) to it
if (templateTestProp && templateTestProp.value.type === 'ObjectExpression') {
const templateProjectsProp =
templateTestProp.value.properties.find(hasProjectsProp);
if (templateProjectsProp && templateProjectsProp.value.type === 'ArrayExpression') {
const templateElements = (templateProjectsProp.value as t.ArrayExpression)
.elements;
(existingProjectsProp.value as t.ArrayExpression).elements.push(
...templateElements
);
}
// Merge other test-level options from template (e.g. coverage) into existing test
for (const templateProp of templateTestProp.value.properties) {
if (
templateProp.type === 'ObjectProperty' &&
templateProp.key.type === 'Identifier' &&
(templateProp.key as t.Identifier).name !== 'projects'
) {
const existingProp = existingTestProp.value.properties.find(
(p) =>
p.type === 'ObjectProperty' &&
p.key.type === 'Identifier' &&
(p.key as t.Identifier).name === (templateProp.key as t.Identifier).name
);
if (!existingProp && templateProp.type === 'ObjectProperty') {
existingTestProp.value.properties.push(templateProp);
}
}
}
}
// Merge only non-test properties from template to avoid re-adding storybook project
const otherTemplateProps = properties.filter(
(p) =>
!(
p.type === 'ObjectProperty' &&
p.key.type === 'Identifier' &&
p.key.name === 'test'
)
);
if (otherTemplateProps.length > 0) {
mergeProperties(otherTemplateProps, targetConfigObject.properties);
}
} else if (templateTestProp && templateTestProp.value.type === 'ObjectExpression') {
// Existing test has no projects array: wrap entire test config as one project
const workspaceOrProjectsProp = templateTestProp.value.properties.find(
(p) =>
p.type === 'ObjectProperty' &&
Expand Down
Loading