diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml
index d50dc7efdf27..c0e166bea164 100644
--- a/.github/workflows/tests-unit.yml
+++ b/.github/workflows/tests-unit.yml
@@ -17,6 +17,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
- name: Set node version
uses: actions/setup-node@v4
with:
diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md
index 1389f9f4a4de..217d74b01133 100644
--- a/CHANGELOG.prerelease.md
+++ b/CHANGELOG.prerelease.md
@@ -1,3 +1,63 @@
+## 8.5.0-beta.2
+
+- Addon Test: Clear coverage data when starting or watching - [#30072](https://github.com/storybookjs/storybook/pull/30072), thanks @ghengeveld!
+- Addon Test: Improve error message on missing coverage package - [#30088](https://github.com/storybookjs/storybook/pull/30088), thanks @JReinhold!
+- UI: Fix test provider event handling on startup - [#30083](https://github.com/storybookjs/storybook/pull/30083), thanks @ghengeveld!
+- UI: Keep failing stories in the sidebar, disregarding filters - [#30086](https://github.com/storybookjs/storybook/pull/30086), thanks @JReinhold!
+
+## 8.5.0-beta.1
+
+- Addon A11y: Add conditional rendering for a11y violation number in Testing Module - [#30073](https://github.com/storybookjs/storybook/pull/30073), thanks @valentinpalkovic!
+- Addon A11y: Remove warnings API - [#30049](https://github.com/storybookjs/storybook/pull/30049), thanks @kasperpeulen!
+- Addon A11y: Show errors of axe properly - [#30050](https://github.com/storybookjs/storybook/pull/30050), thanks @kasperpeulen!
+- Addon Test: Fix printing null% for coverage - [#30061](https://github.com/storybookjs/storybook/pull/30061), thanks @ghengeveld!
+- Telemetry: Add metadata distinguishing "apps" from "design systems" - [#30070](https://github.com/storybookjs/storybook/pull/30070), thanks @tmeasday!
+
+## 8.5.0-beta.0
+
+- Automigration: Improve setup file transformation and version range handling for a11y migration - [#30060](https://github.com/storybookjs/storybook/pull/30060), thanks @valentinpalkovic!
+- Next.js: Support v15.1.1 - [#30068](https://github.com/storybookjs/storybook/pull/30068), thanks @valentinpalkovic!
+
+## 8.5.0-alpha.22
+
+- Addon Docs: Dynamically import rehype - [#29544](https://github.com/storybookjs/storybook/pull/29544), thanks @valentinpalkovic!
+- Addon Test: Fix duplicate `test.include` patterns - [#30029](https://github.com/storybookjs/storybook/pull/30029), thanks @JReinhold!
+- Addon Test: Fix environment variable for Vitest Storybook integration - [#30054](https://github.com/storybookjs/storybook/pull/30054), thanks @valentinpalkovic!
+- Addon Test: Use local storybook binary instead - [#30021](https://github.com/storybookjs/storybook/pull/30021), thanks @kasperpeulen!
+- Addon Test: Wait for 2 seconds before showing result mismatch warning - [#30002](https://github.com/storybookjs/storybook/pull/30002), thanks @ghengeveld!
+- Angular: Support statsJson in angular schemas - [#29233](https://github.com/storybookjs/storybook/pull/29233), thanks @yannbf!
+- Core: Fix `scrollIntoView` behavior and reimplement testing module time rendering - [#30044](https://github.com/storybookjs/storybook/pull/30044), thanks @ghengeveld!
+- Docs: Add code snippet to addons panel - [#29253](https://github.com/storybookjs/storybook/pull/29253), thanks @larsrickert!
+- Next.js: Fix webpack fsCache not working - [#29654](https://github.com/storybookjs/storybook/pull/29654), thanks @sentience!
+- Nextjs-Vite: Add TS docgen support - [#29824](https://github.com/storybookjs/storybook/pull/29824), thanks @yannbf!
+- Nextjs-Vite: Fix docgen types in main config - [#30042](https://github.com/storybookjs/storybook/pull/30042), thanks @yannbf!
+- React: Fix RSC compatibility with addon-themes and hooks - [#26243](https://github.com/storybookjs/storybook/pull/26243), thanks @shilman!
+- UI: Fix controls and parameters on tag-filtered stories - [#30038](https://github.com/storybookjs/storybook/pull/30038), thanks @shilman!
+
+## 8.5.0-alpha.21
+
+- Addon A11y: Add typesVersions support for TypeScript definitions in a11y package - [#30005](https://github.com/storybookjs/storybook/pull/30005), thanks @valentinpalkovic!
+- Addon A11y: Refactor environment variable handling for Vitest integration - [#30022](https://github.com/storybookjs/storybook/pull/30022), thanks @valentinpalkovic!
+- Addon A11y: Run the a11y automigration on postInstall - [#30004](https://github.com/storybookjs/storybook/pull/30004), thanks @kasperpeulen!
+- Addon A11y: Update accessibility status handling in TestProviderRender - [#30027](https://github.com/storybookjs/storybook/pull/30027), thanks @valentinpalkovic!
+- Addon Onboarding: Prebundle react-confetti - [#29996](https://github.com/storybookjs/storybook/pull/29996), thanks @yannbf!
+- Addon Test: Correctly stop Storybook when Vitest closes - [#30012](https://github.com/storybookjs/storybook/pull/30012), thanks @JReinhold!
+- Addon Test: Show sub test provider toggle state in main testing module - [#30019](https://github.com/storybookjs/storybook/pull/30019), thanks @ghengeveld!
+- Addon Test: Wrap sub-paths exported with `require.resolve` - [#30026](https://github.com/storybookjs/storybook/pull/30026), thanks @ndelangen!
+- Core: Fix bundling of React - [#30003](https://github.com/storybookjs/storybook/pull/30003), thanks @yannbf!
+
+## 8.5.0-alpha.20
+
+- Addon Test: Add `@vitest/coverage-v8` during postinstall if no coverage reporter is installed - [#29993](https://github.com/storybookjs/storybook/pull/29993), thanks @ghengeveld!
+- Addon Test: Add support for previewHead - [#29808](https://github.com/storybookjs/storybook/pull/29808), thanks @ndelangen!
+- Addon Test: Always run Vitest in watch mode internally - [#29749](https://github.com/storybookjs/storybook/pull/29749), thanks @JReinhold!
+- Addon Test: Filter out falsy test results in TestProviderRender - [#30001](https://github.com/storybookjs/storybook/pull/30001), thanks @valentinpalkovic!
+- Addon Test: Handle undefined storyId - [#29998](https://github.com/storybookjs/storybook/pull/29998), thanks @ghengeveld!
+- Addon Test: Make component tests status row link to the story's tests panel - [#29992](https://github.com/storybookjs/storybook/pull/29992), thanks @ghengeveld!
+- Addon Test: Merge viteFinal config into vitest config - [#29806](https://github.com/storybookjs/storybook/pull/29806), thanks @ndelangen!
+- Addon Test: Prompt switch to `experimental-nextjs-vite` - [#29814](https://github.com/storybookjs/storybook/pull/29814), thanks @ndelangen!
+- Addon Test: Use ProgressSpinner for stop button in Testing Module - [#29997](https://github.com/storybookjs/storybook/pull/29997), thanks @ghengeveld!
+
## 8.5.0-alpha.19
- Addon A11y: Create a11y test provider and revamp a11y addon - [#29643](https://github.com/storybookjs/storybook/pull/29643), thanks @valentinpalkovic!
diff --git a/MIGRATION.md b/MIGRATION.md
index fd3ce9e10be5..28b4bc5d6ddc 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,6 +1,7 @@
Migration
- [From version 8.4.x to 8.5.x](#from-version-84x-to-85x)
+ - [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)
@@ -425,14 +426,32 @@
## From version 8.4.x to 8.5.x
+### Added source code panel to docs
+
+Starting in 8.5, Storybook Docs (`@storybook/addon-docs`) automatically adds a new addon panel to stories that displays a source snippet beneath each story. This works similarly to the existing [source snippet doc block](https://storybook.js.org/docs/writing-docs/doc-blocks#source), but in the story view. It is intended to replace the [Storysource addon](https://storybook.js.org/addons/@storybook/addon-storysource).
+
+If you wish to disable this panel globally, add the following line to your `.storybook/preview.js` project configuration. You can also selectively disable/enable at the story level.
+
+```js
+export default {
+ parameters: {
+ docs: {
+ codePanel: false,
+ },
+ },
+};
+```
+
### Addon-a11y: Component test integration
-In Storybook 8.4, we introduced a new addon called [addon test](https://storybook.js.org/docs/writing-tests/test-addon). Powered by Vitest under the hood, this addon lets you watch, run, and debug your component tests directly in Storybook.
+In Storybook 8.4, we introduced the [Test addon](https://storybook.js.org/docs/writing-tests/test-addon) (`@storybook/experimental-addon-test`). Powered by Vitest under the hood, this addon lets you watch, run, and debug your component tests directly in Storybook.
+
+In Storybook 8.5, we revamped the [Accessibility addon](https://storybook.js.org/docs/writing-tests/accessibility-testing) (`@storybook/addon-a11y`) to integrate it with the component tests feature. This means you can now extend your component tests to include accessibility tests.
-In Storybook 8.5, we revamped the Accessibility addon (`@storybook/addon-a11y`) to integrate it with the component tests feature. This means you can now extend your component tests to include accessibility tests. If you upgrade to Storybook 8.5 via `npx storybook@latest upgrade`, the Accessibility addon will be automatically configured to work with the component tests. However, if you're upgrading manually and you have the [addon test](https://storybook.js.org/docs/writing-tests/test-addon) installed, adjust your configuration as follows:
+If you upgrade to Storybook 8.5 via `npx storybook@latest upgrade`, the Accessibility addon will be automatically configured to work with the component tests. However, if you're upgrading manually and you have the Test addon installed, adjust your configuration as follows:
```diff
-// .storybook/vitest.config.ts
+// .storybook/vitest.setup.ts
...
+import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';
diff --git a/SECURITY.md b/SECURITY.md
index 123b5d5bbeac..ac75ea2100ff 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -8,6 +8,6 @@ In the event of a high-risk vulnerability, we may backport the security fixes to
## Reporting a Vulnerability
-To report a vulnerability, you can reach out to the maintainers directly on Twitter: https://twitter.com/storybookjs
+To report a vulnerability, you can reach out to the maintainers directly on Twitter: https://twitter.com/storybookjs or Bluesky: https://bsky.app/profile/storybook.js.org
When we fix a security issue, we will post a security advisory on Github/NPM, describe the change in the [release notes](https://github.com/storybookjs/storybook/releases), and also announce notify the community on [our Discord](https://discord.gg/storybook).
diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx
index 017419318b0a..23f95a0c5d5e 100644
--- a/code/.storybook/preview.tsx
+++ b/code/.storybook/preview.tsx
@@ -358,9 +358,6 @@ export const parameters = {
opacity: 0.4,
},
},
- a11y: {
- warnings: ['minor', 'moderate', 'serious', 'critical'],
- },
};
-export const tags = ['test', 'vitest'];
+export const tags = ['test', 'vitest', '!a11ytest'];
diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json
index 08d64b7d4f40..ca38d1848b6c 100644
--- a/code/addons/a11y/package.json
+++ b/code/addons/a11y/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Test component compliance with web accessibility standards",
"keywords": [
"a11y",
@@ -38,11 +38,22 @@
},
"./manager": "./dist/manager.js",
"./register": "./dist/manager.js",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./postinstall": "./dist/postinstall.js"
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "*": [
+ "dist/index.d.ts"
+ ],
+ "preview": [
+ "dist/preview.d.ts"
+ ]
+ }
+ },
"files": [
"dist/**/*",
"README.md",
@@ -64,6 +75,7 @@
"@storybook/global": "^5.0.0",
"@storybook/icons": "^1.2.12",
"@testing-library/react": "^14.0.0",
+ "execa": "^9.5.2",
"picocolors": "^1.1.0",
"pretty-format": "^29.7.0",
"react": "^18.2.0",
@@ -87,6 +99,9 @@
],
"previewEntries": [
"./src/preview.tsx"
+ ],
+ "nodeEntries": [
+ "./src/postinstall.ts"
]
},
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16",
diff --git a/code/addons/a11y/src/components/A11YPanel.tsx b/code/addons/a11y/src/components/A11YPanel.tsx
index dc048d97b941..61ea9a663af4 100644
--- a/code/addons/a11y/src/components/A11YPanel.tsx
+++ b/code/addons/a11y/src/components/A11YPanel.tsx
@@ -133,7 +133,11 @@ export const A11YPanel: React.FC = () => {
<>
The accessibility scan encountered an error.
- {typeof error === 'string' ? error : JSON.stringify(error)}
+ {typeof error === 'string'
+ ? error
+ : error instanceof Error
+ ? error.toString()
+ : JSON.stringify(error)}
>
)}
diff --git a/code/addons/a11y/src/params.ts b/code/addons/a11y/src/params.ts
index dd4357687340..e66a0813a42c 100644
--- a/code/addons/a11y/src/params.ts
+++ b/code/addons/a11y/src/params.ts
@@ -6,8 +6,6 @@ export interface Setup {
options: RunOptions;
}
-type Impact = NonNullable;
-
export interface A11yParameters {
element?: ElementContext;
config?: Spec;
@@ -15,5 +13,4 @@ export interface A11yParameters {
/** @deprecated Use globals.a11y.manual instead */
manual?: boolean;
disable?: boolean;
- warnings?: Impact[];
}
diff --git a/code/addons/a11y/src/postinstall.ts b/code/addons/a11y/src/postinstall.ts
new file mode 100644
index 000000000000..9a64796f5e1b
--- /dev/null
+++ b/code/addons/a11y/src/postinstall.ts
@@ -0,0 +1,15 @@
+import type { PostinstallOptions } from '@storybook/cli/src/add';
+
+// eslint-disable-next-line depend/ban-dependencies
+import { execa } from 'execa';
+
+const $ = execa({
+ preferLocal: true,
+ stdio: 'inherit',
+ // we stream the stderr to the console
+ reject: false,
+});
+
+export default async function postinstall(options: PostinstallOptions) {
+ await $`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`;
+}
diff --git a/code/addons/a11y/src/preview.test.tsx b/code/addons/a11y/src/preview.test.tsx
index 334f7f924aeb..d09ba89462ed 100644
--- a/code/addons/a11y/src/preview.test.tsx
+++ b/code/addons/a11y/src/preview.test.tsx
@@ -156,60 +156,6 @@ describe('afterEach', () => {
});
});
- it('should report warning status when there are only warnings', async () => {
- const context = createContext({
- parameters: {
- a11y: {
- warnings: ['minor'],
- },
- },
- });
- const result = {
- violations: [
- { impact: 'minor', nodes: [] },
- { impact: 'critical', nodes: [] },
- ],
- };
- mockedRun.mockResolvedValue(result as any);
-
- await expect(async () => experimental_afterEach(context)).rejects.toThrow();
-
- expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y);
- expect(context.reporting.addReport).toHaveBeenCalledWith({
- type: 'a11y',
- version: 1,
- result,
- status: 'failed',
- });
- });
-
- it('should report error status when there are warnings and errors', async () => {
- const context = createContext({
- parameters: {
- a11y: {
- warnings: ['minor'],
- },
- },
- });
- const result = {
- violations: [
- { impact: 'minor', nodes: [] },
- { impact: 'critical', nodes: [] },
- ],
- };
- mockedRun.mockResolvedValue(result as any);
-
- await expect(async () => experimental_afterEach(context)).rejects.toThrow();
-
- expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y);
- expect(context.reporting.addReport).toHaveBeenCalledWith({
- type: 'a11y',
- version: 1,
- result,
- status: 'failed',
- });
- });
-
it('should run accessibility checks if "a11ytest" flag is not available and is not running in Vitest', async () => {
const context = createContext({
tags: [],
diff --git a/code/addons/a11y/src/preview.tsx b/code/addons/a11y/src/preview.tsx
index f7d2f9aa43ff..e496894cb113 100644
--- a/code/addons/a11y/src/preview.tsx
+++ b/code/addons/a11y/src/preview.tsx
@@ -21,7 +21,6 @@ export const experimental_afterEach: AfterEach = async ({
}) => {
const a11yParameter: A11yParameters | undefined = parameters.a11y;
const a11yGlobals = globals.a11y;
- const warnings = a11yParameter?.warnings ?? [];
const shouldRunEnvironmentIndependent =
a11yParameter?.manual !== true &&
@@ -38,15 +37,11 @@ export const experimental_afterEach: AfterEach = async ({
if (result) {
const hasViolations = (result?.violations.length ?? 0) > 0;
- const hasErrors = result?.violations.some(
- (violation) => !warnings.includes(violation.impact!)
- );
-
reporting.addReport({
type: 'a11y',
version: 1,
result: result,
- status: hasErrors ? 'failed' : hasViolations ? 'warning' : 'passed',
+ status: hasViolations ? 'failed' : 'passed',
});
/**
@@ -58,7 +53,7 @@ export const experimental_afterEach: AfterEach = async ({
* implement proper try catch handling.
*/
if (getIsVitestStandaloneRun()) {
- if (hasErrors) {
+ if (hasViolations) {
// @ts-expect-error - todo - fix type extension of expect from @storybook/test
expect(result).toHaveNoViolations();
}
diff --git a/code/addons/a11y/src/utils.ts b/code/addons/a11y/src/utils.ts
index 0864a2e3b2f7..f0612d5dfc73 100644
--- a/code/addons/a11y/src/utils.ts
+++ b/code/addons/a11y/src/utils.ts
@@ -1,25 +1,17 @@
export function getIsVitestStandaloneRun() {
try {
- return process.env.VITEST_STORYBOOK === 'false';
- } catch {
- try {
- // @ts-expect-error Suppress TypeScript warning about wrong setting. Doesn't matter, because we don't use tsc for bundling.
- return import.meta.env.VITEST_STORYBOOK === 'false';
- } catch (e) {
- return false;
- }
+ // @ts-expect-error Suppress TypeScript warning about wrong setting. Doesn't matter, because we don't use tsc for bundling.
+ return import.meta.env.VITEST_STORYBOOK === 'false';
+ } catch (e) {
+ return false;
}
}
export function getIsVitestRunning() {
try {
- return process?.env.MODE === 'test';
- } catch {
- try {
- // @ts-expect-error Suppress TypeScript warning about wrong setting. Doesn't matter, because we don't use tsc for bundling.
- return import.meta.env.MODE === 'test';
- } catch (e) {
- return false;
- }
+ // @ts-expect-error Suppress TypeScript warning about wrong setting. Doesn't matter, because we don't use tsc for bundling.
+ return import.meta.env.MODE === 'test';
+ } catch (e) {
+ return false;
}
}
diff --git a/code/addons/actions/package.json b/code/addons/actions/package.json
index fe4159ba7501..0fd1b8a2f8c5 100644
--- a/code/addons/actions/package.json
+++ b/code/addons/actions/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [
"storybook",
diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json
index e16e07c7a3b9..8b0a68e80997 100644
--- a/code/addons/backgrounds/package.json
+++ b/code/addons/backgrounds/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Switch backgrounds to view components in different settings",
"keywords": [
"addon",
diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json
index 0eedc655d161..6076ae2b0695 100644
--- a/code/addons/controls/package.json
+++ b/code/addons/controls/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-controls",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [
"addon",
diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json
index daf9acd64148..c6edb3f14308 100644
--- a/code/addons/docs/package.json
+++ b/code/addons/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Document component usage and properties in Markdown",
"keywords": [
"addon",
@@ -71,7 +71,12 @@
"./angular": "./angular/index.js",
"./angular/index.js": "./angular/index.js",
"./web-components/index.js": "./web-components/index.js",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./manager": {
+ "types": "./dist/manager.d.ts",
+ "import": "./dist/manager.mjs",
+ "require": "./dist/manager.js"
+ }
},
"main": "dist/index.js",
"module": "dist/index.mjs",
@@ -129,7 +134,11 @@
"./src/preview.ts",
"./src/blocks.ts",
"./src/shims/mdx-react-shim.ts",
- "./src/mdx-loader.ts"
+ "./src/mdx-loader.ts",
+ "./src/manager.tsx"
+ ],
+ "managerEntries": [
+ "./src/manager.tsx"
]
},
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16",
diff --git a/code/addons/docs/src/manager.tsx b/code/addons/docs/src/manager.tsx
new file mode 100644
index 000000000000..0b84cd6e36f8
--- /dev/null
+++ b/code/addons/docs/src/manager.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import { AddonPanel, type SyntaxHighlighterFormatTypes } from 'storybook/internal/components';
+import { ADDON_ID, PANEL_ID, PARAM_KEY, SNIPPET_RENDERED } from 'storybook/internal/docs-tools';
+import { addons, types, useAddonState, useChannel } from 'storybook/internal/manager-api';
+
+import { Source } from '@storybook/blocks';
+
+addons.register(ADDON_ID, (api) => {
+ addons.add(PANEL_ID, {
+ title: 'Code',
+ type: types.PANEL,
+ paramKey: PARAM_KEY,
+ /**
+ * This code panel can be disabled by the user by adding this parameter:
+ *
+ * @example
+ *
+ * ```ts
+ * parameters: {
+ * docs: {
+ * codePanel: false,
+ * },
+ * },
+ * ```
+ */
+ disabled: (parameters) => {
+ return (
+ !!parameters &&
+ typeof parameters[PARAM_KEY] === 'object' &&
+ parameters[PARAM_KEY].codePanel === false
+ );
+ },
+ match: ({ viewMode }) => viewMode === 'story',
+ render: ({ active }) => {
+ const [codeSnippet, setSourceCode] = useAddonState<{
+ source: string;
+ format: SyntaxHighlighterFormatTypes;
+ }>(ADDON_ID, {
+ source: '',
+ format: 'html',
+ });
+
+ useChannel({
+ [SNIPPET_RENDERED]: ({ source, format }) => {
+ setSourceCode({ source, format });
+ },
+ });
+
+ return (
+
+
+
+ );
+ },
+ });
+});
diff --git a/code/addons/docs/src/plugins/mdx-plugin.ts b/code/addons/docs/src/plugins/mdx-plugin.ts
index 40d8e88c3f3a..36fe3b691e97 100644
--- a/code/addons/docs/src/plugins/mdx-plugin.ts
+++ b/code/addons/docs/src/plugins/mdx-plugin.ts
@@ -3,8 +3,6 @@ import { dirname, join } from 'node:path';
import type { Options } from 'storybook/internal/types';
import { createFilter } from '@rollup/pluginutils';
-import rehypeExternalLinks from 'rehype-external-links';
-import rehypeSlug from 'rehype-slug';
import type { Plugin } from 'vite';
import type { CompileOptions } from '../compiler';
@@ -24,6 +22,9 @@ export async function mdxPlugin(options: Options): Promise {
const presetOptions = await presets.apply>('options', {});
const mdxPluginOptions = presetOptions?.mdxPluginOptions as CompileOptions;
+ const rehypeSlug = (await import('rehype-slug')).default;
+ const rehypeExternalLinks = (await import('rehype-external-links')).default;
+
return {
name: 'storybook:mdx-plugin',
enforce: 'pre',
diff --git a/code/addons/docs/src/preset.ts b/code/addons/docs/src/preset.ts
index e418b5e2e8aa..9cff707e8472 100644
--- a/code/addons/docs/src/preset.ts
+++ b/code/addons/docs/src/preset.ts
@@ -5,9 +5,6 @@ import type { DocsOptions, Options, PresetProperty } from 'storybook/internal/ty
import type { CsfPluginOptions } from '@storybook/csf-plugin';
-import rehypeExternalLinks from 'rehype-external-links';
-import rehypeSlug from 'rehype-slug';
-
import type { CompileOptions } from './compiler';
/**
@@ -42,6 +39,9 @@ async function webpack(
const { csfPluginOptions = {}, mdxPluginOptions = {} } = options;
+ const rehypeSlug = (await import('rehype-slug')).default;
+ const rehypeExternalLinks = (await import('rehype-external-links')).default;
+
const mdxLoaderOptions: CompileOptions = await options.presets.apply('mdxLoaderOptions', {
...mdxPluginOptions,
mdxCompileOptions: {
@@ -175,6 +175,9 @@ export const viteFinal = async (config: any, options: Options) => {
const { plugins = [] } = config;
const { mdxPlugin } = await import('./plugins/mdx-plugin');
+ const rehypeSlug = (await import('rehype-slug')).default;
+ const rehypeExternalLinks = (await import('rehype-external-links')).default;
+
// Use the resolvedReact preset to alias react and react-dom to either the users version or the version shipped with addon-docs
const { react, reactDom, mdx } = await getResolvedReact(options);
diff --git a/code/addons/docs/template/stories/sourcePanel/index.stories.tsx b/code/addons/docs/template/stories/sourcePanel/index.stories.tsx
new file mode 100644
index 000000000000..9958096cb815
--- /dev/null
+++ b/code/addons/docs/template/stories/sourcePanel/index.stories.tsx
@@ -0,0 +1,23 @@
+export default {
+ component: globalThis.Components.Button,
+ tags: ['autodocs'],
+ parameters: {
+ chromatic: { disable: true },
+ docs: {
+ codePanel: false,
+ },
+ },
+};
+
+export const One = { args: { label: 'One' } };
+
+export const Two = { args: { label: 'Two' } };
+
+export const WithSource = {
+ args: { label: 'Three' },
+ parameters: {
+ docs: {
+ codePanel: true,
+ },
+ },
+};
diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json
index 23bad764705f..7bb91e8bb960 100644
--- a/code/addons/essentials/package.json
+++ b/code/addons/essentials/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-essentials",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Curated addons to bring out the best of Storybook",
"keywords": [
"addon",
@@ -40,6 +40,7 @@
},
"./backgrounds/manager": "./dist/backgrounds/manager.js",
"./controls/manager": "./dist/controls/manager.js",
+ "./docs/manager": "./dist/docs/manager.js",
"./docs/preview": {
"types": "./dist/docs/preview.d.ts",
"import": "./dist/docs/preview.mjs",
@@ -114,10 +115,14 @@
"./src/docs/preset.ts",
"./src/docs/mdx-react-shim.ts"
],
+ "entries": [
+ "./src/docs/manager.ts"
+ ],
"managerEntries": [
"./src/actions/manager.ts",
"./src/backgrounds/manager.ts",
"./src/controls/manager.ts",
+ "./src/docs/manager.ts",
"./src/measure/manager.ts",
"./src/outline/manager.ts",
"./src/toolbars/manager.ts",
diff --git a/code/addons/essentials/src/docs/manager.ts b/code/addons/essentials/src/docs/manager.ts
new file mode 100644
index 000000000000..6101f7d79261
--- /dev/null
+++ b/code/addons/essentials/src/docs/manager.ts
@@ -0,0 +1,2 @@
+// @ts-expect-error (no types needed for this)
+export * from '@storybook/addon-docs/manager';
diff --git a/code/addons/essentials/src/index.ts b/code/addons/essentials/src/index.ts
index 5809420bc1b8..a72554227ba2 100644
--- a/code/addons/essentials/src/index.ts
+++ b/code/addons/essentials/src/index.ts
@@ -88,9 +88,9 @@ export function addons(options: PresetOptions) {
// NOTE: The order of these addons is important.
return [
- 'docs',
'controls',
'actions',
+ 'docs',
'backgrounds',
'viewport',
'toolbars',
diff --git a/code/addons/gfm/package.json b/code/addons/gfm/package.json
index 8641c5a7f4ef..79438d40133c 100644
--- a/code/addons/gfm/package.json
+++ b/code/addons/gfm/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-mdx-gfm",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "GitHub Flavored Markdown in Storybook",
"keywords": [
"addon",
diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json
index f78228db1657..4f9a269b034c 100644
--- a/code/addons/highlight/package.json
+++ b/code/addons/highlight/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-highlight",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Highlight DOM nodes within your stories",
"keywords": [
"storybook-addons",
diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json
index f8123e69278a..86cf84e46183 100644
--- a/code/addons/interactions/package.json
+++ b/code/addons/interactions/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-interactions",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Automate, test and debug user interactions",
"keywords": [
"storybook-addons",
diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json
index 9794ab8acede..d80862cf4d59 100644
--- a/code/addons/jest/package.json
+++ b/code/addons/jest/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",
diff --git a/code/addons/links/package.json b/code/addons/links/package.json
index 18de0fcea3ab..cdb3a91bb48e 100644
--- a/code/addons/links/package.json
+++ b/code/addons/links/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [
"storybook-addons",
diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json
index 181306c74e48..4e6cfa9efb7a 100644
--- a/code/addons/measure/package.json
+++ b/code/addons/measure/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-measure",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Inspect layouts by visualizing the box model",
"keywords": [
"storybook-addons",
diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json
index ce9dbd9ee074..0a92fbc89396 100644
--- a/code/addons/onboarding/package.json
+++ b/code/addons/onboarding/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-onboarding",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Addon Onboarding - Introduces a new onboarding experience",
"keywords": [
"storybook-addons",
diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json
index 42eb08c6be81..6ea09d403517 100644
--- a/code/addons/outline/package.json
+++ b/code/addons/outline/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-outline",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [
"storybook-addons",
diff --git a/code/addons/storysource/package.json b/code/addons/storysource/package.json
index e8a04d1edff9..6f016e3e02a5 100644
--- a/code/addons/storysource/package.json
+++ b/code/addons/storysource/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "View a story’s source code to see how it works and paste into your app",
"keywords": [
"addon",
diff --git a/code/addons/test/package.json b/code/addons/test/package.json
index 1652327a9801..94246c0704de 100644
--- a/code/addons/test/package.json
+++ b/code/addons/test/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/experimental-addon-test",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Integrate Vitest with Storybook",
"keywords": [
"storybook-addons",
@@ -85,7 +85,6 @@
"@storybook/icons": "^1.2.12",
"@storybook/instrumenter": "workspace:*",
"@storybook/test": "workspace:*",
- "@storybook/theming": "workspace:*",
"polished": "^4.2.2",
"prompts": "^2.4.0",
"ts-dedent": "^2.2.0"
@@ -113,6 +112,7 @@
"slash": "^5.0.0",
"strip-ansi": "^7.1.0",
"tinyglobby": "^0.2.10",
+ "tree-kill": "^1.2.2",
"ts-dedent": "^2.2.0",
"typescript": "^5.3.2",
"vitest": "^2.1.3"
diff --git a/code/addons/test/src/components/ContextMenuItem.tsx b/code/addons/test/src/components/ContextMenuItem.tsx
index 52c3b98061f9..229a396b5388 100644
--- a/code/addons/test/src/components/ContextMenuItem.tsx
+++ b/code/addons/test/src/components/ContextMenuItem.tsx
@@ -67,7 +67,7 @@ export const ContextMenuItem: FC<{
padding="small"
disabled={state.crashed || isDisabled}
>
-
+
}
/>
diff --git a/code/addons/test/src/components/Description.tsx b/code/addons/test/src/components/Description.tsx
index a94b775b5c95..58a80dbfdccc 100644
--- a/code/addons/test/src/components/Description.tsx
+++ b/code/addons/test/src/components/Description.tsx
@@ -12,7 +12,7 @@ export const Wrapper = styled.div(({ theme }) => ({
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
fontSize: theme.typography.size.s1,
- color: theme.barTextColor,
+ color: theme.textMutedColor,
}));
const PositiveText = styled.span(({ theme }) => ({
@@ -60,10 +60,10 @@ export function Description({ state, ...props }: DescriptionProps) {
);
} else if (state.progress?.finishedAt) {
description = (
-
+ <>
+ Ran {state.progress.numTotalTests} {state.progress.numTotalTests === 1 ? 'test' : 'tests'}{' '}
+
+ >
);
} else if (state.watching) {
description = 'Watching for file changes';
diff --git a/code/addons/test/src/components/InteractionsPanel.stories.tsx b/code/addons/test/src/components/InteractionsPanel.stories.tsx
index 24eefed2028c..f8cabbe24d53 100644
--- a/code/addons/test/src/components/InteractionsPanel.stories.tsx
+++ b/code/addons/test/src/components/InteractionsPanel.stories.tsx
@@ -58,7 +58,6 @@ const meta = {
endRef: null,
// prop for the AddonPanel used as wrapper of Panel
active: true,
- storyId: 'story-id',
},
} as Meta;
diff --git a/code/addons/test/src/components/InteractionsPanel.tsx b/code/addons/test/src/components/InteractionsPanel.tsx
index 164e28b782fb..896350b926c7 100644
--- a/code/addons/test/src/components/InteractionsPanel.tsx
+++ b/code/addons/test/src/components/InteractionsPanel.tsx
@@ -44,8 +44,6 @@ interface InteractionsPanelProps {
onScrollToEnd?: () => void;
hasResultMismatch?: boolean;
browserTestStatus?: CallStates;
- storyId: StoryId;
- testRunId: string;
}
const Container = styled.div(({ theme }) => ({
@@ -105,20 +103,12 @@ export const InteractionsPanel: React.FC = React.memo(
endRef,
hasResultMismatch,
browserTestStatus,
- storyId,
- testRunId,
}) {
const filter = useAnsiToHtmlFilter();
return (
- {hasResultMismatch && (
-
- )}
+ {hasResultMismatch && }
{(interactions.length > 0 || hasException) && (
(function PanelMemoized({ storyId
// local state
const [scrollTarget, setScrollTarget] = useState(undefined);
const [collapsed, setCollapsed] = useState>(new Set());
+ const [hasResultMismatch, setResultMismatch] = useState(false);
const {
controlStates = INITIAL_CONTROL_STATES,
@@ -226,7 +227,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
interactionsCount: list.filter(({ method }) => method !== 'step').length,
};
});
- }, [collapsed]);
+ }, [set, collapsed]);
const controls = useMemo(
() => ({
@@ -239,7 +240,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
emit(FORCE_REMOUNT, { storyId });
},
}),
- [storyId]
+ [emit, storyId]
);
const storyFilePath = useParameter('fileName', '');
@@ -252,22 +253,48 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
interactions.some((v) => v.status === CallStates.ERROR);
const storyStatus = storyStatuses[storyId]?.[TEST_PROVIDER_ID];
+ const storyTestStatus = storyStatus?.status;
- const browserTestStatus = React.useMemo(() => {
+ const browserTestStatus = useMemo(() => {
if (!isPlaying && (interactions.length > 0 || hasException)) {
return hasException ? CallStates.ERROR : CallStates.DONE;
}
return isPlaying ? CallStates.ACTIVE : null;
}, [isPlaying, interactions, hasException]);
- const hasResultMismatch = React.useMemo(() => {
- return (
- browserTestStatus !== null &&
- browserTestStatus !== CallStates.ACTIVE &&
- storyStatus?.status !== undefined &&
- statusMap[browserTestStatus] !== storyStatus.status
- );
- }, [browserTestStatus, storyStatus]);
+ const { testRunId } = storyStatus?.data || {};
+
+ useEffect(() => {
+ const isMismatch =
+ browserTestStatus &&
+ storyTestStatus &&
+ storyTestStatus !== 'pending' &&
+ storyTestStatus !== statusMap[browserTestStatus];
+
+ if (isMismatch) {
+ const timeout = setTimeout(
+ () =>
+ setResultMismatch((currentValue) => {
+ if (!currentValue) {
+ emit(STORYBOOK_ADDON_TEST_CHANNEL, {
+ type: 'test-discrepancy',
+ payload: {
+ browserStatus: browserTestStatus === CallStates.DONE ? 'PASS' : 'FAIL',
+ cliStatus: browserTestStatus === CallStates.DONE ? 'FAIL' : 'PASS',
+ storyId,
+ testRunId,
+ },
+ });
+ }
+ return true;
+ }),
+ 2000
+ );
+ return () => clearTimeout(timeout);
+ } else {
+ setResultMismatch(false);
+ }
+ }, [emit, browserTestStatus, storyTestStatus, storyId, testRunId]);
if (isErrored) {
return ;
@@ -290,8 +317,6 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
pausedAt={pausedAt}
endRef={endRef}
onScrollToEnd={scrollTarget && scrollToTarget}
- storyId={storyId}
- testRunId={storyStatus?.data?.testRunId}
/>
);
diff --git a/code/addons/test/src/components/RelativeTime.stories.tsx b/code/addons/test/src/components/RelativeTime.stories.tsx
new file mode 100644
index 000000000000..4d3c6af0f6d8
--- /dev/null
+++ b/code/addons/test/src/components/RelativeTime.stories.tsx
@@ -0,0 +1,47 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { RelativeTime } from './RelativeTime';
+
+const meta = {
+ component: RelativeTime,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const JustNow: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 10,
+ },
+};
+
+export const AMinuteAgo: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 60,
+ },
+};
+
+export const MinutesAgo: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 60 * 2,
+ },
+};
+
+export const HoursAgo: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 60 * 60 * 3,
+ },
+};
+
+export const Yesterday: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 60 * 60 * 24,
+ },
+};
+
+export const DaysAgo: Story = {
+ args: {
+ timestamp: Date.now() - 1000 * 60 * 60 * 24 * 3,
+ },
+};
diff --git a/code/addons/test/src/components/RelativeTime.tsx b/code/addons/test/src/components/RelativeTime.tsx
index fa9e7cf6d549..9cb1df1b1b66 100644
--- a/code/addons/test/src/components/RelativeTime.tsx
+++ b/code/addons/test/src/components/RelativeTime.tsx
@@ -1,41 +1,35 @@
import { useEffect, useState } from 'react';
-export function getRelativeTimeString(date: Date): string {
- const delta = Math.round((date.getTime() - Date.now()) / 1000);
- const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
- const units: Intl.RelativeTimeFormatUnit[] = [
- 'second',
- 'minute',
- 'hour',
- 'day',
- 'week',
- 'month',
- 'year',
- ];
-
- const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(delta));
- const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
- const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
- return rtf.format(Math.floor(delta / divisor), units[unitIndex]);
-}
-
-export const RelativeTime = ({ timestamp, testCount }: { timestamp: Date; testCount: number }) => {
- const [relativeTimeString, setRelativeTimeString] = useState(null);
+export const RelativeTime = ({ timestamp }: { timestamp?: number }) => {
+ const [timeAgo, setTimeAgo] = useState(null);
useEffect(() => {
if (timestamp) {
- setRelativeTimeString(getRelativeTimeString(timestamp).replace(/^now$/, 'just now'));
-
- const interval = setInterval(() => {
- setRelativeTimeString(getRelativeTimeString(timestamp).replace(/^now$/, 'just now'));
- }, 10000);
-
+ setTimeAgo(Date.now() - timestamp);
+ const interval = setInterval(() => setTimeAgo(Date.now() - timestamp), 10000);
return () => clearInterval(interval);
}
}, [timestamp]);
- return (
- relativeTimeString &&
- `Ran ${testCount} ${testCount === 1 ? 'test' : 'tests'} ${relativeTimeString}`
- );
+ if (timeAgo === null) {
+ return null;
+ }
+
+ const seconds = Math.round(timeAgo / 1000);
+ if (seconds < 60) {
+ return `just now`;
+ }
+
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) {
+ return minutes === 1 ? `a minute ago` : `${minutes} minutes ago`;
+ }
+
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) {
+ return hours === 1 ? `an hour ago` : `${hours} hours ago`;
+ }
+
+ const days = Math.floor(hours / 24);
+ return days === 1 ? `yesterday` : `${days} days ago`;
};
diff --git a/code/addons/test/src/components/TestDiscrepancyMessage.stories.tsx b/code/addons/test/src/components/TestDiscrepancyMessage.stories.tsx
index 840c08cdf3d3..81553cdc5a32 100644
--- a/code/addons/test/src/components/TestDiscrepancyMessage.stories.tsx
+++ b/code/addons/test/src/components/TestDiscrepancyMessage.stories.tsx
@@ -23,9 +23,6 @@ export default {
parameters: {
layout: 'fullscreen',
},
- args: {
- storyId: 'story-id',
- },
decorators: [
(storyFn) => (
{storyFn()}
diff --git a/code/addons/test/src/components/TestDiscrepancyMessage.tsx b/code/addons/test/src/components/TestDiscrepancyMessage.tsx
index bbdf74e36a65..b23af4a7be6a 100644
--- a/code/addons/test/src/components/TestDiscrepancyMessage.tsx
+++ b/code/addons/test/src/components/TestDiscrepancyMessage.tsx
@@ -33,39 +33,23 @@ const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({
interface TestDiscrepancyMessageProps {
browserTestStatus: CallStates;
- storyId: StoryId;
- testRunId: string;
}
-export const TestDiscrepancyMessage = ({
- browserTestStatus,
- storyId,
- testRunId,
-}: TestDiscrepancyMessageProps) => {
+
+export const TestDiscrepancyMessage = ({ browserTestStatus }: TestDiscrepancyMessageProps) => {
const api = useStorybookApi();
const docsUrl = api.getDocsUrl({
subpath: DOCUMENTATION_DISCREPANCY_LINK,
versioned: true,
renderer: true,
});
- const message = `This component test passed in ${browserTestStatus === CallStates.DONE ? 'this browser' : 'CLI'}, but the tests failed in ${browserTestStatus === CallStates.ERROR ? 'this browser' : 'CLI'}.`;
-
- useEffect(
- () =>
- api.emit(STORYBOOK_ADDON_TEST_CHANNEL, {
- type: 'test-discrepancy',
- payload: {
- browserStatus: browserTestStatus === CallStates.DONE ? 'PASS' : 'FAIL',
- cliStatus: browserTestStatus === CallStates.DONE ? 'FAIL' : 'PASS',
- storyId,
- testRunId,
- },
- }),
- [api, browserTestStatus, storyId, testRunId]
- );
+ const [passed, failed] =
+ browserTestStatus === CallStates.ERROR
+ ? ['the CLI', 'this browser']
+ : ['this browser', 'the CLI'];
return (
- {message}{' '}
+ This component test passed in {passed}, but the tests failed in {failed}.{' '}
Learn what could cause this
diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx
index d9d68aef04d1..e04772447c43 100644
--- a/code/addons/test/src/components/TestProviderRender.tsx
+++ b/code/addons/test/src/components/TestProviderRender.tsx
@@ -76,6 +76,16 @@ const StopIcon = styled(StopAltIcon)({
width: 10,
});
+const ItemTitle = styled.span<{ enabled?: boolean }>(
+ ({ enabled, theme }) =>
+ !enabled && {
+ color: theme.textMutedColor,
+ '&:after': {
+ content: '" (disabled)"',
+ },
+ }
+);
+
const statusOrder: TestStatus[] = ['failed', 'warning', 'pending', 'passed', 'skipped'];
const statusMap: Record['status']> = {
failed: 'negative',
@@ -104,6 +114,8 @@ export const TestProviderRender: FC<
state.config || { a11y: false, coverage: false }
);
+ const isStoryEntry = entryId?.includes('--') ?? false;
+
const a11yResults = useMemo(() => {
if (!isA11yAddon) {
return [];
@@ -111,12 +123,17 @@ export const TestProviderRender: FC<
return state.details?.testResults?.flatMap((result) =>
result.results
+ .filter(Boolean)
.filter((r) => !entryId || r.storyId === entryId || r.storyId?.startsWith(`${entryId}-`))
.map((r) => r.reports.find((report) => report.type === 'a11y'))
);
}, [isA11yAddon, state.details?.testResults, entryId]);
const a11yStatus = useMemo<'positive' | 'warning' | 'negative' | 'unknown'>(() => {
+ if (state.running) {
+ return 'unknown';
+ }
+
if (!isA11yAddon || config.a11y === false) {
return 'unknown';
}
@@ -135,13 +152,13 @@ export const TestProviderRender: FC<
}
return 'positive';
- }, [a11yResults, isA11yAddon, config.a11y]);
+ }, [state.running, isA11yAddon, config.a11y, a11yResults]);
const a11yNotPassedAmount = a11yResults?.filter(
(result) => result?.status === 'failed' || result?.status === 'warning'
).length;
- const storyId = entryId?.includes('--') ? entryId : undefined;
+ const storyId = isStoryEntry ? entryId : undefined;
const results = (state.details?.testResults || [])
.flatMap((test) => {
if (!entryId) {
@@ -153,7 +170,11 @@ export const TestProviderRender: FC<
})
.sort((a, b) => statusOrder.indexOf(a.status) - statusOrder.indexOf(b.status));
- const status = (state.failed ? 'failed' : results[0]?.status) || 'unknown';
+ const status = state.running
+ ? 'unknown'
+ : state.failed
+ ? 'failed'
+ : (results[0]?.status ?? 'unknown');
const openPanel = (id: string, panelId: string) => {
api.selectStory(id);
@@ -232,7 +253,7 @@ export const TestProviderRender: FC<
/>
Coverage}
icon={}
right={
Accessibility}
icon={}
right={
{coverageSummary ? (
Coverage}
href={'/coverage/index.html'}
// @ts-expect-error ListItem doesn't include all anchor attributes in types, but it is an achor element
target="_blank"
+ aria-label="Open coverage report"
icon={
}
- right={`${coverageSummary.percentage}%`}
+ right={
+ coverageSummary.percentage ? (
+
+ {coverageSummary.percentage} %
+
+ ) : null
+ }
/>
) : (
Coverage}
icon={}
/>
)}
{isA11yAddon && (
Accessibility}
onClick={
(a11yStatus === 'negative' || a11yStatus === 'warning') && a11yResults.length
? () => {
@@ -321,7 +349,7 @@ export const TestProviderRender: FC<
: null
}
icon={}
- right={a11yNotPassedAmount || null}
+ right={isStoryEntry ? null : a11yNotPassedAmount || null}
/>
)}
diff --git a/code/addons/test/src/manager.tsx b/code/addons/test/src/manager.tsx
index 4db0c5defcbd..26a8c8f42cd7 100644
--- a/code/addons/test/src/manager.tsx
+++ b/code/addons/test/src/manager.tsx
@@ -73,64 +73,75 @@ addons.register(ADDON_ID, (api) => {
},
stateUpdater: (state, update) => {
- if (!update.details?.testResults) {
- return;
+ const updated = {
+ ...state,
+ ...update,
+ details: { ...state.details, ...update.details },
+ };
+
+ if ((!state.running && update.running) || (!state.watching && update.watching)) {
+ // Clear coverage data when starting test run or enabling watch mode
+ delete updated.details.coverageSummary;
}
- (async () => {
- await api.experimental_updateStatus(
- TEST_PROVIDER_ID,
- Object.fromEntries(
- update.details.testResults.flatMap((testResult) =>
- testResult.results
- .filter(({ storyId }) => storyId)
- .map(({ storyId, status, testRunId, ...rest }) => [
- storyId,
- {
- title: 'Component tests',
- status: statusMap[status],
- description:
- 'failureMessages' in rest && rest.failureMessages
- ? rest.failureMessages.join('\n')
- : '',
- data: { testRunId },
- onClick: openTestsPanel,
- sidebarContextMenu: false,
- } satisfies API_StatusObject,
- ])
+ if (update.details?.testResults) {
+ (async () => {
+ await api.experimental_updateStatus(
+ TEST_PROVIDER_ID,
+ Object.fromEntries(
+ update.details.testResults.flatMap((testResult) =>
+ testResult.results
+ .filter(({ storyId }) => storyId)
+ .map(({ storyId, status, testRunId, ...rest }) => [
+ storyId,
+ {
+ title: 'Component tests',
+ status: statusMap[status],
+ description:
+ 'failureMessages' in rest && rest.failureMessages
+ ? rest.failureMessages.join('\n')
+ : '',
+ data: { testRunId },
+ onClick: openTestsPanel,
+ sidebarContextMenu: false,
+ } satisfies API_StatusObject,
+ ])
+ )
)
- )
- );
+ );
- await api.experimental_updateStatus(
- 'storybook/addon-a11y/test-provider',
- Object.fromEntries(
- update.details.testResults.flatMap((testResult) =>
- testResult.results
- .filter(({ storyId }) => storyId)
- .map(({ storyId, testRunId, reports }) => {
- const a11yReport = reports.find((r: any) => r.type === 'a11y');
- return [
- storyId,
- a11yReport
- ? ({
- title: 'Accessibility tests',
- description: '',
- status: statusMap[a11yReport.status],
- data: { testRunId },
- onClick: () => {
- api.setSelectedPanel('storybook/a11y/panel');
- api.togglePanel(true);
- },
- sidebarContextMenu: false,
- } satisfies API_StatusObject)
- : null,
- ];
- })
+ await api.experimental_updateStatus(
+ 'storybook/addon-a11y/test-provider',
+ Object.fromEntries(
+ update.details.testResults.flatMap((testResult) =>
+ testResult.results
+ .filter(({ storyId }) => storyId)
+ .map(({ storyId, testRunId, reports }) => {
+ const a11yReport = reports.find((r: any) => r.type === 'a11y');
+ return [
+ storyId,
+ a11yReport
+ ? ({
+ title: 'Accessibility tests',
+ description: '',
+ status: statusMap[a11yReport.status],
+ data: { testRunId },
+ onClick: () => {
+ api.setSelectedPanel('storybook/a11y/panel');
+ api.togglePanel(true);
+ },
+ sidebarContextMenu: false,
+ } satisfies API_StatusObject)
+ : null,
+ ];
+ })
+ )
)
- )
- );
- })();
+ );
+ })();
+ }
+
+ return updated;
},
} as Addon_TestProviderType);
}
diff --git a/code/addons/test/src/node/coverage-reporter.ts b/code/addons/test/src/node/coverage-reporter.ts
index 452643cd9d60..cc1dd548000f 100644
--- a/code/addons/test/src/node/coverage-reporter.ts
+++ b/code/addons/test/src/node/coverage-reporter.ts
@@ -8,7 +8,7 @@ import type { TestManager } from './test-manager';
export type StorybookCoverageReporterOptions = {
testManager: TestManager;
- coverageOptions: ResolvedCoverageOptions<'v8'>;
+ coverageOptions: ResolvedCoverageOptions<'v8'> | undefined;
};
export default class StorybookCoverageReporter extends ReportBase implements Partial {
@@ -32,7 +32,7 @@ export default class StorybookCoverageReporter extends ReportBase implements Par
// Fallback to Vitest's default watermarks https://vitest.dev/config/#coverage-watermarks
const [lowWatermark = 50, highWatermark = 80] =
- this.#coverageOptions.watermarks?.statements ?? [];
+ this.#coverageOptions?.watermarks?.statements ?? [];
const coverageDetails: Details['coverageSummary'] = {
percentage,
diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts
index 4770f2b5a174..1595c2da3fa9 100644
--- a/code/addons/test/src/node/test-manager.ts
+++ b/code/addons/test/src/node/test-manager.ts
@@ -53,14 +53,7 @@ export class TestManager {
coverage: this.coverage,
});
} catch (e) {
- const isV8 = e.message?.includes('@vitest/coverage-v8');
- const isIstanbul = e.message?.includes('@vitest/coverage-istanbul');
-
- if (e.message?.includes('Error: Failed to load url') && (isIstanbul || isV8)) {
- const coveragePackage = isIstanbul ? 'coverage-istanbul' : 'coverage-v8';
- e.message = `Please install the @vitest/${coveragePackage} package to run with coverage`;
- }
- this.reportFatalError('Failed to change coverage mode', e);
+ this.reportFatalError('Failed to change coverage configuration', e);
}
}
}
diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts
index 7267f3dcea38..26dbe16ab526 100644
--- a/code/addons/test/src/node/vitest-manager.ts
+++ b/code/addons/test/src/node/vitest-manager.ts
@@ -14,7 +14,7 @@ import type { TestingModuleRunRequestPayload } from 'storybook/internal/core-eve
import type { DocsIndexEntry, StoryIndex, StoryIndexEntry } from '@storybook/types';
-import path, { normalize } from 'pathe';
+import path, { dirname, join, normalize } from 'pathe';
import slash from 'slash';
import { COVERAGE_DIRECTORY, type Config } from '../constants';
@@ -29,6 +29,11 @@ type TagsFilter = {
skip: string[];
};
+const packageDir = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));
+
+// We have to tell Vitest that it runs as part of Storybook
+process.env.VITEST_STORYBOOK = 'true';
+
export class VitestManager {
vitest: Vitest | null = null;
@@ -44,10 +49,10 @@ export class VitestManager {
const { createVitest } = await import('vitest/node');
const storybookCoverageReporter: [string, StorybookCoverageReporterOptions] = [
- '@storybook/experimental-addon-test/internal/coverage-reporter',
+ join(packageDir, 'dist/node/coverage-reporter.js'),
{
testManager: this.testManager,
- coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions<'v8'>,
+ coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions<'v8'> | undefined,
},
];
const coverageOptions = (
@@ -63,31 +68,17 @@ export class VitestManager {
: { enabled: false }
) as CoverageOptions;
- this.vitest = await createVitest(
- 'test',
- {
- watch: true,
- passWithNoTests: false,
- // TODO:
- // Do we want to enable Vite's default reporter?
- // The output in the terminal might be too spamy and it might be better to
- // find a way to just show errors and warnings for example
- // Otherwise it might be hard for the user to discover Storybook related logs
- reporters: ['default', new StorybookReporter(this.testManager)],
- coverage: coverageOptions,
- },
- {
- define: {
- // polyfilling process.env.VITEST_STORYBOOK to 'true' in the browser
- 'process.env.VITEST_STORYBOOK': 'true',
- },
- }
- );
-
- this.vitest.configOverride.env = {
- // We signal to the test runner that we are running it via Storybook
- VITEST_STORYBOOK: 'true',
- };
+ this.vitest = await createVitest('test', {
+ watch: true,
+ passWithNoTests: false,
+ // TODO:
+ // Do we want to enable Vite's default reporter?
+ // The output in the terminal might be too spamy and it might be better to
+ // find a way to just show errors and warnings for example
+ // Otherwise it might be hard for the user to discover Storybook related logs
+ reporters: ['default', new StorybookReporter(this.testManager)],
+ coverage: coverageOptions,
+ });
if (this.vitest) {
this.vitest.onCancel(() => {
@@ -98,15 +89,21 @@ export class VitestManager {
try {
await this.vitest.init();
} catch (e) {
+ let message = 'Failed to initialize Vitest';
const isV8 = e.message?.includes('@vitest/coverage-v8');
const isIstanbul = e.message?.includes('@vitest/coverage-istanbul');
- if (e.message?.includes('Error: Failed to load url') && (isIstanbul || isV8)) {
+ if (
+ (e.message?.includes('Failed to load url') && (isIstanbul || isV8)) ||
+ // Vitest will sometimes not throw the correct missing-package-detection error, so we have to check for this as well
+ (e instanceof TypeError &&
+ e?.message === "Cannot read properties of undefined (reading 'name')")
+ ) {
const coveragePackage = isIstanbul ? 'coverage-istanbul' : 'coverage-v8';
- e.message = `Please install the @vitest/${coveragePackage} package to run with coverage`;
+ message += `\n\nPlease install the @vitest/${coveragePackage} package to collect coverage\n`;
}
-
- this.testManager.reportFatalError('Failed to init Vitest', e);
+ this.testManager.reportFatalError(message, e);
+ return;
}
await this.setupWatchers();
diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts
index fc7fc1e8d862..bb22c7517127 100644
--- a/code/addons/test/src/postinstall.ts
+++ b/code/addons/test/src/postinstall.ts
@@ -2,17 +2,21 @@ import { existsSync } from 'node:fs';
import * as fs from 'node:fs/promises';
import { writeFile } from 'node:fs/promises';
+import { traverse } from 'storybook/internal/babel';
import {
JsPackageManagerFactory,
extractProperFrameworkName,
loadAllPresets,
loadMainConfig,
+ serverResolve,
validateFrameworkName,
+ versions,
} from 'storybook/internal/common';
+import { readConfig, writeConfig } from 'storybook/internal/csf-tools';
import { colors, logger } from 'storybook/internal/node-logger';
// eslint-disable-next-line depend/ban-dependencies
-import { execa } from 'execa';
+import { $ } from 'execa';
import { findUp } from 'find-up';
import { dirname, extname, join, relative, resolve } from 'pathe';
import picocolors from 'picocolors';
@@ -57,6 +61,51 @@ export default async function postInstall(options: PostinstallOptions) {
// if Vitest is installed, we use the same version to keep consistency across Vitest packages
const vitestVersionToInstall = vitestVersionSpecifier ?? 'latest';
+ const mainJsPath = serverResolve(resolve(options.configDir, 'main')) as string;
+ const config = await readConfig(mainJsPath);
+
+ const hasCustomWebpackConfig = !!config.getFieldNode(['webpackFinal']);
+
+ if (info.frameworkPackageName === '@storybook/nextjs' && !hasCustomWebpackConfig) {
+ const out = options.yes
+ ? {
+ migrateToExperimentalNextjsVite: true,
+ }
+ : await prompts({
+ type: 'confirm',
+ name: 'migrateToExperimentalNextjsVite',
+ message: dedent`
+ The addon requires the use of @storybook/experimental-nextjs-vite to work with Next.js.
+ https://storybook.js.org/docs/writing-tests/test-addon#install-and-set-up
+
+ Do you want to migrate?
+ `,
+ initial: true,
+ });
+
+ if (out.migrateToExperimentalNextjsVite) {
+ await packageManager.addDependencies({ installAsDevDependencies: true }, [
+ `@storybook/experimental-nextjs-vite@${versions['@storybook/experimental-nextjs-vite']}`,
+ ]);
+
+ await packageManager.removeDependencies({}, ['@storybook/nextjs']);
+
+ // eslint-disable-next-line no-underscore-dangle
+ traverse(config._ast, {
+ StringLiteral(path) {
+ if (path.node.value === '@storybook/nextjs') {
+ path.node.value = '@storybook/experimental-nextjs-vite';
+ }
+ },
+ });
+
+ await writeConfig(config, mainJsPath);
+
+ info.frameworkPackageName = '@storybook/experimental-nextjs-vite';
+ info.builderPackageName = '@storybook/builder-vite';
+ }
+ }
+
const annotationsImport = [
'@storybook/nextjs',
'@storybook/experimental-nextjs-vite',
@@ -71,11 +120,16 @@ export default async function postInstall(options: PostinstallOptions) {
)
? info.rendererPackageName
: null;
+
const isRendererSupported = !!annotationsImport;
const prerequisiteCheck = async () => {
const reasons = [];
+ if (hasCustomWebpackConfig) {
+ reasons.push('• The addon can not be used with a custom Webpack configuration.');
+ }
+
if (
info.frameworkPackageName !== '@storybook/nextjs' &&
info.builderPackageName !== '@storybook/builder-vite'
@@ -149,9 +203,7 @@ export default async function postInstall(options: PostinstallOptions) {
return;
}
- const interactionsAddon = info.addons.find((addon) => addon.includes(addonInteractionsName));
-
- if (!!interactionsAddon) {
+ if (info.hasAddonInteractions) {
let shouldUninstall = options.yes;
if (!options.yes) {
printInfo(
@@ -175,23 +227,9 @@ export default async function postInstall(options: PostinstallOptions) {
}
if (shouldUninstall) {
- await execa(
- packageManager.getRemoteRunCommand(),
- [
- 'storybook',
- 'remove',
- addonInteractionsName,
- '--package-manager',
- options.packageManager,
- '--config-dir',
- options.configDir,
- ],
- {
- shell: true,
- }
- );
- } else {
- return;
+ await $({
+ stdio: 'inherit',
+ })`storybook remove ${addonInteractionsName} --package-manager ${options.packageManager} --config-dir ${options.configDir}`;
}
}
@@ -282,7 +320,7 @@ export default async function postInstall(options: PostinstallOptions) {
logger.plain(colors.gray(` ${vitestSetupFile}`));
const previewExists = EXTENSIONS.map((ext) => resolve(options.configDir, `preview${ext}`)).some(
- (config) => existsSync(config)
+ existsSync
);
const a11yAddon = info.addons.find((addon) => addon.includes(addonA11yName));
@@ -489,7 +527,7 @@ const getVitestPluginInfo = (framework: string) => {
frameworkPluginCall = 'storybookReactNativeWeb()';
}
- // spaces for file identation
+ // spaces for file indentation
frameworkPluginImport = `\n${frameworkPluginImport}`;
frameworkPluginDocs = frameworkPluginDocs ? `\n ${frameworkPluginDocs}` : '';
frameworkPluginCall = frameworkPluginCall ? `\n ${frameworkPluginCall},` : '';
@@ -507,7 +545,6 @@ async function getStorybookInfo({ configDir, packageManager: pkgMgr }: Postinsta
const frameworkName = typeof framework === 'string' ? framework : framework?.name;
validateFrameworkName(frameworkName);
const frameworkPackageName = extractProperFrameworkName(frameworkName);
- const addons = getAddonNames(config);
const presets = await loadAllPresets({
corePresets: [join(frameworkName, 'preset')],
@@ -519,6 +556,8 @@ async function getStorybookInfo({ configDir, packageManager: pkgMgr }: Postinsta
isCritical: true,
});
+ const hasAddonInteractions = !!(await presets.apply('ADDON_INTERACTIONS_IN_USE', false));
+
const core = await presets.apply('core', {});
const { builder, renderer } = core;
@@ -546,6 +585,7 @@ async function getStorybookInfo({ configDir, packageManager: pkgMgr }: Postinsta
frameworkPackageName,
builderPackageName,
rendererPackageName,
- addons,
+ hasAddonInteractions,
+ addons: getAddonNames(config),
};
}
diff --git a/code/addons/test/src/utils.ts b/code/addons/test/src/utils.ts
index 066f09af15f1..053f4eec4ffe 100644
--- a/code/addons/test/src/utils.ts
+++ b/code/addons/test/src/utils.ts
@@ -1,6 +1,5 @@
import { type StorybookTheme, useTheme } from 'storybook/internal/theming';
-
-import type { StorybookConfig } from '@storybook/types';
+import type { StorybookConfig } from 'storybook/internal/types';
import Filter from 'ansi-to-html';
import stripAnsi from 'strip-ansi';
diff --git a/code/addons/test/src/vitest-plugin/global-setup.ts b/code/addons/test/src/vitest-plugin/global-setup.ts
index bff48208229a..ca287c105e2b 100644
--- a/code/addons/test/src/vitest-plugin/global-setup.ts
+++ b/code/addons/test/src/vitest-plugin/global-setup.ts
@@ -5,6 +5,8 @@ import type { GlobalSetupContext } from 'vitest/node';
import { logger } from 'storybook/internal/node-logger';
+import treeKill from 'tree-kill';
+
let storybookProcess: ChildProcess | null = null;
const getIsVitestStandaloneRun = () => {
@@ -59,14 +61,6 @@ const startStorybookIfNotRunning = async () => {
}
};
-const killProcess = (process: ChildProcess) => {
- return new Promise((resolve, reject) => {
- process.on('close', resolve);
- process.on('error', reject);
- process.kill();
- });
-};
-
export const setup = async ({ config }: GlobalSetupContext) => {
if (config.watch && isVitestStandaloneRun) {
await startStorybookIfNotRunning();
@@ -74,8 +68,19 @@ export const setup = async ({ config }: GlobalSetupContext) => {
};
export const teardown = async () => {
- if (storybookProcess) {
- logger.verbose('Stopping Storybook process');
- await killProcess(storybookProcess);
+ if (!storybookProcess) {
+ return;
}
+ logger.verbose('Stopping Storybook process');
+ await new Promise((resolve, reject) => {
+ // Storybook starts multiple child processes, so we need to kill the whole tree
+ treeKill(storybookProcess.pid, 'SIGTERM', (error) => {
+ if (error) {
+ logger.error('Failed to stop Storybook process:');
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
};
diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts
index eadadb4440e5..e73549ded6d9 100644
--- a/code/addons/test/src/vitest-plugin/index.ts
+++ b/code/addons/test/src/vitest-plugin/index.ts
@@ -1,4 +1,6 @@
/* eslint-disable no-underscore-dangle */
+import { dirname } from 'node:path';
+
import type { Plugin } from 'vitest/config';
import { mergeConfig } from 'vitest/config';
import type { ViteUserConfig } from 'vitest/config';
@@ -23,7 +25,6 @@ import sirv from 'sirv';
import { convertPathToPattern } from 'tinyglobby';
import { dedent } from 'ts-dedent';
-import { TestManager } from '../node/test-manager';
import type { InternalOptions, UserOptions } from './types';
const WORKING_DIR = process.cwd();
@@ -63,6 +64,8 @@ const getStoryGlobsAndFiles = async (
};
};
+const packageDir = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));
+
export const storybookTest = async (options?: UserOptions): Promise => {
const finalOptions = {
...defaultOptions,
@@ -124,7 +127,7 @@ export const storybookTest = async (options?: UserOptions): Promise => {
.replace('', `${headHtmlSnippet ?? ''}`)
.replace('', `${bodyHtmlSnippet ?? ''}`);
},
- async config(inputConfig_DoNotMutate) {
+ async config(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED) {
// ! We're not mutating the input config, instead we're returning a new partial config
// ! see https://vite.dev/guide/api-plugin.html#config
try {
@@ -143,18 +146,23 @@ export const storybookTest = async (options?: UserOptions): Promise => {
// plugin.name?.startsWith('vitest:browser')
// )
+ // We signal the test runner that we are not running it via Storybook
+ // We are overriding the environment variable to 'true' if vitest runs via @storybook/addon-test's backend
+ const vitestStorybook = process.env.VITEST_STORYBOOK ?? 'false';
+
const baseConfig: Omit = {
test: {
setupFiles: [
- '@storybook/experimental-addon-test/internal/setup-file',
+ join(packageDir, '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_DoNotMutate.test?.setupFiles === 'string' &&
- inputConfig_DoNotMutate.test?.setupFiles,
- ].filter(Boolean),
+ typeof inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test
+ ?.setupFiles === 'string' &&
+ inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.setupFiles,
+ ].filter(Boolean) as string[],
...(finalOptions.storybookScript
? {
- globalSetup: ['@storybook/experimental-addon-test/internal/global-setup'],
+ globalSetup: [join(packageDir, 'dist/vitest-plugin/global-setup.mjs')],
}
: {}),
@@ -162,9 +170,8 @@ export const storybookTest = async (options?: UserOptions): Promise => {
...storybookEnv,
// To be accessed by the setup file
__STORYBOOK_URL__: finalOptions.storybookUrl,
- // We signal the test runner that we are not running it via Storybook
- // We are overriding the environment variable to 'true' if vitest runs via @storybook/addon-test's backend
- VITEST_STORYBOOK: 'false',
+
+ VITEST_STORYBOOK: vitestStorybook,
__VITEST_INCLUDE_TAGS__: finalOptions.tags.include.join(','),
__VITEST_EXCLUDE_TAGS__: finalOptions.tags.exclude.join(','),
__VITEST_SKIP_TAGS__: finalOptions.tags.skip.join(','),
@@ -175,7 +182,8 @@ export const storybookTest = async (options?: UserOptions): Promise => {
.map((path) => convertPathToPattern(path)),
// if the existing deps.inline is true, we keep it as-is, because it will inline everything
- ...(inputConfig_DoNotMutate.test?.server?.deps?.inline !== true
+ ...(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.server?.deps
+ ?.inline !== true
? {
server: {
deps: {
@@ -186,7 +194,7 @@ export const storybookTest = async (options?: UserOptions): Promise => {
: {}),
browser: {
- ...inputConfig_DoNotMutate.test?.browser,
+ ...inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.browser,
commands: {
getInitialGlobals: () => {
const envConfig = JSON.parse(process.env.VITEST_STORYBOOK_CONFIG ?? '{}');
@@ -203,8 +211,9 @@ export const storybookTest = async (options?: UserOptions): Promise => {
},
},
// if there is a test.browser config AND test.browser.screenshotFailures is not explicitly set, we set it to false
- ...(inputConfig_DoNotMutate.test?.browser &&
- inputConfig_DoNotMutate.test.browser.screenshotFailures === undefined
+ ...(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.browser &&
+ inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.browser
+ .screenshotFailures === undefined
? {
screenshotFailures: false,
}
@@ -213,7 +222,11 @@ export const storybookTest = async (options?: UserOptions): Promise => {
},
envPrefix: Array.from(
- new Set([...(inputConfig_DoNotMutate.envPrefix || []), 'STORYBOOK_', 'VITE_'])
+ new Set([
+ ...(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.envPrefix || []),
+ 'STORYBOOK_',
+ 'VITE_',
+ ])
),
resolve: {
@@ -239,8 +252,6 @@ export const storybookTest = async (options?: UserOptions): Promise => {
},
define: {
- // polyfilling process.env.VITEST_STORYBOOK to 'false' in the browser
- 'process.env.VITEST_STORYBOOK': JSON.stringify('false'),
...(frameworkName?.includes('vue3')
? { __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' }
: {}),
@@ -254,8 +265,12 @@ export const storybookTest = async (options?: UserOptions): Promise => {
);
// alert the user of problems
- if (inputConfig_DoNotMutate.test.include?.length > 0) {
- console.warn(
+ if (
+ inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include?.length > 0
+ ) {
+ // remove the user's existing include, because we're replacing it with our own heuristic based on main.ts#stories
+ inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include = [];
+ console.log(
picocolors.yellow(dedent`
Warning: Starting in Storybook 8.5.0-alpha.18, the "test.include" option in Vitest is discouraged in favor of just using the "stories" field in your Storybook configuration.
diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json
index 57114fdc2b4a..203ff96d62f6 100644
--- a/code/addons/themes/package.json
+++ b/code/addons/themes/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-themes",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Switch between multiple themes for you components in Storybook",
"keywords": [
"css",
diff --git a/code/addons/themes/src/decorators/class-name.decorator.tsx b/code/addons/themes/src/decorators/class-name.decorator.tsx
index 4bc56202f038..7e44306b9173 100644
--- a/code/addons/themes/src/decorators/class-name.decorator.tsx
+++ b/code/addons/themes/src/decorators/class-name.decorator.tsx
@@ -22,7 +22,7 @@ export const withThemeByClassName = ({
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
- const { themeOverride } = useThemeParameters();
+ const { themeOverride } = useThemeParameters(context);
const selected = pluckThemeFromContext(context);
useEffect(() => {
diff --git a/code/addons/themes/src/decorators/data-attribute.decorator.tsx b/code/addons/themes/src/decorators/data-attribute.decorator.tsx
index 6d4b1cfe26f9..ec5e0d909538 100644
--- a/code/addons/themes/src/decorators/data-attribute.decorator.tsx
+++ b/code/addons/themes/src/decorators/data-attribute.decorator.tsx
@@ -22,7 +22,7 @@ export const withThemeByDataAttribute = ({
}: DataAttributeStrategyConfiguration): DecoratorFunction => {
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
- const { themeOverride } = useThemeParameters();
+ const { themeOverride } = useThemeParameters(context);
const selected = pluckThemeFromContext(context);
useEffect(() => {
diff --git a/code/addons/themes/src/decorators/helpers.ts b/code/addons/themes/src/decorators/helpers.ts
index 237b0943568c..0dd8c007ec7f 100644
--- a/code/addons/themes/src/decorators/helpers.ts
+++ b/code/addons/themes/src/decorators/helpers.ts
@@ -1,4 +1,4 @@
-import { addons, useParameter } from 'storybook/internal/preview-api';
+import { addons } from 'storybook/internal/preview-api';
import type { StoryContext } from 'storybook/internal/types';
import type { ThemeParameters } from '../constants';
@@ -12,8 +12,8 @@ export function pluckThemeFromContext({ globals }: StoryContext): string {
return globals[GLOBAL_KEY] || '';
}
-export function useThemeParameters(): ThemeParameters {
- return useParameter(PARAM_KEY, DEFAULT_THEME_PARAMETERS) as ThemeParameters;
+export function useThemeParameters(context: StoryContext): ThemeParameters {
+ return context.parameters[PARAM_KEY] || DEFAULT_THEME_PARAMETERS;
}
export function initializeThemeState(themeNames: string[], defaultTheme: string) {
diff --git a/code/addons/themes/src/decorators/provider.decorator.tsx b/code/addons/themes/src/decorators/provider.decorator.tsx
index 8985a9773fad..f1214f69f992 100644
--- a/code/addons/themes/src/decorators/provider.decorator.tsx
+++ b/code/addons/themes/src/decorators/provider.decorator.tsx
@@ -32,7 +32,7 @@ export const withThemeFromJSXProvider = ({
// eslint-disable-next-line react/display-name
return (storyFn, context) => {
- const { themeOverride } = useThemeParameters();
+ const { themeOverride } = useThemeParameters(context);
const selected = pluckThemeFromContext(context);
const theme = useMemo(() => {
diff --git a/code/addons/toolbars/package.json b/code/addons/toolbars/package.json
index 67afb0929acc..bddd8a73c217 100644
--- a/code/addons/toolbars/package.json
+++ b/code/addons/toolbars/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-toolbars",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Create your own toolbar items that control story rendering",
"keywords": [
"addon",
diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json
index 7ce39426636e..7aeb1ea7d640 100644
--- a/code/addons/viewport/package.json
+++ b/code/addons/viewport/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Build responsive components by adjusting Storybook’s viewport size and orientation",
"keywords": [
"addon",
diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json
index 75ad6ae45767..e5d76e019f82 100644
--- a/code/builders/builder-vite/package.json
+++ b/code/builders/builder-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/builder-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "A plugin to run and build Storybooks with Vite",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme",
"bugs": {
diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json
index cacc5276d9da..3db13f0231cb 100644
--- a/code/builders/builder-webpack5/package.json
+++ b/code/builders/builder-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/builder-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
diff --git a/code/core/assets/server/addon.tsconfig.json b/code/core/assets/server/addon.tsconfig.json
index 9161d5ff8254..38452bcdfa20 100644
--- a/code/core/assets/server/addon.tsconfig.json
+++ b/code/core/assets/server/addon.tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "jsx": "react-jsx",
+ "jsx": "react",
"jsxImportSource": "react"
}
}
diff --git a/code/core/package.json b/code/core/package.json
index d3f69bfe4d89..1744d188499f 100644
--- a/code/core/package.json
+++ b/code/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/core",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
diff --git a/code/core/src/__mocks__/page.ts b/code/core/src/__mocks__/page.ts
new file mode 100644
index 000000000000..fb87bbd306b8
--- /dev/null
+++ b/code/core/src/__mocks__/page.ts
@@ -0,0 +1 @@
+// empty file only matched on path
diff --git a/code/core/src/__mocks__/path/to/Screens/index.jsx b/code/core/src/__mocks__/path/to/Screens/index.jsx
new file mode 100644
index 000000000000..fb87bbd306b8
--- /dev/null
+++ b/code/core/src/__mocks__/path/to/Screens/index.jsx
@@ -0,0 +1 @@
+// empty file only matched on path
diff --git a/code/core/src/common/versions.ts b/code/core/src/common/versions.ts
index ea14e9cf35be..cdb250c9cd8d 100644
--- a/code/core/src/common/versions.ts
+++ b/code/core/src/common/versions.ts
@@ -1,88 +1,88 @@
// auto generated file, do not edit
export default {
- '@storybook/addon-a11y': '8.5.0-alpha.19',
- '@storybook/addon-actions': '8.5.0-alpha.19',
- '@storybook/addon-backgrounds': '8.5.0-alpha.19',
- '@storybook/addon-controls': '8.5.0-alpha.19',
- '@storybook/addon-docs': '8.5.0-alpha.19',
- '@storybook/addon-essentials': '8.5.0-alpha.19',
- '@storybook/addon-mdx-gfm': '8.5.0-alpha.19',
- '@storybook/addon-highlight': '8.5.0-alpha.19',
- '@storybook/addon-interactions': '8.5.0-alpha.19',
- '@storybook/addon-jest': '8.5.0-alpha.19',
- '@storybook/addon-links': '8.5.0-alpha.19',
- '@storybook/addon-measure': '8.5.0-alpha.19',
- '@storybook/addon-onboarding': '8.5.0-alpha.19',
- '@storybook/addon-outline': '8.5.0-alpha.19',
- '@storybook/addon-storysource': '8.5.0-alpha.19',
- '@storybook/experimental-addon-test': '8.5.0-alpha.19',
- '@storybook/addon-themes': '8.5.0-alpha.19',
- '@storybook/addon-toolbars': '8.5.0-alpha.19',
- '@storybook/addon-viewport': '8.5.0-alpha.19',
- '@storybook/builder-vite': '8.5.0-alpha.19',
- '@storybook/builder-webpack5': '8.5.0-alpha.19',
- '@storybook/core': '8.5.0-alpha.19',
- '@storybook/builder-manager': '8.5.0-alpha.19',
- '@storybook/channels': '8.5.0-alpha.19',
- '@storybook/client-logger': '8.5.0-alpha.19',
- '@storybook/components': '8.5.0-alpha.19',
- '@storybook/core-common': '8.5.0-alpha.19',
- '@storybook/core-events': '8.5.0-alpha.19',
- '@storybook/core-server': '8.5.0-alpha.19',
- '@storybook/csf-tools': '8.5.0-alpha.19',
- '@storybook/docs-tools': '8.5.0-alpha.19',
- '@storybook/manager': '8.5.0-alpha.19',
- '@storybook/manager-api': '8.5.0-alpha.19',
- '@storybook/node-logger': '8.5.0-alpha.19',
- '@storybook/preview': '8.5.0-alpha.19',
- '@storybook/preview-api': '8.5.0-alpha.19',
- '@storybook/router': '8.5.0-alpha.19',
- '@storybook/telemetry': '8.5.0-alpha.19',
- '@storybook/theming': '8.5.0-alpha.19',
- '@storybook/types': '8.5.0-alpha.19',
- '@storybook/angular': '8.5.0-alpha.19',
- '@storybook/ember': '8.5.0-alpha.19',
- '@storybook/experimental-nextjs-vite': '8.5.0-alpha.19',
- '@storybook/html-vite': '8.5.0-alpha.19',
- '@storybook/html-webpack5': '8.5.0-alpha.19',
- '@storybook/nextjs': '8.5.0-alpha.19',
- '@storybook/preact-vite': '8.5.0-alpha.19',
- '@storybook/preact-webpack5': '8.5.0-alpha.19',
- '@storybook/react-native-web-vite': '8.5.0-alpha.19',
- '@storybook/react-vite': '8.5.0-alpha.19',
- '@storybook/react-webpack5': '8.5.0-alpha.19',
- '@storybook/server-webpack5': '8.5.0-alpha.19',
- '@storybook/svelte-vite': '8.5.0-alpha.19',
- '@storybook/svelte-webpack5': '8.5.0-alpha.19',
- '@storybook/sveltekit': '8.5.0-alpha.19',
- '@storybook/vue3-vite': '8.5.0-alpha.19',
- '@storybook/vue3-webpack5': '8.5.0-alpha.19',
- '@storybook/web-components-vite': '8.5.0-alpha.19',
- '@storybook/web-components-webpack5': '8.5.0-alpha.19',
- '@storybook/blocks': '8.5.0-alpha.19',
- storybook: '8.5.0-alpha.19',
- sb: '8.5.0-alpha.19',
- '@storybook/cli': '8.5.0-alpha.19',
- '@storybook/codemod': '8.5.0-alpha.19',
- '@storybook/core-webpack': '8.5.0-alpha.19',
- 'create-storybook': '8.5.0-alpha.19',
- '@storybook/csf-plugin': '8.5.0-alpha.19',
- '@storybook/instrumenter': '8.5.0-alpha.19',
- '@storybook/react-dom-shim': '8.5.0-alpha.19',
- '@storybook/source-loader': '8.5.0-alpha.19',
- '@storybook/test': '8.5.0-alpha.19',
- '@storybook/preset-create-react-app': '8.5.0-alpha.19',
- '@storybook/preset-html-webpack': '8.5.0-alpha.19',
- '@storybook/preset-preact-webpack': '8.5.0-alpha.19',
- '@storybook/preset-react-webpack': '8.5.0-alpha.19',
- '@storybook/preset-server-webpack': '8.5.0-alpha.19',
- '@storybook/preset-svelte-webpack': '8.5.0-alpha.19',
- '@storybook/preset-vue3-webpack': '8.5.0-alpha.19',
- '@storybook/html': '8.5.0-alpha.19',
- '@storybook/preact': '8.5.0-alpha.19',
- '@storybook/react': '8.5.0-alpha.19',
- '@storybook/server': '8.5.0-alpha.19',
- '@storybook/svelte': '8.5.0-alpha.19',
- '@storybook/vue3': '8.5.0-alpha.19',
- '@storybook/web-components': '8.5.0-alpha.19',
+ '@storybook/addon-a11y': '8.5.0-beta.2',
+ '@storybook/addon-actions': '8.5.0-beta.2',
+ '@storybook/addon-backgrounds': '8.5.0-beta.2',
+ '@storybook/addon-controls': '8.5.0-beta.2',
+ '@storybook/addon-docs': '8.5.0-beta.2',
+ '@storybook/addon-essentials': '8.5.0-beta.2',
+ '@storybook/addon-mdx-gfm': '8.5.0-beta.2',
+ '@storybook/addon-highlight': '8.5.0-beta.2',
+ '@storybook/addon-interactions': '8.5.0-beta.2',
+ '@storybook/addon-jest': '8.5.0-beta.2',
+ '@storybook/addon-links': '8.5.0-beta.2',
+ '@storybook/addon-measure': '8.5.0-beta.2',
+ '@storybook/addon-onboarding': '8.5.0-beta.2',
+ '@storybook/addon-outline': '8.5.0-beta.2',
+ '@storybook/addon-storysource': '8.5.0-beta.2',
+ '@storybook/experimental-addon-test': '8.5.0-beta.2',
+ '@storybook/addon-themes': '8.5.0-beta.2',
+ '@storybook/addon-toolbars': '8.5.0-beta.2',
+ '@storybook/addon-viewport': '8.5.0-beta.2',
+ '@storybook/builder-vite': '8.5.0-beta.2',
+ '@storybook/builder-webpack5': '8.5.0-beta.2',
+ '@storybook/core': '8.5.0-beta.2',
+ '@storybook/builder-manager': '8.5.0-beta.2',
+ '@storybook/channels': '8.5.0-beta.2',
+ '@storybook/client-logger': '8.5.0-beta.2',
+ '@storybook/components': '8.5.0-beta.2',
+ '@storybook/core-common': '8.5.0-beta.2',
+ '@storybook/core-events': '8.5.0-beta.2',
+ '@storybook/core-server': '8.5.0-beta.2',
+ '@storybook/csf-tools': '8.5.0-beta.2',
+ '@storybook/docs-tools': '8.5.0-beta.2',
+ '@storybook/manager': '8.5.0-beta.2',
+ '@storybook/manager-api': '8.5.0-beta.2',
+ '@storybook/node-logger': '8.5.0-beta.2',
+ '@storybook/preview': '8.5.0-beta.2',
+ '@storybook/preview-api': '8.5.0-beta.2',
+ '@storybook/router': '8.5.0-beta.2',
+ '@storybook/telemetry': '8.5.0-beta.2',
+ '@storybook/theming': '8.5.0-beta.2',
+ '@storybook/types': '8.5.0-beta.2',
+ '@storybook/angular': '8.5.0-beta.2',
+ '@storybook/ember': '8.5.0-beta.2',
+ '@storybook/experimental-nextjs-vite': '8.5.0-beta.2',
+ '@storybook/html-vite': '8.5.0-beta.2',
+ '@storybook/html-webpack5': '8.5.0-beta.2',
+ '@storybook/nextjs': '8.5.0-beta.2',
+ '@storybook/preact-vite': '8.5.0-beta.2',
+ '@storybook/preact-webpack5': '8.5.0-beta.2',
+ '@storybook/react-native-web-vite': '8.5.0-beta.2',
+ '@storybook/react-vite': '8.5.0-beta.2',
+ '@storybook/react-webpack5': '8.5.0-beta.2',
+ '@storybook/server-webpack5': '8.5.0-beta.2',
+ '@storybook/svelte-vite': '8.5.0-beta.2',
+ '@storybook/svelte-webpack5': '8.5.0-beta.2',
+ '@storybook/sveltekit': '8.5.0-beta.2',
+ '@storybook/vue3-vite': '8.5.0-beta.2',
+ '@storybook/vue3-webpack5': '8.5.0-beta.2',
+ '@storybook/web-components-vite': '8.5.0-beta.2',
+ '@storybook/web-components-webpack5': '8.5.0-beta.2',
+ '@storybook/blocks': '8.5.0-beta.2',
+ storybook: '8.5.0-beta.2',
+ sb: '8.5.0-beta.2',
+ '@storybook/cli': '8.5.0-beta.2',
+ '@storybook/codemod': '8.5.0-beta.2',
+ '@storybook/core-webpack': '8.5.0-beta.2',
+ 'create-storybook': '8.5.0-beta.2',
+ '@storybook/csf-plugin': '8.5.0-beta.2',
+ '@storybook/instrumenter': '8.5.0-beta.2',
+ '@storybook/react-dom-shim': '8.5.0-beta.2',
+ '@storybook/source-loader': '8.5.0-beta.2',
+ '@storybook/test': '8.5.0-beta.2',
+ '@storybook/preset-create-react-app': '8.5.0-beta.2',
+ '@storybook/preset-html-webpack': '8.5.0-beta.2',
+ '@storybook/preset-preact-webpack': '8.5.0-beta.2',
+ '@storybook/preset-react-webpack': '8.5.0-beta.2',
+ '@storybook/preset-server-webpack': '8.5.0-beta.2',
+ '@storybook/preset-svelte-webpack': '8.5.0-beta.2',
+ '@storybook/preset-vue3-webpack': '8.5.0-beta.2',
+ '@storybook/html': '8.5.0-beta.2',
+ '@storybook/preact': '8.5.0-beta.2',
+ '@storybook/react': '8.5.0-beta.2',
+ '@storybook/server': '8.5.0-beta.2',
+ '@storybook/svelte': '8.5.0-beta.2',
+ '@storybook/vue3': '8.5.0-beta.2',
+ '@storybook/web-components': '8.5.0-beta.2',
};
diff --git a/code/core/src/components/components/Loader/Loader.tsx b/code/core/src/components/components/Loader/Loader.tsx
index 26d01ad3f05d..dace42a9b351 100644
--- a/code/core/src/components/components/Loader/Loader.tsx
+++ b/code/core/src/components/components/Loader/Loader.tsx
@@ -63,7 +63,7 @@ const ProgressBar = styled.div(({ theme }) => ({
const ProgressMessage = styled.div(({ theme }) => ({
minHeight: '2em',
fontSize: `${theme.typography.size.s1}px`,
- color: theme.barTextColor,
+ color: theme.textMutedColor,
}));
const ErrorIcon = styled(LightningOffIcon)(({ theme }) => ({
diff --git a/code/core/src/components/components/tooltip/ListItem.tsx b/code/core/src/components/components/tooltip/ListItem.tsx
index 2a93287af503..10e513cc1b6b 100644
--- a/code/core/src/components/components/tooltip/ListItem.tsx
+++ b/code/core/src/components/components/tooltip/ListItem.tsx
@@ -40,7 +40,7 @@ const Title = styled(({ active, loading, disabled, ...rest }: TitleProps) =>
disabled
? {
- color: transparentize(0.7, theme.color.defaultText),
+ color: theme.textMutedColor,
}
: {}
);
diff --git a/code/core/src/manager-api/lib/stories.test.ts b/code/core/src/manager-api/lib/stories.test.ts
index 76aaadd7f99d..3f0cbe63b5be 100644
--- a/code/core/src/manager-api/lib/stories.test.ts
+++ b/code/core/src/manager-api/lib/stories.test.ts
@@ -2,8 +2,10 @@ import { describe, expect, it } from 'vitest';
import type { API_PreparedStoryIndex, StoryIndexV2, StoryIndexV3 } from '@storybook/core/types';
+import type { State } from '../root';
import { mockEntries } from '../tests/mockStoriesEntries';
import {
+ transformStoryIndexToStoriesHash,
transformStoryIndexV2toV3,
transformStoryIndexV3toV4,
transformStoryIndexV4toV5,
@@ -216,3 +218,60 @@ describe('transformStoryIndexV4toV5', () => {
`);
});
});
+
+describe('transformStoryIndexToStoriesHash', () => {
+ it('does not apply filters to failing stories', () => {
+ // Arrange - set up an index with two stories, one of which has a failing status
+ const indexV5: API_PreparedStoryIndex = {
+ v: 5,
+ entries: {
+ '1': {
+ id: '1',
+ type: 'story',
+ title: 'Story 1',
+ name: 'Story 1',
+ importPath: './path/to/story-1.ts',
+ parameters: {},
+ tags: [],
+ },
+ '2': {
+ id: '2',
+ type: 'story',
+ title: 'Story 2',
+ name: 'Story 2',
+ importPath: './path/to/story-2.ts',
+ parameters: {},
+ tags: [],
+ },
+ },
+ };
+
+ const filters: State['filters'] = {
+ someFilter: () => false,
+ };
+
+ const status: State['status'] = {
+ '1': { someStatus: { status: 'error', title: 'broken', description: 'very bad' } },
+ '2': { someStatus: { status: 'success', title: 'perfect', description: 'nice' } },
+ };
+
+ const options = {
+ provider: {
+ getConfig: () => ({ sidebar: {} }),
+ } as any,
+ docsOptions: { docsMode: false },
+ filters,
+ status,
+ };
+
+ // Act - transform the index to hashes
+ const result = transformStoryIndexToStoriesHash(indexV5, options);
+
+ // Assert - the failing story is still present in the result, even though the filters remove all stories
+ expect(Object.keys(result)).toHaveLength(2);
+ expect(result['story-1']).toBeTruthy();
+ expect(result['1']).toBeTruthy();
+ expect(result['story-2']).toBeUndefined();
+ expect(result['2']).toBeUndefined();
+ });
+});
diff --git a/code/core/src/manager-api/lib/stories.ts b/code/core/src/manager-api/lib/stories.ts
index 59b59f070a10..b38d30000531 100644
--- a/code/core/src/manager-api/lib/stories.ts
+++ b/code/core/src/manager-api/lib/stories.ts
@@ -192,11 +192,17 @@ export const transformStoryIndexToStoriesHash = (
const entryValues = Object.values(index.entries).filter((entry: any) => {
let result = true;
+ // All stories with a failing status should always show up, regardless of the applied filters
+ const storyStatus = status[entry.id];
+ if (Object.values(storyStatus ?? {}).some(({ status: s }) => s === 'error')) {
+ return result;
+ }
+
Object.values(filters).forEach((filter: any) => {
if (result === false) {
return;
}
- result = filter({ ...entry, status: status[entry.id] });
+ result = filter({ ...entry, status: storyStatus });
});
return result;
@@ -342,6 +348,7 @@ export const transformStoryIndexToStoriesHash = (
.reduce(addItem, orphanHash);
};
+/** Now we need to patch in the existing prepared stories */
export const addPreparedStories = (newHash: API_IndexHash, oldHash?: API_IndexHash) => {
if (!oldHash) {
return newHash;
diff --git a/code/core/src/manager-api/modules/layout.ts b/code/core/src/manager-api/modules/layout.ts
index 468d51af1b8a..6687c36b1137 100644
--- a/code/core/src/manager-api/modules/layout.ts
+++ b/code/core/src/manager-api/modules/layout.ts
@@ -100,7 +100,7 @@ export const defaultLayoutState: SubState = {
panelPosition: 'bottom',
showTabs: true,
},
- selectedPanel: undefined,
+ selectedPanel: 'chromaui/addon-visual-tests/panel',
theme: create(),
};
diff --git a/code/core/src/manager-api/modules/refs.ts b/code/core/src/manager-api/modules/refs.ts
index 166c04786adc..cb9b8916ce29 100644
--- a/code/core/src/manager-api/modules/refs.ts
+++ b/code/core/src/manager-api/modules/refs.ts
@@ -179,7 +179,15 @@ export const init: ModuleFn = (
},
changeRefVersion: async (id, url) => {
const { versions, title } = api.getRefs()[id];
- const ref: API_SetRefData = { id, url, versions, title, index: {}, expanded: true };
+ const ref: API_SetRefData = {
+ id,
+ url,
+ versions,
+ title,
+ index: {},
+ filteredIndex: {},
+ expanded: true,
+ };
await api.setRef(id, { ...ref, type: 'unknown' }, false);
await api.checkRef(ref);
@@ -292,6 +300,7 @@ export const init: ModuleFn = (
// eslint-disable-next-line @typescript-eslint/naming-convention
let internal_index: StoryIndex | undefined;
let index: API_IndexHash | undefined;
+ let filteredIndex: API_IndexHash | undefined;
const { filters } = store.getState();
const { storyMapper = defaultStoryMapper } = provider.getConfig();
const ref = api.getRefs()[id];
@@ -304,19 +313,28 @@ export const init: ModuleFn = (
: storyIndex;
// @ts-expect-error (could be undefined)
- index = transformStoryIndexToStoriesHash(storyIndex, {
+ filteredIndex = transformStoryIndexToStoriesHash(storyIndex, {
provider,
docsOptions,
filters,
status: {},
});
+ // @ts-expect-error (could be undefined)
+ index = transformStoryIndexToStoriesHash(storyIndex, {
+ provider,
+ docsOptions,
+ filters: {},
+ status: {},
+ });
}
if (index) {
index = addRefIds(index, ref);
}
-
- await api.updateRef(id, { ...ref, ...rest, index, internal_index });
+ if (filteredIndex) {
+ filteredIndex = addRefIds(filteredIndex, ref);
+ }
+ await api.updateRef(id, { ...ref, ...rest, index, filteredIndex, internal_index });
},
updateRef: async (id, data) => {
diff --git a/code/core/src/manager-api/modules/stories.ts b/code/core/src/manager-api/modules/stories.ts
index 3c5aac769c6a..fa295bc20567 100644
--- a/code/core/src/manager-api/modules/stories.ts
+++ b/code/core/src/manager-api/modules/stories.ts
@@ -568,41 +568,61 @@ export const init: ModuleFn = ({
// The story index we receive on fetchStoryIndex is not, but all the prepared fields are optional
// so we can cast one to the other easily enough
setIndex: async (input) => {
- const { index: oldHash, status, filters } = store.getState();
- const newHash = transformStoryIndexToStoriesHash(input, {
+ const { filteredIndex: oldFilteredHash, index: oldHash, status, filters } = store.getState();
+ const newFilteredHash = transformStoryIndexToStoriesHash(input, {
provider,
docsOptions,
status,
filters,
});
+ const newHash = transformStoryIndexToStoriesHash(input, {
+ provider,
+ docsOptions,
+ status,
+ filters: {},
+ });
- // Now we need to patch in the existing prepared stories
- const output = addPreparedStories(newHash, oldHash);
-
- await store.setState({ internal_index: input, index: output, indexError: undefined });
+ await store.setState({
+ internal_index: input,
+ filteredIndex: addPreparedStories(newFilteredHash, oldFilteredHash),
+ index: addPreparedStories(newHash, oldHash),
+ indexError: undefined,
+ });
},
+ // FIXME: is there a bug where filtered stories get added back in on updateStory???
updateStory: async (
storyId: StoryId,
update: StoryUpdate,
ref?: API_ComposedRef
): Promise => {
if (!ref) {
- const { index } = store.getState();
- if (!index) {
- return;
+ const { index, filteredIndex } = store.getState();
+ if (index) {
+ index[storyId] = {
+ ...index[storyId],
+ ...update,
+ } as API_StoryEntry;
+ }
+ if (filteredIndex) {
+ filteredIndex[storyId] = {
+ ...filteredIndex[storyId],
+ ...update,
+ } as API_StoryEntry;
+ }
+ if (index || filteredIndex) {
+ await store.setState({ index, filteredIndex });
}
+ } else {
+ const { id: refId, index, filteredIndex }: any = ref;
index[storyId] = {
...index[storyId],
...update,
} as API_StoryEntry;
- await store.setState({ index });
- } else {
- const { id: refId, index }: any = ref;
- index[storyId] = {
- ...index[storyId],
+ filteredIndex[storyId] = {
+ ...filteredIndex[storyId],
...update,
} as API_StoryEntry;
- await fullAPI.updateRef(refId, { index });
+ await fullAPI.updateRef(refId, { index, filteredIndex });
}
},
updateDocs: async (
@@ -611,22 +631,33 @@ export const init: ModuleFn = ({
ref?: API_ComposedRef
): Promise => {
if (!ref) {
- const { index } = store.getState();
- if (!index) {
- return;
+ const { index, filteredIndex } = store.getState();
+ if (index) {
+ index[docsId] = {
+ ...index[docsId],
+ ...update,
+ } as API_DocsEntry;
}
+ if (filteredIndex) {
+ filteredIndex[docsId] = {
+ ...filteredIndex[docsId],
+ ...update,
+ } as API_DocsEntry;
+ }
+ if (index || filteredIndex) {
+ await store.setState({ index, filteredIndex });
+ }
+ } else {
+ const { id: refId, index, filteredIndex }: any = ref;
index[docsId] = {
...index[docsId],
...update,
} as API_DocsEntry;
- await store.setState({ index });
- } else {
- const { id: refId, index }: any = ref;
- index[docsId] = {
- ...index[docsId],
+ filteredIndex[docsId] = {
+ ...filteredIndex[docsId],
...update,
} as API_DocsEntry;
- await fullAPI.updateRef(refId, { index });
+ await fullAPI.updateRef(refId, { index, filteredIndex });
}
},
setPreviewInitialized: async (ref) => {
diff --git a/code/core/src/manager-api/tests/refs.test.ts b/code/core/src/manager-api/tests/refs.test.ts
index c8d65baafc57..b9d39ee6a88b 100644
--- a/code/core/src/manager-api/tests/refs.test.ts
+++ b/code/core/src/manager-api/tests/refs.test.ts
@@ -291,6 +291,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": undefined,
"id": "fake",
"index": undefined,
"indexError": {
@@ -360,6 +361,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": undefined,
"id": "fake",
"index": undefined,
"indexError": {
@@ -504,6 +506,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -522,6 +525,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -601,6 +605,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -682,6 +687,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -763,6 +769,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": undefined,
"id": "fake",
"index": undefined,
"internal_index": undefined,
@@ -905,6 +912,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": undefined,
"id": "fake",
"index": undefined,
"internal_index": undefined,
@@ -987,6 +995,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -1068,6 +1077,7 @@ describe('Refs API', () => {
{
"refs": {
"fake": {
+ "filteredIndex": {},
"id": "fake",
"index": {},
"internal_index": {
@@ -1227,18 +1237,20 @@ describe('Refs API', () => {
},
};
+ const transformOptions = {
+ provider: provider as any,
+ docsOptions: {},
+ filters: {},
+ status: {},
+ };
const initialState: Partial = {
refs: {
fake: {
id: 'fake',
url: 'https://example.com',
previewInitialized: true,
- index: transformStoryIndexToStoriesHash(index, {
- provider: provider as any,
- docsOptions: {},
- filters: {},
- status: {},
- }),
+ index: transformStoryIndexToStoriesHash(index, transformOptions),
+ filteredIndex: transformStoryIndexToStoriesHash(index, transformOptions),
internal_index: index,
},
},
@@ -1261,10 +1273,10 @@ describe('Refs API', () => {
await api.setRef('fake', { storyIndex: index });
- await expect(api.getRefs().fake.index).toEqual(
+ await expect(api.getRefs().fake.filteredIndex).toEqual(
expect.objectContaining({ 'a--1': expect.anything() })
);
- await expect(api.getRefs().fake.index).not.toEqual(
+ await expect(api.getRefs().fake.filteredIndex).not.toEqual(
expect.objectContaining({ 'a--2': expect.anything() })
);
});
diff --git a/code/core/src/manager-api/tests/stories.test.ts b/code/core/src/manager-api/tests/stories.test.ts
index b652c87cd7a1..6f480061d9ec 100644
--- a/code/core/src/manager-api/tests/stories.test.ts
+++ b/code/core/src/manager-api/tests/stories.test.ts
@@ -765,10 +765,15 @@ describe('stories API', () => {
source: '',
sourceLocation: '',
type: '',
- ref: { id: 'refId', index: { 'a--1': { args: { a: 'b' } } } } as any,
+ ref: {
+ id: 'refId',
+ index: { 'a--1': { args: { a: 'b' } } },
+ filteredIndex: { 'a--1': { args: { a: 'b' } } },
+ } as any,
});
provider.channel.emit(STORY_ARGS_UPDATED, { storyId: 'a--1', args: { foo: 'bar' } });
expect(fullAPI.updateRef).toHaveBeenCalledWith('refId', {
+ filteredIndex: { 'a--1': { args: { foo: 'bar' } } },
index: { 'a--1': { args: { foo: 'bar' } } },
});
});
@@ -1539,6 +1544,7 @@ describe('stories API', () => {
})
);
});
+
it('updates state', async () => {
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
@@ -1565,9 +1571,9 @@ describe('stories API', () => {
await api.setIndex({ v: 5, entries: navigationEntries });
await api.experimental_setFilter('myCustomFilter', (item: any) => item.id.startsWith('a'));
- const { index } = store.getState();
+ const { filteredIndex } = store.getState();
- expect(index).toMatchInlineSnapshot(`
+ expect(filteredIndex).toMatchInlineSnapshot(`
{
"a": {
"children": [
@@ -1624,7 +1630,7 @@ describe('stories API', () => {
);
// empty, because there are no stories with status
- expect(store.getState().index).toMatchInlineSnapshot('{}');
+ expect(store.getState().filteredIndex).toMatchInlineSnapshot('{}');
// setting status should update the index
await api.experimental_updateStatus('a-addon-id', {
@@ -1636,7 +1642,7 @@ describe('stories API', () => {
'a--2': { status: 'success', title: 'a addon title', description: '' },
});
- expect(store.getState().index).toMatchInlineSnapshot(`
+ expect(store.getState().filteredIndex).toMatchInlineSnapshot(`
{
"a": {
"children": [
@@ -1676,9 +1682,9 @@ describe('stories API', () => {
await api.setIndex({ v: 5, entries: navigationEntries });
- const { index } = store.getState();
+ const { filteredIndex } = store.getState();
- expect(index).toMatchInlineSnapshot(`
+ expect(filteredIndex).toMatchInlineSnapshot(`
{
"a": {
"children": [
diff --git a/code/core/src/manager-api/version.ts b/code/core/src/manager-api/version.ts
index 93047b78f0e7..c9d852d16745 100644
--- a/code/core/src/manager-api/version.ts
+++ b/code/core/src/manager-api/version.ts
@@ -1 +1 @@
-export const version = '8.5.0-alpha.19';
+export const version = '8.5.0-beta.2';
diff --git a/code/core/src/manager/components/panel/Panel.tsx b/code/core/src/manager/components/panel/Panel.tsx
index 701cd94925e3..dc80c20949d2 100644
--- a/code/core/src/manager/components/panel/Panel.tsx
+++ b/code/core/src/manager/components/panel/Panel.tsx
@@ -60,7 +60,7 @@ export const AddonPanel = React.memo<{
return (
({
const isActive = rp.path.includes(`tab=${tab.id}`);
return (
{
rp.applyQueryParams({ tab: tabIdToApply });
@@ -146,7 +146,7 @@ export const ToolbarComp = React.memo(function ToolbarComp({
{tabs.map((tab, index) => {
return (
{
api.applyQueryParams({ tab: tab.id === 'canvas' ? undefined : tab.id });
diff --git a/code/core/src/manager/components/sidebar/Explorer.stories.tsx b/code/core/src/manager/components/sidebar/Explorer.stories.tsx
index 908ead2be9f3..34adad0a32a0 100644
--- a/code/core/src/manager/components/sidebar/Explorer.stories.tsx
+++ b/code/core/src/manager/components/sidebar/Explorer.stories.tsx
@@ -34,7 +34,7 @@ const simple: Record = {
url: 'iframe.html',
previewInitialized: true,
// @ts-expect-error (invalid input)
- index: mockDataset.withRoot,
+ filteredIndex: mockDataset.withRoot,
},
};
@@ -47,7 +47,7 @@ const withRefs: Record = {
previewInitialized: true,
type: 'auto-inject',
// @ts-expect-error (invalid input)
- index: mockDataset.noRoot,
+ filteredIndex: mockDataset.noRoot,
},
injected: {
id: 'injected',
@@ -56,7 +56,7 @@ const withRefs: Record = {
previewInitialized: false,
type: 'auto-inject',
// @ts-expect-error (invalid input)
- index: mockDataset.noRoot,
+ filteredIndex: mockDataset.noRoot,
},
unknown: {
id: 'unknown',
@@ -65,7 +65,7 @@ const withRefs: Record = {
previewInitialized: true,
type: 'unknown',
// @ts-expect-error (invalid input)
- index: mockDataset.noRoot,
+ filteredIndex: mockDataset.noRoot,
},
lazy: {
id: 'lazy',
@@ -74,7 +74,7 @@ const withRefs: Record = {
previewInitialized: false,
type: 'lazy',
// @ts-expect-error (invalid input)
- index: mockDataset.withRoot,
+ filteredIndex: mockDataset.withRoot,
},
};
diff --git a/code/core/src/manager/components/sidebar/LegacyRender.tsx b/code/core/src/manager/components/sidebar/LegacyRender.tsx
index 82bc83bd1b34..f8afa4317f7f 100644
--- a/code/core/src/manager/components/sidebar/LegacyRender.tsx
+++ b/code/core/src/manager/components/sidebar/LegacyRender.tsx
@@ -32,7 +32,7 @@ const TitleWrapper = styled.div<{ crashed?: boolean }>(({ crashed, theme }) => (
const DescriptionWrapper = styled.div(({ theme }) => ({
fontSize: theme.typography.size.s1,
- color: theme.barTextColor,
+ color: theme.textMutedColor,
}));
const Progress = styled(ProgressSpinner)({
diff --git a/code/core/src/manager/components/sidebar/Refs.stories.tsx b/code/core/src/manager/components/sidebar/Refs.stories.tsx
index a042970beadc..3b6e37efce50 100644
--- a/code/core/src/manager/components/sidebar/Refs.stories.tsx
+++ b/code/core/src/manager/components/sidebar/Refs.stories.tsx
@@ -37,11 +37,11 @@ export default {
};
const { menu } = standardHeaderData;
-const index = mockDataset.withRoot;
+const filteredIndex = mockDataset.withRoot;
const storyId = '1-12-121';
-export const simpleData = { menu, index, storyId };
-export const loadingData = { menu, index: {} };
+export const simpleData = { menu, filteredIndex, storyId };
+export const loadingData = { menu, filteredIndex: {} };
// @ts-expect-error (non strict)
const indexError: Error = (() => {
@@ -60,14 +60,14 @@ const refs: Record = {
previewInitialized: false,
type: 'lazy',
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
},
empty: {
id: 'empty',
title: 'It is empty because no stories were loaded',
url: 'https://example.com',
type: 'lazy',
- index: {},
+ filteredIndex: {},
previewInitialized: false,
},
startInjected_unknown: {
@@ -77,7 +77,7 @@ const refs: Record = {
type: 'unknown',
previewInitialized: false,
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
},
startInjected_loading: {
id: 'startInjected_loading',
@@ -86,7 +86,7 @@ const refs: Record = {
type: 'auto-inject',
previewInitialized: false,
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
},
startInjected_ready: {
id: 'startInjected_ready',
@@ -95,7 +95,7 @@ const refs: Record = {
type: 'auto-inject',
previewInitialized: true,
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
},
versions: {
id: 'versions',
@@ -103,7 +103,7 @@ const refs: Record = {
url: 'https://example.com',
type: 'lazy',
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
versions: { '1.0.0': 'https://example.com/v1', '2.0.0': 'https://example.com' },
previewInitialized: true,
},
@@ -113,7 +113,7 @@ const refs: Record = {
url: 'https://example.com',
type: 'lazy',
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
versions: { '1.0.0': 'https://example.com/v1', '2.0.0': 'https://example.com/v2' },
previewInitialized: true,
},
@@ -138,7 +138,7 @@ const refs: Record = {
title: 'This storybook has a very very long name for some reason',
url: 'https://example.com',
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
type: 'lazy',
versions: {
'111.111.888-new': 'https://example.com/new',
@@ -154,7 +154,7 @@ const refs: Record = {
previewInitialized: false,
type: 'lazy',
// @ts-expect-error (invalid input)
- index,
+ filteredIndex,
},
};
diff --git a/code/core/src/manager/components/sidebar/Refs.tsx b/code/core/src/manager/components/sidebar/Refs.tsx
index 7412a627e889..b4d2a79ca351 100644
--- a/code/core/src/manager/components/sidebar/Refs.tsx
+++ b/code/core/src/manager/components/sidebar/Refs.tsx
@@ -81,7 +81,7 @@ export const Ref: FC = React.
const { docsOptions } = useStorybookState();
const api = useStorybookApi();
const {
- index,
+ filteredIndex: index,
id: refId,
title = refId,
isLoading: isLoadingMain,
diff --git a/code/core/src/manager/components/sidebar/SearchResults.tsx b/code/core/src/manager/components/sidebar/SearchResults.tsx
index 7573befe34a1..b7689b7d64d0 100644
--- a/code/core/src/manager/components/sidebar/SearchResults.tsx
+++ b/code/core/src/manager/components/sidebar/SearchResults.tsx
@@ -70,7 +70,7 @@ const NoResults = styled.div(({ theme }) => ({
lineHeight: `18px`,
color: theme.color.defaultText,
small: {
- color: theme.barTextColor,
+ color: theme.textMutedColor,
fontSize: `${theme.typography.size.s1}px`,
},
}));
diff --git a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx
index 53f22957f17d..0e2df01f6aed 100644
--- a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx
+++ b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx
@@ -111,7 +111,7 @@ const refs: Record = {
title: 'This is a ref',
url: 'https://example.com',
type: 'lazy',
- index,
+ filteredIndex: index,
previewInitialized: true,
},
};
@@ -123,7 +123,7 @@ const refsError = {
optimized: {
...refs.optimized,
// @ts-expect-error (non strict)
- index: undefined as IndexHash,
+ filteredIndex: undefined as IndexHash,
indexError,
},
};
@@ -132,7 +132,7 @@ const refsEmpty = {
optimized: {
...refs.optimized,
// type: 'auto-inject',
- index: {} as IndexHash,
+ filteredIndex: {} as IndexHash,
},
};
diff --git a/code/core/src/manager/components/sidebar/Sidebar.tsx b/code/core/src/manager/components/sidebar/Sidebar.tsx
index d4784cc8c562..dfec7fba7826 100644
--- a/code/core/src/manager/components/sidebar/Sidebar.tsx
+++ b/code/core/src/manager/components/sidebar/Sidebar.tsx
@@ -93,6 +93,7 @@ const useCombination = (
() => ({
[DEFAULT_REF_ID]: {
index,
+ filteredIndex: index,
indexError,
previewInitialized,
status,
diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.tsx
index 37a872810a6a..38efdae4f682 100644
--- a/code/core/src/manager/components/sidebar/SidebarBottom.tsx
+++ b/code/core/src/manager/components/sidebar/SidebarBottom.tsx
@@ -1,7 +1,7 @@
-import React, { Fragment, useEffect, useRef, useState } from 'react';
+import React, { Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { styled } from '@storybook/core/theming';
-import { type API_FilterFunction, type API_StatusValue } from '@storybook/core/types';
+import { type API_FilterFunction } from '@storybook/core/types';
import {
TESTING_MODULE_CRASH_REPORT,
@@ -119,7 +119,8 @@ export const SidebarBottomBase = ({
api.experimental_setFilter('sidebar-bottom-filter', filter);
}, [api, hasWarnings, hasErrors, warningsActive, errorsActive]);
- useEffect(() => {
+ // Register listeners before the first render
+ useLayoutEffect(() => {
const onCrashReport = ({ providerId, ...details }: TestingModuleCrashReportPayload) => {
api.updateTestProviderState(providerId, {
error: { name: 'Crashed!', message: details.error.message },
diff --git a/code/core/src/manager/components/sidebar/useHighlighted.ts b/code/core/src/manager/components/sidebar/useHighlighted.ts
index d0f06fc87aa8..37701c7b1eec 100644
--- a/code/core/src/manager/components/sidebar/useHighlighted.ts
+++ b/code/core/src/manager/components/sidebar/useHighlighted.ts
@@ -22,6 +22,25 @@ export interface HighlightedProps {
const fromSelection = (selection: Selection): Highlight =>
selection ? { itemId: selection.storyId, refId: selection.refId } : null;
+const scrollToSelector = (
+ selector: string,
+ options: {
+ containerRef?: RefObject;
+ center?: boolean;
+ attempts?: number;
+ delay?: number;
+ } = {},
+ _attempt = 1
+) => {
+ const { containerRef, center = false, attempts = 3, delay = 500 } = options;
+ const element = (containerRef ? containerRef.current : document)?.querySelector(selector);
+ if (element) {
+ scrollIntoView(element, center);
+ } else if (_attempt <= attempts) {
+ setTimeout(scrollToSelector, delay, selector, options, _attempt + 1);
+ }
+};
+
export const useHighlighted = ({
containerRef,
isLoading,
@@ -65,14 +84,10 @@ export const useHighlighted = ({
const highlight = fromSelection(selected);
updateHighlighted(highlight);
if (highlight) {
- const { itemId, refId } = highlight;
- setTimeout(() => {
- scrollIntoView(
- // @ts-expect-error (non strict)
- containerRef.current?.querySelector(`[data-item-id="${itemId}"][data-ref-id="${refId}"]`),
- true // make sure it's clearly visible by centering it
- );
- }, 0);
+ scrollToSelector(`[data-item-id="${highlight.itemId}"][data-ref-id="${highlight.refId}"]`, {
+ containerRef,
+ center: true,
+ });
}
}, [containerRef, selected, updateHighlighted]);
diff --git a/code/core/src/manager/container/Panel.tsx b/code/core/src/manager/container/Panel.tsx
index c81e489d8f68..f8cc2877cef0 100644
--- a/code/core/src/manager/container/Panel.tsx
+++ b/code/core/src/manager/container/Panel.tsx
@@ -32,6 +32,12 @@ const getPanels = (api: API) => {
if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disable) {
return;
}
+ if (
+ panel.disabled === true ||
+ (typeof panel.disabled === 'function' && panel.disabled(parameters))
+ ) {
+ return;
+ }
filteredPanels[id] = panel;
});
diff --git a/code/core/src/manager/container/Sidebar.tsx b/code/core/src/manager/container/Sidebar.tsx
index bc05d1713b59..723d9989ac10 100755
--- a/code/core/src/manager/container/Sidebar.tsx
+++ b/code/core/src/manager/container/Sidebar.tsx
@@ -27,7 +27,7 @@ const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) {
// is actually the stories hash. We should fix this up and make it consistent.
// eslint-disable-next-line @typescript-eslint/naming-convention
internal_index,
- index,
+ filteredIndex: index,
status,
indexError,
previewInitialized,
diff --git a/code/core/src/manager/utils/tree.ts b/code/core/src/manager/utils/tree.ts
index 3002eb97a77f..dfe6dfb4cf24 100644
--- a/code/core/src/manager/utils/tree.ts
+++ b/code/core/src/manager/utils/tree.ts
@@ -85,10 +85,14 @@ export const scrollIntoView = (element: Element, center = false) => {
return;
}
const { top, bottom } = element.getBoundingClientRect();
- const isInView =
- top >= 0 && bottom <= (globalWindow.innerHeight || document.documentElement.clientHeight);
-
- if (!isInView) {
+ if (!top || !bottom) {
+ return;
+ }
+ const bottomOffset =
+ document?.querySelector('#sidebar-bottom-wrapper')?.getBoundingClientRect().top ||
+ globalWindow.innerHeight ||
+ document.documentElement.clientHeight;
+ if (bottom > bottomOffset) {
element.scrollIntoView({ block: center ? 'center' : 'nearest' });
}
};
diff --git a/code/core/src/telemetry/exec-command-count-lines.test.ts b/code/core/src/telemetry/exec-command-count-lines.test.ts
new file mode 100644
index 000000000000..eacfe9f72952
--- /dev/null
+++ b/code/core/src/telemetry/exec-command-count-lines.test.ts
@@ -0,0 +1,71 @@
+import type { Transform } from 'node:stream';
+import { PassThrough } from 'node:stream';
+
+import { beforeEach, describe, expect, it, vitest } from 'vitest';
+
+// eslint-disable-next-line depend/ban-dependencies
+import { execaCommand as rawExecaCommand } from 'execa';
+
+import { execCommandCountLines } from './exec-command-count-lines';
+
+vitest.mock('execa');
+
+const execaCommand = vitest.mocked(rawExecaCommand);
+beforeEach(() => {
+ execaCommand.mockReset();
+});
+
+type ExecaStreamer = typeof Promise & {
+ stdout: Transform;
+ kill: () => void;
+};
+
+function createExecaStreamer() {
+ let resolver: () => void;
+ const promiseLike: ExecaStreamer = new Promise((aResolver, aRejecter) => {
+ resolver = aResolver;
+ }) as any;
+
+ promiseLike.stdout = new PassThrough();
+ // @ts-expect-error technically it is invalid to use resolver "before" it is assigned (but not really)
+ promiseLike.kill = resolver;
+ return promiseLike;
+}
+
+describe('execCommandCountLines', () => {
+ it('counts lines, many', async () => {
+ const streamer = createExecaStreamer();
+ execaCommand.mockReturnValue(streamer as any);
+
+ const promise = execCommandCountLines('some command');
+
+ streamer.stdout.write('First line\n');
+ streamer.stdout.write('Second line\n');
+ streamer.kill();
+
+ expect(await promise).toEqual(2);
+ });
+
+ it('counts lines, one', async () => {
+ const streamer = createExecaStreamer();
+ execaCommand.mockReturnValue(streamer as any);
+
+ const promise = execCommandCountLines('some command');
+
+ streamer.stdout.write('First line\n');
+ streamer.kill();
+
+ expect(await promise).toEqual(1);
+ });
+
+ it('counts lines, none', async () => {
+ const streamer = createExecaStreamer();
+ execaCommand.mockReturnValue(streamer as any);
+
+ const promise = execCommandCountLines('some command');
+
+ streamer.kill();
+
+ expect(await promise).toEqual(0);
+ });
+});
diff --git a/code/core/src/telemetry/exec-command-count-lines.ts b/code/core/src/telemetry/exec-command-count-lines.ts
new file mode 100644
index 000000000000..fdc4547ce464
--- /dev/null
+++ b/code/core/src/telemetry/exec-command-count-lines.ts
@@ -0,0 +1,35 @@
+import { createInterface } from 'node:readline';
+
+// eslint-disable-next-line depend/ban-dependencies
+import { execaCommand } from 'execa';
+
+/**
+ * Execute a command in the local terminal and count the lines in the result
+ *
+ * @param command The command to execute.
+ * @param options Execa options
+ * @returns The number of lines the command returned
+ */
+export async function execCommandCountLines(
+ command: string,
+ options?: Parameters[1]
+) {
+ const process = execaCommand(command, { shell: true, buffer: false, ...options });
+ if (!process.stdout) {
+ // eslint-disable-next-line local-rules/no-uncategorized-errors
+ throw new Error('Unexpected missing stdout');
+ }
+
+ let lineCount = 0;
+ const rl = createInterface(process.stdout);
+ rl.on('line', () => {
+ lineCount += 1;
+ });
+
+ // If the process errors, this will throw
+ await process;
+
+ rl.close();
+
+ return lineCount;
+}
diff --git a/code/core/src/telemetry/get-application-file-count.test.ts b/code/core/src/telemetry/get-application-file-count.test.ts
new file mode 100644
index 000000000000..7fc570689147
--- /dev/null
+++ b/code/core/src/telemetry/get-application-file-count.test.ts
@@ -0,0 +1,14 @@
+import { join } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+import { getApplicationFilesCountUncached } from './get-application-file-count';
+
+const mocksDir = join(__dirname, '..', '__mocks__');
+
+describe('getApplicationFilesCount', () => {
+ it('should find files with correct names', async () => {
+ const files = await getApplicationFilesCountUncached(mocksDir);
+ expect(files).toMatchInlineSnapshot(`2`);
+ });
+});
diff --git a/code/core/src/telemetry/get-application-file-count.ts b/code/core/src/telemetry/get-application-file-count.ts
new file mode 100644
index 000000000000..4f4807ddff00
--- /dev/null
+++ b/code/core/src/telemetry/get-application-file-count.ts
@@ -0,0 +1,32 @@
+import { sep } from 'node:path';
+
+import { execCommandCountLines } from './exec-command-count-lines';
+import { runTelemetryOperation } from './run-telemetry-operation';
+
+// We are looking for files with the word "page" or "screen" somewhere in them with these exts
+const nameMatches = ['page', 'screen'];
+const extensions = ['js', 'jsx', 'ts', 'tsx'];
+
+export const getApplicationFilesCountUncached = async (basePath: string) => {
+ const bothCasesNameMatches = nameMatches.flatMap((match) => [
+ match,
+ [match[0].toUpperCase(), ...match.slice(1)].join(''),
+ ]);
+
+ const globs = bothCasesNameMatches.flatMap((match) =>
+ extensions.map((extension) => `"${basePath}${sep}*${match}*.${extension}"`)
+ );
+
+ try {
+ const command = `git ls-files -- ${globs.join(' ')}`;
+ return await execCommandCountLines(command);
+ } catch {
+ return undefined;
+ }
+};
+
+export const getApplicationFileCount = async (path: string) => {
+ return runTelemetryOperation('applicationFiles', async () =>
+ getApplicationFilesCountUncached(path)
+ );
+};
diff --git a/code/core/src/telemetry/get-has-router-package.test.ts b/code/core/src/telemetry/get-has-router-package.test.ts
new file mode 100644
index 000000000000..8504a5bc4d84
--- /dev/null
+++ b/code/core/src/telemetry/get-has-router-package.test.ts
@@ -0,0 +1,29 @@
+import { expect, it } from 'vitest';
+
+import { getHasRouterPackage } from './get-has-router-package';
+
+it('returns true if there is a routing package in package.json', () => {
+ expect(
+ getHasRouterPackage({
+ dependencies: {
+ react: '^18',
+ 'react-dom': '^18',
+ 'react-router': '^6',
+ },
+ })
+ ).toBe(true);
+});
+
+it('returns false if there is a routing package in package.json dependencies', () => {
+ expect(
+ getHasRouterPackage({
+ dependencies: {
+ react: '^18',
+ 'react-dom': '^18',
+ },
+ devDependencies: {
+ 'react-router': '^6',
+ },
+ })
+ ).toBe(false);
+});
diff --git a/code/core/src/telemetry/get-has-router-package.ts b/code/core/src/telemetry/get-has-router-package.ts
new file mode 100644
index 000000000000..5873c3832d25
--- /dev/null
+++ b/code/core/src/telemetry/get-has-router-package.ts
@@ -0,0 +1,37 @@
+import type { PackageJson } from '../types';
+
+const routerPackages = new Set([
+ 'react-router',
+ 'react-router-dom',
+ 'remix',
+ '@tanstack/react-router',
+ 'expo-router',
+ '@reach/router',
+ 'react-easy-router',
+ '@remix-run/router',
+ 'wouter',
+ 'wouter-preact',
+ 'preact-router',
+ 'vue-router',
+ 'unplugin-vue-router',
+ '@angular/router',
+ '@solidjs/router',
+
+ // metaframeworks that imply routing
+ 'next',
+ 'react-scripts',
+ 'gatsby',
+ 'nuxt',
+ '@sveltejs/kit',
+]);
+
+/**
+ * @param packageJson The package JSON of the project
+ * @returns Boolean Does this project use a routing package?
+ */
+export function getHasRouterPackage(packageJson: PackageJson) {
+ // NOTE: we just check real dependencies; if it is in dev dependencies, it may just be an example
+ return Object.keys(packageJson?.dependencies ?? {}).some((depName) =>
+ routerPackages.has(depName)
+ );
+}
diff --git a/code/core/src/telemetry/get-portable-stories-usage.ts b/code/core/src/telemetry/get-portable-stories-usage.ts
index cd9da7f4f589..0831b484ab69 100644
--- a/code/core/src/telemetry/get-portable-stories-usage.ts
+++ b/code/core/src/telemetry/get-portable-stories-usage.ts
@@ -1,37 +1,18 @@
-// eslint-disable-next-line depend/ban-dependencies
-import { execaCommand } from 'execa';
-
-import { createFileSystemCache, resolvePathInStorybookCache } from '../common';
-
-const cache = createFileSystemCache({
- basePath: resolvePathInStorybookCache('portable-stories'),
- ns: 'storybook',
- ttl: 24 * 60 * 60 * 1000, // 24h
-});
+import { execCommandCountLines } from './exec-command-count-lines';
+import { runTelemetryOperation } from './run-telemetry-operation';
export const getPortableStoriesFileCountUncached = async (path?: string) => {
- const command = `git grep -l composeStor` + (path ? ` -- ${path}` : '');
- const { stdout } = await execaCommand(command, {
- cwd: process.cwd(),
- shell: true,
- });
-
- return stdout.split('\n').filter(Boolean).length;
+ try {
+ const command = `git grep -l composeStor` + (path ? ` -- ${path}` : '');
+ return await execCommandCountLines(command);
+ } catch (err: any) {
+ // exit code 1 if no matches are found
+ return err.exitCode === 1 ? 0 : undefined;
+ }
};
-const CACHE_KEY = 'portableStories';
export const getPortableStoriesFileCount = async (path?: string) => {
- let cached = await cache.get(CACHE_KEY);
- if (!cached) {
- try {
- const count = await getPortableStoriesFileCountUncached();
- cached = { count };
- await cache.set(CACHE_KEY, cached);
- } catch (err: any) {
- // exit code 1 if no matches are found
- const count = err.exitCode === 1 ? 0 : null;
- cached = { count };
- }
- }
- return cached.count;
+ return runTelemetryOperation('portableStories', async () =>
+ getPortableStoriesFileCountUncached(path)
+ );
};
diff --git a/code/core/src/telemetry/run-telemetry-operation.ts b/code/core/src/telemetry/run-telemetry-operation.ts
new file mode 100644
index 000000000000..29d2aee6c721
--- /dev/null
+++ b/code/core/src/telemetry/run-telemetry-operation.ts
@@ -0,0 +1,25 @@
+import { createFileSystemCache, resolvePathInStorybookCache } from '../common';
+
+const cache = createFileSystemCache({
+ basePath: resolvePathInStorybookCache('telemetry'),
+ ns: 'storybook',
+ ttl: 24 * 60 * 60 * 1000, // 24h
+});
+
+/**
+ * Run an (expensive) operation, caching the result in a FS cache for 24 hours.
+ *
+ * NOTE: if the operation returns `undefined` the value will not be cached. Use this to indicate
+ * that the operation failed.
+ */
+export const runTelemetryOperation = async (cacheKey: string, operation: () => Promise) => {
+ let cached = await cache.get(cacheKey);
+ if (cached === undefined) {
+ cached = await operation();
+ // Undefined indicates an error, setting isn't really valuable.
+ if (cached !== undefined) {
+ await cache.set(cacheKey, cached);
+ }
+ }
+ return cached;
+};
diff --git a/code/core/src/telemetry/storybook-metadata.test.ts b/code/core/src/telemetry/storybook-metadata.test.ts
index 06bf355c0688..8d73ff7b437d 100644
--- a/code/core/src/telemetry/storybook-metadata.test.ts
+++ b/code/core/src/telemetry/storybook-metadata.test.ts
@@ -12,6 +12,8 @@ const packageJsonMock: PackageJson = {
version: 'x.x.x',
};
+const packageJsonPath = process.cwd();
+
const mainJsMock: StorybookConfig = {
stories: [],
};
@@ -126,6 +128,7 @@ describe('storybook-metadata', () => {
it('should parse pnp paths for known frameworks', async () => {
const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -144,6 +147,7 @@ describe('storybook-metadata', () => {
const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -164,6 +168,7 @@ describe('storybook-metadata', () => {
it('should parse pnp paths for unknown frameworks', async () => {
const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -178,6 +183,7 @@ describe('storybook-metadata', () => {
const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -198,6 +204,7 @@ describe('storybook-metadata', () => {
const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -215,6 +222,7 @@ describe('storybook-metadata', () => {
cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('C:\\Users\\foo\\my-project');
const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -232,6 +240,7 @@ describe('storybook-metadata', () => {
it('should return frameworkOptions from mainjs', async () => {
const reactResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -250,6 +259,7 @@ describe('storybook-metadata', () => {
const angularResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: {
@@ -279,6 +289,7 @@ describe('storybook-metadata', () => {
'storybook-addon-deprecated': 'x.x.z',
},
} as PackageJson,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
addons: [
@@ -319,6 +330,7 @@ describe('storybook-metadata', () => {
const result = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
features,
@@ -332,6 +344,7 @@ describe('storybook-metadata', () => {
expect(
await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
framework: '@storybook/react-vite',
@@ -347,6 +360,7 @@ describe('storybook-metadata', () => {
it('should return the number of refs', async () => {
const res = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
refs: {
@@ -361,6 +375,7 @@ describe('storybook-metadata', () => {
it('only reports addon options for addon-essentials', async () => {
const res = await computeStorybookMetadata({
packageJson: packageJsonMock,
+ packageJsonPath,
mainConfig: {
...mainJsMock,
addons: [
@@ -395,6 +410,7 @@ describe('storybook-metadata', () => {
[metaFramework]: 'x.x.x',
},
} as PackageJson,
+ packageJsonPath,
mainConfig: mainJsMock,
});
expect(res.metaFramework).toEqual({
diff --git a/code/core/src/telemetry/storybook-metadata.ts b/code/core/src/telemetry/storybook-metadata.ts
index 75804b813fc2..5258eef0ffb7 100644
--- a/code/core/src/telemetry/storybook-metadata.ts
+++ b/code/core/src/telemetry/storybook-metadata.ts
@@ -1,3 +1,5 @@
+import { dirname } from 'node:path';
+
import {
getProjectRoot,
getStorybookConfiguration,
@@ -9,10 +11,12 @@ import type { PackageJson, StorybookConfig } from '@storybook/core/types';
import { readConfig } from '@storybook/core/csf-tools';
import { detect, getNpmVersion } from 'detect-package-manager';
-import { findPackage } from 'fd-package-json';
+import { findPackage, findPackagePath } from 'fd-package-json';
+import { getApplicationFileCount } from './get-application-file-count';
import { getChromaticVersionSpecifier } from './get-chromatic-version';
import { getFrameworkInfo } from './get-framework-info';
+import { getHasRouterPackage } from './get-has-router-package';
import { getMonorepoType } from './get-monorepo-type';
import { getPortableStoriesFileCount } from './get-portable-stories-usage';
import { getActualPackageVersion, getActualPackageVersions } from './package-json';
@@ -41,9 +45,11 @@ export const sanitizeAddonName = (name: string) => {
// Analyze a combination of information from main.js and package.json
// to provide telemetry over a Storybook project
export const computeStorybookMetadata = async ({
+ packageJsonPath,
packageJson,
mainConfig,
}: {
+ packageJsonPath: string;
packageJson: PackageJson;
mainConfig: StorybookConfig & Record;
}): Promise => {
@@ -100,6 +106,8 @@ export const computeStorybookMetadata = async ({
)
);
+ metadata.hasRouterPackage = getHasRouterPackage(packageJson);
+
const monorepoType = getMonorepoType();
if (monorepoType) {
metadata.monorepo = monorepoType;
@@ -209,11 +217,13 @@ export const computeStorybookMetadata = async ({
const storybookVersion = storybookPackages[storybookInfo.frameworkPackage]?.version;
const portableStoriesFileCount = await getPortableStoriesFileCount();
+ const applicationFileCount = await getApplicationFileCount(dirname(packageJsonPath));
return {
...metadata,
...frameworkInfo,
portableStoriesFileCount,
+ applicationFileCount,
storybookVersion,
storybookVersionSpecifier: storybookInfo.version,
language,
@@ -223,13 +233,29 @@ export const computeStorybookMetadata = async ({
};
};
+async function getPackageJsonDetails() {
+ const packageJsonPath = await findPackagePath(process.cwd());
+ if (packageJsonPath) {
+ return {
+ packageJsonPath,
+ packageJson: (await findPackage(packageJsonPath)) || {},
+ };
+ }
+
+ // If we don't find a `package.json`, we assume it "would have" been in the current working directory
+ return {
+ packageJsonPath: process.cwd(),
+ packageJson: {},
+ };
+}
+
let cachedMetadata: StorybookMetadata;
export const getStorybookMetadata = async (_configDir?: string) => {
if (cachedMetadata) {
return cachedMetadata;
}
- const packageJson = (await findPackage(process.cwd())) || {};
+ const { packageJson, packageJsonPath } = await getPackageJsonDetails();
// TODO: improve the way configDir is extracted, as a "storybook" script might not be present
// Scenarios:
// 1. user changed it to something else e.g. "storybook:dev"
@@ -243,6 +269,6 @@ export const getStorybookMetadata = async (_configDir?: string) => {
) as string)) ??
'.storybook';
const mainConfig = await loadMainConfig({ configDir });
- cachedMetadata = await computeStorybookMetadata({ mainConfig, packageJson });
+ cachedMetadata = await computeStorybookMetadata({ mainConfig, packageJson, packageJsonPath });
return cachedMetadata;
};
diff --git a/code/core/src/telemetry/types.ts b/code/core/src/telemetry/types.ts
index 757f5afc197e..e43373e5e61b 100644
--- a/code/core/src/telemetry/types.ts
+++ b/code/core/src/telemetry/types.ts
@@ -59,6 +59,7 @@ export type StorybookMetadata = {
version: string;
};
testPackages?: Record;
+ hasRouterPackage?: boolean;
hasStorybookEslint?: boolean;
hasStaticDirs?: boolean;
hasCustomWebpack?: boolean;
@@ -69,6 +70,7 @@ export type StorybookMetadata = {
usesGlobals?: boolean;
};
portableStoriesFileCount?: number;
+ applicationFileCount?: number;
};
export interface Payload {
diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts
index 47f5aec6412b..b2c7b4ac2f3c 100644
--- a/code/core/src/types/modules/addons.ts
+++ b/code/core/src/types/modules/addons.ts
@@ -5,7 +5,7 @@ import type { TestProviderConfig, TestingModuleProgressReportProgress } from '..
import type { RenderData as RouterData } from '../../router/types';
import type { ThemeVars } from '../../theming/types';
import type { API_SidebarOptions } from './api';
-import type { API_HashEntry, API_StatusState, API_StatusUpdate } from './api-stories';
+import type { API_HashEntry, API_StoryEntry } from './api-stories';
import type {
Args,
ArgsStoryFn as ArgsStoryFnForFramework,
@@ -392,7 +392,7 @@ export interface Addon_BaseType {
/** @unstable */
paramKey?: string;
/** @unstable */
- disabled?: boolean;
+ disabled?: boolean | ((parameters: API_StoryEntry['parameters']) => boolean);
/** @unstable */
hidden?: boolean;
}
diff --git a/code/core/src/types/modules/api.ts b/code/core/src/types/modules/api.ts
index 30edbe36ad29..f5e2d6f32100 100644
--- a/code/core/src/types/modules/api.ts
+++ b/code/core/src/types/modules/api.ts
@@ -155,6 +155,7 @@ export type API_StoryMapper = (ref: API_ComposedRef, story: SetStoriesStory) =>
export interface API_LoadedRefData {
index?: API_IndexHash;
+ filteredIndex?: API_IndexHash;
indexError?: Error;
previewInitialized: boolean;
}
@@ -180,6 +181,7 @@ export type API_ComposedRefUpdate = Partial<
| 'type'
| 'expanded'
| 'index'
+ | 'filteredIndex'
| 'versions'
| 'loginUrl'
| 'version'
diff --git a/code/deprecated/builder-manager/package.json b/code/deprecated/builder-manager/package.json
index a331b3fa6bb3..9998fd59a573 100644
--- a/code/deprecated/builder-manager/package.json
+++ b/code/deprecated/builder-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/builder-manager",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook manager builder",
"keywords": [
"storybook"
diff --git a/code/deprecated/channels/package.json b/code/deprecated/channels/package.json
index a4f681e62e04..988ed72abb2c 100644
--- a/code/deprecated/channels/package.json
+++ b/code/deprecated/channels/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/channels",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/deprecated/client-logger/package.json b/code/deprecated/client-logger/package.json
index 86f72277da7d..f4de8eedbefa 100644
--- a/code/deprecated/client-logger/package.json
+++ b/code/deprecated/client-logger/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/client-logger",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/deprecated/components/package.json b/code/deprecated/components/package.json
index 4f2752072b53..28de35eec46a 100644
--- a/code/deprecated/components/package.json
+++ b/code/deprecated/components/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/components",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook Components",
"keywords": [
"storybook"
diff --git a/code/deprecated/core-common/package.json b/code/deprecated/core-common/package.json
index 6dfb83618c4f..372be0dfea26 100644
--- a/code/deprecated/core-common/package.json
+++ b/code/deprecated/core-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/core-common",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
diff --git a/code/deprecated/core-events/package.json b/code/deprecated/core-events/package.json
index 20dff0d9ce2d..bcf2157df22c 100644
--- a/code/deprecated/core-events/package.json
+++ b/code/deprecated/core-events/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/core-events",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Event names used in storybook core",
"keywords": [
"storybook"
diff --git a/code/deprecated/core-server/package.json b/code/deprecated/core-server/package.json
index 6b77123b1c36..9cf49f637df2 100644
--- a/code/deprecated/core-server/package.json
+++ b/code/deprecated/core-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/core-server",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
diff --git a/code/deprecated/csf-tools/package.json b/code/deprecated/csf-tools/package.json
index 74be5d5e9eb1..3c0aa71c00c3 100644
--- a/code/deprecated/csf-tools/package.json
+++ b/code/deprecated/csf-tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/csf-tools",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Parse and manipulate CSF and Storybook config files",
"keywords": [
"storybook"
diff --git a/code/deprecated/docs-tools/package.json b/code/deprecated/docs-tools/package.json
index fb5a9c6bc527..002c500c1cb0 100644
--- a/code/deprecated/docs-tools/package.json
+++ b/code/deprecated/docs-tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/docs-tools",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Shared utility functions for frameworks to implement docs",
"keywords": [
"storybook"
diff --git a/code/deprecated/manager-api/package.json b/code/deprecated/manager-api/package.json
index 4afb8ef29a3e..a203750e5675 100644
--- a/code/deprecated/manager-api/package.json
+++ b/code/deprecated/manager-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/manager-api",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook Manager API & Context",
"keywords": [
"storybook"
diff --git a/code/deprecated/manager/package.json b/code/deprecated/manager/package.json
index a3890fa1e4f7..b8ad3dc9d18b 100644
--- a/code/deprecated/manager/package.json
+++ b/code/deprecated/manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/manager",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook UI",
"keywords": [
"storybook"
diff --git a/code/deprecated/node-logger/package.json b/code/deprecated/node-logger/package.json
index 7afefb239034..9b854e1e73d8 100644
--- a/code/deprecated/node-logger/package.json
+++ b/code/deprecated/node-logger/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/node-logger",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/deprecated/preview-api/package.json b/code/deprecated/preview-api/package.json
index dbd86a65cc29..682e0a595d1f 100644
--- a/code/deprecated/preview-api/package.json
+++ b/code/deprecated/preview-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preview-api",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/deprecated/preview/package.json b/code/deprecated/preview/package.json
index c7e152ac19b8..91cad5bc2737 100644
--- a/code/deprecated/preview/package.json
+++ b/code/deprecated/preview/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preview",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/deprecated/router/package.json b/code/deprecated/router/package.json
index bb6305160f05..f5c22b5ef835 100644
--- a/code/deprecated/router/package.json
+++ b/code/deprecated/router/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/router",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook Router",
"keywords": [
"storybook"
diff --git a/code/deprecated/telemetry/package.json b/code/deprecated/telemetry/package.json
index 6a54c1ff5729..d5b569580464 100644
--- a/code/deprecated/telemetry/package.json
+++ b/code/deprecated/telemetry/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/telemetry",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Telemetry logging for crash reports and usage statistics",
"keywords": [
"storybook"
diff --git a/code/deprecated/theming/package.json b/code/deprecated/theming/package.json
index fce7c7f67bdb..e7078e2c55f3 100644
--- a/code/deprecated/theming/package.json
+++ b/code/deprecated/theming/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/theming",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook Components",
"keywords": [
"storybook"
diff --git a/code/deprecated/types/package.json b/code/deprecated/types/package.json
index 1e6eb337de10..0bbcb006eae1 100644
--- a/code/deprecated/types/package.json
+++ b/code/deprecated/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/types",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Core Storybook TS Types",
"keywords": [
"storybook"
diff --git a/code/e2e-tests/composition.spec.ts b/code/e2e-tests/composition.spec.ts
index 15bc6c4c4ed9..ec5a96e5898d 100644
--- a/code/e2e-tests/composition.spec.ts
+++ b/code/e2e-tests/composition.spec.ts
@@ -27,8 +27,12 @@ test.describe('composition', () => {
await page.locator('[id="storybook\\@7\\.6\\.18_components-badge"]').click();
await expect(
- page.locator('[id="storybook\\@7\\.6\\.18_components-badge--default"]')
- ).toBeVisible();
+ page
+ .locator('iframe[title="storybook-ref-storybook\\@7\\.6\\.18"]')
+ .contentFrame()
+ .locator('#storybook-root')
+ .getByText('Default')
+ ).toBeVisible({ timeout: 15000 });
// Expect composed stories `to be available in the search
await page.getByPlaceholder('Find components').fill('Button');
diff --git a/code/e2e-tests/preview-api.spec.ts b/code/e2e-tests/preview-api.spec.ts
index cfabaf89674c..31b566027fd9 100644
--- a/code/e2e-tests/preview-api.spec.ts
+++ b/code/e2e-tests/preview-api.spec.ts
@@ -65,6 +65,7 @@ test.describe('preview-api', () => {
const root = sbPage.previewRoot();
+ await sbPage.viewAddonPanel('Controls');
const labelControl = sbPage.page.locator('#control-label');
await expect(root.getByText('Loaded. Click me')).toBeVisible();
diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts
index 057dc91f715d..d9faad22939a 100644
--- a/code/e2e-tests/util.ts
+++ b/code/e2e-tests/util.ts
@@ -1,3 +1,4 @@
+/* eslint-disable local-rules/no-uncategorized-errors */
import { toId } from '@storybook/csf';
import type { Expect, Page } from '@playwright/test';
@@ -61,6 +62,7 @@ export class SbPage {
await this.expect(selected).toHaveAttribute('data-selected', 'true');
await this.previewRoot();
+ await this.waitUntilLoaded();
}
async navigateToUnattachedDocs(title: string, name = 'docs') {
@@ -80,7 +82,25 @@ export class SbPage {
const selected = storyLink;
await this.expect(selected).toHaveAttribute('data-selected', 'true');
- await this.previewRoot();
+ await this.waitForStoryLoaded();
+ }
+
+ async waitForStoryLoaded() {
+ try {
+ const root = this.previewRoot();
+ // Wait until there is at least one child (a story element) in the preview iframe
+ await root.locator(':scope > *').first().waitFor({
+ state: 'attached',
+ timeout: 10000,
+ });
+ } catch (error: any) {
+ if (error.name === 'TimeoutError') {
+ throw new Error(
+ 'The Storybook iframe did not have children within the specified timeout. Did the story load correctly?'
+ );
+ }
+ throw error;
+ }
}
async waitUntilLoaded() {
@@ -112,6 +132,8 @@ export class SbPage {
const storyLoadingPage = root.locator('.sb-preparing-story');
await docsLoadingPage.waitFor({ state: 'hidden' });
await storyLoadingPage.waitFor({ state: 'hidden' });
+
+ await this.waitForStoryLoaded();
}
previewIframe() {
@@ -143,6 +165,30 @@ export class SbPage {
getCanvasBodyElement() {
return this.previewIframe().locator('body');
}
+
+ // utility to try and decrease flake
+ async retryTimes(
+ fn: () => Promise,
+ options?: {
+ retries?: number;
+ delay?: number;
+ }
+ ): Promise {
+ let attempts = 0;
+ const { retries = 3, delay = 0 } = options || {};
+ while (attempts < retries) {
+ try {
+ await fn();
+ return;
+ } catch (error) {
+ attempts++;
+ if (attempts === retries) {
+ throw error;
+ }
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+ }
}
const templateName: keyof typeof allTemplates = process.env.STORYBOOK_TEMPLATE_NAME || ('' as any);
diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json
index 7308e70929ef..576788920f79 100644
--- a/code/frameworks/angular/package.json
+++ b/code/frameworks/angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
"keywords": [
"storybook",
diff --git a/code/frameworks/angular/src/builders/build-storybook/schema.json b/code/frameworks/angular/src/builders/build-storybook/schema.json
index 6d976bf5dd0d..77e455cdb1be 100644
--- a/code/frameworks/angular/src/builders/build-storybook/schema.json
+++ b/code/frameworks/angular/src/builders/build-storybook/schema.json
@@ -77,6 +77,11 @@
"description": "Write Webpack Stats JSON to disk",
"default": false
},
+ "statsJson": {
+ "type": ["boolean", "string"],
+ "description": "Write stats JSON to disk",
+ "default": false
+ },
"previewUrl": {
"type": "string",
"description": "Disables the default storybook preview and lets you use your own"
diff --git a/code/frameworks/angular/src/builders/start-storybook/schema.json b/code/frameworks/angular/src/builders/start-storybook/schema.json
index 729c3ea5b724..64d53bd5481e 100644
--- a/code/frameworks/angular/src/builders/start-storybook/schema.json
+++ b/code/frameworks/angular/src/builders/start-storybook/schema.json
@@ -139,6 +139,11 @@
"description": "Write Webpack Stats JSON to disk",
"default": false
},
+ "statsJson": {
+ "type": ["boolean", "string"],
+ "description": "Write stats JSON to disk",
+ "default": false
+ },
"previewUrl": {
"type": "string",
"description": "Disables the default storybook preview and lets you use your own"
diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json
index 96f7f5cac31c..a397b8c1d5a6 100644
--- a/code/frameworks/ember/package.json
+++ b/code/frameworks/ember/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/ember",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember",
"bugs": {
diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json
index 6eafe4815adc..daf389813ec1 100644
--- a/code/frameworks/experimental-nextjs-vite/package.json
+++ b/code/frameworks/experimental-nextjs-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/experimental-nextjs-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Next.js and Vite",
"keywords": [
"storybook",
@@ -97,6 +97,7 @@
"dependencies": {
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
+ "@storybook/react-vite": "workspace:*",
"@storybook/test": "workspace:*",
"styled-jsx": "5.1.6",
"vite-plugin-storybook-nextjs": "^1.1.0"
diff --git a/code/frameworks/experimental-nextjs-vite/src/preset.ts b/code/frameworks/experimental-nextjs-vite/src/preset.ts
index 0a725be35804..633f62a5dceb 100644
--- a/code/frameworks/experimental-nextjs-vite/src/preset.ts
+++ b/code/frameworks/experimental-nextjs-vite/src/preset.ts
@@ -4,6 +4,7 @@ import path from 'node:path';
import type { PresetProperty } from 'storybook/internal/types';
import type { StorybookConfigVite } from '@storybook/builder-vite';
+import { viteFinal as reactViteFinal } from '@storybook/react-vite/preset';
import { dirname, join } from 'path';
import vitePluginStorybookNextjs from 'vite-plugin-storybook-nextjs';
@@ -34,11 +35,13 @@ export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry =
};
export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, options) => {
- config.plugins = config.plugins || [];
+ const reactConfig = await reactViteFinal(config, options);
+ const { plugins = [] } = reactConfig;
+
const { nextConfigPath } = await options.presets.apply('frameworkOptions');
const nextDir = nextConfigPath ? path.dirname(nextConfigPath) : undefined;
- config.plugins.push(vitePluginStorybookNextjs({ dir: nextDir }));
+ plugins.push(vitePluginStorybookNextjs({ dir: nextDir }));
- return config;
+ return reactConfig;
};
diff --git a/code/frameworks/experimental-nextjs-vite/src/routing/app-router-provider.tsx b/code/frameworks/experimental-nextjs-vite/src/routing/app-router-provider.tsx
index 68e01381948c..eec03995d266 100644
--- a/code/frameworks/experimental-nextjs-vite/src/routing/app-router-provider.tsx
+++ b/code/frameworks/experimental-nextjs-vite/src/routing/app-router-provider.tsx
@@ -44,8 +44,6 @@ function getSelectedParams(currentTree: FlightRouterState, params: Params = {}):
continue;
}
- // Ensure catchAll and optional catchall are turned into an array
-
// Ensure catchAll and optional catchall are turned into an array
const isCatchAll = isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc');
@@ -82,6 +80,16 @@ export const AppRouterProvider: React.FC
@@ -106,10 +114,18 @@ export const AppRouterProvider: React.FC
= v15.1.1
+ parentTree: tree,
+ parentCacheNode: newLazyCacheNode,
+ // END
url: pathname,
+ loading: null,
}}
>
{children}
diff --git a/code/frameworks/experimental-nextjs-vite/src/types.ts b/code/frameworks/experimental-nextjs-vite/src/types.ts
index 8de91a4430d9..0221787dccb6 100644
--- a/code/frameworks/experimental-nextjs-vite/src/types.ts
+++ b/code/frameworks/experimental-nextjs-vite/src/types.ts
@@ -1,9 +1,7 @@
-import type {
- CompatibleString,
- StorybookConfig as StorybookConfigBase,
-} from 'storybook/internal/types';
+import type { CompatibleString } from 'storybook/internal/types';
-import type { BuilderOptions, StorybookConfigVite } from '@storybook/builder-vite';
+import type { BuilderOptions } from '@storybook/builder-vite';
+import type { StorybookConfig as StorybookConfigReactVite } from '@storybook/react-vite';
type FrameworkName = CompatibleString<'@storybook/experimental-nextjs-vite'>;
type BuilderName = CompatibleString<'@storybook/builder-vite'>;
@@ -21,7 +19,7 @@ type StorybookConfigFramework = {
name: FrameworkName;
options: FrameworkOptions;
};
- core?: StorybookConfigBase['core'] & {
+ core?: StorybookConfigReactVite['core'] & {
builder?:
| BuilderName
| {
@@ -32,9 +30,5 @@ type StorybookConfigFramework = {
};
/** The interface for Storybook configuration in `main.ts` files. */
-export type StorybookConfig = Omit<
- StorybookConfigBase,
- keyof StorybookConfigVite | keyof StorybookConfigFramework
-> &
- StorybookConfigVite &
- StorybookConfigFramework & {};
+export type StorybookConfig = Omit &
+ StorybookConfigFramework;
diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json
index 83c716b1076e..d60457db363a 100644
--- a/code/frameworks/html-vite/package.json
+++ b/code/frameworks/html-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/html-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json
index a0709bee9e39..754cc9019674 100644
--- a/code/frameworks/html-webpack5/package.json
+++ b/code/frameworks/html-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/html-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json
index 4e76bf20686d..87a35ff52905 100644
--- a/code/frameworks/nextjs/package.json
+++ b/code/frameworks/nextjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/nextjs",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Next.js",
"keywords": [
"storybook",
diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts
index 76edac25c81c..4b10922c9df9 100644
--- a/code/frameworks/nextjs/src/config/webpack.ts
+++ b/code/frameworks/nextjs/src/config/webpack.ts
@@ -1,8 +1,7 @@
import type { NextConfig } from 'next';
import type { Configuration as WebpackConfig } from 'webpack';
-import { DefinePlugin } from 'webpack';
-import { addScopedAlias, resolveNextConfig, setAlias } from '../utils';
+import { addScopedAlias, resolveNextConfig } from '../utils';
const tryResolve = (path: string) => {
try {
@@ -48,12 +47,15 @@ export const configureConfig = async ({
addScopedAlias(baseConfig, 'react-dom/server', 'next/dist/compiled/react-dom/server');
}
- setupRuntimeConfig(baseConfig, nextConfig);
+ await setupRuntimeConfig(baseConfig, nextConfig);
return nextConfig;
};
-const setupRuntimeConfig = (baseConfig: WebpackConfig, nextConfig: NextConfig): void => {
+const setupRuntimeConfig = async (
+ baseConfig: WebpackConfig,
+ nextConfig: NextConfig
+): Promise => {
const definePluginConfig: Record = {
// this mimics what nextjs does client side
// https://github.com/vercel/next.js/blob/57702cb2a9a9dba4b552e0007c16449cf36cfb44/packages/next/client/index.tsx#L101
@@ -67,5 +69,7 @@ const setupRuntimeConfig = (baseConfig: WebpackConfig, nextConfig: NextConfig):
definePluginConfig['process.env.__NEXT_NEW_LINK_BEHAVIOR'] = newNextLinkBehavior;
- baseConfig.plugins?.push(new DefinePlugin(definePluginConfig));
+ // Load DefinePlugin with a dynamic import to ensure that Next.js can first
+ // replace webpack with its own internal instance, and we get that here.
+ baseConfig.plugins?.push(new (await import('webpack')).default.DefinePlugin(definePluginConfig));
};
diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts
index 3463910175e1..6b90ae543a22 100644
--- a/code/frameworks/nextjs/src/preset.ts
+++ b/code/frameworks/nextjs/src/preset.ts
@@ -10,31 +10,29 @@ import type { ConfigItem, PluginItem, TransformOptions } from '@babel/core';
import { loadPartialConfig } from '@babel/core';
import semver from 'semver';
-import { configureAliases } from './aliases/webpack';
-import { configureBabelLoader } from './babel/loader';
import nextBabelPreset from './babel/preset';
-import { configureCompatibilityAliases } from './compatibility/compatibility-map';
import { configureConfig } from './config/webpack';
-import { configureCss } from './css/webpack';
-import { configureNextExportMocks } from './export-mocks/webpack';
-import { configureFastRefresh } from './fastRefresh/webpack';
import TransformFontImports from './font/babel';
-import { configureNextFont } from './font/webpack/configureNextFont';
-import { configureImages } from './images/webpack';
-import { configureImports } from './imports/webpack';
-import { configureNodePolyfills } from './nodePolyfills/webpack';
-import { configureRSC } from './rsc/webpack';
-import { configureStyledJsx } from './styledJsx/webpack';
-import { configureSWCLoader } from './swc/loader';
import type { FrameworkOptions, StorybookConfig } from './types';
-import { configureRuntimeNextjsVersionResolution, getNextjsVersion } from './utils';
export const addons: PresetProperty<'addons'> = [
dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))),
];
export const core: PresetProperty<'core'> = async (config, options) => {
- const framework = await options.presets.apply('framework');
+ const framework = await options.presets.apply('framework');
+
+ // Load the Next.js configuration before we need it in webpackFinal (below).
+ // This gives Next.js an opportunity to override some of webpack's internals
+ // (see next/dist/server/config-utils.js) before @storybook/builder-webpack5
+ // starts to use it. Without this, webpack's file system cache (fsCache: true)
+ // does not work.
+ await configureConfig({
+ // Pass in a dummy webpack config object for now, since we don't want to
+ // modify the real one yet. We pass in the real one in webpackFinal.
+ baseConfig: {},
+ nextConfigPath: typeof framework === 'string' ? undefined : framework.options.nextConfigPath,
+ });
return {
...config,
@@ -144,6 +142,22 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig,
nextConfigPath,
});
+ // Use dynamic imports to ensure these modules that use webpack load after
+ // Next.js has been configured (above), and has replaced webpack with its precompiled
+ // version.
+ const { configureNextFont } = await import('./font/webpack/configureNextFont');
+ const { configureRuntimeNextjsVersionResolution, getNextjsVersion } = await import('./utils');
+ const { configureImports } = await import('./imports/webpack');
+ const { configureCss } = await import('./css/webpack');
+ const { configureImages } = await import('./images/webpack');
+ const { configureStyledJsx } = await import('./styledJsx/webpack');
+ const { configureNodePolyfills } = await import('./nodePolyfills/webpack');
+ const { configureAliases } = await import('./aliases/webpack');
+ const { configureFastRefresh } = await import('./fastRefresh/webpack');
+ const { configureRSC } = await import('./rsc/webpack');
+ const { configureSWCLoader } = await import('./swc/loader');
+ const { configureBabelLoader } = await import('./babel/loader');
+
const babelRCPath = join(getProjectRoot(), '.babelrc');
const babelConfigPath = join(getProjectRoot(), 'babel.config.js');
const hasBabelConfig = existsSync(babelRCPath) || existsSync(babelConfigPath);
diff --git a/code/frameworks/nextjs/src/routing/app-router-provider.tsx b/code/frameworks/nextjs/src/routing/app-router-provider.tsx
index 36e980b1a5fe..3f06a6668648 100644
--- a/code/frameworks/nextjs/src/routing/app-router-provider.tsx
+++ b/code/frameworks/nextjs/src/routing/app-router-provider.tsx
@@ -80,6 +80,16 @@ export const AppRouterProvider: React.FC
@@ -104,8 +114,16 @@ export const AppRouterProvider: React.FC
= v15.1.1
+ parentTree: tree,
+ parentCacheNode: newLazyCacheNode,
+ // END
url: pathname,
loading: null,
}}
diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json
index 681d0ec88df4..4fc5571a5f1b 100644
--- a/code/frameworks/preact-vite/package.json
+++ b/code/frameworks/preact-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preact-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json
index a26425c1e6ca..c1404318b69e 100644
--- a/code/frameworks/preact-webpack5/package.json
+++ b/code/frameworks/preact-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preact-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [
"storybook"
diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json
index 6a777924f4c0..147f1c243bed 100644
--- a/code/frameworks/react-native-web-vite/package.json
+++ b/code/frameworks/react-native-web-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-native-web-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Develop react-native components an isolated web environment with hot reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/react-native-web-vite/src/preset.ts b/code/frameworks/react-native-web-vite/src/preset.ts
index 3ccd4240bf65..44e847171dda 100644
--- a/code/frameworks/react-native-web-vite/src/preset.ts
+++ b/code/frameworks/react-native-web-vite/src/preset.ts
@@ -1,4 +1,3 @@
-// @ts-expect-error FIXME
import { viteFinal as reactViteFinal } from '@storybook/react-vite/preset';
import { esbuildFlowPlugin, flowPlugin } from '@bunchtogether/vite-plugin-flow';
diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json
index c67870c9567d..a1edb0aa9bc6 100644
--- a/code/frameworks/react-vite/package.json
+++ b/code/frameworks/react-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.",
"keywords": [
"storybook"
@@ -35,6 +35,16 @@
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "*": [
+ "dist/index.d.ts"
+ ],
+ "preset": [
+ "dist/preset.d.ts"
+ ]
+ }
+ },
"files": [
"dist/**/*",
"README.md",
diff --git a/code/frameworks/react-vite/src/preset.ts b/code/frameworks/react-vite/src/preset.ts
index cef1a270f33b..a01721dadacc 100644
--- a/code/frameworks/react-vite/src/preset.ts
+++ b/code/frameworks/react-vite/src/preset.ts
@@ -12,7 +12,7 @@ export const core: PresetProperty<'core'> = {
renderer: getAbsolutePath('@storybook/react'),
};
-export const viteFinal: StorybookConfig['viteFinal'] = async (config, { presets }) => {
+export const viteFinal: NonNullable = async (config, { presets }) => {
const { plugins = [] } = config;
// Add docgen plugin
diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json
index eed70f2c4fb3..7db983e04e07 100644
--- a/code/frameworks/react-webpack5/package.json
+++ b/code/frameworks/react-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json
index a89088118de4..5a5a024bad2a 100644
--- a/code/frameworks/server-webpack5/package.json
+++ b/code/frameworks/server-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/server-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json
index bd0c531637f8..c93bda611e59 100644
--- a/code/frameworks/svelte-vite/package.json
+++ b/code/frameworks/svelte-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json
index 198ff72986af..458e2c9290bc 100644
--- a/code/frameworks/svelte-webpack5/package.json
+++ b/code/frameworks/svelte-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json
index cd3d90298520..11ad71aaf0c2 100644
--- a/code/frameworks/sveltekit/package.json
+++ b/code/frameworks/sveltekit/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/sveltekit",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for SvelteKit",
"keywords": [
"storybook",
diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json
index bab6eddec078..c1f245b7514c 100644
--- a/code/frameworks/vue3-vite/package.json
+++ b/code/frameworks/vue3-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/vue3-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json
index e2968fb6cf99..73e8c8033baf 100644
--- a/code/frameworks/vue3-webpack5/package.json
+++ b/code/frameworks/vue3-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/vue3-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json
index d918f543bc11..7fe54f06d4ef 100644
--- a/code/frameworks/web-components-vite/package.json
+++ b/code/frameworks/web-components-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/web-components-vite",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json
index 57b0679e9f4f..5db1d433cdf9 100644
--- a/code/frameworks/web-components-webpack5/package.json
+++ b/code/frameworks/web-components-webpack5/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/web-components-webpack5",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.",
"keywords": [
"lit",
diff --git a/code/lib/blocks/package.json b/code/lib/blocks/package.json
index 430cfd9969d7..02778aa210a0 100644
--- a/code/lib/blocks/package.json
+++ b/code/lib/blocks/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/blocks",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Doc Blocks",
"keywords": [
"storybook"
diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json
index eb660ccf8b08..99d8537a512d 100644
--- a/code/lib/cli-sb/package.json
+++ b/code/lib/cli-sb/package.json
@@ -1,6 +1,6 @@
{
"name": "sb",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook CLI",
"keywords": [
"storybook"
diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json
index 8a9f87817b3d..8b7d3aae3a32 100644
--- a/code/lib/cli-storybook/package.json
+++ b/code/lib/cli-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/cli",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook CLI",
"keywords": [
"storybook"
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
index 71110733d896..7bd831d082b7 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
@@ -141,7 +141,8 @@ describe('addonA11yAddonTest', () => {
`;
vi.mocked(readFileSync).mockReturnValue(source);
- const transformedCode = transformSetupFile(setupFile);
+ const s = readFileSync(setupFile, 'utf8');
+ const transformedCode = transformSetupFile(s);
expect(transformedCode).toMatchInlineSnapshot(`
"import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { beforeAll } from 'vitest';
@@ -169,7 +170,35 @@ describe('addonA11yAddonTest', () => {
`;
vi.mocked(readFileSync).mockReturnValue(source);
- const transformedCode = transformSetupFile(setupFile);
+ const s = readFileSync(setupFile, 'utf8');
+ const transformedCode = transformSetupFile(s);
+ expect(transformedCode).toMatchInlineSnapshot(`
+ "import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
+ import { beforeAll } from 'vitest';
+ import { setProjectAnnotations } from 'storybook';
+ import * as projectAnnotations from './preview';
+
+ const project = setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
+
+ beforeAll(project.beforeAll);"
+ `);
+ });
+
+ it('should transform setup file correctly - project annotation is not an array', () => {
+ const setupFile = '/path/to/vitest.setup.ts';
+ const source = dedent`
+ import { beforeAll } from 'vitest';
+ import { setProjectAnnotations } from 'storybook';
+ import * as projectAnnotations from './preview';
+
+ const project = setProjectAnnotations(projectAnnotations);
+
+ beforeAll(project.beforeAll);
+ `;
+ vi.mocked(readFileSync).mockReturnValue(source);
+
+ const s = readFileSync(setupFile, 'utf8');
+ const transformedCode = transformSetupFile(s);
expect(transformedCode).toMatchInlineSnapshot(`
"import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { beforeAll } from 'vitest';
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts
index ae848cf57689..6b85146407f6 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts
@@ -22,8 +22,7 @@ interface AddonA11yAddonTestOptions {
*/
export const addonA11yAddonTest: Fix = {
id: 'addonA11yAddonTest',
- // TODO: Change to the correct version after testing
- versionRange: ['<8.5.0', '*'],
+ versionRange: ['<8.5.0', '>=8.5.0'],
promptType(result) {
if (result.setupFile === null) {
@@ -53,7 +52,11 @@ export const addonA11yAddonTest: Fix = {
try {
if (vitestSetupFile) {
- const transformedSetupCode = transformSetupFile(vitestSetupFile);
+ const source = readFileSync(vitestSetupFile, 'utf8');
+ if (source.includes('@storybook/addon-a11y')) {
+ return null;
+ }
+ const transformedSetupCode = transformSetupFile(source);
return {
setupFile: vitestSetupFile,
transformedSetupCode,
@@ -124,8 +127,7 @@ export const addonA11yAddonTest: Fix = {
},
};
-export function transformSetupFile(setupFile: string) {
- const source = readFileSync(setupFile, 'utf8');
+export function transformSetupFile(source: string) {
const j = jscodeshift.withParser('ts');
const root = j(source);
@@ -148,9 +150,14 @@ export function transformSetupFile(setupFile: string) {
throw new Error('Could not find setProjectAnnotations call in vitest.setup file');
}
- // Add a11yAddonAnnotations to the annotations array
- setProjectAnnotationsCall.find(j.ArrayExpression).forEach((p) => {
- p.value.elements.unshift(j.identifier('a11yAddonAnnotations'));
+ // Add a11yAddonAnnotations to the annotations array or create a new array if argument is a string
+ setProjectAnnotationsCall.forEach((p) => {
+ if (p.value.arguments.length === 1 && p.value.arguments[0].type === 'ArrayExpression') {
+ p.value.arguments[0].elements.unshift(j.identifier('a11yAddonAnnotations'));
+ } else if (p.value.arguments.length === 1 && p.value.arguments[0].type === 'Identifier') {
+ const arg = p.value.arguments[0];
+ p.value.arguments[0] = j.arrayExpression([j.identifier('a11yAddonAnnotations'), arg]);
+ }
});
// Add the import declaration at the top
diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json
index 8c4627711f63..c63ae7acdf82 100644
--- a/code/lib/cli/package.json
+++ b/code/lib/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "storybook",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook's CLI - install, dev, build, upgrade, and more",
"keywords": [
"cli",
diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json
index 606e5edba3e6..ec3cad1f1fe0 100644
--- a/code/lib/codemod/package.json
+++ b/code/lib/codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/codemod",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "A collection of codemod scripts written with JSCodeshift",
"keywords": [
"storybook"
diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json
index 24f68690cfa3..3224d7089b96 100644
--- a/code/lib/core-webpack/package.json
+++ b/code/lib/core-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/core-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json
index 237aa9cee837..5e5ee2a48bfc 100644
--- a/code/lib/create-storybook/package.json
+++ b/code/lib/create-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "create-storybook",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Initialize Storybook into your project",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/lib/create-storybook",
"bugs": {
diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json
index cfee2f12bbb7..dc49ef2e1913 100644
--- a/code/lib/csf-plugin/package.json
+++ b/code/lib/csf-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/csf-plugin",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Enrich CSF files via static analysis",
"keywords": [
"storybook"
diff --git a/code/lib/instrumenter/package.json b/code/lib/instrumenter/package.json
index eef050e80786..a4c16dcff69d 100644
--- a/code/lib/instrumenter/package.json
+++ b/code/lib/instrumenter/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/instrumenter",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json
index 79ac3f3060c1..8fa32975c148 100644
--- a/code/lib/react-dom-shim/package.json
+++ b/code/lib/react-dom-shim/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react-dom-shim",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json
index f2fd5488f141..60857a4d2170 100644
--- a/code/lib/source-loader/package.json
+++ b/code/lib/source-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/source-loader",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Source loader",
"keywords": [
"lib",
diff --git a/code/lib/test/package.json b/code/lib/test/package.json
index 63247b810f01..40202d1c5c3e 100644
--- a/code/lib/test/package.json
+++ b/code/lib/test/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/test",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "",
"keywords": [
"storybook"
diff --git a/code/nx.json b/code/nx.json
index 688fc38b5dd6..eaaabd089ea4 100644
--- a/code/nx.json
+++ b/code/nx.json
@@ -3,6 +3,7 @@
"extends": "nx/presets/npm.json",
"nxCloudAccessToken": "NGVmYTkxMmItYzY3OS00MjkxLTk1ZDktZDFmYTFmNmVlNGY4fHJlYWQ=",
"defaultBase": "next",
+ "useLegacyCache": true,
"parallel": 8,
"plugins": [
{
diff --git a/code/package.json b/code/package.json
index 17e8e9c048d6..7b9fd27a6318 100644
--- a/code/package.json
+++ b/code/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/root",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"private": true,
"description": "Storybook root",
"homepage": "https://storybook.js.org/",
@@ -90,11 +90,11 @@
"type-fest": "~2.19"
},
"dependencies": {
- "@chromatic-com/storybook": "^3.2.0",
+ "@chromatic-com/storybook": "^3.2.2",
"@happy-dom/global-registrator": "^14.12.0",
- "@nx/eslint": "20.1.4",
- "@nx/vite": "20.1.4",
- "@nx/workspace": "20.1.4",
+ "@nx/eslint": "20.2.2",
+ "@nx/vite": "20.2.2",
+ "@nx/workspace": "20.2.2",
"@playwright/test": "1.48.1",
"@storybook/addon-a11y": "workspace:*",
"@storybook/addon-actions": "workspace:*",
@@ -202,7 +202,7 @@
"husky": "^4.3.7",
"lint-staged": "^13.2.2",
"mock-require": "^3.0.3",
- "nx": "20.1.4",
+ "nx": "20.2.2",
"prettier": "^3.1.1",
"prettier-plugin-brace-style": "^0.6.2",
"prettier-plugin-css-order": "^2.1.2",
diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json
index e15f9f178ee6..ea8849294e29 100644
--- a/code/presets/create-react-app/package.json
+++ b/code/presets/create-react-app/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-create-react-app",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Create React App preset",
"keywords": [
"storybook"
diff --git a/code/presets/html-webpack/package.json b/code/presets/html-webpack/package.json
index 2d80b196ffb0..9094d24589c5 100644
--- a/code/presets/html-webpack/package.json
+++ b/code/presets/html-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-html-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/presets/preact-webpack/package.json b/code/presets/preact-webpack/package.json
index f483ee8dfce4..d87bfd54c980 100644
--- a/code/presets/preact-webpack/package.json
+++ b/code/presets/preact-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-preact-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [
"storybook"
diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json
index 4c347def5b69..48b04685bd43 100644
--- a/code/presets/react-webpack/package.json
+++ b/code/presets/react-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-react-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading",
"keywords": [
"storybook"
diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json
index 42d0b605a244..aaeb9c2dc159 100644
--- a/code/presets/server-webpack/package.json
+++ b/code/presets/server-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-server-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/presets/svelte-webpack/package.json b/code/presets/svelte-webpack/package.json
index 72c17dd4e8e8..1053d6ec17c7 100644
--- a/code/presets/svelte-webpack/package.json
+++ b/code/presets/svelte-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-svelte-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/presets/vue3-webpack/package.json b/code/presets/vue3-webpack/package.json
index b9a562aefc89..2477b603d753 100644
--- a/code/presets/vue3-webpack/package.json
+++ b/code/presets/vue3-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preset-vue3-webpack",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.",
"keywords": [
"storybook"
diff --git a/code/renderers/html/package.json b/code/renderers/html/package.json
index 4e30884c2d84..3726b3e2a2a1 100644
--- a/code/renderers/html/package.json
+++ b/code/renderers/html/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/html",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook HTML renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json
index 67af51a9bbb4..411e0a72028d 100644
--- a/code/renderers/preact/package.json
+++ b/code/renderers/preact/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/preact",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Preact renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json
index 77a70d1854d3..3a2bc736cfec 100644
--- a/code/renderers/react/package.json
+++ b/code/renderers/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/react",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook React renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/react/src/entry-preview.tsx b/code/renderers/react/src/entry-preview.tsx
index 08d625b5729d..e1b214e449c0 100644
--- a/code/renderers/react/src/entry-preview.tsx
+++ b/code/renderers/react/src/entry-preview.tsx
@@ -10,9 +10,9 @@ export { renderToCanvas } from './renderToCanvas';
export { mount } from './mount';
export const decorators: Decorator[] = [
- (Story, context) => {
+ (story, context) => {
if (!context.parameters?.react?.rsc) {
- return ;
+ return story();
}
const major = semver.major(React.version);
@@ -21,10 +21,6 @@ export const decorators: Decorator[] = [
throw new Error('React Server Components require React >= 18.3');
}
- return (
-
-
-
- );
+ return {story()};
},
];
diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json
index 5aecd20df1d6..484c3eb38f82 100644
--- a/code/renderers/server/package.json
+++ b/code/renderers/server/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/server",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Server renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json
index bf328e615353..7aa8fec6f5c9 100644
--- a/code/renderers/svelte/package.json
+++ b/code/renderers/svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/svelte",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Svelte renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json
index b5b1d437fcf3..4aef83519c25 100644
--- a/code/renderers/vue3/package.json
+++ b/code/renderers/vue3/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/vue3",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook Vue 3 renderer",
"keywords": [
"storybook"
diff --git a/code/renderers/vue3/src/docs/sourceDecorator.ts b/code/renderers/vue3/src/docs/sourceDecorator.ts
index 7eb8734305af..cc2f922f63f1 100644
--- a/code/renderers/vue3/src/docs/sourceDecorator.ts
+++ b/code/renderers/vue3/src/docs/sourceDecorator.ts
@@ -107,7 +107,6 @@ ${template}`;
* Checks if the source code generation should be skipped for the given Story context. Will be true
* if one of the following is true:
*
- * - View mode is not "docs"
* - Story is no arg story
* - Story has set custom source code via parameters.docs.source.code
* - Story has set source type to "code" via parameters.docs.source.type
@@ -120,13 +119,10 @@ export const shouldSkipSourceCodeGeneration = (context: StoryContext): boolean =
}
const isArgsStory = context?.parameters.__isArgsStory;
- const isDocsViewMode = context?.viewMode === 'docs';
// never render if the user is forcing the block to render code, or
// if the user provides code, or if it's not an args story.
- return (
- !isDocsViewMode || !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE
- );
+ return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
};
/**
diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json
index 324f56b82e7a..c2f23a997864 100644
--- a/code/renderers/web-components/package.json
+++ b/code/renderers/web-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@storybook/web-components",
- "version": "8.5.0-alpha.19",
+ "version": "8.5.0-beta.2",
"description": "Storybook web-components renderer",
"keywords": [
"lit",
diff --git a/code/sandbox/experimental-nextjs-vite-default-ts/project.json b/code/sandbox/experimental-nextjs-vite-default-ts/project.json
new file mode 100644
index 000000000000..ae9d595865dd
--- /dev/null
+++ b/code/sandbox/experimental-nextjs-vite-default-ts/project.json
@@ -0,0 +1,21 @@
+{
+ "name": "experimental-nextjs-vite/default-ts",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "implicitDependencies": [
+ "storybook",
+ "core",
+ "addon-essentials",
+ "addon-interactions",
+ "addon-links",
+ "addon-onboarding",
+ "blocks",
+ "experimental-nextjs-vite"
+ ],
+ "targets": {
+ "sandbox": {},
+ "sb:dev": {},
+ "sb:build": {}
+ },
+ "tags": ["ci:normal", "ci:merged", "ci:daily"]
+}
diff --git a/code/yarn.lock b/code/yarn.lock
index 5f206a86fd7a..5c1f23864a42 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -2532,21 +2532,18 @@ __metadata:
languageName: node
linkType: hard
-"@chromatic-com/storybook@npm:^3.2.0":
- version: 3.2.0
- resolution: "@chromatic-com/storybook@npm:3.2.0"
+"@chromatic-com/storybook@npm:^3.2.2":
+ version: 3.2.2
+ resolution: "@chromatic-com/storybook@npm:3.2.2"
dependencies:
- "@storybook/channels": "npm:^8.3.0"
- "@storybook/telemetry": "npm:^8.3.0"
- "@storybook/types": "npm:^8.3.0"
chromatic: "npm:^11.15.0"
filesize: "npm:^10.0.12"
jsonfile: "npm:^6.1.0"
react-confetti: "npm:^6.1.0"
strip-ansi: "npm:^7.1.0"
peerDependencies:
- storybook: "*"
- checksum: 10c0/59485e69a55df6b1998e19bf0e129fa1f9a86d3ae541f4dcb6f6dd945e7b9a6258ce75e244aaa73821039cef8c57424402a3c0bf63a66b4511a2ac0b5611103b
+ storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
+ checksum: 10c0/7b8da1ddb399c804337ff28a28594b548392b7bead52f66615b98e201cdeb4d31184b9e355791ba5d0d8cfdd2bea7d38355ecd0058f26f4790f9a887107bde0f
languageName: node
linkType: hard
@@ -4336,9 +4333,9 @@ __metadata:
languageName: node
linkType: hard
-"@nx/devkit@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/devkit@npm:20.1.4"
+"@nx/devkit@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/devkit@npm:20.2.2"
dependencies:
ejs: "npm:^3.1.7"
enquirer: "npm:~2.3.6"
@@ -4350,32 +4347,32 @@ __metadata:
yargs-parser: "npm:21.1.1"
peerDependencies:
nx: ">= 19 <= 21"
- checksum: 10c0/e8ae35e9b421cf558b35b792fcff1f769438be45808c2d4e1317e6c56779b773d2d05c3234e2431c258670b1880b5b4fbe22ac472ff639320df0b5bcf3b2ee95
+ checksum: 10c0/2e23120846f6f03a25a81dd69b3cacb5ddfd6bae070411668dcb12c507db8265d81b577a88db348ce4ef89dd8177af6532c9514a42524369d6ed33473a41abe2
languageName: node
linkType: hard
-"@nx/eslint@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/eslint@npm:20.1.4"
+"@nx/eslint@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/eslint@npm:20.2.2"
dependencies:
- "@nx/devkit": "npm:20.1.4"
- "@nx/js": "npm:20.1.4"
+ "@nx/devkit": "npm:20.2.2"
+ "@nx/js": "npm:20.2.2"
semver: "npm:^7.5.3"
tslib: "npm:^2.3.0"
- typescript: "npm:~5.4.2"
+ typescript: "npm:~5.6.2"
peerDependencies:
"@zkochan/js-yaml": 0.0.7
eslint: ^8.0.0 || ^9.0.0
peerDependenciesMeta:
"@zkochan/js-yaml":
optional: true
- checksum: 10c0/3fa816de9232b478469e9d7f6eaea4a0d47f22c8ee5a510f7d4759261497b11248c49a73a771f2fa356524f5c4f02c673f9c47af3c5503c445c21a9924a0a8fd
+ checksum: 10c0/79b7f28b391b8f49572063d1056f2a15097d8e544c4c65ab982879e2c6680eca5c42d3e30b0d8544fb4e6372a4b124a19d00e78e7567e231e294379fa2eb1470
languageName: node
linkType: hard
-"@nx/js@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/js@npm:20.1.4"
+"@nx/js@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/js@npm:20.2.2"
dependencies:
"@babel/core": "npm:^7.23.2"
"@babel/plugin-proposal-decorators": "npm:^7.22.7"
@@ -4384,8 +4381,8 @@ __metadata:
"@babel/preset-env": "npm:^7.23.2"
"@babel/preset-typescript": "npm:^7.22.5"
"@babel/runtime": "npm:^7.22.6"
- "@nx/devkit": "npm:20.1.4"
- "@nx/workspace": "npm:20.1.4"
+ "@nx/devkit": "npm:20.2.2"
+ "@nx/workspace": "npm:20.2.2"
"@zkochan/js-yaml": "npm:0.0.7"
babel-plugin-const-enum: "npm:^1.0.1"
babel-plugin-macros: "npm:^2.8.0"
@@ -4394,7 +4391,6 @@ __metadata:
columnify: "npm:^1.6.0"
detect-port: "npm:^1.5.1"
enquirer: "npm:~2.3.6"
- fast-glob: "npm:3.2.7"
ignore: "npm:^5.0.4"
js-tokens: "npm:^4.0.0"
jsonc-parser: "npm:3.2.0"
@@ -4404,6 +4400,7 @@ __metadata:
ora: "npm:5.3.0"
semver: "npm:^7.5.3"
source-map-support: "npm:0.5.19"
+ tinyglobby: "npm:^0.2.10"
ts-node: "npm:10.9.1"
tsconfig-paths: "npm:^4.1.2"
tslib: "npm:^2.3.0"
@@ -4412,86 +4409,86 @@ __metadata:
peerDependenciesMeta:
verdaccio:
optional: true
- checksum: 10c0/cefd563fffebcb8858fe528005aff69f44232bb42e4bfadfa703e37b0a1db9010f8400126639be3fd19beb81e2135c8a8b239ff37ed3f203b040a311038206bf
+ checksum: 10c0/2f920ca70187f81c1afcac5254aa9d41a8047e149803b0e036b14fade9fde1b00cad7b4ff6b0c46e9ec78d878c8f128857e8288ed0fe9d0d7bf27db6310d718d
languageName: node
linkType: hard
-"@nx/nx-darwin-arm64@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-darwin-arm64@npm:20.1.4"
+"@nx/nx-darwin-arm64@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-darwin-arm64@npm:20.2.2"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@nx/nx-darwin-x64@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-darwin-x64@npm:20.1.4"
+"@nx/nx-darwin-x64@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-darwin-x64@npm:20.2.2"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@nx/nx-freebsd-x64@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-freebsd-x64@npm:20.1.4"
+"@nx/nx-freebsd-x64@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-freebsd-x64@npm:20.2.2"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@nx/nx-linux-arm-gnueabihf@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-linux-arm-gnueabihf@npm:20.1.4"
+"@nx/nx-linux-arm-gnueabihf@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-linux-arm-gnueabihf@npm:20.2.2"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@nx/nx-linux-arm64-gnu@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-linux-arm64-gnu@npm:20.1.4"
+"@nx/nx-linux-arm64-gnu@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-linux-arm64-gnu@npm:20.2.2"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@nx/nx-linux-arm64-musl@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-linux-arm64-musl@npm:20.1.4"
+"@nx/nx-linux-arm64-musl@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-linux-arm64-musl@npm:20.2.2"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@nx/nx-linux-x64-gnu@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-linux-x64-gnu@npm:20.1.4"
+"@nx/nx-linux-x64-gnu@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-linux-x64-gnu@npm:20.2.2"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@nx/nx-linux-x64-musl@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-linux-x64-musl@npm:20.1.4"
+"@nx/nx-linux-x64-musl@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-linux-x64-musl@npm:20.2.2"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@nx/nx-win32-arm64-msvc@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-win32-arm64-msvc@npm:20.1.4"
+"@nx/nx-win32-arm64-msvc@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-win32-arm64-msvc@npm:20.2.2"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@nx/nx-win32-x64-msvc@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/nx-win32-x64-msvc@npm:20.1.4"
+"@nx/nx-win32-x64-msvc@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/nx-win32-x64-msvc@npm:20.2.2"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@nx/vite@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/vite@npm:20.1.4"
+"@nx/vite@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/vite@npm:20.2.2"
dependencies:
- "@nx/devkit": "npm:20.1.4"
- "@nx/js": "npm:20.1.4"
+ "@nx/devkit": "npm:20.2.2"
+ "@nx/js": "npm:20.2.2"
"@phenomnomnominal/tsquery": "npm:~5.0.1"
"@swc/helpers": "npm:~0.5.0"
enquirer: "npm:~2.3.6"
@@ -4500,21 +4497,21 @@ __metadata:
peerDependencies:
vite: ^5.0.0
vitest: ^1.3.1 || ^2.0.0
- checksum: 10c0/48230903e2b5c2c6610677fe501e144285e543a6137500715b643063e8758b6df14fc2c2bd39dbd6ff69612539185f15e24ffc5e1a1d1e245e146b8bb902cf14
+ checksum: 10c0/f46877b8e8dfd2989a11b48cb57d2b95e62e1e88f81892ede414dff762983e886d01dc2455e7a34b37c8ff1bab9e73d83cdfecf698b2457e618e1fa5dac4daaa
languageName: node
linkType: hard
-"@nx/workspace@npm:20.1.4":
- version: 20.1.4
- resolution: "@nx/workspace@npm:20.1.4"
+"@nx/workspace@npm:20.2.2":
+ version: 20.2.2
+ resolution: "@nx/workspace@npm:20.2.2"
dependencies:
- "@nx/devkit": "npm:20.1.4"
+ "@nx/devkit": "npm:20.2.2"
chalk: "npm:^4.1.0"
enquirer: "npm:~2.3.6"
- nx: "npm:20.1.4"
+ nx: "npm:20.2.2"
tslib: "npm:^2.3.0"
yargs-parser: "npm:21.1.1"
- checksum: 10c0/ad0bd040f87fce8bc7e01bc5122c8d52698743cbb5652cac423f54b64aedc7fe48ba090d8030dd9f4b205cd20f3e6a393936a395ab39247bd8c6b7ae0ac1692e
+ checksum: 10c0/ab3a7bd1787d7534682ac6b1dbe928aea168c15c95636f68fa7885f6da2f9e4da4ff34991c385bacc5f27f0bfd850ab1553883651c5f94f4162dbac8118e2c7b
languageName: node
linkType: hard
@@ -5403,6 +5400,13 @@ __metadata:
languageName: node
linkType: hard
+"@sec-ant/readable-stream@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "@sec-ant/readable-stream@npm:0.4.1"
+ checksum: 10c0/64e9e9cf161e848067a5bf60cdc04d18495dc28bb63a8d9f8993e4dd99b91ad34e4b563c85de17d91ffb177ec17a0664991d2e115f6543e73236a906068987af
+ languageName: node
+ linkType: hard
+
"@sideway/address@npm:^4.1.3":
version: 4.1.4
resolution: "@sideway/address@npm:4.1.4"
@@ -5496,6 +5500,13 @@ __metadata:
languageName: node
linkType: hard
+"@sindresorhus/merge-streams@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "@sindresorhus/merge-streams@npm:4.0.0"
+ checksum: 10c0/482ee543629aa1933b332f811a1ae805a213681ecdd98c042b1c1b89387df63e7812248bb4df3910b02b3cc5589d3d73e4393f30e197c9dde18046ccd471fc6b
+ languageName: node
+ linkType: hard
+
"@storybook/addon-a11y@workspace:*, @storybook/addon-a11y@workspace:addons/a11y":
version: 0.0.0-use.local
resolution: "@storybook/addon-a11y@workspace:addons/a11y"
@@ -5506,6 +5517,7 @@ __metadata:
"@storybook/test": "workspace:*"
"@testing-library/react": "npm:^14.0.0"
axe-core: "npm:^4.2.0"
+ execa: "npm:^9.5.2"
picocolors: "npm:^1.1.0"
pretty-format: "npm:^29.7.0"
react: "npm:^18.2.0"
@@ -6022,15 +6034,6 @@ __metadata:
languageName: unknown
linkType: soft
-"@storybook/channels@npm:^8.3.0":
- version: 8.3.6
- resolution: "@storybook/channels@npm:8.3.6"
- peerDependencies:
- storybook: ^8.3.6
- checksum: 10c0/3c34ed2b03c60c6ed1160d9a0efdb836be892e333556848ff492c16ab6d92521207512670d42f69d681f521e50f130a00f692610a3ca63228a8d2b49be57f4fa
- languageName: node
- linkType: hard
-
"@storybook/channels@workspace:deprecated/channels":
version: 0.0.0-use.local
resolution: "@storybook/channels@workspace:deprecated/channels"
@@ -6403,7 +6406,6 @@ __metadata:
"@storybook/icons": "npm:^1.2.12"
"@storybook/instrumenter": "workspace:*"
"@storybook/test": "workspace:*"
- "@storybook/theming": "workspace:*"
"@types/istanbul-lib-report": "npm:^3.0.3"
"@types/node": "npm:^22.0.0"
"@types/semver": "npm:^7"
@@ -6427,6 +6429,7 @@ __metadata:
slash: "npm:^5.0.0"
strip-ansi: "npm:^7.1.0"
tinyglobby: "npm:^0.2.10"
+ tree-kill: "npm:^1.2.2"
ts-dedent: "npm:^2.2.0"
typescript: "npm:^5.3.2"
vitest: "npm:^2.1.3"
@@ -6451,6 +6454,7 @@ __metadata:
dependencies:
"@storybook/builder-vite": "workspace:*"
"@storybook/react": "workspace:*"
+ "@storybook/react-vite": "workspace:*"
"@storybook/test": "workspace:*"
"@types/node": "npm:^18.0.0"
next: "npm:^15.0.3"
@@ -7019,11 +7023,11 @@ __metadata:
version: 0.0.0-use.local
resolution: "@storybook/root@workspace:."
dependencies:
- "@chromatic-com/storybook": "npm:^3.2.0"
+ "@chromatic-com/storybook": "npm:^3.2.2"
"@happy-dom/global-registrator": "npm:^14.12.0"
- "@nx/eslint": "npm:20.1.4"
- "@nx/vite": "npm:20.1.4"
- "@nx/workspace": "npm:20.1.4"
+ "@nx/eslint": "npm:20.2.2"
+ "@nx/vite": "npm:20.2.2"
+ "@nx/workspace": "npm:20.2.2"
"@playwright/test": "npm:1.48.1"
"@storybook/addon-a11y": "workspace:*"
"@storybook/addon-actions": "workspace:*"
@@ -7131,7 +7135,7 @@ __metadata:
husky: "npm:^4.3.7"
lint-staged: "npm:^13.2.2"
mock-require: "npm:^3.0.3"
- nx: "npm:20.1.4"
+ nx: "npm:20.2.2"
prettier: "npm:^3.1.1"
prettier-plugin-brace-style: "npm:^0.6.2"
prettier-plugin-css-order: "npm:^2.1.2"
@@ -7309,15 +7313,6 @@ __metadata:
languageName: unknown
linkType: soft
-"@storybook/telemetry@npm:^8.3.0":
- version: 8.3.6
- resolution: "@storybook/telemetry@npm:8.3.6"
- peerDependencies:
- storybook: ^8.3.6
- checksum: 10c0/b4fd8d0e238335249aa82dea49bde56813e0c771e67dd7110fb9e038d1e2bd64aa03e76ee865ef6a7f0fd5220d02bd7bcf3273bb4c19403e3d527d9dd7a258d4
- languageName: node
- linkType: hard
-
"@storybook/telemetry@workspace:deprecated/telemetry":
version: 0.0.0-use.local
resolution: "@storybook/telemetry@workspace:deprecated/telemetry"
@@ -7367,15 +7362,6 @@ __metadata:
languageName: unknown
linkType: soft
-"@storybook/types@npm:^8.3.0":
- version: 8.3.6
- resolution: "@storybook/types@npm:8.3.6"
- peerDependencies:
- storybook: ^8.3.6
- checksum: 10c0/482f55e34877f9eb94a8ff4627a254f3b5442f91f13363e6837e3a9c220a369be1c6ce4652b870b7fa4e522c3365825651f9e04a21bda76b104a6c1f7435e274
- languageName: node
- linkType: hard
-
"@storybook/types@workspace:*, @storybook/types@workspace:deprecated/types":
version: 0.0.0-use.local
resolution: "@storybook/types@workspace:deprecated/types"
@@ -15392,6 +15378,26 @@ __metadata:
languageName: node
linkType: hard
+"execa@npm:^9.5.2":
+ version: 9.5.2
+ resolution: "execa@npm:9.5.2"
+ dependencies:
+ "@sindresorhus/merge-streams": "npm:^4.0.0"
+ cross-spawn: "npm:^7.0.3"
+ figures: "npm:^6.1.0"
+ get-stream: "npm:^9.0.0"
+ human-signals: "npm:^8.0.0"
+ is-plain-obj: "npm:^4.1.0"
+ is-stream: "npm:^4.0.1"
+ npm-run-path: "npm:^6.0.0"
+ pretty-ms: "npm:^9.0.0"
+ signal-exit: "npm:^4.1.0"
+ strip-final-newline: "npm:^4.0.0"
+ yoctocolors: "npm:^2.0.0"
+ checksum: 10c0/94782a6282e03253224406c29068d18f9095cc251a45d1f19ac3d8f2a9db2cbe32fb8ceb039db1451d8fce3531135a6c0c559f76d634f85416268fc4a6995365
+ languageName: node
+ linkType: hard
+
"exectimer@npm:^2.2.2":
version: 2.2.2
resolution: "exectimer@npm:2.2.2"
@@ -15553,19 +15559,6 @@ __metadata:
languageName: node
linkType: hard
-"fast-glob@npm:3.2.7":
- version: 3.2.7
- resolution: "fast-glob@npm:3.2.7"
- dependencies:
- "@nodelib/fs.stat": "npm:^2.0.2"
- "@nodelib/fs.walk": "npm:^1.2.3"
- glob-parent: "npm:^5.1.2"
- merge2: "npm:^1.3.0"
- micromatch: "npm:^4.0.4"
- checksum: 10c0/cc820a9acbd99c51267d525ed3c0c368b57d273f8d34e2401eef824390ff38ff419af3c0308d4ec1aef3dae0e24d1ac1dfe3156e5c702d63416a4c877ab7e0c4
- languageName: node
- linkType: hard
-
"fast-glob@npm:3.3.2, fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.1, fast-glob@npm:^3.3.2":
version: 3.3.2
resolution: "fast-glob@npm:3.3.2"
@@ -15702,6 +15695,15 @@ __metadata:
languageName: node
linkType: hard
+"figures@npm:^6.1.0":
+ version: 6.1.0
+ resolution: "figures@npm:6.1.0"
+ dependencies:
+ is-unicode-supported: "npm:^2.0.0"
+ checksum: 10c0/9159df4264d62ef447a3931537de92f5012210cf5135c35c010df50a2169377581378149abfe1eb238bd6acbba1c0d547b1f18e0af6eee49e30363cedaffcfe4
+ languageName: node
+ linkType: hard
+
"file-entry-cache@npm:^6.0.1":
version: 6.0.1
resolution: "file-entry-cache@npm:6.0.1"
@@ -16556,6 +16558,16 @@ __metadata:
languageName: node
linkType: hard
+"get-stream@npm:^9.0.0":
+ version: 9.0.1
+ resolution: "get-stream@npm:9.0.1"
+ dependencies:
+ "@sec-ant/readable-stream": "npm:^0.4.1"
+ is-stream: "npm:^4.0.1"
+ checksum: 10c0/d70e73857f2eea1826ac570c3a912757dcfbe8a718a033fa0c23e12ac8e7d633195b01710e0559af574cbb5af101009b42df7b6f6b29ceec8dbdf7291931b948
+ languageName: node
+ linkType: hard
+
"get-symbol-description@npm:^1.0.2":
version: 1.0.2
resolution: "get-symbol-description@npm:1.0.2"
@@ -17592,6 +17604,13 @@ __metadata:
languageName: node
linkType: hard
+"human-signals@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "human-signals@npm:8.0.0"
+ checksum: 10c0/e4dac4f7d3eb791ed04129fc6a85bd454a9102d3e3b76c911d0db7057ebd60b2956b435b5b5712aec18960488ede3c21ef7c56e42cdd70760c0d84d3c05cd92e
+ languageName: node
+ linkType: hard
+
"humanize-ms@npm:^1.2.1":
version: 1.2.1
resolution: "humanize-ms@npm:1.2.1"
@@ -18372,7 +18391,7 @@ __metadata:
languageName: node
linkType: hard
-"is-plain-obj@npm:^4.0.0":
+"is-plain-obj@npm:^4.0.0, is-plain-obj@npm:^4.1.0":
version: 4.1.0
resolution: "is-plain-obj@npm:4.1.0"
checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e
@@ -18458,6 +18477,13 @@ __metadata:
languageName: node
linkType: hard
+"is-stream@npm:^4.0.1":
+ version: 4.0.1
+ resolution: "is-stream@npm:4.0.1"
+ checksum: 10c0/2706c7f19b851327ba374687bc4a3940805e14ca496dc672b9629e744d143b1ad9c6f1b162dece81c7bfbc0f83b32b61ccc19ad2e05aad2dd7af347408f60c7f
+ languageName: node
+ linkType: hard
+
"is-string@npm:^1.0.5, is-string@npm:^1.0.7":
version: 1.0.7
resolution: "is-string@npm:1.0.7"
@@ -18492,6 +18518,13 @@ __metadata:
languageName: node
linkType: hard
+"is-unicode-supported@npm:^2.0.0":
+ version: 2.1.0
+ resolution: "is-unicode-supported@npm:2.1.0"
+ checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5
+ languageName: node
+ linkType: hard
+
"is-weakmap@npm:^2.0.1":
version: 2.0.1
resolution: "is-weakmap@npm:2.0.1"
@@ -22103,6 +22136,16 @@ __metadata:
languageName: node
linkType: hard
+"npm-run-path@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "npm-run-path@npm:6.0.0"
+ dependencies:
+ path-key: "npm:^4.0.0"
+ unicorn-magic: "npm:^0.3.0"
+ checksum: 10c0/b223c8a0dcd608abf95363ea5c3c0ccc3cd877daf0102eaf1b0f2390d6858d8337fbb7c443af2403b067a7d2c116d10691ecd22ab3c5273c44da1ff8d07753bd
+ languageName: node
+ linkType: hard
+
"npmlog@npm:^6.0.0":
version: 6.0.2
resolution: "npmlog@npm:6.0.2"
@@ -22136,21 +22179,21 @@ __metadata:
languageName: node
linkType: hard
-"nx@npm:20.1.4":
- version: 20.1.4
- resolution: "nx@npm:20.1.4"
+"nx@npm:20.2.2":
+ version: 20.2.2
+ resolution: "nx@npm:20.2.2"
dependencies:
"@napi-rs/wasm-runtime": "npm:0.2.4"
- "@nx/nx-darwin-arm64": "npm:20.1.4"
- "@nx/nx-darwin-x64": "npm:20.1.4"
- "@nx/nx-freebsd-x64": "npm:20.1.4"
- "@nx/nx-linux-arm-gnueabihf": "npm:20.1.4"
- "@nx/nx-linux-arm64-gnu": "npm:20.1.4"
- "@nx/nx-linux-arm64-musl": "npm:20.1.4"
- "@nx/nx-linux-x64-gnu": "npm:20.1.4"
- "@nx/nx-linux-x64-musl": "npm:20.1.4"
- "@nx/nx-win32-arm64-msvc": "npm:20.1.4"
- "@nx/nx-win32-x64-msvc": "npm:20.1.4"
+ "@nx/nx-darwin-arm64": "npm:20.2.2"
+ "@nx/nx-darwin-x64": "npm:20.2.2"
+ "@nx/nx-freebsd-x64": "npm:20.2.2"
+ "@nx/nx-linux-arm-gnueabihf": "npm:20.2.2"
+ "@nx/nx-linux-arm64-gnu": "npm:20.2.2"
+ "@nx/nx-linux-arm64-musl": "npm:20.2.2"
+ "@nx/nx-linux-x64-gnu": "npm:20.2.2"
+ "@nx/nx-linux-x64-musl": "npm:20.2.2"
+ "@nx/nx-win32-arm64-msvc": "npm:20.2.2"
+ "@nx/nx-win32-x64-msvc": "npm:20.2.2"
"@yarnpkg/lockfile": "npm:^1.1.0"
"@yarnpkg/parsers": "npm:3.0.2"
"@zkochan/js-yaml": "npm:0.0.7"
@@ -22180,6 +22223,7 @@ __metadata:
tmp: "npm:~0.2.1"
tsconfig-paths: "npm:^4.1.2"
tslib: "npm:^2.3.0"
+ yaml: "npm:^2.6.0"
yargs: "npm:^17.6.2"
yargs-parser: "npm:21.1.1"
peerDependencies:
@@ -22214,7 +22258,7 @@ __metadata:
bin:
nx: bin/nx.js
nx-cloud: bin/nx-cloud.js
- checksum: 10c0/a9407ea70ecf9b2f465c94552124ef01d90828025540fa5733e1d3d35e6adab41adfe0c0545ebed37b0e7059e4dfee972351949fd9b643adb13e6daba4dd0214
+ checksum: 10c0/2046068295be6d33fe11505978ec1a770af272c959ef9aba5341c343f590dbed634958a07dc365d90e9feb0b62b9a06289c7cf3d88090e13062ece13e187b6f3
languageName: node
linkType: hard
@@ -22847,6 +22891,13 @@ __metadata:
languageName: node
linkType: hard
+"parse-ms@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "parse-ms@npm:4.0.0"
+ checksum: 10c0/a7900f4f1ebac24cbf5e9708c16fb2fd482517fad353aecd7aefb8c2ba2f85ce017913ccb8925d231770404780df46244ea6fec598b3bde6490882358b4d2d16
+ languageName: node
+ linkType: hard
+
"parse-node-version@npm:^1.0.1":
version: 1.0.1
resolution: "parse-node-version@npm:1.0.1"
@@ -23689,6 +23740,15 @@ __metadata:
languageName: node
linkType: hard
+"pretty-ms@npm:^9.0.0":
+ version: 9.2.0
+ resolution: "pretty-ms@npm:9.2.0"
+ dependencies:
+ parse-ms: "npm:^4.0.0"
+ checksum: 10c0/ab6d066f90e9f77020426986e1b018369f41575674544c539aabec2e63a20fec01166d8cf6571d0e165ad11cfe5a8134a2a48a36d42ab291c59c6deca5264cbb
+ languageName: node
+ linkType: hard
+
"prettyjson@npm:^1.2.1":
version: 1.2.5
resolution: "prettyjson@npm:1.2.5"
@@ -27013,6 +27073,13 @@ __metadata:
languageName: node
linkType: hard
+"strip-final-newline@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "strip-final-newline@npm:4.0.0"
+ checksum: 10c0/b0cf2b62d597a1b0e3ebc42b88767f0a0d45601f89fd379a928a1812c8779440c81abba708082c946445af1d6b62d5f16e2a7cf4f30d9d6587b89425fae801ff
+ languageName: node
+ linkType: hard
+
"strip-indent@npm:^3.0.0":
version: 3.0.0
resolution: "strip-indent@npm:3.0.0"
@@ -27756,7 +27823,7 @@ __metadata:
languageName: node
linkType: hard
-"tree-kill@npm:1.2.2":
+"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2":
version: 1.2.2
resolution: "tree-kill@npm:1.2.2"
bin:
@@ -28112,7 +28179,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@npm:^5.3.2, typescript@npm:~5.4.2":
+"typescript@npm:^5.3.2":
version: 5.4.5
resolution: "typescript@npm:5.4.5"
bin:
@@ -28122,6 +28189,16 @@ __metadata:
languageName: node
linkType: hard
+"typescript@npm:~5.6.2":
+ version: 5.6.3
+ resolution: "typescript@npm:5.6.3"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799
+ languageName: node
+ linkType: hard
+
"typescript@patch:typescript@npm%3A5.4.3#optional!builtin":
version: 5.4.3
resolution: "typescript@patch:typescript@npm%3A5.4.3#optional!builtin::version=5.4.3&hash=5adc0c"
@@ -28142,7 +28219,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@patch:typescript@npm%3A^5.3.2#optional!builtin, typescript@patch:typescript@npm%3A~5.4.2#optional!builtin":
+"typescript@patch:typescript@npm%3A^5.3.2#optional!builtin":
version: 5.4.5
resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c"
bin:
@@ -28152,6 +28229,16 @@ __metadata:
languageName: node
linkType: hard
+"typescript@patch:typescript@npm%3A~5.6.2#optional!builtin":
+ version: 5.6.3
+ resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=b45daf"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/ac8307bb06bbfd08ae7137da740769b7d8c3ee5943188743bb622c621f8ad61d244767480f90fbd840277fbf152d8932aa20c33f867dea1bb5e79b187ca1a92f
+ languageName: node
+ linkType: hard
+
"unbox-primitive@npm:^1.0.2":
version: 1.0.2
resolution: "unbox-primitive@npm:1.0.2"
@@ -28226,6 +28313,13 @@ __metadata:
languageName: node
linkType: hard
+"unicorn-magic@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "unicorn-magic@npm:0.3.0"
+ checksum: 10c0/0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271
+ languageName: node
+ linkType: hard
+
"unified-args@npm:^11.0.0":
version: 11.0.1
resolution: "unified-args@npm:11.0.1"
@@ -30129,6 +30223,15 @@ __metadata:
languageName: node
linkType: hard
+"yaml@npm:^2.6.0":
+ version: 2.6.1
+ resolution: "yaml@npm:2.6.1"
+ bin:
+ yaml: bin.mjs
+ checksum: 10c0/aebf07f61c72b38c74d2b60c3a3ccf89ee4da45bcd94b2bfb7899ba07a5257625a7c9f717c65a6fc511563d48001e01deb1d9e55f0133f3e2edf86039c8c1be7
+ languageName: node
+ linkType: hard
+
"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1":
version: 21.1.1
resolution: "yargs-parser@npm:21.1.1"
@@ -30230,6 +30333,13 @@ __metadata:
languageName: node
linkType: hard
+"yoctocolors@npm:^2.0.0":
+ version: 2.1.1
+ resolution: "yoctocolors@npm:2.1.1"
+ checksum: 10c0/85903f7fa96f1c70badee94789fade709f9d83dab2ec92753d612d84fcea6d34c772337a9f8914c6bed2f5fc03a428ac5d893e76fab636da5f1236ab725486d0
+ languageName: node
+ linkType: hard
+
"zimmerframe@npm:^1.1.2":
version: 1.1.2
resolution: "zimmerframe@npm:1.1.2"
diff --git a/docs/_assets/writing-tests/addon-a11y-with-addon-test.png b/docs/_assets/writing-tests/addon-a11y-with-addon-test.png
new file mode 100644
index 000000000000..a8656ac25d1d
Binary files /dev/null and b/docs/_assets/writing-tests/addon-a11y-with-addon-test.png differ
diff --git a/docs/_assets/writing-tests/addon-test-module-coverage-summary.png b/docs/_assets/writing-tests/addon-test-module-coverage-summary.png
new file mode 100644
index 000000000000..99aa10a0055f
Binary files /dev/null and b/docs/_assets/writing-tests/addon-test-module-coverage-summary.png differ
diff --git a/docs/_assets/writing-tests/addon-test-module-edit-coverage.png b/docs/_assets/writing-tests/addon-test-module-edit-coverage.png
new file mode 100644
index 000000000000..9e783a324380
Binary files /dev/null and b/docs/_assets/writing-tests/addon-test-module-edit-coverage.png differ
diff --git a/docs/_assets/writing-tests/addon-test-module-edit.png b/docs/_assets/writing-tests/addon-test-module-edit.png
new file mode 100644
index 000000000000..8634fa9e5bb7
Binary files /dev/null and b/docs/_assets/writing-tests/addon-test-module-edit.png differ
diff --git a/docs/_assets/writing-tests/addon-test-module-expanded-with-vta.png b/docs/_assets/writing-tests/addon-test-module-expanded-with-vta.png
index c879b46c39c7..2debcbd05e46 100644
Binary files a/docs/_assets/writing-tests/addon-test-module-expanded-with-vta.png and b/docs/_assets/writing-tests/addon-test-module-expanded-with-vta.png differ
diff --git a/docs/_assets/writing-tests/addon-test-module-expanded.png b/docs/_assets/writing-tests/addon-test-module-expanded.png
index 6bebb8fc4e9b..7ed6e20858b0 100644
Binary files a/docs/_assets/writing-tests/addon-test-module-expanded.png and b/docs/_assets/writing-tests/addon-test-module-expanded.png differ
diff --git a/docs/_assets/writing-tests/addon-test-overview.mp4 b/docs/_assets/writing-tests/addon-test-overview.mp4
index 301011486506..19afb7c3d1dc 100644
Binary files a/docs/_assets/writing-tests/addon-test-overview.mp4 and b/docs/_assets/writing-tests/addon-test-overview.mp4 differ
diff --git a/docs/_assets/writing-tests/addon-test-sidebar-item-menu-edit.png b/docs/_assets/writing-tests/addon-test-sidebar-item-menu-edit.png
new file mode 100644
index 000000000000..0734d73c2f23
Binary files /dev/null and b/docs/_assets/writing-tests/addon-test-sidebar-item-menu-edit.png differ
diff --git a/docs/_assets/writing-tests/addon-test-sidebar-item-menu.png b/docs/_assets/writing-tests/addon-test-sidebar-item-menu.png
new file mode 100644
index 000000000000..42939fa197af
Binary files /dev/null and b/docs/_assets/writing-tests/addon-test-sidebar-item-menu.png differ
diff --git a/docs/_assets/writing-tests/addon-test-vitest-error.png b/docs/_assets/writing-tests/addon-test-vitest-error.png
index 0b3e4b052022..feaad36ab1d7 100644
Binary files a/docs/_assets/writing-tests/addon-test-vitest-error.png and b/docs/_assets/writing-tests/addon-test-vitest-error.png differ
diff --git a/docs/_assets/writing-tests/vitest-plugin-vscode-coverage.png b/docs/_assets/writing-tests/vitest-plugin-vscode-coverage.png
new file mode 100644
index 000000000000..1442d1c1242f
Binary files /dev/null and b/docs/_assets/writing-tests/vitest-plugin-vscode-coverage.png differ
diff --git a/docs/_snippets/addon-a11y-meta-tag.md b/docs/_snippets/addon-a11y-meta-tag.md
new file mode 100644
index 000000000000..3ef18124964e
--- /dev/null
+++ b/docs/_snippets/addon-a11y-meta-tag.md
@@ -0,0 +1,236 @@
+```ts filename="Button.stories.ts" renderer="angular" language="ts"
+import type { Meta } from '@storybook/angular/';
+
+import { Button } from './button.component';
+
+const meta: Meta