Merge branch 'next' into issue-26056-fix-mixed-extends-import-problem

This commit is contained in:
Dario Baumberger 2024-05-24 18:37:07 +02:00 committed by GitHub
commit cda2e71f1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 676 additions and 456 deletions

View File

@ -2,7 +2,6 @@ import { dirname, join, resolve } from 'path';
import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, ProvidePlugin } from 'webpack';
import type { Configuration } from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
// @ts-expect-error (I removed this on purpose, because it's incorrect)
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import TerserWebpackPlugin from 'terser-webpack-plugin';
@ -54,11 +53,7 @@ const storybookPaths: Record<string, string> = {
};
export default async (
options: Options & {
typescriptOptions: TypescriptOptions;
/* Build entries, which should not be linked in the iframe HTML file */
excludeChunks?: string[];
}
options: Options & { typescriptOptions: TypescriptOptions }
): Promise<Configuration> => {
const {
outputDir = join('.', 'public'),
@ -69,7 +64,6 @@ export default async (
previewUrl,
typescriptOptions,
features,
excludeChunks = [],
} = options;
const isProd = configType === 'PRODUCTION';
@ -178,7 +172,6 @@ export default async (
alwaysWriteToDisk: true,
inject: false,
template,
excludeChunks,
templateParameters: {
version: packageJson.version,
globals: {

View File

@ -0,0 +1,43 @@
import { test, expect } from '@playwright/test';
import { SbPage } from './util';
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006';
// This is a slow test, and (presumably) framework independent, so only run it on one sandbox
const skipTest = process.env.STORYBOOK_TEMPLATE_NAME !== 'react-vite/default-ts';
test.describe('composition', () => {
test.beforeEach(async ({ page }) => {
if (skipTest) return;
await page.goto(storybookUrl);
await new SbPage(page).waitUntilLoaded();
});
test('should correctly filter composed stories', async ({ page }) => {
if (skipTest) return;
// Expect that composed Storybooks are visible
await expect(await page.getByTitle('Storybook 8.0.0')).toBeVisible();
await expect(await page.getByTitle('Storybook 7.6.18')).toBeVisible();
// Expect composed stories to be available in the sidebar
await page.locator('[id="storybook\\@8\\.0\\.0_components-badge"]').click();
await expect(
await page.locator('[id="storybook\\@8\\.0\\.0_components-badge--default"]')
).toBeVisible();
await page.locator('[id="storybook\\@7\\.6\\.18_components-badge"]').click();
await expect(
await page.locator('[id="storybook\\@7\\.6\\.18_components-badge--default"]')
).toBeVisible();
// Expect composed stories `to be available in the search
await page.getByPlaceholder('Find components').fill('Button');
await expect(
await page.getByRole('option', { name: 'Button Storybook 8.0.0 / @blocks / examples' })
).toBeVisible();
await expect(
await page.getByRole('option', { name: 'Button Storybook 7.6.18 / @blocks / examples' })
).toBeVisible();
});
});

View File

@ -1,22 +1,36 @@
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import {
BuilderContext,
BuilderHandlerFn,
BuilderOutput,
BuilderOutputLike,
Target,
createBuilder,
targetFromTargetString,
} from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { from, of, throwError } from 'rxjs';
import { catchError, map, mapTo, switchMap } from 'rxjs/operators';
import { sync as findUpSync } from 'find-up';
import { sync as readUpSync } from 'read-pkg-up';
import { BrowserBuilderOptions, StylePreprocessorOptions } from '@angular-devkit/build-angular';
import { CLIOptions } from '@storybook/types';
import { getEnvConfig, versions } from '@storybook/core-common';
import { addToGlobalContext } from '@storybook/telemetry';
import { buildStaticStandalone, withTelemetry } from '@storybook/core-server';
import { StyleClass } from '@angular-devkit/build-angular/src/builders/browser/schema';
import {
AssetPattern,
SourceMapUnion,
StyleElement,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
import { StandaloneOptions } from '../utils/standalone-options';
import { runCompodoc } from '../utils/run-compodoc';
import { errorSummary, printErrorDetails } from '../utils/error-handler';
import { AngularBuilderOptions, setup } from '../utils/setup';
addToGlobalContext('cliVersion', versions.storybook);
export type StorybookBuilderOptions = AngularBuilderOptions & {
export type StorybookBuilderOptions = JsonObject & {
browserTarget?: string | null;
tsConfig?: string;
test: boolean;
@ -24,6 +38,10 @@ export type StorybookBuilderOptions = AngularBuilderOptions & {
compodoc: boolean;
compodocArgs: string[];
enableProdMode?: boolean;
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
} & Pick<
// makes sure the option exists
CLIOptions,
@ -41,77 +59,112 @@ export type StorybookBuilderOptions = AngularBuilderOptions & {
export type StorybookBuilderOutput = JsonObject & BuilderOutput & { [key: string]: any };
type StandaloneBuildOptions = StandaloneOptions & { outputDir: string; excludeChunks: string[] };
type StandaloneBuildOptions = StandaloneOptions & { outputDir: string };
const commandBuilder = async (
options: StorybookBuilderOptions,
context: BuilderContext
): Promise<BuilderOutput> => {
const { tsConfig, angularBuilderContext, angularBuilderOptions } = await setup(options, context);
const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (
options,
context
): BuilderOutputLike => {
const builder = from(setup(options, context)).pipe(
switchMap(({ tsConfig }) => {
const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir });
const runCompodoc$ = options.compodoc
? runCompodoc(
{ compodocArgs: options.compodocArgs, tsconfig: docTSConfig ?? tsConfig },
context
).pipe(mapTo({ tsConfig }))
: of({});
const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir });
return runCompodoc$.pipe(mapTo({ tsConfig }));
}),
map(({ tsConfig }) => {
getEnvConfig(options, {
staticDir: 'SBCONFIG_STATIC_DIR',
outputDir: 'SBCONFIG_OUTPUT_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
});
if (options.compodoc) {
await runCompodoc(
{ compodocArgs: options.compodocArgs, tsconfig: docTSConfig ?? tsConfig },
context
);
}
const {
browserTarget,
stylePreprocessorOptions,
styles,
configDir,
docs,
loglevel,
test,
outputDir,
quiet,
enableProdMode = true,
webpackStatsJson,
statsJson,
debugWebpack,
disableTelemetry,
assets,
previewUrl,
sourceMap = false,
} = options;
getEnvConfig(options, {
staticDir: 'SBCONFIG_STATIC_DIR',
outputDir: 'SBCONFIG_OUTPUT_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
});
const standaloneOptions: StandaloneBuildOptions = {
packageJson: readUpSync({ cwd: __dirname }).packageJson,
configDir,
...(docs ? { docs } : {}),
loglevel,
outputDir,
test,
quiet,
enableProdMode,
disableTelemetry,
angularBrowserTarget: browserTarget,
angularBuilderContext: context,
angularBuilderOptions: {
...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}),
...(styles ? { styles } : {}),
...(assets ? { assets } : {}),
sourceMap,
},
tsConfig,
webpackStatsJson,
statsJson,
debugWebpack,
previewUrl,
};
const {
configDir,
docs,
loglevel,
test,
outputDir,
quiet,
enableProdMode = true,
webpackStatsJson,
statsJson,
debugWebpack,
disableTelemetry,
previewUrl,
} = options;
return standaloneOptions;
}),
switchMap((standaloneOptions) => runInstance({ ...standaloneOptions, mode: 'static' })),
map(() => {
return { success: true };
})
);
const standaloneOptions: StandaloneBuildOptions = {
packageJson: readUpSync({ cwd: __dirname }).packageJson,
configDir,
...(docs ? { docs } : {}),
excludeChunks: angularBuilderOptions.styles
?.filter((style) => typeof style !== 'string' && style.inject === false)
.map((s: StyleClass) => s.bundleName),
loglevel,
outputDir,
test,
quiet,
enableProdMode,
disableTelemetry,
angularBrowserTarget: options.browserTarget,
angularBuilderContext,
angularBuilderOptions,
tsConfig,
webpackStatsJson,
statsJson,
debugWebpack,
previewUrl,
};
await runInstance({ ...standaloneOptions, mode: 'static' });
return { success: true };
return builder as any as BuilderOutput;
};
export default createBuilder(commandBuilder);
async function runInstance(options: StandaloneBuildOptions) {
try {
await withTelemetry(
async function setup(options: StorybookBuilderOptions, context: BuilderContext) {
let browserOptions: (JsonObject & BrowserBuilderOptions) | undefined;
let browserTarget: Target | undefined;
if (options.browserTarget) {
browserTarget = targetFromTargetString(options.browserTarget);
browserOptions = await context.validateOptions<JsonObject & BrowserBuilderOptions>(
await context.getTargetOptions(browserTarget),
await context.getBuilderNameForTarget(browserTarget)
);
}
return {
tsConfig:
options.tsConfig ??
findUpSync('tsconfig.json', { cwd: options.configDir }) ??
browserOptions.tsConfig,
};
}
function runInstance(options: StandaloneBuildOptions) {
return from(
withTelemetry(
'build',
{
cliOptions: options,
@ -119,8 +172,6 @@ async function runInstance(options: StandaloneBuildOptions) {
printError: printErrorDetails,
},
() => buildStaticStandalone(options)
);
} catch (error) {
throw new Error(errorSummary(error));
}
)
).pipe(catchError((error: any) => throwError(errorSummary(error))));
}

View File

@ -1,6 +1,15 @@
import { BuilderHandlerFn, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import {
BuilderContext,
BuilderHandlerFn,
BuilderOutput,
Target,
createBuilder,
targetFromTargetString,
} from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { StylePreprocessorOptions } from '@angular-devkit/build-angular';
import { BrowserBuilderOptions, StylePreprocessorOptions } from '@angular-devkit/build-angular';
import { from, Observable, of } from 'rxjs';
import { map, switchMap, mapTo } from 'rxjs/operators';
import { sync as findUpSync } from 'find-up';
import { sync as readUpSync } from 'read-pkg-up';
@ -11,22 +20,24 @@ import { buildDevStandalone, withTelemetry } from '@storybook/core-server';
import {
AssetPattern,
SourceMapUnion,
StyleClass,
StyleElement,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
import { StandaloneOptions } from '../utils/standalone-options';
import { runCompodoc } from '../utils/run-compodoc';
import { printErrorDetails, errorSummary } from '../utils/error-handler';
import { AngularBuilderOptions, setup } from '../utils/setup';
addToGlobalContext('cliVersion', versions.storybook);
export type StorybookBuilderOptions = AngularBuilderOptions & {
export type StorybookBuilderOptions = JsonObject & {
browserTarget?: string | null;
tsConfig?: string;
compodoc: boolean;
compodocArgs: string[];
enableProdMode?: boolean;
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
} & Pick<
// makes sure the option exists
CLIOptions,
@ -53,96 +64,131 @@ export type StorybookBuilderOptions = AngularBuilderOptions & {
export type StorybookBuilderOutput = JsonObject & BuilderOutput & {};
const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = async (options, context) => {
const { tsConfig, angularBuilderContext, angularBuilderOptions } = await setup(options, context);
const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (options, context) => {
const builder = from(setup(options, context)).pipe(
switchMap(({ tsConfig }) => {
const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir });
const docTSConfig = findUpSync('tsconfig.doc.json', { cwd: options.configDir });
const runCompodoc$ = options.compodoc
? runCompodoc(
{
compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])],
tsconfig: docTSConfig ?? tsConfig,
},
context
).pipe(mapTo({ tsConfig }))
: of({});
if (options.compodoc) {
await runCompodoc(
{
compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])],
tsconfig: docTSConfig ?? tsConfig,
},
context
);
}
return runCompodoc$.pipe(mapTo({ tsConfig }));
}),
map(({ tsConfig }) => {
getEnvConfig(options, {
port: 'SBCONFIG_PORT',
host: 'SBCONFIG_HOSTNAME',
staticDir: 'SBCONFIG_STATIC_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
ci: 'CI',
});
getEnvConfig(options, {
port: 'SBCONFIG_PORT',
host: 'SBCONFIG_HOSTNAME',
staticDir: 'SBCONFIG_STATIC_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
ci: 'CI',
});
options.port = parseInt(`${options.port}`, 10);
options.port = parseInt(`${options.port}`, 10);
const {
browserTarget,
stylePreprocessorOptions,
styles,
ci,
configDir,
docs,
host,
https,
port,
quiet,
enableProdMode = false,
smokeTest,
sslCa,
sslCert,
sslKey,
disableTelemetry,
assets,
initialPath,
open,
debugWebpack,
loglevel,
webpackStatsJson,
statsJson,
previewUrl,
sourceMap = false,
} = options;
const {
browserTarget,
ci,
configDir,
docs,
host,
https,
port,
quiet,
enableProdMode = false,
smokeTest,
sslCa,
sslCert,
sslKey,
disableTelemetry,
initialPath,
open,
debugWebpack,
loglevel,
webpackStatsJson,
statsJson,
previewUrl,
} = options;
const standaloneOptions: StandaloneOptions = {
packageJson: readUpSync({ cwd: __dirname }).packageJson,
ci,
configDir,
...(docs ? { docs } : {}),
host,
https,
port,
quiet,
enableProdMode,
smokeTest,
sslCa,
sslCert,
sslKey,
disableTelemetry,
angularBrowserTarget: browserTarget,
angularBuilderContext: context,
angularBuilderOptions: {
...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}),
...(styles ? { styles } : {}),
...(assets ? { assets } : {}),
sourceMap,
},
tsConfig,
initialPath,
open,
debugWebpack,
webpackStatsJson,
statsJson,
loglevel,
previewUrl,
};
const standaloneOptions: StandaloneOptions = {
packageJson: readUpSync({ cwd: __dirname }).packageJson,
ci,
configDir,
...(docs ? { docs } : {}),
excludeChunks: angularBuilderOptions.styles
?.filter((style) => typeof style !== 'string' && style.inject === false)
.map((s: StyleClass) => s.bundleName),
host,
https,
port,
quiet,
enableProdMode,
smokeTest,
sslCa,
sslCert,
sslKey,
disableTelemetry,
angularBrowserTarget: browserTarget,
angularBuilderContext,
angularBuilderOptions,
tsConfig,
initialPath,
open,
debugWebpack,
webpackStatsJson,
statsJson,
loglevel,
previewUrl,
};
return standaloneOptions;
}),
switchMap((standaloneOptions) => runInstance(standaloneOptions)),
map((port: number) => {
return { success: true, info: { port } };
})
);
const devPort = await runInstance(standaloneOptions);
return { success: true, info: { port: devPort } };
return builder as any as BuilderOutput;
};
export default createBuilder(commandBuilder);
async function runInstance(options: StandaloneOptions): Promise<number> {
try {
const { port } = await withTelemetry(
async function setup(options: StorybookBuilderOptions, context: BuilderContext) {
let browserOptions: (JsonObject & BrowserBuilderOptions) | undefined;
let browserTarget: Target | undefined;
if (options.browserTarget) {
browserTarget = targetFromTargetString(options.browserTarget);
browserOptions = await context.validateOptions<JsonObject & BrowserBuilderOptions>(
await context.getTargetOptions(browserTarget),
await context.getBuilderNameForTarget(browserTarget)
);
}
return {
tsConfig:
options.tsConfig ??
findUpSync('tsconfig.json', { cwd: options.configDir }) ??
browserOptions.tsConfig,
};
}
function runInstance(options: StandaloneOptions) {
return new Observable<number>((observer) => {
// This Observable intentionally never complete, leaving the process running ;)
withTelemetry(
'dev',
{
cliOptions: options,
@ -150,9 +196,10 @@ async function runInstance(options: StandaloneOptions): Promise<number> {
printError: printErrorDetails,
},
() => buildDevStandalone(options)
);
return port;
} catch (error) {
throw new Error(errorSummary(error));
}
)
.then(({ port }) => observer.next(port))
.catch((error) => {
observer.error(errorSummary(error));
});
});
}

View File

@ -1,6 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { vi, describe, afterEach, it, expect } from 'vitest';
import { LoggerApi } from '@angular-devkit/core/src/logger';
import { take } from 'rxjs/operators';
import { BuilderContext } from '@angular-devkit/architect';
import { runCompodoc } from './run-compodoc';
@ -36,13 +37,15 @@ describe('runCompodoc', () => {
} as BuilderContext;
it('should run compodoc with tsconfig from context', async () => {
await runCompodoc(
runCompodoc(
{
compodocArgs: [],
tsconfig: 'path/to/tsconfig.json',
},
builderContextMock
);
)
.pipe(take(1))
.subscribe();
expect(mockRunScript).toHaveBeenCalledWith(
'compodoc',
@ -53,13 +56,15 @@ describe('runCompodoc', () => {
});
it('should run compodoc with tsconfig from compodocArgs', async () => {
await runCompodoc(
runCompodoc(
{
compodocArgs: ['-p', 'path/to/tsconfig.stories.json'],
tsconfig: 'path/to/tsconfig.json',
},
builderContextMock
);
)
.pipe(take(1))
.subscribe();
expect(mockRunScript).toHaveBeenCalledWith(
'compodoc',
@ -70,13 +75,15 @@ describe('runCompodoc', () => {
});
it('should run compodoc with default output folder.', async () => {
await runCompodoc(
runCompodoc(
{
compodocArgs: [],
tsconfig: 'path/to/tsconfig.json',
},
builderContextMock
);
)
.pipe(take(1))
.subscribe();
expect(mockRunScript).toHaveBeenCalledWith(
'compodoc',
@ -87,13 +94,15 @@ describe('runCompodoc', () => {
});
it('should run with custom output folder specified with --output compodocArgs', async () => {
await runCompodoc(
runCompodoc(
{
compodocArgs: ['--output', 'path/to/customFolder'],
tsconfig: 'path/to/tsconfig.json',
},
builderContextMock
);
)
.pipe(take(1))
.subscribe();
expect(mockRunScript).toHaveBeenCalledWith(
'compodoc',
@ -104,13 +113,15 @@ describe('runCompodoc', () => {
});
it('should run with custom output folder specified with -d compodocArgs', async () => {
await runCompodoc(
runCompodoc(
{
compodocArgs: ['-d', 'path/to/customFolder'],
tsconfig: 'path/to/tsconfig.json',
},
builderContextMock
);
)
.pipe(take(1))
.subscribe();
expect(mockRunScript).toHaveBeenCalledWith(
'compodoc',

View File

@ -1,4 +1,5 @@
import { BuilderContext } from '@angular-devkit/architect';
import { Observable } from 'rxjs';
import * as path from 'path';
import { JsPackageManagerFactory } from '@storybook/core-common';
@ -12,30 +13,34 @@ const toRelativePath = (pathToTsConfig: string) => {
return path.isAbsolute(pathToTsConfig) ? path.relative('.', pathToTsConfig) : pathToTsConfig;
};
export const runCompodoc = async (
export const runCompodoc = (
{ compodocArgs, tsconfig }: { compodocArgs: string[]; tsconfig: string },
context: BuilderContext
): Promise<void> => {
const tsConfigPath = toRelativePath(tsconfig);
const finalCompodocArgs = [
...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsConfigPath]),
...(hasOutputArg(compodocArgs) ? [] : ['-d', `${context.workspaceRoot || '.'}`]),
...compodocArgs,
];
): Observable<void> => {
return new Observable<void>((observer) => {
const tsConfigPath = toRelativePath(tsconfig);
const finalCompodocArgs = [
...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsConfigPath]),
...(hasOutputArg(compodocArgs) ? [] : ['-d', `${context.workspaceRoot || '.'}`]),
...compodocArgs,
];
const packageManager = JsPackageManagerFactory.getPackageManager();
const packageManager = JsPackageManagerFactory.getPackageManager();
try {
const stdout = packageManager.runPackageCommandSync(
'compodoc',
finalCompodocArgs,
context.workspaceRoot,
'inherit'
);
try {
const stdout = packageManager.runPackageCommandSync(
'compodoc',
finalCompodocArgs,
context.workspaceRoot,
'inherit'
);
context.logger.info(stdout);
} catch (e) {
context.logger.error(e);
throw e;
}
context.logger.info(stdout);
observer.next();
observer.complete();
} catch (e) {
context.logger.error(e);
observer.error();
}
});
};

View File

@ -1,109 +0,0 @@
import { Target, targetFromTargetString } from '@angular-devkit/architect';
import { BuilderContext } from '@angular-devkit/architect';
import { JsonObject, logging } from '@angular-devkit/core';
import { sync as findUpSync } from 'find-up';
import { BrowserBuilderOptions } from '@angular-devkit/build-angular';
import { logger } from '@storybook/node-logger';
export type AngularBuilderOptions = BrowserBuilderOptions & {
browserTarget?: string | null;
configDir?: string;
};
export async function setup(
{ stylePreprocessorOptions, styles, assets, sourceMap, ...options }: AngularBuilderOptions,
context: BuilderContext
) {
let browserOptions: BrowserBuilderOptions | undefined;
let browserTarget: Target | undefined;
if (options.browserTarget) {
browserTarget = targetFromTargetString(options.browserTarget);
browserOptions = await context.validateOptions<any>(
await context.getTargetOptions(browserTarget),
await context.getBuilderNameForTarget(browserTarget)
);
}
const tsConfig =
options.tsConfig ??
findUpSync('tsconfig.json', { cwd: options.configDir }) ??
browserOptions.tsConfig;
const angularBuilderContext = getBuilderContext(context);
const angularBuilderOptions = await getBuilderOptions(
options.browserTarget,
{
...options,
...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}),
...(styles ? { styles } : {}),
...(assets ? { assets } : {}),
sourceMap: sourceMap ?? false,
},
tsConfig,
options.configDir,
angularBuilderContext
);
return {
tsConfig,
angularBuilderContext,
angularBuilderOptions,
};
}
/**
* Get Builder Context
* If storybook is not start by angular builder create dumb BuilderContext
*/
function getBuilderContext(builderContext: BuilderContext): BuilderContext {
return (
builderContext ??
({
target: { project: 'noop-project', builder: '', options: {} },
workspaceRoot: process.cwd(),
getProjectMetadata: () => ({}),
getTargetOptions: () => ({}),
logger: new logging.Logger('Storybook'),
} as unknown as BuilderContext)
);
}
/**
* Get builder options
* Merge target options from browser target and from storybook options
*/
async function getBuilderOptions(
angularBrowserTarget: string,
angularBuilderOptions: AngularBuilderOptions,
tsConfig: string,
configDir: string,
builderContext: BuilderContext
): Promise<BrowserBuilderOptions> {
/**
* Get Browser Target options
*/
let browserTargetOptions: JsonObject = {};
if (angularBrowserTarget) {
const browserTarget = targetFromTargetString(angularBrowserTarget);
browserTargetOptions = await builderContext.getTargetOptions(browserTarget);
}
/**
* Merge target options from browser target options and from storybook options
*/
const builderOptions = {
...browserTargetOptions,
...angularBuilderOptions,
tsConfig:
tsConfig ??
findUpSync('tsconfig.json', { cwd: configDir }) ??
(browserTargetOptions.tsConfig as string),
};
logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`);
return builderOptions;
}

View File

@ -1,7 +1,11 @@
import { BuilderContext } from '@angular-devkit/architect';
import {
AssetPattern,
SourceMapUnion,
StyleElement,
StylePreprocessorOptions,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
import { LoadOptions, CLIOptions, BuilderOptions } from '@storybook/types';
import { AngularBuilderOptions } from './setup';
export type StandaloneOptions = CLIOptions &
LoadOptions &
@ -9,8 +13,12 @@ export type StandaloneOptions = CLIOptions &
mode?: 'static' | 'dev';
enableProdMode: boolean;
angularBrowserTarget?: string | null;
angularBuilderOptions?: AngularBuilderOptions;
angularBuilderOptions?: Record<string, any> & {
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
};
angularBuilderContext?: BuilderContext | null;
tsConfig?: string;
excludeChunks?: string[];
};

View File

@ -1,7 +1,7 @@
import { JsonObject } from '@angular-devkit/core';
import { BuilderContext } from '@angular-devkit/architect';
import { AngularBuilderOptions } from '../builders/utils/setup';
export declare function getWebpackConfig(
baseConfig: any,
options: { builderOptions: AngularBuilderOptions; builderContext: BuilderContext }
options: { builderOptions: JsonObject; builderContext: BuilderContext }
): any;

View File

@ -56,7 +56,6 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }
*/
const { getCommonConfig, getStylesConfig, getDevServerConfig, getTypeScriptConfig } =
getAngularWebpackUtils();
const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext(
{
// Default options
@ -66,15 +65,10 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }
// Options provided by user
...builderOptions,
styles: builderOptions.styles?.map((style) =>
typeof style === 'string'
? {
input: style,
inject: true,
bundleName: style.split('/').pop(),
}
: style
),
styles: builderOptions.styles
?.map((style) => (typeof style === 'string' ? style : style.input))
.filter((style) => typeof style === 'string' || style.inject !== false),
// Fixed options
optimization: false,
namedChunks: false,

View File

@ -1,6 +1,9 @@
import webpack from 'webpack';
import { logger } from '@storybook/node-logger';
import { AngularLegacyBuildOptionsError } from '@storybook/core-events/server-errors';
import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect';
import { sync as findUpSync } from 'find-up';
import { JsonObject, logging } from '@angular-devkit/core';
import { getWebpackConfig as getCustomWebpackConfig } from './angular-cli-webpack';
import { moduleIsAvailable } from './utils/module-is-available';
@ -14,15 +17,74 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: P
checkForLegacyBuildOptions(options);
const builderContext = getBuilderContext(options);
const builderOptions = await getBuilderOptions(options, builderContext);
return getCustomWebpackConfig(baseConfig, {
builderOptions: {
watch: options.configType === 'DEVELOPMENT',
...options.angularBuilderOptions,
...builderOptions,
},
builderContext: options.angularBuilderContext,
builderContext,
});
}
/**
* Get Builder Context
* If storybook is not start by angular builder create dumb BuilderContext
*/
function getBuilderContext(options: PresetOptions): BuilderContext {
return (
options.angularBuilderContext ??
({
target: { project: 'noop-project', builder: '', options: {} },
workspaceRoot: process.cwd(),
getProjectMetadata: () => ({}),
getTargetOptions: () => ({}),
logger: new logging.Logger('Storybook'),
} as unknown as BuilderContext)
);
}
/**
* Get builder options
* Merge target options from browser target and from storybook options
*/
async function getBuilderOptions(
options: PresetOptions,
builderContext: BuilderContext
): Promise<JsonObject> {
/**
* Get Browser Target options
*/
let browserTargetOptions: JsonObject = {};
if (options.angularBrowserTarget) {
const browserTarget = targetFromTargetString(options.angularBrowserTarget);
logger.info(
`=> Using angular browser target options from "${browserTarget.project}:${
browserTarget.target
}${browserTarget.configuration ? `:${browserTarget.configuration}` : ''}"`
);
browserTargetOptions = await builderContext.getTargetOptions(browserTarget);
}
/**
* Merge target options from browser target options and from storybook options
*/
const builderOptions = {
...browserTargetOptions,
...(options.angularBuilderOptions as JsonObject),
tsConfig:
options.tsConfig ??
findUpSync('tsconfig.json', { cwd: options.configDir }) ??
browserTargetOptions.tsConfig,
};
logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`);
return builderOptions;
}
/**
* Checks if using legacy configuration that doesn't use builder and logs message referring to migration docs.
*/

View File

@ -1,13 +1,17 @@
import { Options as CoreOptions } from '@storybook/types';
import { BuilderContext } from '@angular-devkit/architect';
import { AngularBuilderOptions } from '../builders/utils/setup';
import { StylePreprocessorOptions } from '@angular-devkit/build-angular';
import { StyleElement } from '@angular-devkit/build-angular/src/builders/browser/schema';
export type PresetOptions = CoreOptions & {
/* Allow to get the options of a targeted "browser builder" */
angularBrowserTarget?: string | null;
/* Defined set of options. These will take over priority from angularBrowserTarget options */
angularBuilderOptions?: AngularBuilderOptions;
angularBuilderOptions?: {
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
};
/* Angular context from builder */
angularBuilderContext?: BuilderContext | null;
tsConfig?: string;

View File

@ -88,7 +88,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -119,7 +119,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -150,7 +150,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -192,7 +192,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -294,7 +294,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -440,7 +440,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -593,7 +593,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -656,7 +656,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -714,7 +714,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -771,7 +771,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -789,7 +789,7 @@ describe('StoryIndexGenerator', () => {
expect(await generator.getIndex()).toMatchInlineSnapshot(`
{
"entries": {},
"v": 4,
"v": 5,
}
`);
});
@ -832,7 +832,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -933,7 +933,7 @@ describe('StoryIndexGenerator', () => {
"type": "docs",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -1066,7 +1066,7 @@ describe('StoryIndexGenerator', () => {
"type": "docs",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -1127,7 +1127,7 @@ describe('StoryIndexGenerator', () => {
"type": "docs",
},
},
"v": 4,
"v": 5,
}
`);
});
@ -1176,7 +1176,7 @@ describe('StoryIndexGenerator', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
});

View File

@ -592,7 +592,7 @@ export class StoryIndexGenerator {
);
this.lastIndex = {
v: 4,
v: 5,
entries: sorted,
};

View File

@ -263,7 +263,7 @@ describe('useStoriesJson', () => {
"type": "story",
},
},
"v": 4,
"v": 5,
}
`);
}, 20_000);

View File

@ -38,7 +38,7 @@ describe('summarizeIndex', () => {
it('example stories', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'example-introduction--docs': {
id: 'example-introduction--docs',
@ -146,14 +146,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 0,
"storyCount": 0,
"version": 4,
"version": 5,
}
`);
});
it('onboarding stories', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'example-introduction--docs': {
id: 'example-introduction--docs',
@ -204,14 +204,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 0,
"storyCount": 0,
"version": 4,
"version": 5,
}
`);
});
it('user stories', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'stories-renderers-react-errors--story-contains-unrenderable': {
id: 'stories-renderers-react-errors--story-contains-unrenderable',
@ -260,14 +260,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 0,
"storyCount": 4,
"version": 4,
"version": 5,
}
`);
});
it('pages', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'example-page--logged-out': {
id: 'example-page--logged-out',
@ -317,14 +317,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 1,
"storiesMdxCount": 0,
"storyCount": 1,
"version": 4,
"version": 5,
}
`);
});
it('storiesMdx', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'stories-renderers-react-react-mdx--docs': {
id: 'stories-renderers-react-react-mdx--docs',
@ -374,14 +374,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 1,
"storyCount": 3,
"version": 4,
"version": 5,
}
`);
});
it('autodocs', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'example-button--docs': {
id: 'example-button--docs',
@ -432,14 +432,14 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 0,
"storyCount": 0,
"version": 4,
"version": 5,
}
`);
});
it('mdx', () => {
expect(
summarizeIndex({
v: 4,
v: 5,
entries: {
'example-introduction--docs': {
id: 'example-introduction--docs',
@ -483,7 +483,7 @@ describe('summarizeIndex', () => {
"playStoryCount": 0,
"storiesMdxCount": 0,
"storyCount": 0,
"version": 4,
"version": 5,
}
`);
});

