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

Addon Test: Refactor test addon to include stories automatically #29367

Merged
merged 12 commits into from
Dec 4, 2024
Merged
11 changes: 2 additions & 9 deletions code/.storybook/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,13 @@ 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)',
],
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
exclude: [
...defaultExclude,
'../node_modules/**',
'**/__mockdata__/**',
'../**/__mockdata__/**',
// expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED
'**/Zoom.stories.tsx',
'**/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!
Expand Down
1 change: 1 addition & 0 deletions code/addons/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 3 additions & 7 deletions code/addons/test/src/postinstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,6 @@ export default async function postInstall(options: PostinstallOptions) {
// If there's an existing config, we create a workspace file so we can run Storybook tests alongside.
const extension = extname(rootConfig);
const browserWorkspaceFile = resolve(dirname(rootConfig), `vitest.workspace${extension}`);
// to be set in vitest config
const vitestSetupFilePath = relative(dirname(browserWorkspaceFile), vitestSetupFile);

logger.line(1);
logger.plain(`${step} Creating a Vitest project workspace file:`);
Expand All @@ -355,6 +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
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
],
Expand All @@ -366,9 +365,7 @@ 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: ['${vitestSetupFilePath}'],
setupFiles: ['./.storybook/vitest.setup.ts'],
},
},
]);
Expand All @@ -393,6 +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
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest({ configDir: '${options.configDir}' }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
],
Expand All @@ -404,8 +402,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: ['${vitestSetupFilePath}'],
},
});
Expand Down
47 changes: 41 additions & 6 deletions code/addons/test/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import type { Plugin } from 'vitest/config';
import {
getInterpretedFile,
loadAllPresets,
normalizeStories,
validateConfigurationFiles,
} from 'storybook/internal/common';
import { StoryIndexGenerator } from 'storybook/internal/core-server';
import { readConfig, vitestTransform } from 'storybook/internal/csf-tools';
import { MainFileMissingError } from 'storybook/internal/server-errors';
import type { StoriesEntry } from 'storybook/internal/types';
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';
Expand Down Expand Up @@ -51,15 +55,15 @@ export const storybookTest = (options?: UserOptions): Plugin => {
process.env.__STORYBOOK_URL__ = storybookUrl;
process.env.__STORYBOOK_SCRIPT__ = finalOptions.storybookScript;

let stories: StoriesEntry[];

if (!finalOptions.configDir) {
finalOptions.configDir = resolve(join(process.cwd(), '.storybook'));
} else {
finalOptions.configDir = resolve(process.cwd(), finalOptions.configDir);
}

let previewLevelTags: string[];
let storiesGlobs: StoriesEntry[];
let storiesFiles: string[];

return {
name: 'vite-plugin-storybook-test',
Expand All @@ -82,7 +86,27 @@ export const storybookTest = (options?: UserOptions): Plugin => {
packageJson: {},
});

stories = await presets.apply('stories', []);
const workingDir = process.cwd();
const directories = {
configDir,
workingDir,
};
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
storiesGlobs = await presets.apply('stories');
const indexers = await presets.apply('experimental_indexers', []);
const docsOptions = await presets.apply<DocsOptions>('docs', {});
const normalizedStories = normalizeStories(await storiesGlobs, directories);

const generator = new StoryIndexGenerator(normalizedStories, {
...directories,
indexers: indexers,
docs: docsOptions,
workingDir,
});

await generator.initialize();

storiesFiles = generator.storyFileNames();

previewLevelTags = await extractTagsFromPreview(configDir);

const framework = await presets.apply('framework', undefined);
Expand All @@ -92,8 +116,19 @@ export const storybookTest = (options?: UserOptions): Plugin => {
// const isRunningInBrowserMode = config.plugins.find((plugin: Plugin) =>
// plugin.name?.startsWith('vitest:browser')
// )

config.test ??= {};

config.test.include ??= [];
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');

config.test.env ??= {};
config.test.env = {
...config.test.env,
Expand Down Expand Up @@ -163,13 +198,13 @@ export const storybookTest = (options?: UserOptions): Plugin => {
return code;
}

if (id.match(/(story|stories)\.[cm]?[jt]sx?$/)) {
if (storiesFiles.includes(id)) {
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
return vitestTransform({
code,
fileName: id,
configDir: finalOptions.configDir,
tagsFilter: finalOptions.tags,
stories,
stories: storiesGlobs,
previewLevelTags,
});
}
Expand Down
1 change: 1 addition & 0 deletions code/core/src/core-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './build-static';
export * from './build-dev';
export * from './withTelemetry';
export { default as build } from './standalone';
export { StoryIndexGenerator } from './utils/StoryIndexGenerator';
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Exposing internal utilities like StoryIndexGenerator could make it harder to refactor internals in the future. Consider creating a dedicated public API for story file discovery instead.

1 change: 1 addition & 0 deletions code/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 1 addition & 4 deletions scripts/tasks/sandbox-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",' : ''}
],
Comment on lines +505 to 506
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Svelte stories exclusion should be conditional on both renderer AND framework being Svelte to handle SvelteKit case

/**
* TODO: Either fix or acknowledge limitation of:
Expand Down
Loading