View File

@ -1,6 +1,11 @@
import { describe, it, expect } from 'vitest';
import type { StoryIndexV2, StoryIndexV3 } from '@storybook/types';
import { transformStoryIndexV2toV3, transformStoryIndexV3toV4 } from './stories';
import type { StoryIndexV2, StoryIndexV3, API_PreparedStoryIndex } from '@storybook/types';
import {
transformStoryIndexV2toV3,
transformStoryIndexV3toV4,
transformStoryIndexV4toV5,
} from './stories';
import { mockEntries } from '../tests/mockStoriesEntries';
const baseV2: StoryIndexV2['stories'][0] = {
id: '1',
@ -151,3 +156,61 @@ describe('transformStoryIndexV3toV4', () => {
`);
});
});
describe('transformStoryIndexV4toV5', () => {
it('transforms a StoryIndexV4 to an API_PreparedStoryIndex correctly', () => {
const indexV4: API_PreparedStoryIndex = {
v: 4,
entries: mockEntries,
};
expect(transformStoryIndexV4toV5(indexV4)).toMatchInlineSnapshot(`
{
"entries": {
"component-a--docs": {
"id": "component-a--docs",
"importPath": "./path/to/component-a.ts",
"name": "Docs",
"storiesImports": [],
"tags": [
"dev",
],
"title": "Component A",
"type": "docs",
},
"component-a--story-1": {
"id": "component-a--story-1",
"importPath": "./path/to/component-a.ts",
"name": "Story 1",
"tags": [
"dev",
],
"title": "Component A",
"type": "story",
},
"component-a--story-2": {
"id": "component-a--story-2",
"importPath": "./path/to/component-a.ts",
"name": "Story 2",
"tags": [
"dev",
],
"title": "Component A",
"type": "story",
},
"component-b--story-3": {
"id": "component-b--story-3",
"importPath": "./path/to/component-b.ts",
"name": "Story 3",
"tags": [
"dev",
],
"title": "Component B",
"type": "story",
},
},
"v": 5,
}
`);
});
});

View File

@ -86,7 +86,7 @@ export const transformSetStoriesStoryDataToPreparedStoryIndex = (
{} as API_PreparedStoryIndex['entries']
);
return { v: 4, entries };
return { v: 5, entries };
};
export const transformStoryIndexV2toV3 = (index: StoryIndexV2): StoryIndexV3 => {
@ -139,6 +139,30 @@ export const transformStoryIndexV3toV4 = (index: StoryIndexV3): API_PreparedStor
};
};
/**
* Storybook 8.0 and below did not automatically tag stories with 'dev'.
* Therefore Storybook 8.1 and above would not show composed 8.0 stories by default.
* This function adds the 'dev' tag to all stories in the index to workaround this issue.
*/
export const transformStoryIndexV4toV5 = (
index: API_PreparedStoryIndex
): API_PreparedStoryIndex => {
return {
v: 5,
entries: Object.values(index.entries).reduce(
(acc, entry) => {
acc[entry.id] = {
...entry,
tags: entry.tags ? [...entry.tags, 'dev'] : ['dev'],
};
return acc;
},
{} as API_PreparedStoryIndex['entries']
),
};
};
type ToStoriesHashOptions = {
provider: API_Provider<API>;
docsOptions: DocsOptions;
@ -157,6 +181,7 @@ export const transformStoryIndexToStoriesHash = (
let index = input;
index = index.v === 2 ? transformStoryIndexV2toV3(index as any) : index;
index = index.v === 3 ? transformStoryIndexV3toV4(index as any) : index;
index = index.v === 4 ? transformStoryIndexV4toV5(index as any) : index;
index = index as API_PreparedStoryIndex;
const entryValues = Object.values(index.entries).filter((entry: any) => {

View File

@ -448,7 +448,7 @@ describe('Refs API', () => {
setupResponses({
indexPrivate: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPrivate: {
ok: true,
@ -506,7 +506,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -524,7 +524,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -542,7 +542,7 @@ describe('Refs API', () => {
setupResponses({
indexPrivate: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPrivate: {
ok: true,
@ -603,7 +603,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -622,7 +622,7 @@ describe('Refs API', () => {
setupResponses({
indexPrivate: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPrivate: {
ok: true,
@ -684,7 +684,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -781,7 +781,7 @@ describe('Refs API', () => {
setupResponses({
indexPrivate: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPrivate: {
ok: true,
@ -927,7 +927,7 @@ describe('Refs API', () => {
setupResponses({
indexPublic: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPublic: {
ok: true,
@ -989,7 +989,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -1008,7 +1008,7 @@ describe('Refs API', () => {
setupResponses({
indexPrivate: {
ok: true,
response: async () => ({ v: 4, entries: {} }),
response: async () => ({ v: 5, entries: {} }),
},
storiesPrivate: {
ok: true,
@ -1070,7 +1070,7 @@ describe('Refs API', () => {
"index": {},
"internal_index": {
"entries": {},
"v": 4,
"v": 5,
},
"title": "Fake",
"type": "lazy",
@ -1206,7 +1206,7 @@ describe('Refs API', () => {
describe('setRef', () => {
it('can filter', async () => {
const index: StoryIndex = {
v: 4,
v: 5,
entries: {
'a--1': {
id: 'a--1',

View File

@ -38,7 +38,7 @@ vi.mock('../lib/events', () => ({
vi.mock('@storybook/global', () => ({
global: {
...globalThis,
fetch: vi.fn(() => ({ json: () => ({ v: 4, entries: mockGetEntries() }) })),
fetch: vi.fn(() => ({ json: () => ({ v: 5, entries: mockGetEntries() }) })),
CONFIG_TYPE: 'DEVELOPMENT',
},
}));
@ -97,7 +97,7 @@ describe('stories API', () => {
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
const { index } = store.getState();
// We need exact key ordering, even if in theory JS doesn't guarantee it
expect(Object.keys(index!)).toEqual([
@ -139,7 +139,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({
v: 4,
v: 5,
entries: {
'design-system-some-component--my-story': {
type: 'story',
@ -176,7 +176,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({
v: 4,
v: 5,
entries: {
'root-first--story-1': {
type: 'story',
@ -213,7 +213,7 @@ describe('stories API', () => {
const { store, provider } = moduleArgs;
provider.getConfig.mockReturnValue({ sidebar: { showRoots: true } });
api.setIndex({
v: 4,
v: 5,
entries: {
'a-b--1': {
type: 'story',
@ -252,7 +252,7 @@ describe('stories API', () => {
const { store, provider } = moduleArgs;
provider.getConfig.mockReturnValue({ sidebar: { showRoots: true } });
api.setIndex({
v: 4,
v: 5,
entries: {
'a--1': {
type: 'story',
@ -287,7 +287,7 @@ describe('stories API', () => {
const { store, provider } = moduleArgs;
provider.getConfig.mockReturnValue({ sidebar: { showRoots: true } });
api.setIndex({
v: 4,
v: 5,
entries: {
'a--1': { type: 'story', title: 'a', name: '1', id: 'a--1', importPath: './a.ts' },
'b--1': { type: 'story', title: 'b', name: '1', id: 'b--1', importPath: './b.ts' },
@ -314,7 +314,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({
v: 4,
v: 5,
entries: {
'prepared--story': {
type: 'story',
@ -344,7 +344,7 @@ describe('stories API', () => {
const moduleArgs = createMockModuleArgs({ fullAPI });
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store, provider } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
provider.channel.emit(STORY_PREPARED, {
id: 'component-a--story-1',
parameters: { a: 'b' },
@ -357,7 +357,7 @@ describe('stories API', () => {
parameters: { a: 'b' },
args: { c: 'd' },
});
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
// Let the promise/await chain resolve
await new Promise((r) => setTimeout(r, 0));
expect(store.getState().index!['component-a--story-1'] as API_StoryEntry).toMatchObject({
@ -373,7 +373,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: docsEntries });
api.setIndex({ v: 5, entries: docsEntries });
const { index } = store.getState();
// We need exact key ordering, even if in theory JS doesn't guarantee it
expect(Object.keys(index!)).toEqual([
@ -398,7 +398,7 @@ describe('stories API', () => {
docsOptions: { docsMode: true },
});
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: docsEntries });
api.setIndex({ v: 5, entries: docsEntries });
const { index } = store.getState();
expect(Object.keys(index!)).toEqual(['component-b', 'component-b--docs']);
});
@ -413,7 +413,7 @@ describe('stories API', () => {
initStories(moduleArgs as unknown as ModuleArgs);
const { store, provider } = moduleArgs;
provider.channel.emit(SET_INDEX, { v: 4, entries: mockEntries });
provider.channel.emit(SET_INDEX, { v: 5, entries: mockEntries });
expect(store.getState().index).toEqual(
expect.objectContaining({
'component-a': expect.any(Object),
@ -433,7 +433,7 @@ describe('stories API', () => {
getCurrentParameter: vi.fn().mockReturnValue('options'),
});
provider.channel.emit(SET_INDEX, { v: 4, entries: mockEntries });
provider.channel.emit(SET_INDEX, { v: 5, entries: mockEntries });
expect(fullAPI.setOptions).toHaveBeenCalledWith('options');
});
});
@ -461,7 +461,7 @@ describe('stories API', () => {
status: 200,
ok: true,
json: () => ({
v: 4,
v: 5,
entries: {
'component-a--story-1': {
type: 'story',
@ -512,7 +512,7 @@ describe('stories API', () => {
status: 200,
ok: true,
json: () => ({
v: 4,
v: 5,
entries: {
'component-a--story-1': {
type: 'story',
@ -616,7 +616,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider, store } = moduleArgs;
api.setIndex({ v: 4, entries: preparedEntries });
api.setIndex({ v: 5, entries: preparedEntries });
const { index } = store.getState();
expect((index!['a--1'] as API_StoryEntry).args).toEqual({ a: 'b' });
expect((index!['b--1'] as API_StoryEntry).args).toEqual({ x: 'y' });
@ -653,7 +653,7 @@ describe('stories API', () => {
const listener = vi.fn();
provider.channel.on(UPDATE_STORY_ARGS, listener);
api.setIndex({ v: 4, entries: preparedEntries });
api.setIndex({ v: 5, entries: preparedEntries });
api.updateStoryArgs({ id: 'a--1' } as API_StoryEntry, { foo: 'bar' });
expect(listener).toHaveBeenCalledWith({
@ -677,7 +677,7 @@ describe('stories API', () => {
const listener = vi.fn();
provider.channel.on(UPDATE_STORY_ARGS, listener);
api.setIndex({ v: 4, entries: preparedEntries });
api.setIndex({ v: 5, entries: preparedEntries });
api.updateStoryArgs({ id: 'a--1', refId: 'refId' } as API_StoryEntry, { foo: 'bar' });
expect(listener).toHaveBeenCalledWith({
storyId: 'a--1',
@ -695,7 +695,7 @@ describe('stories API', () => {
const listener = vi.fn();
provider.channel.on(RESET_STORY_ARGS, listener);
api.setIndex({ v: 4, entries: preparedEntries });
api.setIndex({ v: 5, entries: preparedEntries });
api.resetStoryArgs({ id: 'a--1' } as API_StoryEntry, ['foo']);
expect(listener).toHaveBeenCalledWith({
@ -719,7 +719,7 @@ describe('stories API', () => {
const listener = vi.fn();
provider.channel.on(RESET_STORY_ARGS, listener);
api.setIndex({ v: 4, entries: preparedEntries });
api.setIndex({ v: 5, entries: preparedEntries });
api.resetStoryArgs({ id: 'a--1', refId: 'refId' } as API_StoryEntry, ['foo']);
expect(listener).toHaveBeenCalledWith({
storyId: 'a--1',
@ -738,7 +738,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToStory(1);
expect(navigate).toHaveBeenCalledWith('/story/a--2');
@ -749,7 +749,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToStory(-1);
expect(navigate).toHaveBeenCalledWith('/story/a--1');
@ -764,7 +764,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToStory(1);
expect(navigate).not.toHaveBeenCalled();
});
@ -774,7 +774,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToStory(-1);
expect(navigate).not.toHaveBeenCalled();
});
@ -784,7 +784,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToStory(1);
expect(navigate).not.toHaveBeenCalled();
});
@ -797,7 +797,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
const result = api.findSiblingStoryId('a--1', store.getState().index!, 1, false);
expect(result).toBe('a--2');
});
@ -807,7 +807,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
const result = api.findSiblingStoryId('a--1', store.getState().index!, 1, true);
expect(result).toBe('b-c--1');
});
@ -819,7 +819,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToComponent(1);
expect(navigate).toHaveBeenCalledWith('/story/b-c--1');
});
@ -833,7 +833,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToComponent(-1);
expect(navigate).toHaveBeenCalledWith('/story/a--1');
});
@ -847,7 +847,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToComponent(1);
expect(navigate).not.toHaveBeenCalled();
});
@ -857,7 +857,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.jumpToComponent(-1);
expect(navigate).not.toHaveBeenCalled();
});
@ -869,7 +869,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('a--2');
expect(navigate).toHaveBeenCalledWith('/story/a--2');
});
@ -880,7 +880,7 @@ describe('stories API', () => {
const { navigate } = moduleArgs;
api.setIndex({
v: 4,
v: 5,
entries: {
...navigationEntries,
'intro--docs': {
@ -902,7 +902,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('a--1');
expect(store.getState().settings.lastTrackedStoryId).toBe('a--1');
});
@ -913,7 +913,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('a', '2');
expect(navigate).toHaveBeenCalledWith('/story/a--2');
});
@ -923,7 +923,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory(undefined, '2');
expect(navigate).toHaveBeenCalledWith('/story/a--2');
});
@ -934,7 +934,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('a--2');
expect(navigate).toHaveBeenCalledWith('/story/a--2');
});
@ -944,7 +944,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('a');
expect(navigate).toHaveBeenCalledWith('/story/a--1');
});
@ -954,7 +954,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('b');
expect(navigate).toHaveBeenCalledWith('/story/b-c--1');
});
@ -964,7 +964,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('A');
expect(navigate).toHaveBeenCalledWith('/story/a--1');
});
@ -974,7 +974,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory();
expect(navigate).toHaveBeenCalledWith('/story/a--1');
});
@ -985,7 +985,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('b/e', '1');
expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
});
@ -995,7 +995,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('custom-id', '1');
expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
});
@ -1005,7 +1005,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { navigate } = moduleArgs;
api.setIndex({ v: 4, entries: navigationEntries });
api.setIndex({ v: 5, entries: navigationEntries });
api.selectStory('b/e');
expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
});
@ -1019,7 +1019,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider, store } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
provider.channel.emit(STORY_PREPARED, {
id: 'component-a--story-1',
@ -1045,7 +1045,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
provider.channel.emit(STORY_PREPARED, {
id: 'component-a--story-1',
@ -1068,7 +1068,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider, store } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
provider.channel.emit(DOCS_PREPARED, {
id: 'component-a--docs',
@ -1092,7 +1092,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider, store } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
provider.channel.emit(CONFIG_ERROR, { message: 'Failed to run configure' });
const { previewInitialized } = store.getState();
@ -1104,7 +1104,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { provider } = moduleArgs;
api.setIndex({ v: 4, entries: mockEntries });
api.setIndex({ v: 5, entries: mockEntries });
getEventMetadata.mockReturnValueOnce({
sourceType: 'external',
@ -1225,7 +1225,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
await expect(
api.experimental_updateStatus('a-addon-id', {
@ -1279,7 +1279,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
await expect(
api.experimental_updateStatus('a-addon-id', {
@ -1315,7 +1315,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
await expect(
api.experimental_updateStatus('a-addon-id', {
@ -1353,7 +1353,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
// setup initial state
await expect(
@ -1403,7 +1403,7 @@ describe('stories API', () => {
const moduleArgs = createMockModuleArgs({});
const { state, api } = initStories(moduleArgs as unknown as ModuleArgs);
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
expect(state).toEqual(
expect.objectContaining({
@ -1416,7 +1416,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: mockEntries });
await api.setIndex({ v: 5, entries: mockEntries });
api.experimental_setFilter('myCustomFilter', () => true);
@ -1434,7 +1434,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: navigationEntries });
await api.setIndex({ v: 5, entries: navigationEntries });
await api.experimental_setFilter('myCustomFilter', (item: any) => item.id.startsWith('a'));
const { index } = store.getState();
@ -1484,7 +1484,7 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: navigationEntries });
await api.setIndex({ v: 5, entries: navigationEntries });
await api.experimental_setFilter(
'myCustomFilter',
(item: any) =>
@ -1538,10 +1538,10 @@ describe('stories API', () => {
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
await api.setIndex({ v: 4, entries: navigationEntries });
await api.setIndex({ v: 5, entries: navigationEntries });
await api.experimental_setFilter('myCustomFilter', (item: any) => item.id.startsWith('a'));
await api.setIndex({ v: 4, entries: navigationEntries });
await api.setIndex({ v: 5, entries: navigationEntries });
const { index } = store.getState();

View File

@ -76,7 +76,7 @@ export const projectAnnotations = {
export const getProjectAnnotations = vi.fn(() => projectAnnotations as any);
export const storyIndex: StoryIndex = {
v: 4,
v: 5,
entries: {
'component-one--docs': {
type: 'docs',

View File

@ -314,7 +314,7 @@ describe('PreviewWeb', () => {
preview.onStoriesChanged({
importFn: newImportFn,
storyIndex: {
v: 4,
v: 5,
entries: {
...storyIndex.entries,
'component-one--missing': {
@ -369,7 +369,7 @@ describe('PreviewWeb', () => {
preview.onStoriesChanged({
importFn: newImportFn,
storyIndex: {
v: 4,
v: 5,
entries: {
...storyIndex.entries,
'component-one--missing': {
@ -2977,7 +2977,7 @@ describe('PreviewWeb', () => {
const newImportFn = vi.fn(async (path) => ({ ...componentOneExports }));
const newStoryIndex = {
v: 4,
v: 5,
entries: {
...storyIndex.entries,
'component-one--a': {
@ -3174,7 +3174,7 @@ describe('PreviewWeb', () => {
});
const newStoryIndex = {
v: 4,
v: 5,
entries: {
'component-one--b': storyIndex.entries['component-one--b'],
},

View File

@ -6,7 +6,7 @@ import { StoryIndexStore } from './StoryIndexStore';
vi.mock('@storybook/channel-websocket', () => () => ({ on: vi.fn() }));
const storyIndex: StoryIndex = {
v: 4,
v: 5,
entries: {
'component-one--a': {
type: 'story',
@ -34,7 +34,7 @@ const storyIndex: StoryIndex = {
const makeStoryIndex = (titlesAndNames: any) => {
return {
v: 4,
v: 5,
entries: Object.fromEntries(
titlesAndNames.map(([title, name]: [any, any]) => {
const id = `${title}--${name}`.replace('/', '-');

View File

@ -24,7 +24,7 @@ const getImportPathMap = memoize(1)((entries: StoryIndex['entries']) =>
export class StoryIndexStore {
entries: StoryIndex['entries'];
constructor({ entries }: StoryIndex = { v: 4, entries: {} }) {
constructor({ entries }: StoryIndex = { v: 5, entries: {} }) {
this.entries = entries;
}

View File

@ -49,7 +49,7 @@ const projectAnnotations: ProjectAnnotations<any> = {
};
const storyIndex: StoryIndex = {
v: 4,
v: 5,
entries: {
'component-one--a': {
type: 'story',
@ -183,7 +183,7 @@ describe('StoryStore', () => {
expect(prepareStory).toHaveBeenCalledTimes(1);
// The stories are no longer in the index
await store.onStoriesChanged({ storyIndex: { v: 4, entries: {} } });
await store.onStoriesChanged({ storyIndex: { v: 5, entries: {} } });
await expect(store.loadStory({ storyId: 'component-one--a' })).rejects.toThrow();
@ -202,7 +202,7 @@ describe('StoryStore', () => {
// Add a new story to the index that isn't different
await store.onStoriesChanged({
storyIndex: {
v: 4,
v: 5,
entries: {
...storyIndex.entries,
'new-component--story': {
@ -233,7 +233,7 @@ describe('StoryStore', () => {
await store.onStoriesChanged({
importFn: newImportFn,
storyIndex: {
v: 4,
v: 5,
entries: {
'component-one--a': {
type: 'story',
@ -262,7 +262,7 @@ describe('StoryStore', () => {
await store.onStoriesChanged({
importFn: newImportFn,
storyIndex: {
v: 4,
v: 5,
entries: {
'component-one--a': {
type: 'story',
@ -334,7 +334,7 @@ describe('StoryStore', () => {
it('returns them in the order they are in the index, not the file', async () => {
const reversedIndex = {
v: 4,
v: 5,
entries: {
'component-one--b': storyIndex.entries['component-one--b'],
'component-one--a': storyIndex.entries['component-one--a'],
@ -587,7 +587,7 @@ describe('StoryStore', () => {
it('does not include (modern) docs entries ever', async () => {
const unnattachedStoryIndex: StoryIndex = {
v: 4,
v: 5,
entries: {
...storyIndex.entries,
'introduction--docs': {

View File

@ -31,7 +31,7 @@ export class ExternalPreview<TRenderer extends Renderer = Renderer> extends Prev
private titles = new ConstantMap<MetaExports, ComponentTitle>('title-');
private storyIndex: StoryIndex = { v: 4, entries: {} };
private storyIndex: StoryIndex = { v: 5, entries: {} };
private moduleExportsByImportPath: Record<Path, ModuleExports> = {};

View File

@ -94,6 +94,7 @@ If you're already using any of those flags in your project, you should be able t
| `--url` | Define the URL to run tests in. Useful for custom Storybook URLs <br/>`test-storybook --url http://the-storybook-url-here.com` |
| `--browsers` | Define browsers to run tests in. One or multiple of: chromium, firefox, webkit <br/>`test-storybook --browsers firefox chromium` |
| `--maxWorkers [amount]` | Specifies the maximum number of workers the worker-pool will spawn for running tests <br/>`test-storybook --maxWorkers=2` |
| `--testTimeout [amount]` | Defines the maximum time in milliseconds that a test can run before it is automatically marked as failed. Useful for long-running tests <br/> `test-storybook --testTimeout=60000` |
| `--no-cache` | Disable the cache <br/>`test-storybook --no-cache` |
| `--clearCache` | Deletes the Jest cache directory and then exits without running tests <br/>`test-storybook --clearCache` |
| `--verbose` | Display individual test results with the test suite hierarchy <br/>`test-storybook --verbose` |
@ -202,12 +203,12 @@ The test-runner renders a story and executes its [play function](../writing-stor
The test-runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle _before_ and _after_ the story is rendered.
Listed below are the available hooks and an overview of how to use them.
| Hook | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------ |
| `prepare` | Prepares the browser for tests<br/>`async prepare({ page, browserContext, testRunnerConfig }) {}` |
| `setup` | Executes once before all the tests run<br/>`setup() {}` |
| Hook | Description |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `prepare` | Prepares the browser for tests<br/>`async prepare({ page, browserContext, testRunnerConfig }) {}` |
| `setup` | Executes once before all the tests run<br/>`setup() {}` |
| `preVisit` | Executes before a story is initially visited and rendered in the browser<br/>`async preVisit(page, context) {}` |
| `postVisit` | Executes after the story is visited and fully rendered<br/>`async postVisit(page, context) {}` |
| `postVisit` | Executes after the story is visited and fully rendered<br/>`async postVisit(page, context) {}` |
<Callout variant="info" icon="💡">

View File

@ -282,6 +282,23 @@ function addStoriesEntry(mainConfig: ConfigFile, path: string, disableDocs: bool
mainConfig.setFieldValue(['stories'], [...stories, entry]);
}
// Add refs to older versions of storybook to test out composition
function addRefs(mainConfig: ConfigFile) {
const refs = mainConfig.getFieldValue(['refs']) as Record<string, string>;
mainConfig.setFieldValue(['refs'], {
...refs,
'storybook@8.0.0': {
title: 'Storybook 8.0.0',
url: 'https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/',
},
'storybook@7.6.18': {
title: 'Storybook 7.6.18',
url: 'https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/',
},
} as Record<string, any>);
}
function getStoriesFolderWithVariant(variant?: string, folder = 'stories') {
return variant ? `${folder}_${variant}` : folder;
}
@ -516,9 +533,14 @@ export const addStories: Task['run'] = async (
await writeConfig(mainConfig);
};
export const extendMain: Task['run'] = async ({ template, sandboxDir }, { disableDocs }) => {
export const extendMain: Task['run'] = async ({ template, sandboxDir, key }, { disableDocs }) => {
logger.log('📝 Extending main.js');
const mainConfig = await readMainConfig({ cwd: sandboxDir });
if (key === 'react-vite/default-ts') {
addRefs(mainConfig);
}
const templateConfig = template.modifications?.mainConfig || {};
const configToAdd = {
...templateConfig,