From 1efccbe46433eea26e92065b4e198614efba1926 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Mon, 15 Apr 2024 12:24:59 -0600
Subject: [PATCH 01/46] WIP
---
...ch-ts.mdx => document-screen-fetch.ts.mdx} | 0
docs/toc.js | 5 +
docs/writing-stories/mocks.md | 309 ++++++++++++++++++
3 files changed, 314 insertions(+)
rename docs/snippets/react/{document-screen-fetch-ts.mdx => document-screen-fetch.ts.mdx} (100%)
create mode 100644 docs/writing-stories/mocks.md
diff --git a/docs/snippets/react/document-screen-fetch-ts.mdx b/docs/snippets/react/document-screen-fetch.ts.mdx
similarity index 100%
rename from docs/snippets/react/document-screen-fetch-ts.mdx
rename to docs/snippets/react/document-screen-fetch.ts.mdx
diff --git a/docs/toc.js b/docs/toc.js
index a4d3e85cf6be..1a8530bbc6bd 100644
--- a/docs/toc.js
+++ b/docs/toc.js
@@ -141,6 +141,11 @@ module.exports = {
title: 'Naming components and hierarchy',
type: 'link',
},
+ {
+ pathSegment: 'mocks',
+ title: 'Mocking data and modules',
+ type: 'link',
+ },
{
pathSegment: 'build-pages-with-storybook',
title: 'Build pages and screens',
diff --git a/docs/writing-stories/mocks.md b/docs/writing-stories/mocks.md
new file mode 100644
index 000000000000..67b354eff23c
--- /dev/null
+++ b/docs/writing-stories/mocks.md
@@ -0,0 +1,309 @@
+---
+title: 'Mocking data and modules'
+---
+
+1. Intro
+2. Providers
+ 1. Decorators
+3. Network requests
+ 1. MSW
+4. Modules
+ 1. Mock files
+ 1. Naming convention
+ 2. Cannot use subpath imports in a mocked file; must use relative
+ 3. Watch out for side effects
+ 2. Subpath imports
+ 1. Relative imports not supported
+ 2. Fallback to builder aliases
+ 3. Next.js callout
+ 3. Configuring mocks
+ 1. `fn.mock` and friends
+ 2. `beforeEach`
+
+Components that rely on external data or modules can be difficult to use in isolation. Mocking is a way to replace these dependencies with fake data or modules that you control. This allows you to develop and test your components without worrying about the behavior or stability of the external dependencies.
+
+Storybook provides different tools and techniques to help you mock data and modules.
+
+
+
+## Providers
+
+Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
+
+
+
+
+
+
+
+
+
+## Network requests
+
+For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data.
+
+The MSW addon adds this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
+
+
+Set up MSW in Storybook
+
+First, run this command to install MSW:
+
+
+
+```sh
+npm install msw --save-dev
+```
+
+Next, install and register the MSW addon:
+
+
+
+```sh
+npx storybook@latest add msw-storybook-addon
+```
+
+Then, generate the service worker file necessary for MSW to work:
+
+
+
+
+
+
+
+
+
+
+
+
+Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`).
+
+
+
+
+
+Initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders):
+
+
+
+
+
+
+
+
+Finally, ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file:
+
+
+
+
+
+
+
+
+
+
+Let's use an example of a document screen component that requests data from an API to demonstrate how to mock network requests in Storybook. The following snippets show an example implementation using `fetch` for a REST API and/or GraphQL:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+These examples use the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) or GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/), [URQL](https://formidable.com/open-source/urql/), or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook.
+
+
+
+### Mocking REST requests
+
+To mock a REST request, you can use the `msw` library to intercept the request and respond with mock data. Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that fetches data successfully and another that fails.
+
+
+
+
+
+
+
+
+Note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level.
+
+### Mocking GraphQL requests
+
+In addition to mocking RESTful requests, the MSW addon can also mock GraphQL requests.
+
+Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that queries data successfully and another that fails.
+
+
+
+
+
+
+
+
+## Modules
+
+Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
+
+There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component.
+
+For either approach, relative imports of the mocked module are not supported.
+
+### Mock files
+
+To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior.
+
+Here's an example of a mock file for a module named `api`:
+
+```js
+// api.mock.js
+TK: Add snippet
+```
+
+
+
+When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking.
+
+Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component.
+
+
+
+### Subpath imports
+
+The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
+
+To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules:
+
+TK: External module example?
+
+```json
+// package.json
+{
+ "imports": {
+ "#api": {
+ "storybook": "./api.mock.ts",
+ "default": "./api.ts"
+ },
+ "#prisma/prisma": {
+ "storybook": "./prisma/prisma.mock.ts",
+ "default": "./prisma/prisma.ts"
+ },
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
+
+
+
+Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
+
+
+
+You can then update your component file to use the subpath import:
+
+```ts
+TK: Component snippet
+```
+
+#### Conditional imports
+
+Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project.
+
+The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
+
+### Builder aliases
+
+If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file.
+
+```js
+// .storybook/main.ts
+
+webpackFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ 'next/headers': require.resolve('./next-headers'),
+ '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'),
+ }
+ }
+
+ return config
+},
+```
From 024237154fc718da4c186273eeb283db5d8ed744 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Mon, 15 Apr 2024 23:28:21 -0600
Subject: [PATCH 02/46] Split into 3 pages
---
docs/toc.js | 21 +-
docs/writing-stories/mocking-modules.md | 93 ++++++
.../mocking-network-requests.md | 178 ++++++++++
docs/writing-stories/mocking-providers.md | 24 ++
docs/writing-stories/mocks.md | 309 ------------------
5 files changed, 314 insertions(+), 311 deletions(-)
create mode 100644 docs/writing-stories/mocking-modules.md
create mode 100644 docs/writing-stories/mocking-network-requests.md
create mode 100644 docs/writing-stories/mocking-providers.md
delete mode 100644 docs/writing-stories/mocks.md
diff --git a/docs/toc.js b/docs/toc.js
index 1a8530bbc6bd..7dbc4bceec4c 100644
--- a/docs/toc.js
+++ b/docs/toc.js
@@ -142,9 +142,26 @@ module.exports = {
type: 'link',
},
{
- pathSegment: 'mocks',
+ pathSegment: '',
title: 'Mocking data and modules',
- type: 'link',
+ type: 'menu',
+ children: [
+ {
+ pathSegment: 'mocking-providers',
+ title: 'Providers',
+ type: 'link',
+ },
+ {
+ pathSegment: 'mocking-network-requests',
+ title: 'Network requests',
+ type: 'link',
+ },
+ {
+ pathSegment: 'mocking-modules',
+ title: 'Modules',
+ type: 'link',
+ },
+ ],
},
{
pathSegment: 'build-pages-with-storybook',
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
new file mode 100644
index 000000000000..3c0365382b3a
--- /dev/null
+++ b/docs/writing-stories/mocking-modules.md
@@ -0,0 +1,93 @@
+---
+title: Mocking modules
+---
+
+Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
+
+There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component.
+
+For either approach, relative imports of the mocked module are not supported.
+
+### Mock files
+
+To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior.
+
+Here's an example of a mock file for a module named `api`:
+
+```js
+// api.mock.js
+TK: Add snippet
+```
+
+
+
+When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking.
+
+Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component.
+
+
+
+### Subpath imports
+
+The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
+
+To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules:
+
+TK: External module example?
+
+```json
+// package.json
+{
+ "imports": {
+ "#api": {
+ "storybook": "./api.mock.ts",
+ "default": "./api.ts"
+ },
+ "#prisma/prisma": {
+ "storybook": "./prisma/prisma.mock.ts",
+ "default": "./prisma/prisma.ts"
+ },
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
+
+
+
+Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
+
+
+
+You can then update your component file to use the subpath import:
+
+```ts
+TK: Component snippet
+```
+
+#### Conditional imports
+
+Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project.
+
+The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
+
+### Builder aliases
+
+If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file.
+
+
+
+```js
+// .storybook/main.ts
+
+webpackFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ 'next/headers': require.resolve('./next-headers'),
+ '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'),
+ }
+ }
+
+ return config
+},
+```
diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md
new file mode 100644
index 000000000000..28afb5e12697
--- /dev/null
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -0,0 +1,178 @@
+---
+title: Mocking network requests
+---
+
+For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data.
+
+The [MSW addon]https://storybook.js.org/addons/msw-storybook-addon/ brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
+
+## Set up the MSW addon
+
+First, if necessary, run this command to install MSW:
+
+
+
+```sh
+npm install msw --save-dev
+```
+
+Then generate the service worker file necessary for MSW to work:
+
+
+
+
+
+
+
+
+
+
+
+
+Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`).
+
+
+
+
+
+Next, install and register the MSW addon:
+
+
+
+```sh
+npx storybook@latest add msw-storybook-addon
+```
+
+Then ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file:
+
+
+
+
+
+
+
+
+Finally, initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders):
+
+
+
+
+
+
+
+
+## Mocking REST requests
+
+If your component fetches data from a REST API, you can use MSW to mock those requests in Storybook. As an example, consider this document screen component:
+
+
+
+
+
+
+
+
+
+This example uses the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/)), you can apply the same principles to mock network requests in Storybook.
+
+
+
+With the MSW addon, we can write stories that use MSW to mock the REST requests. Here's an example of two stories for the document screen component: one that fetches data successfully and another that fails.
+
+
+
+
+
+
+
+
+## Mocking GraphQL requests
+
+GraphQL is another common way to fetch data in components. You can use MSW to mock GraphQL requests in Storybook. Here's an example of a document screen component that fetches data from a GraphQL API:
+
+
+
+
+
+
+
+
+
+This example uses GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [URQL](https://formidable.com/open-source/urql/) or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook.
+
+
+
+The MSW addon allows you to write stories that use MSW to mock the GraphQL requests. Here's an example demonstrating two stories for the document screen component. The first story fetches data successfully, while the second story fails.
+
+
+
+
+
+
+
+
+## Configuring MSW for stories
+
+In the examples above, note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level, allowing you to share the same mock server configuration across multiple stories.
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
new file mode 100644
index 000000000000..a6000ff59558
--- /dev/null
+++ b/docs/writing-stories/mocking-providers.md
@@ -0,0 +1,24 @@
+---
+title: Mocking providers
+---
+
+
+
+## Providers
+
+Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
+
+
+
+
+
+
+
+TK: Something about the `context` argument?
diff --git a/docs/writing-stories/mocks.md b/docs/writing-stories/mocks.md
deleted file mode 100644
index 67b354eff23c..000000000000
--- a/docs/writing-stories/mocks.md
+++ /dev/null
@@ -1,309 +0,0 @@
----
-title: 'Mocking data and modules'
----
-
-1. Intro
-2. Providers
- 1. Decorators
-3. Network requests
- 1. MSW
-4. Modules
- 1. Mock files
- 1. Naming convention
- 2. Cannot use subpath imports in a mocked file; must use relative
- 3. Watch out for side effects
- 2. Subpath imports
- 1. Relative imports not supported
- 2. Fallback to builder aliases
- 3. Next.js callout
- 3. Configuring mocks
- 1. `fn.mock` and friends
- 2. `beforeEach`
-
-Components that rely on external data or modules can be difficult to use in isolation. Mocking is a way to replace these dependencies with fake data or modules that you control. This allows you to develop and test your components without worrying about the behavior or stability of the external dependencies.
-
-Storybook provides different tools and techniques to help you mock data and modules.
-
-
-
-## Providers
-
-Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
-
-
-
-
-
-
-
-
-
-## Network requests
-
-For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data.
-
-The MSW addon adds this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
-
-
-Set up MSW in Storybook
-
-First, run this command to install MSW:
-
-
-
-```sh
-npm install msw --save-dev
-```
-
-Next, install and register the MSW addon:
-
-
-
-```sh
-npx storybook@latest add msw-storybook-addon
-```
-
-Then, generate the service worker file necessary for MSW to work:
-
-
-
-
-
-
-
-
-
-
-
-
-Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`).
-
-
-
-
-
-Initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders):
-
-
-
-
-
-
-
-
-Finally, ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file:
-
-
-
-
-
-
-
-
-
-
-Let's use an example of a document screen component that requests data from an API to demonstrate how to mock network requests in Storybook. The following snippets show an example implementation using `fetch` for a REST API and/or GraphQL:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-These examples use the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) or GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/), [URQL](https://formidable.com/open-source/urql/), or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook.
-
-
-
-### Mocking REST requests
-
-To mock a REST request, you can use the `msw` library to intercept the request and respond with mock data. Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that fetches data successfully and another that fails.
-
-
-
-
-
-
-
-
-Note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level.
-
-### Mocking GraphQL requests
-
-In addition to mocking RESTful requests, the MSW addon can also mock GraphQL requests.
-
-Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that queries data successfully and another that fails.
-
-
-
-
-
-
-
-
-## Modules
-
-Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
-
-There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component.
-
-For either approach, relative imports of the mocked module are not supported.
-
-### Mock files
-
-To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior.
-
-Here's an example of a mock file for a module named `api`:
-
-```js
-// api.mock.js
-TK: Add snippet
-```
-
-
-
-When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking.
-
-Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component.
-
-
-
-### Subpath imports
-
-The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
-
-To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules:
-
-TK: External module example?
-
-```json
-// package.json
-{
- "imports": {
- "#api": {
- "storybook": "./api.mock.ts",
- "default": "./api.ts"
- },
- "#prisma/prisma": {
- "storybook": "./prisma/prisma.mock.ts",
- "default": "./prisma/prisma.ts"
- },
- "#*": ["./*", "./*.ts", "./*.tsx"]
- }
-}
-```
-
-
-
-Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
-
-
-
-You can then update your component file to use the subpath import:
-
-```ts
-TK: Component snippet
-```
-
-#### Conditional imports
-
-Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project.
-
-The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
-
-### Builder aliases
-
-If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file.
-
-```js
-// .storybook/main.ts
-
-webpackFinal: async (config) => {
- if (config.resolve) {
- config.resolve.alias = {
- ...config.resolve.alias,
- 'next/headers': require.resolve('./next-headers'),
- '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'),
- }
- }
-
- return config
-},
-```
From ff3f2e9f966f4580d7b8a23552ba03af72646800 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Tue, 16 Apr 2024 15:06:44 +0200
Subject: [PATCH 03/46] Add Modules section to Next.js docs
---
docs/get-started/nextjs.md | 104 +++++++++++++++++++++++++------------
1 file changed, 71 insertions(+), 33 deletions(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index b17cf19661b3..f1e024ef6049 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -885,54 +885,43 @@ You can refer to the [Install `sharp` to Use Built-In Image Optimization](https:
## API
-### Parameters
+### Modules
-This framework contributes the following [parameters](../writing-stories/parameters.md) to Storybook, under the `nextjs` namespace:
+The `@storybook/nextjs` package exports a number of modules that enables you to [mock](/TODO_LINK_TO_MOCKING) Next.js's internal behavior.
-#### `appDirectory`
+#### `@storybook/nextjs/headers.mock`
-Type: `boolean`
+Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js
-Default: `false`
+Exports _writable_ mocks that replaces the actual implementation of `next/headers` exports. Use this to set up cookies or headers that are read in your story, and to later assert that they have been called.
-If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](../api/parameters.md#story-parameters), [all stories for a component](../api/parameters.md#meta-parameters), or [every story in your Storybook](../api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details.
+Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers:
-#### `navigation`
+- **`headers().append(name: string, value: string)`**: Appends the value to the header if it exists already.
+- **`headers().delete(name: string)`**: Deletes the header
+- **`headers().set(name: string, value: string)`**: Sets the header to the value provided.
-Type:
+For cookies, you can use the existing API to write them, eg. `cookies().set('firstName', 'Jane')`.
-```ts
-{
- asPath?: string;
- pathname?: string;
- query?: Record;
- segments?: (string | [string, string])[];
-}
-```
+Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
-Default value:
+#### `@storybook/nextjs/navigation.mock`
-```js
-{
- segments: [];
-}
-```
+Type: `typeof import('next/navigation')`
-The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details.
+Exports mocks that replaces the actual implementation of `next/navigation` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
-#### `router`
+#### `@storybook/nextjs/router.mock`
-Type:
+Type: `typeof import('next/router')`
-```ts
-{
- asPath?: string;
- pathname?: string;
- query?: Record;
-}
-```
+Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
-The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details.
+#### `@storybook/nextjs/export-mocks`
+
+Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
+
+`getPackageAliases` returns the aliases needed to set up Portable Stories in a Jest environment. See the [Portable Stories](TODO) section.
### Options
@@ -976,6 +965,55 @@ Type: `string`
The absolute path to the `next.config.js` file. This is necessary if you have a custom `next.config.js` file that is not in the root directory of your project.
+### Parameters
+
+This framework contributes the following [parameters](../writing-stories/parameters.md) to Storybook, under the `nextjs` namespace:
+
+#### `appDirectory`
+
+Type: `boolean`
+
+Default: `false`
+
+If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](../api/parameters.md#story-parameters), [all stories for a component](../api/parameters.md#meta-parameters), or [every story in your Storybook](../api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details.
+
+#### `navigation`
+
+Type:
+
+```ts
+{
+ asPath?: string;
+ pathname?: string;
+ query?: Record;
+ segments?: (string | [string, string])[];
+}
+```
+
+Default value:
+
+```js
+{
+ segments: [];
+}
+```
+
+The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details.
+
+#### `router`
+
+Type:
+
+```ts
+{
+ asPath?: string;
+ pathname?: string;
+ query?: Record;
+}
+```
+
+The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details.
+
From 92e13821355628eb7a383e9058ee9d539b360afa Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 16 Apr 2024 13:17:37 -0600
Subject: [PATCH 04/46] More mocking content
- Providers
- Configuring mocked providers with parameters
- Network requests
- Bad link
- Modules
- Better mock files section
- Using mocked files in stories
---
docs/writing-stories/mocking-modules.md | 148 +++++++++++++++---
.../mocking-network-requests.md | 2 +-
docs/writing-stories/mocking-providers.md | 68 +++++++-
3 files changed, 196 insertions(+), 22 deletions(-)
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 3c0365382b3a..e59f0f7c2b59 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -8,26 +8,27 @@ There are two primary approaches to mocking modules in Storybook. They both invo
For either approach, relative imports of the mocked module are not supported.
-### Mock files
+## Mock files
-To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior.
+To mock a module, create a file with the same name and in the same directory as the module you want to mock. For example, to mock a module named `session`, create a file next to it named `session.mock.js|ts`, with a few characteristics:
-Here's an example of a mock file for a module named `api`:
+- It should re-export all exports from the original module.
+- It should use the `fn` utility to mock any necessary functionality from the original module.
+- It cannot use subpath imports or builder aliases for its dependencies.
+- It should not introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the module they are mocking.
-```js
-// api.mock.js
-TK: Add snippet
-```
-
-
-
-When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking.
+Here's an example of a mock file for a module named `session`:
-Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component.
+```js
+// session.mock.js
+import { fn } from '@storybook/test';
+import * as actual from './session';
-
+export * from './session';
+export const getUserFromSession = fn(actual.getUserFromSession);
+```
-### Subpath imports
+## Subpath imports
The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
@@ -64,17 +65,17 @@ You can then update your component file to use the subpath import:
TK: Component snippet
```
-#### Conditional imports
+### Conditional imports
Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project.
The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
-### Builder aliases
+## Builder aliases
-If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file.
+If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-
+TK: Vite version, too
```js
// .storybook/main.ts
@@ -91,3 +92,114 @@ webpackFinal: async (config) => {
return config
},
```
+
+## Using mocked modules in stories
+
+When you use the `fn` utility to mock a module, you create full [mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation.
+
+Here, we define `beforeEach` on a story (which will run before the story is rendered) to set a mocked return value for the `getUserFromSession` function used by the Page component:
+
+
+
+```js
+// Page.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+import { fn } from '@storybook/test';
+
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
+
+
+
+If you are [writing your stories in TypeScript](./typescript.md), you will need to import your mock modules using the full mocked file name to have the mocked function correctly typed in your stories. You do **not** need do this in your component files, that's what the [subpath import](#subpath-imports) or [builder alias](#builder-aliases) is for.
+
+
+
+### Spying on mocked modules
+
+The `fn` utility also spies on the original module's functions, which you can use to assert their behavior in your tests. For example, you can use [interaction tests](../writing-tests/interaction-testing.md) to verify that a function was called with specific arguments.
+
+For example, this story checks that the `saveNote` function was called when the user clicks the save button:
+
+```ts
+// NoteUI.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+const meta = {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
+
+### Shared setup and clearing mocks
+
+You can use `beforeEach` at the component level to perform shared setup or clear the mocks between stories. This ensures that each test starts with a clean slate and is not affected by the mocks from previous stories.
+
+
+
+```js
+// Page.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+import { fn } from '@storybook/test';
+
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+ async beforeEach() {
+ // 👇 Do this for each story
+ // TK
+ // 👇 Clear the mock between stories
+ getUserFromSession.mockClear();
+ },
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ // TK
+};
+```
diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md
index 28afb5e12697..461574f7c856 100644
--- a/docs/writing-stories/mocking-network-requests.md
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -4,7 +4,7 @@ title: Mocking network requests
For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data.
-The [MSW addon]https://storybook.js.org/addons/msw-storybook-addon/ brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
+The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
## Set up the MSW addon
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index a6000ff59558..4c5a8b20a496 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -4,8 +4,6 @@ title: Mocking providers
-## Providers
-
Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
@@ -21,4 +19,68 @@ Components can receive data or configuration from context providers. For example
-TK: Something about the `context` argument?
+## Configuring the mock provider
+
+When mocking a provider, it may be necessary to configure the provider to supply a different value for individual stories. For example, you might want to test a component with different themes or user roles.
+
+One way to do this is to define the decorator for each story individually. But if you imagine a scenario where you wish to create stories for each of your components in both light and dark themes, this approach can quickly become cumbersome.
+
+For a better way, with much less repetition, you can use the [decorator function's second "context" argument](./decorators.md#context-for-mocking) to access a story's [`parameters`](./parameters.md) and adjust the provided value. This way, you can define the provider once and adjust its value for each story.
+
+For example, we can adjust the decorator from above to read from `parameters.theme` to determine which theme to provide:
+
+
+
+```ts
+// .storybook/preview.js
+import React from 'react';
+import { Preview } from '@storybook/react';
+import { ThemeProvider } from 'styled-components';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading the theme value from parameters
+ const theme = parameters.theme || 'default';
+ return (
+
+
+
+ );
+ },
+ ],
+};
+
+export default preview;
+```
+
+Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator:
+
+
+
+```ts
+// Button.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+
+import { Button } from './Button';
+
+const meta: Meta = {
+ component: Button,
+};
+export default meta;
+
+type Story = StoryObj;
+
+// Wrapped in default theme
+export const Default: Story = {};
+
+// Wrapped in dark theme
+export const Dark: Story = {
+ parameters: {
+ theme: 'dark',
+ },
+};
+```
+
+This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable.
From 75b0820ab13048ef27d47a967d77c745b9e50aeb Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 16 Apr 2024 14:28:54 -0600
Subject: [PATCH 05/46] Updates for Next.js
- Warning for absolute imports & mocking
- Module aliases
- Subpath imports
- Mocking modules
- `@storybook/nextjs/cache.mock` module API
---
docs/get-started/nextjs.md | 122 ++++++++++++++++++++++++++++++++++---
1 file changed, 115 insertions(+), 7 deletions(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index f1e024ef6049..8f109ff3202c 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -671,6 +671,106 @@ import 'styles/globals.scss';
// ...
```
+
+
+Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information.
+
+
+
+## Module aliases
+
+[Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported.
+
+```jsx
+// index.js
+// All good!
+import Button from '@/components/button';
+// Also good!
+import styles from '@/styles/HomePage.module.css';
+
+export default function HomePage() {
+ return (
+ <>
+
Hello World
+
+ >
+ );
+}
+```
+
+## Subpath imports
+
+As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules).
+
+To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `components` and `styles` internal modules:
+
+```json
+// package.json
+{
+ "imports": {
+ "#components": "./components",
+ "#styles": "./styles",
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
+
+
+
+Because subpath imports take the place of module aliases, you can remove the path aliases from your TypeScript configuration.
+
+
+
+Which can then be used like this:
+
+```jsx
+// index.js
+import Button from '#components/button';
+import styles from '#styles/HomePage.module.css';
+
+export default function HomePage() {
+ return (
+ <>
+
Hello World
+
+ >
+ );
+}
+```
+
+## Mocking modules
+
+Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](../writing-stories/mocking-modules.md) to control and assert their behavior.
+
+### Built-in mocked modules
+
+This framework provides mocks for many of Next.js' internal modules:
+
+1. [`@storybook/nextjs/cache.mock`](#storybooknextjscachemock)
+2. [`@storybook/nextjs/headers.mock`](#storybooknextjsheadersmock)
+3. [`@storybook/nextjs/navigation.mock`](#storybooknextjsnavigationmock)
+4. [`@storybook/nextjs/router.mock`](#storybooknextjsroutermock)
+
+### Mocking other modules
+
+How you mock other modules in Storybook depends on how you import the module into your component.
+
+The first step, with either approach, is to [create a mock file](../writing-stories/mocking-modules.md#mock-files).
+
+TK: More here?
+
+#### With subpath imports
+
+If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook.
+
+TK: Add example of mocking modules with subpath imports
+
+#### With module aliases
+
+If you're using [module aliases](#module-aliases), you can add a Webpack alias to your Storybook configuration to point to the mock file.
+
+TK: Add example of mocking modules with module aliases
+
## Runtime config
Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime.
@@ -887,7 +987,21 @@ You can refer to the [Install `sharp` to Use Built-In Image Optimization](https:
### Modules
-The `@storybook/nextjs` package exports a number of modules that enables you to [mock](/TODO_LINK_TO_MOCKING) Next.js's internal behavior.
+The `@storybook/nextjs` package exports a number of modules that enables you to [mock](#mocking-modules) Next.js's internal behavior.
+
+#### `@storybook/nextjs/export-mocks`
+
+Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
+
+`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories in a Jest environment](../api/portable-stories-jest.md).
+
+TK: Example snippet
+
+#### `@storybook/nextjs/cache.mock`
+
+Type: `typeof import('next/cache')`
+
+TK
#### `@storybook/nextjs/headers.mock`
@@ -917,12 +1031,6 @@ Type: `typeof import('next/router')`
Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
-#### `@storybook/nextjs/export-mocks`
-
-Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
-
-`getPackageAliases` returns the aliases needed to set up Portable Stories in a Jest environment. See the [Portable Stories](TODO) section.
-
### Options
You can pass an options object for additional configuration if needed:
From 299a2c2a7829cd361a9f938dfa7af17de01e64cf Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 16 Apr 2024 16:46:27 -0600
Subject: [PATCH 06/46] Simplify Building pages with Storybook guide
---
.../build-pages-with-storybook.md | 221 +-----------------
docs/writing-stories/mocking-modules.md | 22 ++
.../mocking-network-requests.md | 2 +-
docs/writing-stories/mocking-providers.md | 8 +-
4 files changed, 37 insertions(+), 216 deletions(-)
diff --git a/docs/writing-stories/build-pages-with-storybook.md b/docs/writing-stories/build-pages-with-storybook.md
index aeb293e09fc8..eb6fefd7842b 100644
--- a/docs/writing-stories/build-pages-with-storybook.md
+++ b/docs/writing-stories/build-pages-with-storybook.md
@@ -73,226 +73,19 @@ This approach is beneficial when the various subcomponents export a complex list
## Mocking connected components
-If you need to render a connected component in Storybook, you can mock the network requests to fetch its data. There are various layers in which you can do that.
+If you need to render a connected component in Storybook, you can mock the data or modules that component depends on. There are various layers in which you can do that.
-### Mocking providers
+### [Mocking providers](./mocking-providers.md)
-Suppose you are using a provider that supplies data via the context. In that case, you can wrap your story in a decorator that provides a mocked version of that provider. For example, in the [Screens](https://storybook.js.org/tutorials/intro-to-storybook/react/en/screen/) chapter of the Intro to Storybook tutorial, we mock a Redux provider with mock data.
+Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories.
-### Mocking API Services
+### [Mocking API Services](./mocking-network-requests.md)
-Connected applications such as Twitter, Instagram, amongst others, are everywhere, consuming data from REST or GraphQL endpoints. Suppose you're working in an application that relies on either of these data providers. In that case, you can add Mock Service Worker (MSW) via [Storybook's MSW addon](https://storybook.js.org/addons/msw-storybook-addon) to mock data alongside your app and stories.
+For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests in your stories.
-[Mock Service Worker](https://mswjs.io/) is an API mocking library. It relies on service workers to capture network requests and provides mocked data in response. The MSW addon adds this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
+### [Mocking imports](./mocking-modules.md)
-Run the following commands to install MSW, the addon, and generate a mock service worker.
-
-
-
-
-
-
-
-
-
-If you're working with Angular, you'll need to adjust the command to save the mock service worker file in a different directory (e.g., `src`).
-
-
-
-Update your `.storybook/preview.js` file and enable the addon via a [global decorator](./decorators.md#global-decorators).
-
-
-
-
-
-
-
-Finally, update your [`.storybook/main.js|ts`](../configure/index.md#using-storybook-api) to allow Storybook to load the generated mock service worker file as follows:
-
-
-
-
-
-
-
-#### Mocking REST requests with MSW addon
-
-If you're working with pure presentational screens, adding stories through [args composition](#args-composition-for-presentational-screens) is recommended. You can easily encode all the data via [args](../writing-stories/args.md), removing the need for handling it with "wrapper components". However, this approach loses its flexibility if the screen's data is retrieved from a RESTful endpoint within the screen itself. For instance, if your screen had a similar implementation to retrieve a list of documents:
-
-
-
-
-
-
-
-To test your screen with the mocked data, you could write a similar set of stories:
-
-
-
-
-
-
-
-
-
-This example details how you can mock the REST request with fetch. Similar HTTP clients such as [`axios`](https://axios-http.com/) can be used as well.
-
-
-
-The mocked data (i.e., `TestData`) will be injected via [parameters](./parameters.md), enabling you to configure it per-story basis.
-
-#### Mocking GraphQL queries with MSW addon
-
-In addition to mocking RESTful requests, the other noteworthy feature of the [MSW addon](https://msw-sb.vercel.app/?path=/story/guides-introduction--page) is the ability to mock incoming data from any of the mainstream [GraphQL](https://www.apollographql.com/docs/react/integrations/integrations/) clients (e.g., [Apollo Client](https://www.apollographql.com/docs/), [URQL](https://formidable.com/open-source/urql/) or [React Query](https://react-query.tanstack.com/)). For instance, if your screen retrieves the user's information and a list of documents based on a query result, you could have a similar implementation:
-
-
-
-
-
-
-
-To test your screen with the GraphQL mocked data, you could write the following stories:
-
-
-
-
-
-
-
-### Mocking imports
-
-It is also possible to mock imports directly, as you might in a unit test, using Webpack’s aliasing. It's advantageous if your component makes network requests directly with third-party libraries.
-
-We'll use [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch) as an example.
-
-Inside a directory called `__mocks__`, create a new file called
-`isomorphic-fetch.js` with the following code:
-
-
-
-
-
-
-
-The code above creates a decorator which reads story-specific data off the story's [parameters](./parameters.md), enabling you to configure the mock on a per-story basis.
-
-To use the mock in place of the real import, we use [Webpack aliasing](https://webpack.js.org/configuration/resolve/#resolvealias):
-
-
-
-
-
-
-
-Add the decorator you've just implemented to your [`storybook/preview.js`](../configure/index.md#configure-story-rendering):
-
-
-
-
-
-
-
-Finally, we can set the mock values in a specific story. Let's borrow an example from this [blog post](https://medium.com/@edogc/visual-unit-testing-with-react-storybook-and-fetch-mock-4594d3a281e6):
-
-
-
-
-
-
-
-### Specific mocks
-
-Another mocking approach is to use libraries that intercept calls at a lower level. For instance, you can use [`fetch-mock`](https://www.npmjs.com/package/fetch-mock) to mock fetch requests specifically.
-
-Like the [import mocking](##mocking-imports) above, once you have a mock, you’ll still want to set the return value of the mock per-story basis. Do this in Storybook with a [decorator](./decorators.md) that reads the story's [parameters](./parameters.md).
+Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index e59f0f7c2b59..32a123333fff 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -93,6 +93,28 @@ webpackFinal: async (config) => {
},
```
+
+
+
+
+
+
+
+
+
+
+
+
+
## Using mocked modules in stories
When you use the `fn` utility to mock a module, you create full [mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation.
diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md
index 461574f7c856..ec41c806ea9c 100644
--- a/docs/writing-stories/mocking-network-requests.md
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -2,7 +2,7 @@
title: Mocking network requests
---
-For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data.
+For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW is an API mocking library, which relies on service workers to capture network requests and provides mocked data in response.
The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon.
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index 4c5a8b20a496..1b3872371cb8 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -4,7 +4,7 @@ title: Mocking providers
-Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
+Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
@@ -19,6 +19,12 @@ Components can receive data or configuration from context providers. For example
+
+
+For another example, reference the [Screens](https://storybook.js.org/tutorials/intro-to-storybook/react/en/screen/) chapter of the Intro to Storybook tutorial, where we mock a Redux provider with mock data.
+
+
+
## Configuring the mock provider
When mocking a provider, it may be necessary to configure the provider to supply a different value for individual stories. For example, you might want to test a component with different themes or user roles.
From ec6742b1eaaca94b1af87f0259afc0064d98ac4c Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 16 Apr 2024 22:04:42 -0600
Subject: [PATCH 07/46] Update Decorators and Interaction tests pages
- Decorators
- Focus the "context" section on only that argument
- Move irrelevant examples and snippets to the Story rendering page
- Interaction Testing
- Under the Write an interaction test section, add:
- Run code before each test section
- Mocked modules section
---
docs/configure/story-rendering.md | 23 ++++++-
docs/writing-stories/decorators.md | 62 +++++++++---------
docs/writing-stories/mocking-modules.md | 4 +-
docs/writing-tests/interaction-testing.md | 76 +++++++++++++++++++++++
4 files changed, 133 insertions(+), 32 deletions(-)
diff --git a/docs/configure/story-rendering.md b/docs/configure/story-rendering.md
index fff549e8ef4c..1ca2d09c76d8 100644
--- a/docs/configure/story-rendering.md
+++ b/docs/configure/story-rendering.md
@@ -2,7 +2,28 @@
title: 'Story rendering'
---
-In Storybook, your stories render in a particular “preview” iframe (Canvas tab) inside the larger Storybook web application. The JavaScript build configuration of the preview is controlled by a [webpack](../builders/webpack.md) config, but you also may want to directly control the rendered HTML to help your stories render correctly.
+In Storybook, your stories render in a particular “preview” iframe (also called the Canvas) inside the larger Storybook web application. The JavaScript build configuration of the preview is controlled by a [builder](../builders/index.md) config, but you also may want to run some code for every story or directly control the rendered HTML to help your stories render correctly.
+
+## Running code for every story
+
+Code executed in the preview file (`.storybook/preview.js|ts`) runs for every story in your Storybook. This is useful for setting up global styles, initializing libraries, or anything else required to render your components.
+
+For example, with Vue, you can extend Storybook's application and register your library (e.g., [Fontawesome](https://github.com/FortAwesome/vue-fontawesome)). Or with Angular, add the package ([localize](https://angular.io/api/localize)) into your `polyfills.ts` and import it:
+
+
+
+
+
+
## Adding to <head>
diff --git a/docs/writing-stories/decorators.md b/docs/writing-stories/decorators.md
index 8c3160302cf8..da53026077e3 100644
--- a/docs/writing-stories/decorators.md
+++ b/docs/writing-stories/decorators.md
@@ -40,32 +40,7 @@ Some components require a “harness” to render in a useful way. For instance,
## “Context” for mocking
-Framework-specific libraries (e.g., [Styled Components](https://styled-components.com/), [Fontawesome](https://github.com/FortAwesome/vue-fontawesome) for Vue, Angular's [localize](https://angular.io/api/localize)) may require additional configuration to render correctly in Storybook.
-
-For example, if you're working with React's Styled Components and your components use themes, add a single global decorator to [`.storybook/preview.js`](../configure/index.md#configure-story-rendering) to enable them. With Vue, extend Storybook's application and register your library. Or with Angular, add the package into your `polyfills.ts` and import it:
-
-
-
-
-
-
-
-In the example above, the values provided are hardcoded. Still, you may want to vary them, either per-story basis (i.e., if the values you're adding are relevant to a specific story) or in a user-controlled way (e.g., provide a theme switcher or a different set of icons).
-
-The second argument to a decorator function is the **story context** which in particular contains the keys:
+The second argument to a decorator function is the **story context** which contains the properties:
- `args` - the story arguments. You can use some [`args`](./args.md) in your decorators and drop them in the story implementation itself.
- `argTypes`- Storybook's [argTypes](../api/arg-types.md) allow you to customize and fine-tune your stories [`args`](./args.md).
@@ -74,15 +49,42 @@ The second argument to a decorator function is the **story context** which in pa
- `parameters`- the story's static metadata, most commonly used to control Storybook's behavior of features and addons.
- `viewMode`- Storybook's current active window (e.g., canvas, docs).
-
+This context can be used to adjust the behavior of your decorator based on the story's arguments or other metadata. For example, you could create a decorator that wraps the story in a layout, unless the `noLayout` parameter is set to `true`:
+
+
+
+```ts
+// .storybook/preview.js
+import React from 'react';
+import { Preview } from '@storybook/react';
+
+import { Layout } from '../components/Layout';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { noLayout } = parameters;
+ return noLayout ? (
+
+ ) : (
+
+
+
+ );
+ },
+ ],
+};
-This pattern can also be applied to your own stories. Some of Storybook's supported frameworks already use it (e.g., Vue 3).
+export default preview;
+```
-
+For another example, see the section on [configuring the mock provider](./mocking-providers.md#configuring-the-mock-provider), which demonstrates how to use the same technique to change which theme is provided to the component.
### Using decorators to provide data
-If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data -- read more in the [building pages in Storybook](./build-pages-with-storybook.md) section.
+If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data. Read more in the [building pages in Storybook](./build-pages-with-storybook.md) section.
## Story decorators
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 32a123333fff..d4e8007663b5 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -117,7 +117,7 @@ webpackFinal: async (config) => {
## Using mocked modules in stories
-When you use the `fn` utility to mock a module, you create full [mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation.
+When you use the `fn` utility to mock a module, you create full [Vitest mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation.
Here, we define `beforeEach` on a story (which will run before the story is rendered) to set a mocked return value for the `getUserFromSession` function used by the Page component:
@@ -158,6 +158,8 @@ The `fn` utility also spies on the original module's functions, which you can us
For example, this story checks that the `saveNote` function was called when the user clicks the save button:
+
+
```ts
// NoteUI.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
diff --git a/docs/writing-tests/interaction-testing.md b/docs/writing-tests/interaction-testing.md
index b949aa284a8a..c235e1122fe7 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -90,6 +90,38 @@ Once the story loads in the UI, it simulates the user's behavior and verifies th
/>
+### Run code before each test
+
+It can be helpful to run code before each test to set up the initial state of the component or reset the state of modules. You can do this by adding an `async beforeEach` function to the meta in your stories file. This function will run before each test in the story file.
+
+
+
+```js
+// Page.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+import { fn } from '@storybook/test';
+
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+ async beforeEach() {
+ // 👇 Do this for each story
+ // TK
+ // 👇 Clear the mock between stories
+ getUserFromSession.mockClear();
+ },
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ // TK
+};
+```
+
### API for user-events
Under the hood, Storybook’s `@storybook/test` package provides Testing Library’s [`user-events`](https://testing-library.com/docs/user-event/intro/) APIs. If you’re familiar with [Testing Library](https://testing-library.com/), you should be at home in Storybook.
@@ -152,6 +184,50 @@ This will show your interactions nested in a collapsible group:
![Interaction testing with labeled steps](./storybook-addon-interactions-steps.png)
+### Mocked modules
+
+If your component depends on modules that are imported into the component file, you can mock those modules to control and assert on their behavior. This is detailed in the [mocking modules](./mocking-modules.md) guide.
+
+You can then import the mocked module (which has all of the helpful methods of a [Vitest mocked function](https://vitest.dev/api/mock.html)) into your story and use it to assert on the behavior of your component:
+
+
+
+```ts
+// NoteUI.stories.tsx
+import { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+const meta = {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
+
### Interactive debugger
If you check your interactions panel, you'll see the step-by-step flow. It also offers a handy set of UI controls to pause, resume, rewind, and step through each interaction.
From abc9b7cd8bd4d0a3c972dfd054ba7bd219e3e298 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 16 Apr 2024 22:06:47 -0600
Subject: [PATCH 08/46] Add missing TODOs
---
docs/get-started/nextjs.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 8f109ff3202c..7f66b0c2486a 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -1001,7 +1001,9 @@ TK: Example snippet
Type: `typeof import('next/cache')`
-TK
+TK: Description
+
+TK: Example snippet
#### `@storybook/nextjs/headers.mock`
@@ -1019,18 +1021,24 @@ For cookies, you can use the existing API to write them, eg. `cookies().set('fir
Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
+TK: Example snippet
+
#### `@storybook/nextjs/navigation.mock`
Type: `typeof import('next/navigation')`
Exports mocks that replaces the actual implementation of `next/navigation` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
+TK: Example snippet
+
#### `@storybook/nextjs/router.mock`
Type: `typeof import('next/router')`
Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
+TK: Example snippet
+
### Options
You can pass an options object for additional configuration if needed:
From 194a9bcbc0637a847ea79520a27a7e4bfa7ef039 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Wed, 17 Apr 2024 12:04:16 -0600
Subject: [PATCH 09/46] Add vite example
Co-authored-by: Jeppe Reinhold
---
docs/writing-stories/mocking-modules.md | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index d4e8007663b5..e26ed559c981 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -75,7 +75,22 @@ The Storybook environment will match the conditions `storybook` and `test`, so y
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-TK: Vite version, too
+```js
+// .storybook/main.ts
+
+viteFinal: async (config) => {
+ return {
+ ...config,
+ resolve: {
+ ...config.resolve,
+ alias: {
+ ...config.resolve?.alias,
+ 'lodash': require.resolve('./lodash.mock'),
+ '@/api/todo': path.resolve(__dirname, './api/todo.mock.ts')
+ }
+ }
+ }
+},
```js
// .storybook/main.ts
From f2f4d13716a8c0ca266910e5c3dd2217c4df4da8 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Wed, 17 Apr 2024 20:37:40 +0200
Subject: [PATCH 10/46] clarify mock->original import limitations.
---
docs/writing-stories/mocking-modules.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index e26ed559c981..c4f249f5cc79 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -12,9 +12,8 @@ For either approach, relative imports of the mocked module are not supported.
To mock a module, create a file with the same name and in the same directory as the module you want to mock. For example, to mock a module named `session`, create a file next to it named `session.mock.js|ts`, with a few characteristics:
-- It should re-export all exports from the original module.
+- It should re-export all exports from the original module - using relative imports to import the original, as using a subpath or alias import would result in it importing itself.
- It should use the `fn` utility to mock any necessary functionality from the original module.
-- It cannot use subpath imports or builder aliases for its dependencies.
- It should not introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the module they are mocking.
Here's an example of a mock file for a module named `session`:
@@ -75,7 +74,7 @@ The Storybook environment will match the conditions `storybook` and `test`, so y
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-```js
+````js
// .storybook/main.ts
viteFinal: async (config) => {
@@ -106,7 +105,7 @@ webpackFinal: async (config) => {
return config
},
-```
+````
From e7c06768fc90a3ba6bb5b45f7fc1b06e1f271255 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Wed, 17 Apr 2024 21:10:48 +0200
Subject: [PATCH 11/46] add paragraph on mocking external modules.
---
docs/writing-stories/mocking-modules.md | 31 +++++++++++++++++++++----
1 file changed, 27 insertions(+), 4 deletions(-)
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index c4f249f5cc79..61ef7881fe44 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -31,7 +31,7 @@ export const getUserFromSession = fn(actual.getUserFromSession);
The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
-To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules:
+To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for four internal modules:
TK: External module example?
@@ -43,15 +43,38 @@ TK: External module example?
"storybook": "./api.mock.ts",
"default": "./api.ts"
},
- "#prisma/prisma": {
- "storybook": "./prisma/prisma.mock.ts",
- "default": "./prisma/prisma.ts"
+ "#app/actions": {
+ "storybook": "./app/actions.mock.ts",
+ "default": "./app/actions.ts"
+ },
+ "#lib/session": {
+ "storybook": "./lib/session.mock.ts",
+ "default": "./lib/session.ts"
+ },
+ "#lib/db": {
+ "storybook": "./lib/db.mock.ts",
+ "default": "./lib/db.ts"
},
"#*": ["./*", "./*.ts", "./*.tsx"]
}
}
```
+You can't directly mock an external module like `uuid` or `node:fs`, so instead of importing them directly in your components you can wrap them in your own modules that you import from instead, and that are mockable like any other internal module. Here's an example of wrapping `uuid` and creating a mock for the wrapper:
+
+```ts
+// lib/uuid.ts
+import { v4 } from 'uuid';
+export const uuidv4 = v4;
+```
+
+```ts
+// lib/uuid.mock.ts
+import { fn } from '@storybook/test';
+import * as actual from './uuid';
+export const uuidv4 = fn(actual.uuidv4);
+```
+
Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
From 5d8ee2820935b5e6c541e6863d2caf5968982f2c Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Wed, 17 Apr 2024 22:20:12 +0200
Subject: [PATCH 12/46] add docs on `beforeEach`, `cleanup`, `parameters.test`
---
docs/api/parameters.md | 45 +++++++++++++++++++++++++
docs/writing-stories/mocking-modules.md | 22 ++++++++----
2 files changed, 60 insertions(+), 7 deletions(-)
diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index 506dc051b3df..7b8ec1446804 100644
--- a/docs/api/parameters.md
+++ b/docs/api/parameters.md
@@ -141,6 +141,51 @@ When specifying a custom sorting function, the function behaves like a typical J
See [the guide](../writing-stories/naming-components-and-hierarchy/#sorting-stories) for usage examples.
+### `test`
+
+Type:
+
+```ts
+{
+ clearMocks?: boolean;
+ mockReset?: boolean;
+ restoreMocks?: boolean;
+ dangerouslyIgnoreUnhandledErrors?: boolean;
+}
+```
+
+#### `clearMocks`
+
+Type: `boolean`
+
+Default: `false`
+
+[Similar to Vitest](https://vitest.dev/config/#clearmocks), it will call `.mockClear()` on all spies created with `fn()` from `@storybook/test` when a story unmounts. This will clear mock history, but not reset its implementation to the default one.
+
+#### `mockReset`
+
+Type: `boolean`
+
+Default: `false`
+
+[Similar to Vitest](https://vitest.dev/config/#mockreset), it will call `.mockReset()` on all spies created with `fn()` from `@storybook/test` when a story unmounts. This will clear mock history and reset its implementation to an empty function (will return `undefined`).
+
+#### `restoreMocks`
+
+Type: `boolean`
+
+Default: `true`
+
+[Similar to Vitest](https://vitest.dev/config/#restoremocks), it will call `.restoreMocks()` on all spies created with `fn()` from `@storybook/test` when a story unmounts. This will clear mock history and reset its implementation to the original one.
+
+#### `dangerouslyIgnoreUnhandledErrors`
+
+Type: `boolean`
+
+Default: `false`
+
+TK: Description
+
---
### Essential addons
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 61ef7881fe44..8063b9f6a718 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -233,16 +233,24 @@ export const SaveFlow: Story = {
};
```
-### Shared setup and clearing mocks
+### Setting up and cleaning up
-You can use `beforeEach` at the component level to perform shared setup or clear the mocks between stories. This ensures that each test starts with a clean slate and is not affected by the mocks from previous stories.
+You can use `beforeEach` at the project, component or story level to perform any setup that you need, eg. setting up mock behavior. You can also return a cleanup-function from `beforeEach` which will be called after your story unmounts. This is useful for unsubscribing observers etc.
+
+
+
+It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Storybook will already do that automatically before rendering a story. See the [`parameters.test`](../api/parameters.md#test) API for more information.
+
+
+
+Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and resetting it when the story unmounts.
```js
// Page.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
-import { fn } from '@storybook/test';
+import MockDate from 'mockdate';
import { getUserFromSession } from '#api/session.mock';
import { Page } from './Page';
@@ -250,10 +258,10 @@ import { Page } from './Page';
const meta: Meta = {
component: Page,
async beforeEach() {
- // 👇 Do this for each story
- // TK
- // 👇 Clear the mock between stories
- getUserFromSession.mockClear();
+ MockDate.set('2024-02-14');
+ return () => {
+ MockDate.reset();
+ };
},
};
export default meta;
From 4faf1aed873a759c294207cccbb33c29a233d659 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Wed, 17 Apr 2024 22:22:00 +0200
Subject: [PATCH 13/46] simplify subpath example in nextjs
---
docs/get-started/nextjs.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 7f66b0c2486a..408e11df3a49 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -702,14 +702,12 @@ export default function HomePage() {
As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules).
-To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `components` and `styles` internal modules:
+To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project:
```json
// package.json
{
"imports": {
- "#components": "./components",
- "#styles": "./styles",
"#*": ["./*", "./*.ts", "./*.tsx"]
}
}
From 2ac03c424800b7a0577f18609fe734eec322d85c Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Wed, 17 Apr 2024 23:27:34 -0600
Subject: [PATCH 14/46] Address feedback
- Next.js
- Add portable stories section
- Mocking modules
- Clarify requirements of mock files
- Prose and snippet tweaks
- Interaction testing
- Bring over `mockdate` example
- Prose and snippet tweaks
---
docs/api/parameters.md | 2 +-
docs/get-started/nextjs.md | 8 ++-
docs/writing-stories/mocking-modules.md | 65 ++++++++++++++---------
docs/writing-tests/interaction-testing.md | 29 +++++++---
4 files changed, 69 insertions(+), 35 deletions(-)
diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index 7b8ec1446804..e8b0d6529387 100644
--- a/docs/api/parameters.md
+++ b/docs/api/parameters.md
@@ -184,7 +184,7 @@ Type: `boolean`
Default: `false`
-TK: Description
+Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when an uncaught exception is thrown.
---
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 408e11df3a49..678be46c6b2d 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -881,6 +881,12 @@ If your server components access data via the network, we recommend using the [M
In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions).
+## Portable stories
+
+You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API.
+
+When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment.
+
## Notes for Yarn v2 and v3 users
If you're using [Yarn](https://yarnpkg.com/) v2 or v3, you may run into issues where Storybook can't resolve `style-loader` or `css-loader`. For example, you might get errors like:
@@ -991,7 +997,7 @@ The `@storybook/nextjs` package exports a number of modules that enables you to
Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
-`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories in a Jest environment](../api/portable-stories-jest.md).
+`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories](#portable-stories).
TK: Example snippet
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 8063b9f6a718..a7f2ea886023 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -12,7 +12,9 @@ For either approach, relative imports of the mocked module are not supported.
To mock a module, create a file with the same name and in the same directory as the module you want to mock. For example, to mock a module named `session`, create a file next to it named `session.mock.js|ts`, with a few characteristics:
-- It should re-export all exports from the original module - using relative imports to import the original, as using a subpath or alias import would result in it importing itself.
+- It must import the original module using a relative import.
+ - Using a subpath or alias import would result in it importing itself.
+- It should re-export all exports from the original module.
- It should use the `fn` utility to mock any necessary functionality from the original module.
- It should not introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the module they are mocking.
@@ -27,14 +29,36 @@ export * from './session';
export const getUserFromSession = fn(actual.getUserFromSession);
```
+### Mock files for external modules
+
+You can't directly mock an external module like `uuid` or `node:fs`. Instead, you must wrap the module in you own module, which you can then mock like any other internal module. In this example, we wrap `uuid`:
+
+
+
+```ts
+// lib/uuid.ts
+import { v4 } from 'uuid';
+
+export const uuidv4 = v4;
+```
+
+And create a mock for the wrapper:
+
+```ts
+// lib/uuid.mock.ts
+import { fn } from '@storybook/test';
+
+import * as actual from './uuid';
+
+export const uuidv4 = fn(actual.uuidv4);
+```
+
## Subpath imports
The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md).
To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for four internal modules:
-TK: External module example?
-
```json
// package.json
{
@@ -60,21 +84,6 @@ TK: External module example?
}
```
-You can't directly mock an external module like `uuid` or `node:fs`, so instead of importing them directly in your components you can wrap them in your own modules that you import from instead, and that are mockable like any other internal module. Here's an example of wrapping `uuid` and creating a mock for the wrapper:
-
-```ts
-// lib/uuid.ts
-import { v4 } from 'uuid';
-export const uuidv4 = v4;
-```
-
-```ts
-// lib/uuid.mock.ts
-import { fn } from '@storybook/test';
-import * as actual from './uuid';
-export const uuidv4 = fn(actual.uuidv4);
-```
-
Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
@@ -97,7 +106,7 @@ The Storybook environment will match the conditions `storybook` and `test`, so y
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-````js
+```js
// .storybook/main.ts
viteFinal: async (config) => {
@@ -113,6 +122,7 @@ viteFinal: async (config) => {
}
}
},
+```
```js
// .storybook/main.ts
@@ -128,7 +138,7 @@ webpackFinal: async (config) => {
return config
},
-````
+```
@@ -235,15 +245,17 @@ export const SaveFlow: Story = {
### Setting up and cleaning up
-You can use `beforeEach` at the project, component or story level to perform any setup that you need, eg. setting up mock behavior. You can also return a cleanup-function from `beforeEach` which will be called after your story unmounts. This is useful for unsubscribing observers etc.
+You can use the asynchronous `beforeEach` function to perform any setup that you need before the story is rendered, eg. setting up mock behavior. It can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.js|ts`, which will run for all stories in the project) level.
+
+You can also return a cleanup function from `beforeEach` which will be called after your story unmounts. This is useful for tasks like unsubscribing observers, etc.
-It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Storybook will already do that automatically before rendering a story. See the [`parameters.test`](../api/parameters.md#test) API for more information.
+It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Storybook will already do that automatically before rendering a story. See the [`parameters.test.restoreMocks` API](../api/parameters.md#restoremocks) for more information.
-Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and resetting it when the story unmounts.
+Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts.
@@ -257,8 +269,11 @@ import { Page } from './Page';
const meta: Meta = {
component: Page,
+ // 👇 Set the current date for every story in the file
async beforeEach() {
MockDate.set('2024-02-14');
+
+ // 👇 Reset the date after each test
return () => {
MockDate.reset();
};
@@ -268,7 +283,5 @@ export default meta;
type Story = StoryObj;
-export const Default: Story = {
- // TK
-};
+export const Default: Story = {};
```
diff --git a/docs/writing-tests/interaction-testing.md b/docs/writing-tests/interaction-testing.md
index c235e1122fe7..1c9b34509aaf 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -92,25 +92,38 @@ Once the story loads in the UI, it simulates the user's behavior and verifies th
### Run code before each test
-It can be helpful to run code before each test to set up the initial state of the component or reset the state of modules. You can do this by adding an `async beforeEach` function to the meta in your stories file. This function will run before each test in the story file.
+It can be helpful to run code before each test to set up the initial state of the component or reset the state of modules. You can do this by adding an asynchronous `beforeEach` function to the story, meta (which will run before each story in the file), or the preview file (`.storybook/preview.js|ts`, which will run before every story in the project).
+
+Additionally, if you return a cleanup function from the `beforeEach` function, it will run **after** each test, when the story is remounted or navigated away from.
+
+
+
+It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Storybook will already do that automatically before rendering a story. See the [`parameters.test.restoreMocks` API](../api/parameters.md#restoremocks) for more information.
+
+
+
+Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts.
```js
// Page.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
-import { fn } from '@storybook/test';
+import MockDate from 'mockdate';
import { getUserFromSession } from '#api/session.mock';
import { Page } from './Page';
const meta: Meta = {
component: Page,
+ // 👇 Set the current date for every story in the file
async beforeEach() {
- // 👇 Do this for each story
- // TK
- // 👇 Clear the mock between stories
- getUserFromSession.mockClear();
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the date after each test
+ return () => {
+ MockDate.reset();
+ };
},
};
export default meta;
@@ -118,7 +131,9 @@ export default meta;
type Story = StoryObj;
export const Default: Story = {
- // TK
+ async play({ canvasElement }) {
+ // ... This will run with the mocked date
+ },
};
```
From b1da9c9168564ac22135f9c39e9b7ec1f67441ec Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Thu, 18 Apr 2024 08:58:29 +0200
Subject: [PATCH 15/46] add getPackageAliases example
---
docs/get-started/nextjs.md | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 678be46c6b2d..8cc1b3b219bb 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -999,7 +999,31 @@ Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories](#portable-stories).
-TK: Example snippet
+```ts
+import type { Config } from 'jest';
+import nextJest from 'next/jest.js';
+// 👇 import the utility function
+import { getPackageAliases } from '@storybook/nextjs/export-mocks';
+
+const createJestConfig = nextJest({
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
+ dir: './',
+});
+
+// Add any custom config to be passed to Jest
+const config: Config = {
+ coverageProvider: 'v8',
+ testEnvironment: 'jsdom',
+ // Add more setup options before each test is run
+ // setupFilesAfterEnv: ['/jest.setup.ts'],
+ moduleNameMapper: {
+ ...getPackageAliases(), // 👈 add the utility as a moduleNameMapper
+ },
+};
+
+// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
+export default createJestConfig(config);
+```
#### `@storybook/nextjs/cache.mock`
From 71a4cea8b88390bec915ccaf4946dc5dddf30d41 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Thu, 18 Apr 2024 12:07:47 +0200
Subject: [PATCH 16/46] add examples to next mock docs
---
docs/get-started/nextjs.md | 143 ++++++++++++++++++++++++++++++++++---
1 file changed, 134 insertions(+), 9 deletions(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 8cc1b3b219bb..78197e490e7d 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -1029,9 +1029,34 @@ export default createJestConfig(config);
Type: `typeof import('next/cache')`
-TK: Description
+Exports mocks that replaces the actual implementation of `next/cache` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
-TK: Example snippet
+```ts
+import { expect, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 import from the Storybook package to get correct mock types
+import { revalidatePath } from '@storybook/nextjs/cache';
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Submitted: Story = {
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const submitButton = canvas.getByRole('button', { name: /submit/i });
+ await userEvent.click(saveButton);
+ // 👇 use any mock assertions on the function
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ },
+};
+```
#### `@storybook/nextjs/headers.mock`
@@ -1049,23 +1074,123 @@ For cookies, you can use the existing API to write them, eg. `cookies().set('fir
Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
-TK: Example snippet
+```ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 import from the Storybook package to get correct mock types
+import { cookies, headers } from '@storybook/nextjs/headers';
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const LoggedInEurope: Story = {
+ async beforeEach() {
+ // 👇 set mock cookies and headers ahead of rendering
+ cookies().set('username', 'Sol');
+ headers().set('timezone', 'Central European Summer Time');
+ },
+ play: () => {
+ // 👇 assert that your component called the mocks
+ expect(cookies().get).toHaveBeenCalledOnce();
+ expect(cookies().get).toHaveBeenCalledWith('username');
+ expect(headers().get).toHaveBeenCalledOnce();
+ expect(cookies().get).toHaveBeenCalledWith('timezone');
+ },
+};
+```
#### `@storybook/nextjs/navigation.mock`
-Type: `typeof import('next/navigation')`
+Type: `typeof import('next/navigation') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/navigation` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
+Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), so that the properties can be manipulated and asserted on.
+Use these to mock implementations or assert on mock calls in a story's `play`-function.
-TK: Example snippet
+```ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 import from the Storybook package to get correct mock types
+import { redirect, getRouter } from '@storybook/nextjs/navigation';
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+ parameters: {
+ nextjs: {
+ // 👇 As in the Next.js application, next/navigation only works using App Router
+ appDirectory: true,
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Unauthenticated: Story = {
+ play: () => {
+ // 👇 assert that your component called redirect()
+ expect(redirect).toHaveBeenCalledWith('/login', 'replace');
+ },
+};
+
+export const GoBack: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
#### `@storybook/nextjs/router.mock`
-Type: `typeof import('next/router')`
+Type: `typeof import('next/router') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
+Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), so that the properties can be manipulated and asserted on.
+Use these to mock implementations or assert on mock calls in a story's `play`-function.
-TK: Example snippet
+```ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 import from the Storybook package to get correct mock types
+import { getRouter } from '@storybook/nextjs/router';
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+ parameters: {
+ nextjs: {
+ // 👇 As in the Next.js application, next/router only works using Pages Router
+ appDirectory: false,
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const GoBack: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
### Options
From 5bab73ae83f0213db1925e186b5410d1e08b8bab Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Thu, 18 Apr 2024 12:09:05 +0200
Subject: [PATCH 17/46] improve documentation for
parameters.test.dangerouslyIgnoreUnhandledErrors
---
docs/api/parameters.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index e8b0d6529387..0e37ebdf06e6 100644
--- a/docs/api/parameters.md
+++ b/docs/api/parameters.md
@@ -184,7 +184,9 @@ Type: `boolean`
Default: `false`
-Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when an uncaught exception is thrown.
+Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when unhandled errors are thrown during rendering or playing.
+
+Unhandled errors might cause false positive assertions, they can be ignored by setting this parameter to `true`.
---
From 46c31ebbd945c8879ec11c42eb84b7b39b7d4c41 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Thu, 18 Apr 2024 13:14:20 +0200
Subject: [PATCH 18/46] remove docs on default router and navigation contexts
---
docs/get-started/nextjs.md | 83 +------------------
...navigation-push-override-in-preview.js.mdx | 21 -----
...navigation-push-override-in-preview.ts.mdx | 24 ------
...tjs-router-push-override-in-preview.js.mdx | 21 -----
...tjs-router-push-override-in-preview.ts.mdx | 24 ------
5 files changed, 2 insertions(+), 171 deletions(-)
delete mode 100644 docs/snippets/react/nextjs-navigation-push-override-in-preview.js.mdx
delete mode 100644 docs/snippets/react/nextjs-navigation-push-override-in-preview.ts.mdx
delete mode 100644 docs/snippets/react/nextjs-router-push-override-in-preview.js.mdx
delete mode 100644 docs/snippets/react/nextjs-router-push-override-in-preview.ts.mdx
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 78197e490e7d..4a9d4cca092a 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -301,41 +301,6 @@ The default values on the stubbed router are as follows (see [globals](../essent
```ts
// Default router
const defaultRouter = {
- push(...args) {
- action('nextRouter.push')(...args);
- return Promise.resolve(true);
- },
- replace(...args) {
- action('nextRouter.replace')(...args);
- return Promise.resolve(true);
- },
- reload(...args) {
- action('nextRouter.reload')(...args);
- },
- back(...args) {
- action('nextRouter.back')(...args);
- },
- forward() {
- action('nextRouter.forward')();
- },
- prefetch(...args) {
- action('nextRouter.prefetch')(...args);
- return Promise.resolve();
- },
- beforePopState(...args) {
- action('nextRouter.beforePopState')(...args);
- },
- events: {
- on(...args) {
- action('nextRouter.events.on')(...args);
- },
- off(...args) {
- action('nextRouter.events.off')(...args);
- },
- emit(...args) {
- action('nextRouter.events.emit')(...args);
- },
- },
// The locale should be configured globally: https://storybook.js.org/docs/essentials/toolbars-and-globals#globals
locale: globals?.locale,
asPath: '/',
@@ -350,20 +315,7 @@ const defaultRouter = {
};
```
-### Actions integration caveats
-
-If you override a function, you lose the automatic action tab integration and have to build it out yourself, which looks something like this (make sure you install the `@storybook/addon-actions` package):
-
-
-
-
-
-
+All the functions (such as `back()`, `push()`) are mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
## Next.js navigation
@@ -493,43 +445,12 @@ The default values on the stubbed navigation context are as follows:
```ts
// Default navigation context
const defaultNavigationContext = {
- push(...args) {
- action('nextNavigation.push')(...args);
- },
- replace(...args) {
- action('nextNavigation.replace')(...args);
- },
- forward(...args) {
- action('nextNavigation.forward')(...args);
- },
- back(...args) {
- action('nextNavigation.back')(...args);
- },
- prefetch(...args) {
- action('nextNavigation.prefetch')(...args);
- },
- refresh: () => {
- action('nextNavigation.refresh')();
- },
pathname: '/',
query: {},
};
```
-### Actions integration caveats
-
-If you override a function, you lose the automatic action tab integration and have to build it out yourself, which looks something like this (make sure you install the `@storybook/addon-actions` package):
-
-
-
-
-
-
+All the functions (such as `back()`, `push()`) are mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
## Next.js Head
diff --git a/docs/snippets/react/nextjs-navigation-push-override-in-preview.js.mdx b/docs/snippets/react/nextjs-navigation-push-override-in-preview.js.mdx
deleted file mode 100644
index 03e44ad039a7..000000000000
--- a/docs/snippets/react/nextjs-navigation-push-override-in-preview.js.mdx
+++ /dev/null
@@ -1,21 +0,0 @@
-```js
-// .storybook/preview.js
-
-export default {
- // ...
- parameters: {
- // ...
- nextjs: {
- navigation: {
- push(...args) {
- // Custom logic can go here
- // This logs to the Actions panel
- action('nextNavigation.push')(...args);
- // Return whatever you want here
- return Promise.resolve(true);
- },
- },
- },
- },
-};
-```
diff --git a/docs/snippets/react/nextjs-navigation-push-override-in-preview.ts.mdx b/docs/snippets/react/nextjs-navigation-push-override-in-preview.ts.mdx
deleted file mode 100644
index 9534d5dd227f..000000000000
--- a/docs/snippets/react/nextjs-navigation-push-override-in-preview.ts.mdx
+++ /dev/null
@@ -1,24 +0,0 @@
-```ts
-// .storybook/preview.ts
-import { Preview } from '@storybook/react';
-
-const preview: Preview = {
- // ...
- parameters: {
- // ...
- nextjs: {
- navigation: {
- push(...args) {
- // Custom logic can go here
- // This logs to the Actions panel
- action('nextNavigation.push')(...args);
- // Return whatever you want here
- return Promise.resolve(true);
- },
- },
- },
- },
-};
-
-export default preview;
-```
diff --git a/docs/snippets/react/nextjs-router-push-override-in-preview.js.mdx b/docs/snippets/react/nextjs-router-push-override-in-preview.js.mdx
deleted file mode 100644
index b5e94878eee6..000000000000
--- a/docs/snippets/react/nextjs-router-push-override-in-preview.js.mdx
+++ /dev/null
@@ -1,21 +0,0 @@
-```js
-// .storybook/preview.js
-
-export default {
- // ...
- parameters: {
- // ...
- nextjs: {
- router: {
- push(...args) {
- // Custom logic can go here
- // This logs to the Actions panel
- action('nextRouter.push')(...args);
- // Return whatever you want here
- return Promise.resolve(true);
- },
- },
- },
- },
-};
-```
diff --git a/docs/snippets/react/nextjs-router-push-override-in-preview.ts.mdx b/docs/snippets/react/nextjs-router-push-override-in-preview.ts.mdx
deleted file mode 100644
index a1ae3c70fe6d..000000000000
--- a/docs/snippets/react/nextjs-router-push-override-in-preview.ts.mdx
+++ /dev/null
@@ -1,24 +0,0 @@
-```ts
-// .storybook/preview.ts
-import { Preview } from '@storybook/react';
-
-const preview: Preview = {
- // ...
- parameters: {
- // ...
- nextjs: {
- router: {
- push(...args) {
- // Custom logic can go here
- // This logs to the Actions panel
- action('nextRouter.push')(...args);
- // Return whatever you want here
- return Promise.resolve(true);
- },
- },
- },
- },
-};
-
-export default preview;
-```
From eef32904a4308dd60cecf09cef818a96cf1df514 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Thu, 18 Apr 2024 13:24:53 +0200
Subject: [PATCH 19/46] add reasons for aliases in nextjs+jest
---
docs/get-started/nextjs.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 4a9d4cca092a..cbc883408964 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -806,7 +806,7 @@ In the future we will provide better mocking support in Storybook and support fo
You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API.
-When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment.
+When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because - as Next.js does - Storybook sets up a set of aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well.
## Notes for Yarn v2 and v3 users
From f88bea51699bddc6928db97b0a19c2cd5955be6e Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Thu, 18 Apr 2024 20:36:02 -0600
Subject: [PATCH 20/46] Add back nextjs router/navigation override instructions
Other improvements:
- Consistency improvements in snippets throughout
- Prose tweaks
---
docs/api/parameters.md | 2 +-
docs/api/portable-stories-jest.md | 3 +-
docs/get-started/nextjs.md | 217 +++++++++++++++++-----
docs/writing-stories/mocking-modules.md | 91 +++++----
docs/writing-stories/mocking-providers.md | 4 +-
docs/writing-tests/interaction-testing.md | 6 +-
6 files changed, 219 insertions(+), 104 deletions(-)
diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index 0e37ebdf06e6..be7b02cc4b83 100644
--- a/docs/api/parameters.md
+++ b/docs/api/parameters.md
@@ -186,7 +186,7 @@ Default: `false`
Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when unhandled errors are thrown during rendering or playing.
-Unhandled errors might cause false positive assertions, they can be ignored by setting this parameter to `true`.
+Unhandled errors might cause false positive assertions. They can be ignored by setting this parameter to `true`.
---
diff --git a/docs/api/portable-stories-jest.md b/docs/api/portable-stories-jest.md
index 4c4141a53922..cdc92d922e26 100644
--- a/docs/api/portable-stories-jest.md
+++ b/docs/api/portable-stories-jest.md
@@ -26,10 +26,11 @@ Normally, Storybok composes a story and its [annotations](#annotations) automati
-**Using `Next.js`?** You need to do two things differently when using portable stories in Jest with Next.js projects:
+**Using `Next.js`?** You need to do three things differently when using portable stories in Jest with Next.js projects:
- Configure the [`next/jest.js` transformer](https://nextjs.org/docs/pages/building-your-application/testing/jest#manual-setup), which will handle all of the necessary Next.js configuration for you.
- Import [`composeStories`](#composestories) or [`composeStory`](#composestory) from the `@storybook/nextjs` package (e.g. `import { composeStories } from '@storybook/nextjs'`).
+- Set up [internal module aliases](../get-started/nextjs.md#storybooknextjsexport-mocks) to be able to mock and assert on them.
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index cbc883408964..3c41ed990db7 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -131,7 +131,7 @@ This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/
[Local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) are supported.
```jsx
-// index.js
+// index.jsx
import Image from 'next/image';
import profilePic from '../public/me.png';
@@ -158,7 +158,7 @@ function Home() {
[Remote images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#remote-images) are also supported.
```jsx
-// index.js
+// index.jsx
import Image from 'next/image';
export default function Home() {
@@ -315,7 +315,33 @@ const defaultRouter = {
};
```
-All the functions (such as `back()`, `push()`) are mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
+Additionally, the [`router` object](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
+
+To override these defaults, you can use [parameters](../writing-stories/parameters.md) and [`beforeEach`](../writing-stories/mocking-modules.md#setting-up-and-cleaning-up):
+
+```ts
+// .storybook/preview.ts
+import { Preview } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/router.mock';
+
+const preview: Preview = {
+ paramters: {
+ nextjs: {
+ // 👇 Override the default router properties
+ router: {
+ basePath: '/app/',
+ },
+ },
+ },
+ async beforeEach() {
+ // 👇 Manipulate the default router method mocks
+ getRouter().push.mockImplementation(() => {
+ /* ... */
+ });
+ },
+};
+```
## Next.js navigation
@@ -450,7 +476,33 @@ const defaultNavigationContext = {
};
```
-All the functions (such as `back()`, `push()`) are mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
+Additionally, the [`router` object](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html).
+
+To override these defaults, you can use [parameters](../writing-stories/parameters.md) and [`beforeEach`](../writing-stories/mocking-modules.md#setting-up-and-cleaning-up):
+
+```ts
+// .storybook/preview.ts
+import { Preview } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/navigation.mock';
+
+const preview: Preview = {
+ paramters: {
+ nextjs: {
+ // 👇 Override the default navigation properties
+ navigation: {
+ pathname: '/app/',
+ },
+ },
+ },
+ async beforeEach() {
+ // 👇 Manipulate the default navigation method mocks
+ getRouter().push.mockImplementation(() => {
+ /* ... */
+ });
+ },
+};
+```
## Next.js Head
@@ -566,7 +618,7 @@ This allows for cool things like zero-config Tailwind! (See [Next.js' example](h
[Absolute imports](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases#absolute-imports) from the root directory are supported.
```jsx
-// index.js
+// index.jsx
// All good!
import Button from 'components/button';
// Also good!
@@ -603,7 +655,7 @@ Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules
[Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported.
```jsx
-// index.js
+// index.jsx
// All good!
import Button from '@/components/button';
// Also good!
@@ -643,7 +695,7 @@ Because subpath imports take the place of module aliases, you can remove the pat
Which can then be used like this:
```jsx
-// index.js
+// index.jsx
import Button from '#components/button';
import styles from '#styles/HomePage.module.css';
@@ -674,21 +726,81 @@ This framework provides mocks for many of Next.js' internal modules:
How you mock other modules in Storybook depends on how you import the module into your component.
-The first step, with either approach, is to [create a mock file](../writing-stories/mocking-modules.md#mock-files).
+The first step, with either approach, is to [create a mock file](../writing-stories/mocking-modules.md#mock-files). Here's an example of a mock file for a module named `session`:
+
+
-TK: More here?
+```ts
+// lib/session.mock.ts
+import { fn } from '@storybook/test';
+import * as actual from './session';
+
+export * from './session';
+export const getUserFromSession = fn(actual.getUserFromSession);
+```
#### With subpath imports
-If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook.
+If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook:
-TK: Add example of mocking modules with subpath imports
+
+
+```json
+// package.json
+{
+ "imports": {
+ "#api": {
+ "storybook": "./api.mock.ts",
+ "default": "./api.ts"
+ },
+ "#app/actions": {
+ "storybook": "./app/actions.mock.ts",
+ "default": "./app/actions.ts"
+ },
+ "#lib/session": {
+ "storybook": "./lib/session.mock.ts",
+ "default": "./lib/session.ts"
+ },
+ "#lib/db": {
+ "storybook": "./lib/db.mock.ts",
+ "default": "./lib/db.ts"
+ },
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
+
+
+
+Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
+
+
#### With module aliases
If you're using [module aliases](#module-aliases), you can add a Webpack alias to your Storybook configuration to point to the mock file.
-TK: Add example of mocking modules with module aliases
+
+
+```ts
+// .storybook/main.ts
+webpackFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api$': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
+ }
+ }
+
+ return config;
+},
+```
## Runtime config
@@ -806,7 +918,7 @@ In the future we will provide better mocking support in Storybook and support fo
You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API.
-When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because - as Next.js does - Storybook sets up a set of aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well.
+When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because, to replicate Next.js configuration, Storybook sets up aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well.
## Notes for Yarn v2 and v3 users
@@ -921,9 +1033,10 @@ Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories](#portable-stories).
```ts
+// jest.config.ts
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
-// 👇 import the utility function
+// 👇 Import the utility function
import { getPackageAliases } from '@storybook/nextjs/export-mocks';
const createJestConfig = nextJest({
@@ -931,18 +1044,14 @@ const createJestConfig = nextJest({
dir: './',
});
-// Add any custom config to be passed to Jest
const config: Config = {
- coverageProvider: 'v8',
testEnvironment: 'jsdom',
- // Add more setup options before each test is run
- // setupFilesAfterEnv: ['/jest.setup.ts'],
+ // ... rest of Jest config
moduleNameMapper: {
- ...getPackageAliases(), // 👈 add the utility as a moduleNameMapper
+ ...getPackageAliases(), // 👈 Add the utility as mapped module names
},
};
-// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config);
```
@@ -950,13 +1059,16 @@ export default createJestConfig(config);
Type: `typeof import('next/cache')`
-Exports mocks that replaces the actual implementation of `next/cache` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.
+Exports mocks that replaces the actual implementation of `next/cache` exports. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+
+
```ts
+// MyForm.stories.ts
import { expect, userEvent, within } from '@storybook/test';
import { Meta, StoryObj } from '@storybook/react';
-// 👇 import from the Storybook package to get correct mock types
-import { revalidatePath } from '@storybook/nextjs/cache';
+// 👇 Must use this import path to have mocks typed correctly
+import { revalidatePath } from '@storybook/nextjs/cache.mock';
import MyForm from './my-form';
const meta = {
@@ -968,12 +1080,12 @@ export default meta;
type Story = StoryObj;
export const Submitted: Story = {
- play: async ({ canvasElement, step }) => {
+ async play({ canvasElement }) {
const canvas = within(canvasElement);
const submitButton = canvas.getByRole('button', { name: /submit/i });
await userEvent.click(saveButton);
- // 👇 use any mock assertions on the function
+ // 👇 Use any mock assertions on the function
await expect(revalidatePath).toHaveBeenCalledWith('/');
},
};
@@ -995,11 +1107,14 @@ For cookies, you can use the existing API to write them, eg. `cookies().set('fir
Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
+
+
```ts
+// MyForm.stories.ts
import { expect, fireEvent, userEvent, within } from '@storybook/test';
import { Meta, StoryObj } from '@storybook/react';
-// 👇 import from the Storybook package to get correct mock types
-import { cookies, headers } from '@storybook/nextjs/headers';
+// 👇 Must use this import path to have mocks typed correctly
+import { cookies, headers } from '@storybook/nextjs/headers.mock';
import MyForm from './my-form';
const meta = {
@@ -1012,16 +1127,16 @@ type Story = StoryObj;
export const LoggedInEurope: Story = {
async beforeEach() {
- // 👇 set mock cookies and headers ahead of rendering
+ // 👇 Set mock cookies and headers ahead of rendering
cookies().set('username', 'Sol');
headers().set('timezone', 'Central European Summer Time');
},
- play: () => {
- // 👇 assert that your component called the mocks
- expect(cookies().get).toHaveBeenCalledOnce();
- expect(cookies().get).toHaveBeenCalledWith('username');
- expect(headers().get).toHaveBeenCalledOnce();
- expect(cookies().get).toHaveBeenCalledWith('timezone');
+ async play() {
+ // 👇 Assert that your component called the mocks
+ await expect(cookies().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('username');
+ await expect(headers().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('timezone');
},
};
```
@@ -1030,14 +1145,16 @@ export const LoggedInEurope: Story = {
Type: `typeof import('next/navigation') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), so that the properties can be manipulated and asserted on.
-Use these to mock implementations or assert on mock calls in a story's `play`-function.
+Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+
+
```ts
+// MyForm.stories.ts
import { expect, fireEvent, userEvent, within } from '@storybook/test';
import { Meta, StoryObj } from '@storybook/react';
-// 👇 import from the Storybook package to get correct mock types
-import { redirect, getRouter } from '@storybook/nextjs/navigation';
+// 👇 Must use this import path to have mocks typed correctly
+import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
import MyForm from './my-form';
const meta = {
@@ -1055,19 +1172,19 @@ export default meta;
type Story = StoryObj;
export const Unauthenticated: Story = {
- play: () => {
- // 👇 assert that your component called redirect()
- expect(redirect).toHaveBeenCalledWith('/login', 'replace');
+ async play() => {
+ // 👇 Assert that your component called redirect()
+ await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
},
};
export const GoBack: Story = {
- play: async ({ canvasElement }) => {
+ async play({ canvasElement }) {
const canvas = within(canvasElement);
const backBtn = await canvas.findByText('Go back');
await userEvent.click(backBtn);
- // 👇 assert that your component called back()
+ // 👇 Assert that your component called back()
await expect(getRouter().back).toHaveBeenCalled();
},
};
@@ -1077,14 +1194,16 @@ export const GoBack: Story = {
Type: `typeof import('next/router') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), so that the properties can be manipulated and asserted on.
-Use these to mock implementations or assert on mock calls in a story's `play`-function.
+Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+
+
```ts
+// MyForm.stories.ts
import { expect, fireEvent, userEvent, within } from '@storybook/test';
import { Meta, StoryObj } from '@storybook/react';
-// 👇 import from the Storybook package to get correct mock types
-import { getRouter } from '@storybook/nextjs/router';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/router.mock';
import MyForm from './my-form';
const meta = {
@@ -1102,12 +1221,12 @@ export default meta;
type Story = StoryObj;
export const GoBack: Story = {
- play: async ({ canvasElement }) => {
+ async play({ canvasElement }) {
const canvas = within(canvasElement);
const backBtn = await canvas.findByText('Go back');
await userEvent.click(backBtn);
- // 👇 assert that your component called back()
+ // 👇 Assert that your component called back()
await expect(getRouter().back).toHaveBeenCalled();
},
};
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index a7f2ea886023..43db0d9b1f67 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -20,8 +20,10 @@ To mock a module, create a file with the same name and in the same directory as
Here's an example of a mock file for a module named `session`:
-```js
-// session.mock.js
+
+
+```ts
+// lib/session.mock.ts
import { fn } from '@storybook/test';
import * as actual from './session';
@@ -33,8 +35,6 @@ export const getUserFromSession = fn(actual.getUserFromSession);
You can't directly mock an external module like `uuid` or `node:fs`. Instead, you must wrap the module in you own module, which you can then mock like any other internal module. In this example, we wrap `uuid`:
-
-
```ts
// lib/uuid.ts
import { v4 } from 'uuid';
@@ -59,6 +59,8 @@ The recommended method for mocking modules is to use [subpath imports](https://n
To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for four internal modules:
+
+
```json
// package.json
{
@@ -93,7 +95,14 @@ Each subpath must begin with `#`, to differentiate it from a regular module path
You can then update your component file to use the subpath import:
```ts
-TK: Component snippet
+// AuthButton.ts
+import { getUserFromSession } from '#lib/session';
+
+export const AuthButton = (props) => {
+ const user = getUserFromSession();
+
+ // ...
+};
```
### Conditional imports
@@ -106,62 +115,48 @@ The Storybook environment will match the conditions `storybook` and `test`, so y
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-```js
-// .storybook/main.ts
+
+```ts
+// .storybook/main.ts
viteFinal: async (config) => {
- return {
- ...config,
- resolve: {
- ...config.resolve,
- alias: {
- ...config.resolve?.alias,
- 'lodash': require.resolve('./lodash.mock'),
- '@/api/todo': path.resolve(__dirname, './api/todo.mock.ts')
- }
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve?.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
}
}
+
+ return config;
},
```
-```js
+```ts
// .storybook/main.ts
-
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
- 'next/headers': require.resolve('./next-headers'),
- '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'),
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api$': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
}
}
- return config
+ return config;
},
```
-
-
-
-
-
-
-
-
-
-
-
-
-
## Using mocked modules in stories
When you use the `fn` utility to mock a module, you create full [Vitest mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation.
@@ -170,8 +165,8 @@ Here, we define `beforeEach` on a story (which will run before the story is rend
-```js
-// Page.stories.tsx
+```ts
+// Page.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
@@ -208,7 +203,7 @@ For example, this story checks that the `saveNote` function was called when the
```ts
-// NoteUI.stories.tsx
+// NoteUI.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
@@ -259,8 +254,8 @@ Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate
-```js
-// Page.stories.tsx
+```ts
+// Page.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import MockDate from 'mockdate';
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index 1b3872371cb8..adeec3072f67 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -38,7 +38,7 @@ For example, we can adjust the decorator from above to read from `parameters.the
```ts
-// .storybook/preview.js
+// .storybook/preview.ts
import React from 'react';
import { Preview } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
@@ -66,7 +66,7 @@ Now, you can define a `theme` parameter in your stories to adjust the theme prov
```ts
-// Button.stories.tsx
+// Button.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
diff --git a/docs/writing-tests/interaction-testing.md b/docs/writing-tests/interaction-testing.md
index 1c9b34509aaf..4a28b9258abf 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -106,8 +106,8 @@ Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate
-```js
-// Page.stories.tsx
+```ts
+// Page.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import MockDate from 'mockdate';
@@ -208,7 +208,7 @@ You can then import the mocked module (which has all of the helpful methods of a
```ts
-// NoteUI.stories.tsx
+// NoteUI.stories.ts|tsx
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
From 3f7e44ca6e6bc64d20fdd55339b3f93c55360892 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Thu, 18 Apr 2024 20:37:34 -0600
Subject: [PATCH 21/46] Improve MSW snippets
- Use MSW 2.0 API
- Use loader instead of decorator
- Improve snippet filename format
---
...w-addon-configure-handlers-graphql.ts.mdx} | 44 +++++++------
...dx => msw-addon-configure-handlers.ts.mdx} | 27 +++++---
... msw-addon-configure-handlers-http.js.mdx} | 29 +++++----
...-addon-configure-handlers-http.ts-4-9.mdx} | 27 +++++---
... msw-addon-configure-handlers-http.ts.mdx} | 27 +++++---
.../common/msw-addon-initialize.js.mdx | 16 +++++
...don.ts.mdx => msw-addon-initialize.ts.mdx} | 15 ++---
...=> msw-generate-service-worker.npx.js.mdx} | 0
...> msw-generate-service-worker.pnpm.js.mdx} | 0
docs/snippets/common/msw-install.npm.js.mdx | 3 +
docs/snippets/common/msw-install.pnpm.js.mdx | 3 +
docs/snippets/common/msw-install.yarn.js.mdx | 3 +
...torybook-preview-register-msw-addon.js.mdx | 24 --------
...w-addon-configure-handlers-graphql.js.mdx} | 44 +++++++------
...don-configure-handlers-graphql.ts-4-9.mdx} | 50 ++++++++-------
...w-addon-configure-handlers-graphql.ts.mdx} | 52 ++++++++--------
...w-addon-configure-handlers-graphql.js.mdx} | 53 +++++++++-------
...don-configure-handlers-graphql.ts-4-9.mdx} | 47 ++++++++------
...w-addon-configure-handlers-graphql.ts.mdx} | 47 ++++++++------
...w-addon-configure-handlers-graphql.js.mdx} | 61 ++++++++++---------
...don-configure-handlers-graphql.ts-4-9.mdx} | 61 ++++++++++---------
...w-addon-configure-handlers-graphql.ts.mdx} | 61 ++++++++++---------
... msw-addon-configure-handlers-http.js.mdx} | 27 +++++---
... msw-addon-configure-handlers-http.ts.mdx} | 27 +++++---
.../mocking-network-requests.md | 56 +++++++++--------
25 files changed, 453 insertions(+), 351 deletions(-)
rename docs/snippets/angular/{documentscreen-story-msw-graphql-query.ts.mdx => msw-addon-configure-handlers-graphql.ts.mdx} (73%)
rename docs/snippets/angular/{documentscreen-story-msw-rest-request.ts.mdx => msw-addon-configure-handlers.ts.mdx} (71%)
rename docs/snippets/common/{documentscreen-story-msw-rest-request.js.mdx => msw-addon-configure-handlers-http.js.mdx} (64%)
rename docs/snippets/common/{documentscreen-story-msw-rest-request.ts-4-9.mdx => msw-addon-configure-handlers-http.ts-4-9.mdx} (72%)
rename docs/snippets/common/{documentscreen-story-msw-rest-request.ts.mdx => msw-addon-configure-handlers-http.ts.mdx} (72%)
create mode 100644 docs/snippets/common/msw-addon-initialize.js.mdx
rename docs/snippets/common/{storybook-preview-register-msw-addon.ts.mdx => msw-addon-initialize.ts.mdx} (59%)
rename docs/snippets/common/{storybook-msw-generate.msw.js.mdx => msw-generate-service-worker.npx.js.mdx} (100%)
rename docs/snippets/common/{storybook-msw-generate.msw-pnpm.js.mdx => msw-generate-service-worker.pnpm.js.mdx} (100%)
create mode 100644 docs/snippets/common/msw-install.npm.js.mdx
create mode 100644 docs/snippets/common/msw-install.pnpm.js.mdx
create mode 100644 docs/snippets/common/msw-install.yarn.js.mdx
delete mode 100644 docs/snippets/common/storybook-preview-register-msw-addon.js.mdx
rename docs/snippets/react/{documentscreen-story-msw-graphql-query.js.mdx => msw-addon-configure-handlers-graphql.js.mdx} (70%)
rename docs/snippets/react/{documentscreen-story-msw-graphql-query.ts-4-9.mdx => msw-addon-configure-handlers-graphql.ts-4-9.mdx} (73%)
rename docs/snippets/react/{documentscreen-story-msw-graphql-query.ts.mdx => msw-addon-configure-handlers-graphql.ts.mdx} (70%)
rename docs/snippets/svelte/{documentscreen-story-msw-graphql-query.js.mdx => msw-addon-configure-handlers-graphql.js.mdx} (64%)
rename docs/snippets/svelte/{documentscreen-story-msw-graphql-query.ts-4-9.mdx => msw-addon-configure-handlers-graphql.ts-4-9.mdx} (64%)
rename docs/snippets/svelte/{documentscreen-story-msw-graphql-query.ts.mdx => msw-addon-configure-handlers-graphql.ts.mdx} (64%)
rename docs/snippets/vue/{documentscreen-story-msw-graphql-query.js.mdx => msw-addon-configure-handlers-graphql.js.mdx} (58%)
rename docs/snippets/vue/{documentscreen-story-msw-graphql-query.ts-4-9.mdx => msw-addon-configure-handlers-graphql.ts-4-9.mdx} (62%)
rename docs/snippets/vue/{documentscreen-story-msw-graphql-query.ts.mdx => msw-addon-configure-handlers-graphql.ts.mdx} (62%)
rename docs/snippets/web-components/{documentscreen-story-msw-rest-request.js.mdx => msw-addon-configure-handlers-http.js.mdx} (66%)
rename docs/snippets/web-components/{documentscreen-story-msw-rest-request.ts.mdx => msw-addon-configure-handlers-http.ts.mdx} (69%)
diff --git a/docs/snippets/angular/documentscreen-story-msw-graphql-query.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
similarity index 73%
rename from docs/snippets/angular/documentscreen-story-msw-graphql-query.ts.mdx
rename to docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
index 94e0e7ddc942..b70e270726ac 100644
--- a/docs/snippets/angular/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
@@ -58,28 +58,38 @@ type Story = StoryObj;
export const MockedSuccess: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/angular/documentscreen-story-msw-rest-request.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers.ts.mdx
similarity index 71%
rename from docs/snippets/angular/documentscreen-story-msw-rest-request.ts.mdx
rename to docs/snippets/angular/msw-addon-configure-handlers.ts.mdx
index 271ad00de38f..202051838b1d 100644
--- a/docs/snippets/angular/documentscreen-story-msw-rest-request.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers.ts.mdx
@@ -41,21 +41,28 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/common/documentscreen-story-msw-rest-request.js.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
similarity index 64%
rename from docs/snippets/common/documentscreen-story-msw-rest-request.js.mdx
rename to docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
index 7eb1e65ba90d..99509f949f4c 100644
--- a/docs/snippets/common/documentscreen-story-msw-rest-request.js.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
@@ -1,7 +1,7 @@
```js
// YourPage.stories.js|jsx
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -36,21 +36,28 @@ const TestData = {
export const MockedSuccess = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/common/documentscreen-story-msw-rest-request.ts-4-9.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
similarity index 72%
rename from docs/snippets/common/documentscreen-story-msw-rest-request.ts-4-9.mdx
rename to docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
index 50dde4300363..717809b8b18e 100644
--- a/docs/snippets/common/documentscreen-story-msw-rest-request.ts-4-9.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
@@ -42,21 +42,28 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/common/documentscreen-story-msw-rest-request.ts.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
similarity index 72%
rename from docs/snippets/common/documentscreen-story-msw-rest-request.ts.mdx
rename to docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
index a4ae3d1723f7..ab1f4ab92256 100644
--- a/docs/snippets/common/documentscreen-story-msw-rest-request.ts.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
@@ -42,21 +42,28 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/common/msw-addon-initialize.js.mdx b/docs/snippets/common/msw-addon-initialize.js.mdx
new file mode 100644
index 000000000000..a7bf9aab66d7
--- /dev/null
+++ b/docs/snippets/common/msw-addon-initialize.js.mdx
@@ -0,0 +1,16 @@
+```js
+// .storybook/preview.js
+import { initialize, mswLoader } from 'msw-storybook-addon';
+
+/*
+ * Initializes MSW
+ * See https://github.com/mswjs/msw-storybook-addon#configuring-msw
+ * to learn how to customize it
+ */
+initialize();
+
+export default {
+ // ... rest of preview configuration
+ loaders: [mswLoader], // 👈 Add the MSW loader to all stories
+};
+```
diff --git a/docs/snippets/common/storybook-preview-register-msw-addon.ts.mdx b/docs/snippets/common/msw-addon-initialize.ts.mdx
similarity index 59%
rename from docs/snippets/common/storybook-preview-register-msw-addon.ts.mdx
rename to docs/snippets/common/msw-addon-initialize.ts.mdx
index d7c3ba6e0991..6dcb8080ebc7 100644
--- a/docs/snippets/common/storybook-preview-register-msw-addon.ts.mdx
+++ b/docs/snippets/common/msw-addon-initialize.ts.mdx
@@ -1,10 +1,10 @@
```ts
// .storybook/preview.ts
-// Replace your-framework with the framework you are using (e.g., react, vue3)
+// Replace your-framework with the framework you are using (e.g., nextjs, vue3-vite)
import { Preview } from '@storybook/your-framework';
-import { initialize, mswDecorator } from 'msw-storybook-addon';
+import { initialize, mswLoader } from 'msw-storybook-addon';
/*
* Initializes MSW
@@ -14,15 +14,8 @@ import { initialize, mswDecorator } from 'msw-storybook-addon';
initialize();
const preview: Preview = {
- decorators: [mswDecorator],
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
- },
+ // ... rest of preview configuration
+ loaders: [mswLoader], // 👈 Add the MSW loader to all stories
};
export default preview;
diff --git a/docs/snippets/common/storybook-msw-generate.msw.js.mdx b/docs/snippets/common/msw-generate-service-worker.npx.js.mdx
similarity index 100%
rename from docs/snippets/common/storybook-msw-generate.msw.js.mdx
rename to docs/snippets/common/msw-generate-service-worker.npx.js.mdx
diff --git a/docs/snippets/common/storybook-msw-generate.msw-pnpm.js.mdx b/docs/snippets/common/msw-generate-service-worker.pnpm.js.mdx
similarity index 100%
rename from docs/snippets/common/storybook-msw-generate.msw-pnpm.js.mdx
rename to docs/snippets/common/msw-generate-service-worker.pnpm.js.mdx
diff --git a/docs/snippets/common/msw-install.npm.js.mdx b/docs/snippets/common/msw-install.npm.js.mdx
new file mode 100644
index 000000000000..3bc959fdf516
--- /dev/null
+++ b/docs/snippets/common/msw-install.npm.js.mdx
@@ -0,0 +1,3 @@
+```sh
+npm install msw --save-dev
+```
\ No newline at end of file
diff --git a/docs/snippets/common/msw-install.pnpm.js.mdx b/docs/snippets/common/msw-install.pnpm.js.mdx
new file mode 100644
index 000000000000..88ed6296508e
--- /dev/null
+++ b/docs/snippets/common/msw-install.pnpm.js.mdx
@@ -0,0 +1,3 @@
+```sh
+pnpm add msw --save-dev
+```
\ No newline at end of file
diff --git a/docs/snippets/common/msw-install.yarn.js.mdx b/docs/snippets/common/msw-install.yarn.js.mdx
new file mode 100644
index 000000000000..38ef49b23d8f
--- /dev/null
+++ b/docs/snippets/common/msw-install.yarn.js.mdx
@@ -0,0 +1,3 @@
+```sh
+yarn add msw --save-dev
+```
\ No newline at end of file
diff --git a/docs/snippets/common/storybook-preview-register-msw-addon.js.mdx b/docs/snippets/common/storybook-preview-register-msw-addon.js.mdx
deleted file mode 100644
index 7071b6f180fe..000000000000
--- a/docs/snippets/common/storybook-preview-register-msw-addon.js.mdx
+++ /dev/null
@@ -1,24 +0,0 @@
-```js
-// .storybook/preview.js
-
-import { initialize, mswDecorator } from 'msw-storybook-addon';
-
-/*
- * Initializes MSW
- * See https://github.com/mswjs/msw-storybook-addon#configuring-msw
- * to learn how to customize it
- */
-initialize();
-
-export default {
- decorators: [mswDecorator],
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/react/documentscreen-story-msw-graphql-query.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
similarity index 70%
rename from docs/snippets/react/documentscreen-story-msw-graphql-query.js.mdx
rename to docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
index 05a784a42e73..02d3494a8701 100644
--- a/docs/snippets/react/documentscreen-story-msw-graphql-query.js.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
@@ -60,28 +60,38 @@ export default {
export const MockedSuccess = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/react/documentscreen-story-msw-graphql-query.ts-4-9.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
similarity index 73%
rename from docs/snippets/react/documentscreen-story-msw-graphql-query.ts-4-9.mdx
rename to docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index d70235f57e74..66fc89ef54b2 100644
--- a/docs/snippets/react/documentscreen-story-msw-graphql-query.ts-4-9.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -64,34 +64,38 @@ type Story = StoryObj;
export const MockedSuccess: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.data({
- allFilms: {
- films,
- },
- })
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ])
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/react/documentscreen-story-msw-graphql-query.ts.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
similarity index 70%
rename from docs/snippets/react/documentscreen-story-msw-graphql-query.ts.mdx
rename to docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
index d3422a78bc8e..6e38aad3b85b 100644
--- a/docs/snippets/react/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
@@ -5,7 +5,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
-import { graphql } from 'msw';
+import { graphql, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -64,34 +64,38 @@ type Story = StoryObj;
export const MockedSuccess: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.data({
- allFilms: {
- films,
- },
- })
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ])
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
similarity index 64%
rename from docs/snippets/svelte/documentscreen-story-msw-graphql-query.js.mdx
rename to docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
index 8b0bc550d896..13856ae98299 100644
--- a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.js.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
@@ -8,6 +8,9 @@ import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
export default {
component: DocumentScreen,
+ render: () => ({
+ Component: DocumentScreen,
+ }),
decorators: [() => MockApolloWrapperClient],
};
@@ -37,35 +40,39 @@ const TestData = {
};
export const MockedSuccess = {
- render: () => ({
- Component: DocumentScreen,
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError = {
- render: () => ({
- Component: DocumentScreen,
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
similarity index 64%
rename from docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts-4-9.mdx
rename to docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 250c35581100..747bd95e40e1 100644
--- a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts-4-9.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -10,6 +10,9 @@ import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
const meta = {
component: DocumentScreen,
+ render: () => ({
+ Component: DocumentScreen,
+ }),
decorators: [() => MockApolloWrapperClient],
} satisfies Meta;
@@ -43,28 +46,38 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
similarity index 64%
rename from docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts.mdx
rename to docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
index 489e4f17ebe0..16fc69e8726b 100644
--- a/docs/snippets/svelte/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
@@ -10,6 +10,9 @@ import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
const meta: Meta = {
component: DocumentScreen,
+ render: () => ({
+ Component: DocumentScreen,
+ }),
decorators: [() => MockApolloWrapperClient],
};
@@ -43,28 +46,38 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/vue/documentscreen-story-msw-graphql-query.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
similarity index 58%
rename from docs/snippets/vue/documentscreen-story-msw-graphql-query.js.mdx
rename to docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
index d13973f9804d..57169a97bcf9 100644
--- a/docs/snippets/vue/documentscreen-story-msw-graphql-query.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -9,6 +9,10 @@ import { graphql } from 'msw';
export default {
component: DocumentScreen,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
};
//👇The mocked data that will be used in the story
@@ -36,43 +40,40 @@ const TestData = {
],
};
-/*
- *👇 Render functions are a framework specific feature to allow you control on how the component renders.
- * See https://storybook.js.org/docs/api/csf
- * to learn how to use render functions.
- */
export const MockedSuccess = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/vue/documentscreen-story-msw-graphql-query.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
similarity index 62%
rename from docs/snippets/vue/documentscreen-story-msw-graphql-query.ts-4-9.mdx
rename to docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index f1b9f80932f8..62dd097b0d10 100644
--- a/docs/snippets/vue/documentscreen-story-msw-graphql-query.ts-4-9.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -11,6 +11,10 @@ import WrapperComponent from './ApolloWrapperClient.vue';
const meta = {
component: DocumentScreen,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
} satisfies Meta;
//👇The mocked data that will be used in the story
@@ -41,43 +45,40 @@ const TestData = {
export default meta;
type Story = StoryObj;
-/*
- *👇 Render functions are a framework specific feature to allow you control on how the component renders.
- * See https://storybook.js.org/docs/api/csf
- * to learn how to use render functions.
- */
export const MockedSuccess: Story = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/vue/documentscreen-story-msw-graphql-query.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
similarity index 62%
rename from docs/snippets/vue/documentscreen-story-msw-graphql-query.ts.mdx
rename to docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
index db042ec13480..fc2cf264d4c3 100644
--- a/docs/snippets/vue/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -11,6 +11,10 @@ import WrapperComponent from './ApolloWrapperClient.vue';
const meta: Meta = {
component: DocumentScreen,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
};
//👇The mocked data that will be used in the story
@@ -41,43 +45,40 @@ const TestData = {
export default meta;
type Story = StoryObj;
-/*
- *👇 Render functions are a framework specific feature to allow you control on how the component renders.
- * See https://storybook.js.org/docs/api/csf
- * to learn how to use render functions.
- */
export const MockedSuccess: Story = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(ctx.data(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', () => {
+ return new HttpResponse.json({
+ data: {
+ allFilms: {
+ films,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
- render: () => ({
- components: { DocumentScreen, WrapperComponent },
- template: '',
- }),
parameters: {
- msw: [
- graphql.query('AllInfoQuery', (req, res, ctx) => {
- return res(
- ctx.delay(800),
- ctx.errors([
- {
- message: 'Access denied',
- },
- ]),
- );
- }),
- ],
+ msw: {
+ handlers: [
+ graphql.query('AllInfoQuery', async () => {
+ delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/web-components/documentscreen-story-msw-rest-request.js.mdx b/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
similarity index 66%
rename from docs/snippets/web-components/documentscreen-story-msw-rest-request.js.mdx
rename to docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
index 6a4fce7cc13c..395391e6c154 100644
--- a/docs/snippets/web-components/documentscreen-story-msw-rest-request.js.mdx
+++ b/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
@@ -34,21 +34,28 @@ const TestData = {
export const MockedSuccess = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/snippets/web-components/documentscreen-story-msw-rest-request.ts.mdx b/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
similarity index 69%
rename from docs/snippets/web-components/documentscreen-story-msw-rest-request.ts.mdx
rename to docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
index 9329c3b3f293..f7022c42c624 100644
--- a/docs/snippets/web-components/documentscreen-story-msw-rest-request.ts.mdx
+++ b/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
@@ -39,21 +39,28 @@ const TestData = {
export const MockedSuccess: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint/', (_req, res, ctx) => {
- return res(ctx.json(TestData));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint/', () => {
+ return new HttpResponse.json(TestData);
+ }),
+ ],
+ },
},
};
export const MockedError: Story = {
parameters: {
- msw: [
- rest.get('https://your-restful-endpoint', (_req, res, ctx) => {
- return res(ctx.delay(800), ctx.status(403));
- }),
- ],
+ msw: {
+ handlers: [
+ http.get('https://your-restful-endpoint', async () => {
+ await delay(800);
+ return new HttpResponse(null, {
+ status: 403,
+ });
+ }),
+ ],
+ },
},
};
```
diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md
index ec41c806ea9c..d27ae6766483 100644
--- a/docs/writing-stories/mocking-network-requests.md
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -10,21 +10,26 @@ The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings thi
First, if necessary, run this command to install MSW:
-
+
-```sh
-npm install msw --save-dev
-```
+
+
+
Then generate the service worker file necessary for MSW to work:
-
@@ -48,14 +53,14 @@ Next, install and register the MSW addon:
npx storybook@latest add msw-storybook-addon
```
-Then ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file:
+Then ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file (in `/public`, by default):
-
@@ -65,11 +70,10 @@ Finally, initialize the addon and apply it to all stories with a [project-level
-
@@ -107,14 +111,13 @@ With the MSW addon, we can write stories that use MSW to mock the REST requests.
-
-
Date: Fri, 19 Apr 2024 10:35:42 -0600
Subject: [PATCH 22/46] Address MSW snippets feedback
---
.../msw-addon-configure-handlers-graphql.ts.mdx | 14 +++++---------
...dx => msw-addon-configure-handlers-http.ts.mdx} | 3 +--
.../msw-addon-configure-handlers-http.js.mdx | 1 -
.../msw-addon-configure-handlers-http.ts-4-9.mdx | 5 ++---
.../msw-addon-configure-handlers-http.ts.mdx | 5 ++---
docs/snippets/common/msw-addon-initialize.ts.mdx | 4 ++--
.../msw-addon-configure-handlers-graphql.js.mdx | 6 ++----
...msw-addon-configure-handlers-graphql.ts-4-9.mdx | 6 ++----
.../msw-addon-configure-handlers-graphql.ts.mdx | 4 +---
.../msw-addon-configure-handlers-graphql.js.mdx | 10 +++-------
...msw-addon-configure-handlers-graphql.ts-4-9.mdx | 10 +++-------
.../msw-addon-configure-handlers-graphql.ts.mdx | 10 +++-------
.../msw-addon-configure-handlers-graphql.js.mdx | 8 +++-----
...msw-addon-configure-handlers-graphql.ts-4-9.mdx | 8 +++-----
.../msw-addon-configure-handlers-graphql.ts.mdx | 8 +++-----
.../msw-addon-configure-handlers-http.js.mdx | 3 +--
.../msw-addon-configure-handlers-http.ts.mdx | 3 +--
17 files changed, 37 insertions(+), 71 deletions(-)
rename docs/snippets/angular/{msw-addon-configure-handlers.ts.mdx => msw-addon-configure-handlers-http.ts.mdx} (96%)
diff --git a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
index b70e270726ac..0b2fa61c216b 100644
--- a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
@@ -1,20 +1,16 @@
```ts
// YourPage.stories.ts
-
-import { moduleMetadata } from '@storybook/angular';
-
import type { Meta, StoryObj } from '@storybook/angular';
+import { moduleMetadata } from '@storybook/angular';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
+import { graphql, HttpResponse, delay } from 'msw';
-import { graphql } from 'msw';
-
-import { DocumentScreen } from './YourPage.component';
-import { DocumentList } from './DocumentList.component';
import { DocumentHeader } from './DocumentHeader.component';
+import { DocumentList } from './DocumentList.component';
import { PageLayout } from './PageLayout.component';
-
+import { DocumentScreen } from './YourPage.component';
import { MockGraphQLModule } from './mock-graphql.module';
const meta: Meta = {
@@ -79,7 +75,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/angular/msw-addon-configure-handlers.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-http.ts.mdx
similarity index 96%
rename from docs/snippets/angular/msw-addon-configure-handlers.ts.mdx
rename to docs/snippets/angular/msw-addon-configure-handlers-http.ts.mdx
index 202051838b1d..820857b94b48 100644
--- a/docs/snippets/angular/msw-addon-configure-handlers.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-http.ts.mdx
@@ -1,9 +1,8 @@
```ts
// YourPage.stories.ts
-
import type { Meta, StoryObj } from '@storybook/angular';
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage.component';
diff --git a/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
index 99509f949f4c..cd2138c50a0f 100644
--- a/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.js.mdx
@@ -1,6 +1,5 @@
```js
// YourPage.stories.js|jsx
-
import { http, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
diff --git a/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
index 717809b8b18e..663a9fd0f1f2 100644
--- a/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.ts-4-9.mdx
@@ -1,10 +1,9 @@
```ts
// YourPage.stories.ts|tsx
-
-// Replace your-framework with the name of your framework
+// Replace your-framework with the name of your framework (e.g. nextjs, vue3-vite)
import type { Meta, StoryObj } from '@storybook/your-framework';
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
import { MyComponent } from './MyComponent';
diff --git a/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
index ab1f4ab92256..26d7ffaae623 100644
--- a/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
+++ b/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
@@ -1,10 +1,9 @@
```ts
// YourPage.stories.ts|tsx
-
-// Replace your-framework with the name of your framework
+// Replace your-framework with the name of your framework (e.g. nextjs, vue3-vite)
import type { Meta, StoryObj } from '@storybook/your-framework';
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
import { MyComponent } from './MyComponent';
diff --git a/docs/snippets/common/msw-addon-initialize.ts.mdx b/docs/snippets/common/msw-addon-initialize.ts.mdx
index 6dcb8080ebc7..e0fa098e8490 100644
--- a/docs/snippets/common/msw-addon-initialize.ts.mdx
+++ b/docs/snippets/common/msw-addon-initialize.ts.mdx
@@ -1,8 +1,8 @@
```ts
// .storybook/preview.ts
-// Replace your-framework with the framework you are using (e.g., nextjs, vue3-vite)
-import { Preview } from '@storybook/your-framework';
+// Replace your-renderer with the renderer you are using (e.g., react, vue, etc.)
+import { Preview } from '@storybook/your-renderer';
import { initialize, mswLoader } from 'msw-storybook-addon';
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
index 02d3494a8701..4b67d992d5dd 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
@@ -1,9 +1,7 @@
```js
// YourPage.stories.js|jsx
-
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
-
-import { graphql } from 'msw';
+import { graphql, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -81,7 +79,7 @@ export const MockedError = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 66fc89ef54b2..2fd10eed5dec 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -1,11 +1,9 @@
```ts
// YourPage.stories.ts|tsx
-
import type { Meta, StoryObj } from '@storybook/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
-
-import { graphql } from 'msw';
+import { graphql, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -85,7 +83,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
index 6e38aad3b85b..4367a53a25e9 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
@@ -1,10 +1,8 @@
```ts
// YourPage.stories.ts|tsx
-
import type { Meta, StoryObj } from '@storybook/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
-
import { graphql, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -85,7 +83,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
index 13856ae98299..345db9fb4c43 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
@@ -1,16 +1,12 @@
```js
// YourPage.stories.js
+import { graphql, HttpResponse, delay } from 'msw';
-import { graphql } from 'msw';
-
-import DocumentScreen from './YourPage.svelte';
import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
+import DocumentScreen from './YourPage.svelte';
export default {
component: DocumentScreen,
- render: () => ({
- Component: DocumentScreen,
- }),
decorators: [() => MockApolloWrapperClient],
};
@@ -62,7 +58,7 @@ export const MockedError = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 747bd95e40e1..7f033a7dfb67 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -1,18 +1,14 @@
```ts
// YourPage.stories.ts
-
import type { Meta, StoryObj } from '@storybook/svelte';
-import { graphql } from 'msw';
+import { graphql, HttpResponse, delay } from 'msw';
-import DocumentScreen from './YourPage.svelte';
import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
+import DocumentScreen from './YourPage.svelte';
const meta = {
component: DocumentScreen,
- render: () => ({
- Component: DocumentScreen,
- }),
decorators: [() => MockApolloWrapperClient],
} satisfies Meta;
@@ -67,7 +63,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
index 16fc69e8726b..4b7d818e75a2 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
@@ -1,18 +1,14 @@
```ts
// YourPage.stories.ts
-
import type { Meta, StoryObj } from '@storybook/svelte';
-import { graphql } from 'msw';
+import { graphql, HttpResponse, delay } from 'msw';
-import DocumentScreen from './YourPage.svelte';
import MockApolloWrapperClient from './MockApolloWrapperClient.svelte';
+import DocumentScreen from './YourPage.svelte';
const meta: Meta = {
component: DocumentScreen,
- render: () => ({
- Component: DocumentScreen,
- }),
decorators: [() => MockApolloWrapperClient],
};
@@ -67,7 +63,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
index 57169a97bcf9..cfef3b12956b 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -1,11 +1,9 @@
```js
// YourPage.stories.js
-
-import DocumentScreen from './YourPage.vue';
+import { graphql, HttpResponse, delay } from 'msw';
import WrapperComponent from './ApolloWrapperClient.vue';
-
-import { graphql } from 'msw';
+import DocumentScreen from './YourPage.vue';
export default {
component: DocumentScreen,
@@ -63,7 +61,7 @@ export const MockedError = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 62dd097b0d10..b0d12117418f 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -1,13 +1,11 @@
```ts
// YourPage.stories.ts
-
-import { graphql } from 'msw';
-
import type { Meta, StoryObj } from '@storybook/vue3';
-import DocumentScreen from './YourPage.vue';
+import { graphql, HttpResponse, delay } from 'msw';
import WrapperComponent from './ApolloWrapperClient.vue';
+import DocumentScreen from './YourPage.vue';
const meta = {
component: DocumentScreen,
@@ -68,7 +66,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
index fc2cf264d4c3..d98251c2bd5b 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -1,13 +1,11 @@
```ts
// YourPage.stories.ts
-
-import { graphql } from 'msw';
-
import type { Meta, StoryObj } from '@storybook/vue3';
-import DocumentScreen from './YourPage.vue';
+import { graphql, HttpResponse, delay } from 'msw';
import WrapperComponent from './ApolloWrapperClient.vue';
+import DocumentScreen from './YourPage.vue';
const meta: Meta = {
component: DocumentScreen,
@@ -68,7 +66,7 @@ export const MockedError: Story = {
msw: {
handlers: [
graphql.query('AllInfoQuery', async () => {
- delay(800);
+ await delay(800);
return new HttpResponse.json({
errors: [
{
diff --git a/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx b/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
index 395391e6c154..d1c3cda8bc9f 100644
--- a/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
+++ b/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
@@ -1,7 +1,6 @@
```js
// YourPage.stories.js
-
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
export default {
component: 'demo-document-screen',
diff --git a/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx b/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
index f7022c42c624..b12fd8f93a41 100644
--- a/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
+++ b/docs/snippets/web-components/msw-addon-configure-handlers-http.ts.mdx
@@ -1,9 +1,8 @@
```ts
// YourPage.stories.ts
-
import type { Meta, StoryObj } from '@storybook/web-components';
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
const meta: Meta = {
component: 'demo-document-screen',
From f4914e3215b3087612881f9065eea12b5a94fbd4 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Fri, 19 Apr 2024 10:44:52 -0600
Subject: [PATCH 23/46] Prettify snippets
---
.../angular/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/snippets/common/msw-install.npm.js.mdx | 2 +-
docs/snippets/common/msw-install.pnpm.js.mdx | 2 +-
docs/snippets/common/msw-install.yarn.js.mdx | 2 +-
docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx | 2 +-
.../snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx | 2 +-
docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
11 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
index 0b2fa61c216b..dc3347cfd980 100644
--- a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
@@ -62,7 +62,7 @@ export const MockedSuccess: Story = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/common/msw-install.npm.js.mdx b/docs/snippets/common/msw-install.npm.js.mdx
index 3bc959fdf516..cf6e91f4e0aa 100644
--- a/docs/snippets/common/msw-install.npm.js.mdx
+++ b/docs/snippets/common/msw-install.npm.js.mdx
@@ -1,3 +1,3 @@
```sh
npm install msw --save-dev
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/msw-install.pnpm.js.mdx b/docs/snippets/common/msw-install.pnpm.js.mdx
index 88ed6296508e..c4332df90cd0 100644
--- a/docs/snippets/common/msw-install.pnpm.js.mdx
+++ b/docs/snippets/common/msw-install.pnpm.js.mdx
@@ -1,3 +1,3 @@
```sh
pnpm add msw --save-dev
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/msw-install.yarn.js.mdx b/docs/snippets/common/msw-install.yarn.js.mdx
index 38ef49b23d8f..6a8d76b5ff15 100644
--- a/docs/snippets/common/msw-install.yarn.js.mdx
+++ b/docs/snippets/common/msw-install.yarn.js.mdx
@@ -1,3 +1,3 @@
```sh
yarn add msw --save-dev
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
index 4b67d992d5dd..dee1f7976ec4 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
@@ -66,7 +66,7 @@ export const MockedSuccess = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
index 345db9fb4c43..829ec728386a 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
@@ -45,7 +45,7 @@ export const MockedSuccess = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 7f033a7dfb67..866603602f16 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -50,7 +50,7 @@ export const MockedSuccess: Story = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
index 4b7d818e75a2..d78ffb4ce4e8 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
@@ -50,7 +50,7 @@ export const MockedSuccess: Story = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
index cfef3b12956b..251b119a6629 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -48,7 +48,7 @@ export const MockedSuccess = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index b0d12117418f..c4b55f47f6ef 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -53,7 +53,7 @@ export const MockedSuccess: Story = {
allFilms: {
films,
},
- }
+ },
});
}),
],
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
index d98251c2bd5b..6d1b27cb6946 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -53,7 +53,7 @@ export const MockedSuccess: Story = {
allFilms: {
films,
},
- }
+ },
});
}),
],
From cf2be736709e1fc86787743a504fb833ccfe693b Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Wed, 17 Apr 2024 11:01:37 +0200
Subject: [PATCH 24/46] Add new actions to the bottom of the panel and auto
scroll
---
.../src/components/ActionLogger/index.tsx | 74 +++++++++++--------
.../src/containers/ActionLogger/index.tsx | 4 +-
.../src/components/ScrollArea/ScrollArea.tsx | 65 ++++++++--------
3 files changed, 76 insertions(+), 67 deletions(-)
diff --git a/code/addons/actions/src/components/ActionLogger/index.tsx b/code/addons/actions/src/components/ActionLogger/index.tsx
index a3e48337107f..4244f75cf449 100644
--- a/code/addons/actions/src/components/ActionLogger/index.tsx
+++ b/code/addons/actions/src/components/ActionLogger/index.tsx
@@ -1,22 +1,23 @@
-import type { FC, PropsWithChildren } from 'react';
-import React, { Fragment } from 'react';
-import { styled, withTheme } from '@storybook/theming';
+import type { ElementRef, ReactNode } from 'react';
+import React, { forwardRef, Fragment, useEffect, useRef } from 'react';
import type { Theme } from '@storybook/theming';
+import { styled, withTheme } from '@storybook/theming';
import { Inspector } from 'react-inspector';
import { ActionBar, ScrollArea } from '@storybook/components';
-import { Action, InspectorContainer, Counter } from './style';
+import { Action, Counter, InspectorContainer } from './style';
import type { ActionDisplay } from '../../models';
-const UnstyledWrapped: FC> = ({
- children,
- className,
-}) => (
-
- {children}
-
+const UnstyledWrapped = forwardRef(
+ ({ children, className }, ref) => (
+
+ {children}
+
+ )
);
+UnstyledWrapped.displayName = 'UnstyledWrapped';
+
export const Wrapper = styled(UnstyledWrapped)({
margin: 0,
padding: '10px 5px 20px',
@@ -39,24 +40,33 @@ interface ActionLoggerProps {
onClear: () => void;
}
-export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => (
-
-
- {actions.map((action: ActionDisplay) => (
-
- {action.count > 1 && {action.count}}
-
-
-
-
- ))}
-
-
-
-
-);
+export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => {
+ const wrapperRef = useRef>(null);
+ const wrapper = wrapperRef.current;
+ const wasAtBottom = wrapper && wrapper.scrollHeight - wrapper.scrollTop === wrapper.clientHeight;
+
+ useEffect(() => {
+ if (wasAtBottom) wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight;
+ }, [wasAtBottom, actions.length]);
+
+ return (
+
+
+ {actions.map((action: ActionDisplay) => (
+
+ {action.count > 1 && {action.count}}
+
+
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/code/addons/actions/src/containers/ActionLogger/index.tsx b/code/addons/actions/src/containers/ActionLogger/index.tsx
index bb88fd7f5b2a..6f5d5f3a1366 100644
--- a/code/addons/actions/src/containers/ActionLogger/index.tsx
+++ b/code/addons/actions/src/containers/ActionLogger/index.tsx
@@ -63,12 +63,12 @@ export default class ActionLogger extends Component {
this.setState((prevState: ActionLoggerState) => {
const actions = [...prevState.actions];
- const previous = actions.length && actions[0];
+ const previous = actions.length && actions[actions.length - 1];
if (previous && safeDeepEqual(previous.data, action.data)) {
previous.count++;
} else {
action.count = 1;
- actions.unshift(action);
+ actions.push(action);
}
return { actions: actions.slice(0, action.options.limit) };
});
diff --git a/code/ui/components/src/components/ScrollArea/ScrollArea.tsx b/code/ui/components/src/components/ScrollArea/ScrollArea.tsx
index f3f965af8783..cd697523d44d 100644
--- a/code/ui/components/src/components/ScrollArea/ScrollArea.tsx
+++ b/code/ui/components/src/components/ScrollArea/ScrollArea.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { forwardRef } from 'react';
import { styled } from '@storybook/theming';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
@@ -75,36 +75,35 @@ const ScrollAreaThumb = styled(ScrollAreaPrimitive.Thumb)(({ theme }) => ({
},
}));
-export const ScrollArea = ({
- children,
- horizontal = false,
- vertical = false,
- offset = 2,
- scrollbarSize = 6,
- className,
-}: ScrollAreaProps) => (
-
- {children}
- {horizontal && (
-
-
-
- )}
- {vertical && (
-
-
-
- )}
- {horizontal && vertical && }
-
+export const ScrollArea = forwardRef(
+ (
+ { children, horizontal = false, vertical = false, offset = 2, scrollbarSize = 6, className },
+ ref
+ ) => (
+
+ {children}
+ {horizontal && (
+
+
+
+ )}
+ {vertical && (
+
+
+
+ )}
+ {horizontal && vertical && }
+
+ )
);
+ScrollArea.displayName = 'ScrollArea';
From 42773e33cabdce84dacf78148fee51708f70b6f2 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Wed, 17 Apr 2024 15:16:35 +0200
Subject: [PATCH 25/46] Address review
---
code/addons/actions/src/components/ActionLogger/index.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/code/addons/actions/src/components/ActionLogger/index.tsx b/code/addons/actions/src/components/ActionLogger/index.tsx
index 4244f75cf449..ec2e6ca2e102 100644
--- a/code/addons/actions/src/components/ActionLogger/index.tsx
+++ b/code/addons/actions/src/components/ActionLogger/index.tsx
@@ -46,6 +46,7 @@ export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => {
const wasAtBottom = wrapper && wrapper.scrollHeight - wrapper.scrollTop === wrapper.clientHeight;
useEffect(() => {
+ // Scroll to bottom, when the action panel was already scrolled down
if (wasAtBottom) wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight;
}, [wasAtBottom, actions.length]);
From e850d364ffc73cd5d755e956237284de7b58d910 Mon Sep 17 00:00:00 2001
From: Jeppe Reinhold
Date: Wed, 17 Apr 2024 14:37:05 +0200
Subject: [PATCH 26/46] add cache.mock entrypoint to nextjs
---
code/frameworks/nextjs/package.json | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json
index b09b2f383295..04757586be87 100644
--- a/code/frameworks/nextjs/package.json
+++ b/code/frameworks/nextjs/package.json
@@ -52,21 +52,26 @@
"require": "./dist/export-mocks/index.js",
"import": "./dist/export-mocks/index.mjs"
},
+ "./cache.mock": {
+ "types": "./dist/export-mocks/cache/index.d.ts",
+ "require": "./dist/export-mocks/cache/index.js",
+ "import": "./dist/export-mocks/cache/index.mjs"
+ },
"./headers.mock": {
"types": "./dist/export-mocks/headers/index.d.ts",
"require": "./dist/export-mocks/headers/index.js",
"import": "./dist/export-mocks/headers/index.mjs"
},
- "./router.mock": {
- "types": "./dist/export-mocks/router/index.d.ts",
- "require": "./dist/export-mocks/router/index.js",
- "import": "./dist/export-mocks/router/index.mjs"
- },
"./navigation.mock": {
"types": "./dist/export-mocks/navigation/index.d.ts",
"require": "./dist/export-mocks/navigation/index.js",
"import": "./dist/export-mocks/navigation/index.mjs"
},
+ "./router.mock": {
+ "types": "./dist/export-mocks/router/index.d.ts",
+ "require": "./dist/export-mocks/router/index.js",
+ "import": "./dist/export-mocks/router/index.mjs"
+ },
"./package.json": "./package.json"
},
"main": "dist/index.js",
From 401fd2681b3a48ab407a2b4f9e849530eeb36278 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 11:42:57 +0200
Subject: [PATCH 27/46] Try another mock naming convention
---
.../nextjs/src/export-mocks/cache/index.ts | 4 +-
.../src/export-mocks/headers/cookies.ts | 112 ++----------------
.../src/export-mocks/headers/headers.ts | 98 ++-------------
.../src/export-mocks/navigation/index.ts | 59 ++++-----
.../nextjs/src/export-mocks/router/index.ts | 30 +++--
5 files changed, 64 insertions(+), 239 deletions(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/cache/index.ts b/code/frameworks/nextjs/src/export-mocks/cache/index.ts
index 1692979f0159..b9f2fa8dd431 100644
--- a/code/frameworks/nextjs/src/export-mocks/cache/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/cache/index.ts
@@ -3,8 +3,8 @@ import { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cac
import { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store';
// mock utilities/overrides (as of Next v14.2.0)
-const revalidatePath = fn().mockName('revalidatePath');
-const revalidateTag = fn().mockName('revalidateTag');
+const revalidatePath = fn().mockName('next/cache::revalidatePath');
+const revalidateTag = fn().mockName('next/cache::revalidateTag');
const cacheExports = {
unstable_cache,
diff --git a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
index 75022baaea26..c3272dc0e335 100644
--- a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
+++ b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
@@ -1,116 +1,22 @@
-/* eslint-disable no-underscore-dangle */
import { fn } from '@storybook/test';
-import type { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
-import {
- parseCookie,
- stringifyCookie,
- type RequestCookie,
-} from 'next/dist/compiled/@edge-runtime/cookies';
+import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
// We need this import to be a singleton, and because it's used in multiple entrypoints
// both in ESM and CJS, importing it via the package name instead of having a local import
// is the only way to achieve it actually being a singleton
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore we must ignore types here as during compilation they are not generated yet
-import { headers, type HeadersStore } from '@storybook/nextjs/headers.mock';
+import { headers } from '@storybook/nextjs/headers.mock';
-const stringifyCookies = (map: Map) => {
- return Array.from(map)
- .map(([_, v]) => stringifyCookie(v).replace(/; /, ''))
- .join('; ');
-};
-
-// Mostly copied from https://github.com/vercel/edge-runtime/blob/c25e2ded39104e2a3be82efc08baf8dc8fb436b3/packages/cookies/src/request-cookies.ts#L7
-class RequestCookiesMock implements RequestCookies {
- /** @internal */
- private readonly _headers: HeadersStore;
-
- _parsed: Map = new Map();
-
- constructor(requestHeaders: HeadersStore) {
- this._headers = requestHeaders;
- const header = requestHeaders?.get('cookie');
- if (header) {
- const parsed = parseCookie(header);
- for (const [name, value] of parsed) {
- this._parsed.set(name, { name, value });
- }
- }
- }
-
- [Symbol.iterator]() {
- return this._parsed[Symbol.iterator]();
- }
-
- get size(): number {
- return this._parsed.size;
- }
+class RequestCookiesMock extends RequestCookies {
+ get = fn(super.get).mockName('next/headers::get');
- get = fn((...args: [name: string] | [RequestCookie]) => {
- const name = typeof args[0] === 'string' ? args[0] : args[0].name;
- return this._parsed.get(name);
- }).mockName('cookies().get');
+ getAll = fn(super.getAll).mockName('next/headers::cookies().getAll');
- getAll = fn((...args: [name: string] | [RequestCookie] | []) => {
- const all = Array.from(this._parsed);
- if (!args.length) {
- return all.map(([_, value]) => value);
- }
+ has = fn(super.has).mockName('next/headers::cookies().has');
- const name = typeof args[0] === 'string' ? args[0] : args[0]?.name;
- return all.filter(([n]) => n === name).map(([_, value]) => value);
- }).mockName('cookies().getAll');
+ set = fn(super.set).mockName('next/headers::cookies().set');
- has = fn((name: string) => {
- return this._parsed.has(name);
- }).mockName('cookies().has');
-
- set = fn((...args: [key: string, value: string] | [options: RequestCookie]): this => {
- const [name, value] = args.length === 1 ? [args[0].name, args[0].value] : args;
-
- const map = this._parsed;
- map.set(name, { name, value });
-
- this._headers.set('cookie', stringifyCookies(map));
- return this;
- }).mockName('cookies().set');
-
- /**
- * Delete the cookies matching the passed name or names in the request.
- */
- delete = fn(
- (
- /** Name or names of the cookies to be deleted */
- names: string | string[]
- ): boolean | boolean[] => {
- const map = this._parsed;
- const result = !Array.isArray(names)
- ? map.delete(names)
- : names.map((name) => map.delete(name));
- this._headers.set('cookie', stringifyCookies(map));
- return result;
- }
- ).mockName('cookies().delete');
-
- /**
- * Delete all the cookies in the cookies in the request.
- */
- clear = fn((): this => {
- this.delete(Array.from(this._parsed.keys()));
- return this;
- }).mockName('cookies().clear');
-
- /**
- * Format the cookies in the request as a string for logging
- */
- [Symbol.for('edge-runtime.inspect.custom')]() {
- return `RequestCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`;
- }
-
- toString() {
- return [...this._parsed.values()]
- .map((v) => `${v.name}=${encodeURIComponent(v.value)}`)
- .join('; ');
- }
+ delete = fn(super.delete).mockName('next/headers::cookies().delete');
}
let requestCookiesMock: RequestCookiesMock;
@@ -120,7 +26,7 @@ export const cookies = fn(() => {
requestCookiesMock = new RequestCookiesMock(headers());
}
return requestCookiesMock;
-});
+}).mockName('next/headers::cookies()');
const originalRestore = cookies.mockRestore.bind(null);
diff --git a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
index a7511064fa1f..02703265e583 100644
--- a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
+++ b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
@@ -1,101 +1,29 @@
import { fn } from '@storybook/test';
-import type { IncomingHttpHeaders } from 'http';
-import type { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers';
-// Mostly copied from https://github.com/vercel/next.js/blob/763b9a660433ec5278a10e59d7ae89d4010ba212/packages/next/src/server/web/spec-extension/adapters/headers.ts#L20
-// @ts-expect-error unfortunately the headers property is private (and not protected) in HeadersAdapter
-// and we can't access it so we need to redefine it, but that clashes with the type, hence the ts-expect-error comment.
-class HeadersAdapterMock extends Headers implements HeadersAdapter {
- private headers: IncomingHttpHeaders = {};
+import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers';
- /**
- * Merges a header value into a string. This stores multiple values as an
- * array, so we need to merge them into a string.
- *
- * @param value a header value
- * @returns a merged header value (a string)
- */
- private merge(value: string | string[]): string {
- if (Array.isArray(value)) return value.join(', ');
-
- return value;
+class HeadersAdapterMock extends HeadersAdapter {
+ constructor() {
+ super({});
}
- public append = fn((name: string, value: string): void => {
- const existing = this.headers[name];
- if (typeof existing === 'string') {
- this.headers[name] = [existing, value];
- } else if (Array.isArray(existing)) {
- existing.push(value);
- } else {
- this.headers[name] = value;
- }
- }).mockName('headers().append');
-
- public delete = fn((name: string) => {
- delete this.headers[name];
- }).mockName('headers().delete');
-
- public get = fn((name: string): string | null => {
- const value = this.headers[name];
- if (typeof value !== 'undefined') return this.merge(value);
+ append = fn(super.append).mockName('next/headers::headers().append');
- return null;
- }).mockName('headers().get');
+ delete = fn(super.delete).mockName('next/headers::headers().delete');
- public has = fn((name: string): boolean => {
- return typeof this.headers[name] !== 'undefined';
- }).mockName('headers().has');
+ get = fn(super.get).mockName('next/headers::headers().get');
- public set = fn((name: string, value: string): void => {
- this.headers[name] = value;
- }).mockName('headers().set');
+ has = fn(super.has).mockName('next/headers::headers().has');
- public forEach = fn(
- (callbackfn: (value: string, name: string, parent: Headers) => void, thisArg?: any): void => {
- for (const [name, value] of this.entries()) {
- callbackfn.call(thisArg, value, name, this);
- }
- }
- ).mockName('headers().forEach');
+ set = fn(super.set).mockName('next/headers::headers().set');
- public entries = fn(
- function* (this: HeadersAdapterMock): IterableIterator<[string, string]> {
- for (const key of Object.keys(this.headers)) {
- const name = key.toLowerCase();
- // We assert here that this is a string because we got it from the
- // Object.keys() call above.
- const value = this.get(name) as string;
+ forEach = fn(super.forEach).mockName('next/headers::headers().forEach');
- yield [name, value];
- }
- }.bind(this)
- ).mockName('headers().entries');
+ entries = fn(super.entries).mockName('next/headers::headers().entries');
- public keys = fn(
- function* (this: HeadersAdapterMock): IterableIterator {
- for (const key of Object.keys(this.headers)) {
- const name = key.toLowerCase();
- yield name;
- }
- }.bind(this)
- ).mockName('headers().keys');
+ keys = fn(super.keys).mockName('next/headers::headers().keys');
- public values = fn(
- function* (this: HeadersAdapterMock): IterableIterator {
- for (const key of Object.keys(this.headers)) {
- // We assert here that this is a string because we got it from the
- // Object.keys() call above.
- const value = this.get(key) as string;
-
- yield value;
- }
- }.bind(this)
- ).mockName('headers().values');
-
- public [Symbol.iterator](): IterableIterator<[string, string]> {
- return this.entries();
- }
+ values = fn(super.values).mockName('next/headers::headers().values');
}
let headersAdapterMock: HeadersAdapterMock;
diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
index 47a7e5801b56..fbbeb8ced25c 100644
--- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
@@ -17,14 +17,14 @@ let navigationAPI: {
* @ignore
* @internal
* */
-const createNavigation = (overrides: any) => {
+export const createNavigation = (overrides: any) => {
const navigationActions = {
- push: fn().mockName('useRouter().push'),
- replace: fn().mockName('useRouter().replace'),
- forward: fn().mockName('useRouter().forward'),
- back: fn().mockName('useRouter().back'),
- prefetch: fn().mockName('useRouter().prefetch'),
- refresh: fn().mockName('useRouter().refresh'),
+ push: fn().mockName('next/navigation::useRouter().push'),
+ replace: fn().mockName('next/navigation::useRouter().replace'),
+ forward: fn().mockName('next/navigation::useRouter().forward'),
+ back: fn().mockName('next/navigation::useRouter().back'),
+ prefetch: fn().mockName('next/navigation::useRouter().prefetch'),
+ refresh: fn().mockName('next/navigation::useRouter().refresh'),
};
if (overrides) {
@@ -42,7 +42,7 @@ const createNavigation = (overrides: any) => {
return navigationAPI;
};
-const getRouter = () => {
+export const getRouter = () => {
if (!navigationAPI) {
throw new NextjsRouterMocksNotAvailable({
importType: 'next/navigation',
@@ -56,41 +56,34 @@ const getRouter = () => {
export * from 'next/dist/client/components/navigation';
// mock utilities/overrides (as of Next v14.2.0)
-const redirect = fn().mockName('redirect');
+export const redirect = fn().mockName('next/navigation::redirect');
// passthrough mocks - keep original implementation but allow for spying
-const useSearchParams = fn(originalNavigation.useSearchParams).mockName('useSearchParams');
-const usePathname = fn(originalNavigation.usePathname).mockName('usePathname');
-const useSelectedLayoutSegment = fn(originalNavigation.useSelectedLayoutSegment).mockName(
+export const useSearchParams = fn(originalNavigation.useSearchParams).mockName(
+ 'next/navigation::useSearchParams'
+);
+export const usePathname = fn(originalNavigation.usePathname).mockName(
+ 'next/navigation::usePathname'
+);
+export const useSelectedLayoutSegment = fn(originalNavigation.useSelectedLayoutSegment).mockName(
'useSelectedLayoutSegment'
);
-const useSelectedLayoutSegments = fn(originalNavigation.useSelectedLayoutSegments).mockName(
+export const useSelectedLayoutSegments = fn(originalNavigation.useSelectedLayoutSegments).mockName(
'useSelectedLayoutSegments'
);
-const useRouter = fn(originalNavigation.useRouter).mockName('useRouter');
-const useServerInsertedHTML = fn(originalNavigation.useServerInsertedHTML).mockName(
+export const useRouter = fn(originalNavigation.useRouter).mockName('next/navigation::useRouter');
+export const useServerInsertedHTML = fn(originalNavigation.useServerInsertedHTML).mockName(
'useServerInsertedHTML'
);
-const notFound = fn(originalNavigation.notFound).mockName('notFound');
-const permanentRedirect = fn(originalNavigation.permanentRedirect).mockName('permanentRedirect');
+export const notFound = fn(originalNavigation.notFound).mockName('next/navigation::notFound');
+export const permanentRedirect = fn(originalNavigation.permanentRedirect).mockName(
+ 'permanentRedirect'
+);
// Params, not exported by Next.js, is manually declared to avoid inference issues.
interface Params {
[key: string]: string | string[];
}
-const useParams = fn<[], Params>(originalNavigation.useParams).mockName('useParams');
-
-export {
- createNavigation,
- getRouter,
- redirect,
- useSearchParams,
- usePathname,
- useSelectedLayoutSegment,
- useSelectedLayoutSegments,
- useParams,
- useRouter,
- useServerInsertedHTML,
- notFound,
- permanentRedirect,
-};
+export const useParams = fn<[], Params>(originalNavigation.useParams).mockName(
+ 'next/navigation::useParams'
+);
diff --git a/code/frameworks/nextjs/src/export-mocks/router/index.ts b/code/frameworks/nextjs/src/export-mocks/router/index.ts
index c9f81f3a72be..b3c5e37faa43 100644
--- a/code/frameworks/nextjs/src/export-mocks/router/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/router/index.ts
@@ -36,27 +36,27 @@ let routerAPI: {
* @ignore
* @internal
* */
-const createRouter = (overrides: Partial) => {
+export const createRouter = (overrides: Partial) => {
const routerActions: Partial = {
push: fn((..._args: any[]) => {
return Promise.resolve(true);
- }).mockName('useRouter().push'),
+ }).mockName('next/router::useRouter().push'),
replace: fn((..._args: any[]) => {
return Promise.resolve(true);
- }).mockName('useRouter().replace'),
- reload: fn((..._args: any[]) => {}).mockName('useRouter().reload'),
- back: fn((..._args: any[]) => {}).mockName('useRouter().back'),
- forward: fn(() => {}).mockName('useRouter().forward'),
+ }).mockName('next/router::useRouter().replace'),
+ reload: fn((..._args: any[]) => {}).mockName('next/router::useRouter().reload'),
+ back: fn((..._args: any[]) => {}).mockName('next/router::useRouter().back'),
+ forward: fn(() => {}).mockName('next/router::useRouter().forward'),
prefetch: fn((..._args: any[]) => {
return Promise.resolve();
- }).mockName('useRouter().prefetch'),
- beforePopState: fn((..._args: any[]) => {}).mockName('useRouter().beforePopState'),
+ }).mockName('next/router::useRouter().prefetch'),
+ beforePopState: fn((..._args: any[]) => {}).mockName('next/router::useRouter().beforePopState'),
};
const routerEvents: NextRouter['events'] = {
- on: fn((..._args: any[]) => {}).mockName('useRouter().events.on'),
- off: fn((..._args: any[]) => {}).mockName('useRouter().events.off'),
- emit: fn((..._args: any[]) => {}).mockName('useRouter().events.emit'),
+ on: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.on'),
+ off: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.off'),
+ emit: fn((..._args: any[]) => {}).mockName('next/router::useRouter().events.emit'),
};
if (overrides) {
@@ -95,7 +95,7 @@ const createRouter = (overrides: Partial) => {
return routerAPI as unknown as NextRouter;
};
-const getRouter = () => {
+export const getRouter = () => {
if (!routerAPI) {
throw new NextjsRouterMocksNotAvailable({
importType: 'next/router',
@@ -111,7 +111,5 @@ export default singletonRouter;
// mock utilities/overrides (as of Next v14.2.0)
// passthrough mocks - keep original implementation but allow for spying
-const useRouter = fn(originalRouter.useRouter).mockName('useRouter');
-const withRouter = fn(originalRouter.withRouter).mockName('withRouter');
-
-export { createRouter, getRouter, useRouter, withRouter };
+export const useRouter = fn(originalRouter.useRouter).mockName('next/router::useRouter');
+export const withRouter = fn(originalRouter.withRouter).mockName('next/router::withRouter');
From 9920353e677f7e9eaf73afb7dbc3e069126e71ab Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 12:11:13 +0200
Subject: [PATCH 28/46] Bind this
---
.../nextjs/src/export-mocks/headers/cookies.ts | 10 +++++-----
.../nextjs/src/export-mocks/headers/headers.ts | 18 +++++++++---------
.../nextjs/yarn.lock | 8 ++++----
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
index c3272dc0e335..3fc9fdc1ae59 100644
--- a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
+++ b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
@@ -8,15 +8,15 @@ import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import { headers } from '@storybook/nextjs/headers.mock';
class RequestCookiesMock extends RequestCookies {
- get = fn(super.get).mockName('next/headers::get');
+ get = fn(super.get.bind(this)).mockName('next/headers::get');
- getAll = fn(super.getAll).mockName('next/headers::cookies().getAll');
+ getAll = fn(super.getAll.bind(this)).mockName('next/headers::cookies().getAll');
- has = fn(super.has).mockName('next/headers::cookies().has');
+ has = fn(super.has.bind(this)).mockName('next/headers::cookies().has');
- set = fn(super.set).mockName('next/headers::cookies().set');
+ set = fn(super.set.bind(this)).mockName('next/headers::cookies().set');
- delete = fn(super.delete).mockName('next/headers::cookies().delete');
+ delete = fn(super.delete.bind(this)).mockName('next/headers::cookies().delete');
}
let requestCookiesMock: RequestCookiesMock;
diff --git a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
index 02703265e583..d9eb5177b447 100644
--- a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
+++ b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts
@@ -7,23 +7,23 @@ class HeadersAdapterMock extends HeadersAdapter {
super({});
}
- append = fn(super.append).mockName('next/headers::headers().append');
+ append = fn(super.append.bind(this)).mockName('next/headers::headers().append');
- delete = fn(super.delete).mockName('next/headers::headers().delete');
+ delete = fn(super.delete.bind(this)).mockName('next/headers::headers().delete');
- get = fn(super.get).mockName('next/headers::headers().get');
+ get = fn(super.get.bind(this)).mockName('next/headers::headers().get');
- has = fn(super.has).mockName('next/headers::headers().has');
+ has = fn(super.has.bind(this)).mockName('next/headers::headers().has');
- set = fn(super.set).mockName('next/headers::headers().set');
+ set = fn(super.set.bind(this)).mockName('next/headers::headers().set');
- forEach = fn(super.forEach).mockName('next/headers::headers().forEach');
+ forEach = fn(super.forEach.bind(this)).mockName('next/headers::headers().forEach');
- entries = fn(super.entries).mockName('next/headers::headers().entries');
+ entries = fn(super.entries.bind(this)).mockName('next/headers::headers().entries');
- keys = fn(super.keys).mockName('next/headers::headers().keys');
+ keys = fn(super.keys.bind(this)).mockName('next/headers::headers().keys');
- values = fn(super.values).mockName('next/headers::headers().values');
+ values = fn(super.values.bind(this)).mockName('next/headers::headers().values');
}
let headersAdapterMock: HeadersAdapterMock;
diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock
index e0efcfd9d6d9..71f8b86f9807 100644
--- a/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock
+++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/yarn.lock
@@ -3167,7 +3167,7 @@ __metadata:
"@storybook/nextjs@file:../../../code/frameworks/nextjs::locator=portable-stories-nextjs%40workspace%3A.":
version: 8.1.0-alpha.7
- resolution: "@storybook/nextjs@file:../../../code/frameworks/nextjs#../../../code/frameworks/nextjs::hash=246816&locator=portable-stories-nextjs%40workspace%3A."
+ resolution: "@storybook/nextjs@file:../../../code/frameworks/nextjs#../../../code/frameworks/nextjs::hash=28a407&locator=portable-stories-nextjs%40workspace%3A."
dependencies:
"@babel/core": "npm:^7.23.2"
"@babel/plugin-syntax-bigint": "npm:^7.8.3"
@@ -3227,7 +3227,7 @@ __metadata:
optional: true
webpack:
optional: true
- checksum: 10/71732cc220e381872106dab3824883f4a77dcbb60901c939f58599bf92f156564a34fe646ce1159c307702be03cf532fbffaa1a515e271a6623f340d19000283
+ checksum: 10/fd9a469074e0b7aba065a0b0f81f272e75e708370362ff047a653d9a19c5b753fcfab8b2f30b3f9ae226a12739b7b11054358afe4707e8d7185b9dca11c75a7c
languageName: node
linkType: hard
@@ -3326,7 +3326,7 @@ __metadata:
"@storybook/react@file:../../../code/renderers/react::locator=portable-stories-nextjs%40workspace%3A.":
version: 8.1.0-alpha.7
- resolution: "@storybook/react@file:../../../code/renderers/react#../../../code/renderers/react::hash=cd73fa&locator=portable-stories-nextjs%40workspace%3A."
+ resolution: "@storybook/react@file:../../../code/renderers/react#../../../code/renderers/react::hash=faf3f4&locator=portable-stories-nextjs%40workspace%3A."
dependencies:
"@storybook/client-logger": "workspace:*"
"@storybook/docs-tools": "workspace:*"
@@ -3356,7 +3356,7 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/834084c0092a8b179f7048c1111308b6a2381cf3ee119372ed2940833b02e13577b5bc334c3f14c3224bd2d8b786939a74fbaf4dea6e8423ff610b34047412b4
+ checksum: 10/f89fb42e87e17377af48b57b337dd4708053ae83d8558a0ae35af479b1faa6d7206b07934e18113707febed5ebcf94510caa89b8c92bc87cf7b5391296bbfef2
languageName: node
linkType: hard
From b55673f1d8067a2770c69b98b91f6856bade4137 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 13:02:34 +0200
Subject: [PATCH 29/46] Update snapshot
---
.../nextjs/stories/__snapshots__/portable-stories.test.tsx.snap | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
index 2b8af2b9cd59..a22fa06a8ab3 100644
--- a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
+++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
@@ -794,7 +794,7 @@ exports[`renders nextHeaderStories stories renders Default 1`] = `
- firstName=Jane; lastName=Doe
+ firstName=Jane; ; lastName=Doe;
Date: Tue, 16 Apr 2024 13:11:55 +0200
Subject: [PATCH 30/46] Fix name
---
code/frameworks/nextjs/src/export-mocks/headers/cookies.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
index 3fc9fdc1ae59..3d84ecba3885 100644
--- a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
+++ b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts
@@ -8,7 +8,7 @@ import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import { headers } from '@storybook/nextjs/headers.mock';
class RequestCookiesMock extends RequestCookies {
- get = fn(super.get.bind(this)).mockName('next/headers::get');
+ get = fn(super.get.bind(this)).mockName('next/headers::cookies().get');
getAll = fn(super.getAll.bind(this)).mockName('next/headers::cookies().getAll');
From 537fbb22a01d14137fa12492c8bfd88655387e0e Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:08:31 +0200
Subject: [PATCH 31/46] Hide some junk of next for nwo
---
code/addons/actions/src/loaders.ts | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts
index d49a048da231..a1fff870b7be 100644
--- a/code/addons/actions/src/loaders.ts
+++ b/code/addons/actions/src/loaders.ts
@@ -18,7 +18,19 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => {
typeof global.__STORYBOOK_TEST_ON_MOCK_CALL__ === 'function'
) {
const onMockCall = global.__STORYBOOK_TEST_ON_MOCK_CALL__ as typeof onMockCallType;
- onMockCall((mock, args) => action(mock.getMockName())(args));
+ onMockCall((mock, args) => {
+ const name = mock.getMockName();
+
+ // TODO: Make this a configurable API in 8.2
+ if (
+ !/^next\/.*::/.test(name) ||
+ ((name.startsWith('next/headers::cookies()') ||
+ name.startsWith('next/headers::headers()')) &&
+ (name.endsWith('set') || name.endsWith('delete')))
+ ) {
+ action(name)(args);
+ }
+ });
subscribed = true;
}
};
From 7cacdfab2aea2c479e9a3a93c18f4f0ff7a007b0 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:13:47 +0200
Subject: [PATCH 32/46] Update
code/frameworks/nextjs/src/export-mocks/navigation/index.ts
Co-authored-by: Yann Braga
---
code/frameworks/nextjs/src/export-mocks/navigation/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
index fbbeb8ced25c..61344102c242 100644
--- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
@@ -77,7 +77,7 @@ export const useServerInsertedHTML = fn(originalNavigation.useServerInsertedHTML
);
export const notFound = fn(originalNavigation.notFound).mockName('next/navigation::notFound');
export const permanentRedirect = fn(originalNavigation.permanentRedirect).mockName(
- 'permanentRedirect'
+ 'next/navigation::permanentRedirect'
);
// Params, not exported by Next.js, is manually declared to avoid inference issues.
From af31a1fd822c56f5279e2c3c4b956758be1dd199 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:13:53 +0200
Subject: [PATCH 33/46] Update
code/frameworks/nextjs/src/export-mocks/navigation/index.ts
Co-authored-by: Yann Braga
---
code/frameworks/nextjs/src/export-mocks/navigation/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
index 61344102c242..2973c65120ed 100644
--- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
@@ -66,7 +66,7 @@ export const usePathname = fn(originalNavigation.usePathname).mockName(
'next/navigation::usePathname'
);
export const useSelectedLayoutSegment = fn(originalNavigation.useSelectedLayoutSegment).mockName(
- 'useSelectedLayoutSegment'
+ 'next/navigation::useSelectedLayoutSegment'
);
export const useSelectedLayoutSegments = fn(originalNavigation.useSelectedLayoutSegments).mockName(
'useSelectedLayoutSegments'
From 789991fefed476e17f08caf198cd4bed42a713f0 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:14:02 +0200
Subject: [PATCH 34/46] Update
code/frameworks/nextjs/src/export-mocks/navigation/index.ts
Co-authored-by: Yann Braga
---
code/frameworks/nextjs/src/export-mocks/navigation/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
index 2973c65120ed..63458b0fc927 100644
--- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
@@ -73,7 +73,7 @@ export const useSelectedLayoutSegments = fn(originalNavigation.useSelectedLayout
);
export const useRouter = fn(originalNavigation.useRouter).mockName('next/navigation::useRouter');
export const useServerInsertedHTML = fn(originalNavigation.useServerInsertedHTML).mockName(
- 'useServerInsertedHTML'
+ 'next/navigation::useServerInsertedHTML'
);
export const notFound = fn(originalNavigation.notFound).mockName('next/navigation::notFound');
export const permanentRedirect = fn(originalNavigation.permanentRedirect).mockName(
From 17c5152d5d43b4f7e94b3566245fa0013c708682 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:14:07 +0200
Subject: [PATCH 35/46] Update
code/frameworks/nextjs/src/export-mocks/navigation/index.ts
Co-authored-by: Yann Braga
---
code/frameworks/nextjs/src/export-mocks/navigation/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
index 63458b0fc927..dd9e9a692e6f 100644
--- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
+++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts
@@ -69,7 +69,7 @@ export const useSelectedLayoutSegment = fn(originalNavigation.useSelectedLayoutS
'next/navigation::useSelectedLayoutSegment'
);
export const useSelectedLayoutSegments = fn(originalNavigation.useSelectedLayoutSegments).mockName(
- 'useSelectedLayoutSegments'
+ 'next/navigation::useSelectedLayoutSegments'
);
export const useRouter = fn(originalNavigation.useRouter).mockName('next/navigation::useRouter');
export const useServerInsertedHTML = fn(originalNavigation.useServerInsertedHTML).mockName(
From fc7e66439d9adffb29e62e76b04ee7c1738e86e3 Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 14:19:52 +0200
Subject: [PATCH 36/46] Hide some junk of next for now
---
code/addons/actions/src/loaders.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts
index a1fff870b7be..7f279bba5775 100644
--- a/code/addons/actions/src/loaders.ts
+++ b/code/addons/actions/src/loaders.ts
@@ -24,6 +24,8 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => {
// TODO: Make this a configurable API in 8.2
if (
!/^next\/.*::/.test(name) ||
+ name.startsWith('next/router::useRouter()') ||
+ name.startsWith('next/navigation::useRouter()') ||
((name.startsWith('next/headers::cookies()') ||
name.startsWith('next/headers::headers()')) &&
(name.endsWith('set') || name.endsWith('delete')))
From 5fafb468807e041e93d1bbb9ed9245058723a82b Mon Sep 17 00:00:00 2001
From: Kasper Peulen
Date: Tue, 16 Apr 2024 15:09:15 +0200
Subject: [PATCH 37/46] Show redirect and next/cache spies
---
code/addons/actions/src/loaders.ts | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts
index 7f279bba5775..eebb09acb71b 100644
--- a/code/addons/actions/src/loaders.ts
+++ b/code/addons/actions/src/loaders.ts
@@ -24,11 +24,16 @@ const logActionsWhenMockCalled: LoaderFunction = (context) => {
// TODO: Make this a configurable API in 8.2
if (
!/^next\/.*::/.test(name) ||
- name.startsWith('next/router::useRouter()') ||
- name.startsWith('next/navigation::useRouter()') ||
- ((name.startsWith('next/headers::cookies()') ||
- name.startsWith('next/headers::headers()')) &&
- (name.endsWith('set') || name.endsWith('delete')))
+ [
+ 'next/router::useRouter()',
+ 'next/navigation::useRouter()',
+ 'next/navigation::redirect',
+ 'next/cache::',
+ 'next/headers::cookies().set',
+ 'next/headers::cookies().delete',
+ 'next/headers::headers().set',
+ 'next/headers::headers().delete',
+ ].some((prefix) => name.startsWith(prefix))
) {
action(name)(args);
}
From a9ce1d4e2d926be30b6e945b233caf5c621417fb Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 30 Apr 2024 11:45:15 -0600
Subject: [PATCH 38/46] Apply suggestions from code review
Co-authored-by: jonniebigodes
---
docs/get-started/nextjs.md | 8 ++++----
docs/writing-stories/build-pages-with-storybook.md | 2 +-
docs/writing-stories/mocking-modules.md | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 3c41ed990db7..99314381ae23 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -688,7 +688,7 @@ To configure subpath imports, you define the `imports` property in your project'
-Because subpath imports take the place of module aliases, you can remove the path aliases from your TypeScript configuration.
+Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration.
@@ -726,7 +726,7 @@ This framework provides mocks for many of Next.js' internal modules:
How you mock other modules in Storybook depends on how you import the module into your component.
-The first step, with either approach, is to [create a mock file](../writing-stories/mocking-modules.md#mock-files). Here's an example of a mock file for a module named `session`:
+With either approach, the first step is to [create a mock file](../writing-stories/mocking-modules.md#mock-files). Here's an example of a mock file for a module named `session`:
@@ -1024,13 +1024,13 @@ You can refer to the [Install `sharp` to Use Built-In Image Optimization](https:
### Modules
-The `@storybook/nextjs` package exports a number of modules that enables you to [mock](#mocking-modules) Next.js's internal behavior.
+The `@storybook/nextjs` package exports several modules that enable you to [mock](#mocking-modules) Next.js's internal behavior.
#### `@storybook/nextjs/export-mocks`
Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
-`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories](#portable-stories).
+`getPackageAliases` is a helper for generating the aliases needed to set up [portable stories](#portable-stories).
```ts
// jest.config.ts
diff --git a/docs/writing-stories/build-pages-with-storybook.md b/docs/writing-stories/build-pages-with-storybook.md
index eb6fefd7842b..dc7b34bd8a44 100644
--- a/docs/writing-stories/build-pages-with-storybook.md
+++ b/docs/writing-stories/build-pages-with-storybook.md
@@ -81,7 +81,7 @@ Components can receive data or configuration from context providers. For example
### [Mocking API Services](./mocking-network-requests.md)
-For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests in your stories.
+For components that make network requests (e.g., fetching data from a REST or GraphQL API), you can mock those requests in your stories.
### [Mocking imports](./mocking-modules.md)
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 43db0d9b1f67..b5d9e670f1af 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -33,7 +33,7 @@ export const getUserFromSession = fn(actual.getUserFromSession);
### Mock files for external modules
-You can't directly mock an external module like `uuid` or `node:fs`. Instead, you must wrap the module in you own module, which you can then mock like any other internal module. In this example, we wrap `uuid`:
+You can't directly mock an external module like [`uuid`](https://github.com/uuidjs/uuid) or `node:fs`. Instead, you must wrap it in your own module, which you can mock like any other internal one. For example, with `uuid`, you could do the following:
```ts
// lib/uuid.ts
@@ -190,7 +190,7 @@ export const Default: Story = {
-If you are [writing your stories in TypeScript](./typescript.md), you will need to import your mock modules using the full mocked file name to have the mocked function correctly typed in your stories. You do **not** need do this in your component files, that's what the [subpath import](#subpath-imports) or [builder alias](#builder-aliases) is for.
+If you are [writing your stories in TypeScript](./typescript.md), you must import your mock modules using the full mocked file name to have the functions correctly typed in your stories. You do **not** need to do this in your component files. That's what the [subpath import](#subpath-imports) or [builder alias](#builder-aliases) is for.
From 01c65098a82f68b8b9311ea4b8c0e7bc241ed014 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 30 Apr 2024 13:53:17 -0600
Subject: [PATCH 39/46] Address feedback
- Re-order TOC items
- Fix conditional content
- Improve nextjs module API references
- Make example snippets more consistent
- Re-organize subpath imports section of module mocking guide
- Fix typos, grammar
---
docs/configure/story-rendering.md | 26 ++++++++++++++++
docs/get-started/nextjs.md | 30 +++++++++----------
...sw-addon-configure-handlers-graphql.ts.mdx | 4 +--
...sw-addon-configure-handlers-graphql.js.mdx | 4 +--
...ddon-configure-handlers-graphql.ts-4-9.mdx | 4 +--
...sw-addon-configure-handlers-graphql.ts.mdx | 4 +--
...sw-addon-configure-handlers-graphql.js.mdx | 4 +--
...ddon-configure-handlers-graphql.ts-4-9.mdx | 4 +--
...sw-addon-configure-handlers-graphql.ts.mdx | 4 +--
...sw-addon-configure-handlers-graphql.js.mdx | 4 +--
...ddon-configure-handlers-graphql.ts-4-9.mdx | 4 +--
...sw-addon-configure-handlers-graphql.ts.mdx | 4 +--
docs/toc.js | 8 ++---
.../build-pages-with-storybook.md | 10 +++----
docs/writing-stories/decorators.md | 4 +++
docs/writing-stories/mocking-modules.md | 27 +++++++----------
docs/writing-stories/mocking-providers.md | 18 ++++++++++-
17 files changed, 101 insertions(+), 62 deletions(-)
diff --git a/docs/configure/story-rendering.md b/docs/configure/story-rendering.md
index 1ca2d09c76d8..87f8bc8c8d9b 100644
--- a/docs/configure/story-rendering.md
+++ b/docs/configure/story-rendering.md
@@ -8,6 +8,30 @@ In Storybook, your stories render in a particular “preview” iframe (also cal
Code executed in the preview file (`.storybook/preview.js|ts`) runs for every story in your Storybook. This is useful for setting up global styles, initializing libraries, or anything else required to render your components.
+
+
+Here's an example of how you might use the preview file to initialize a library that must run before your components render:
+
+```ts
+// .storybook/preview.ts
+// Replace your-renderer with the renderer you are using (e.g., react, vue3)
+import { Preview } from '@storybook/your-renderer';
+
+import { initialize } from '../lib/your-library';
+
+initialize();
+
+const preview: Preview = {
+ // ...
+};
+
+export default preview;
+```
+
+
+
+
+
For example, with Vue, you can extend Storybook's application and register your library (e.g., [Fontawesome](https://github.com/FortAwesome/vue-fontawesome)). Or with Angular, add the package ([localize](https://angular.io/api/localize)) into your `polyfills.ts` and import it:
@@ -25,6 +49,8 @@ For example, with Vue, you can extend Storybook's application and register your
+
+
## Adding to <head>
If you need to add extra elements to the `head` of the preview iframe, for instance, to load static stylesheets, font files, or similar, you can create a file called [`.storybook/preview-head.html`](./index.md#configure-story-rendering) and add tags like this:
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 99314381ae23..36140f9def16 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -326,7 +326,7 @@ import { Preview } from '@storybook/react';
import { getRouter } from '@storybook/nextjs/router.mock';
const preview: Preview = {
- paramters: {
+ parameters: {
nextjs: {
// 👇 Override the default router properties
router: {
@@ -487,7 +487,7 @@ import { Preview } from '@storybook/react';
import { getRouter } from '@storybook/nextjs/navigation.mock';
const preview: Preview = {
- paramters: {
+ parameters: {
nextjs: {
// 👇 Override the default navigation properties
navigation: {
@@ -591,7 +591,7 @@ export default HelloWorld;
You can use your own babel config too. This is an example of how you can customize styled-jsx.
-```json
+```jsonc
// .babelrc (or whatever config file you use)
{
"presets": [
@@ -677,7 +677,7 @@ As an alternative to [module aliases](#module-aliases), you can use [subpath imp
To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project:
-```json
+```jsonc
// package.json
{
"imports": {
@@ -741,11 +741,11 @@ export const getUserFromSession = fn(actual.getUserFromSession);
#### With subpath imports
-If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook:
+If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#subpath-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook:
-```json
+```jsonc
// package.json
{
"imports": {
@@ -827,7 +827,7 @@ module.exports = {
Calls to `getConfig` would return the following object when called within Storybook:
-```json
+```jsonc
// Runtime config
{
"serverRuntimeConfig": {},
@@ -862,7 +862,7 @@ Below is an example of how to add SVGR support to Storybook with this framework.
Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box.
-```json
+```jsonc
// tsconfig.json
{
"compilerOptions": {
@@ -918,7 +918,7 @@ In the future we will provide better mocking support in Storybook and support fo
You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API.
-When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because, to replicate Next.js configuration, Storybook sets up aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well.
+When using portable stories with Next.js, you need to mock the Next.js modules on which your components depend. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because, to replicate Next.js configuration, Storybook sets up aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well.
## Notes for Yarn v2 and v3 users
@@ -980,7 +980,7 @@ Make sure you are treating image imports the same way you treat them when using
Before using this framework, image imports would import the raw path to the image (e.g. `'static/media/stories/assets/logo.svg'`). Now image imports work the "Next.js way", meaning that you now get an object when importing an image. For example:
-```json
+```jsonc
// Image import object
{
"src": "static/media/stories/assets/logo.svg",
@@ -1059,7 +1059,7 @@ export default createJestConfig(config);
Type: `typeof import('next/cache')`
-Exports mocks that replaces the actual implementation of `next/cache` exports. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+This module exports mocked implementations of the `next/cache` module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
@@ -1095,7 +1095,7 @@ export const Submitted: Story = {
Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js
-Exports _writable_ mocks that replaces the actual implementation of `next/headers` exports. Use this to set up cookies or headers that are read in your story, and to later assert that they have been called.
+This module exports _writable_ mocked implementations of the `next/headers` module's exports. You can use it to set up cookies or headers that are read in your story, and to later assert that they have been called.
Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers:
@@ -1103,7 +1103,7 @@ Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functi
- **`headers().delete(name: string)`**: Deletes the header
- **`headers().set(name: string, value: string)`**: Sets the header to the value provided.
-For cookies, you can use the existing API to write them, eg. `cookies().set('firstName', 'Jane')`.
+For cookies, you can use the existing API to write them. E.g., `cookies().set('firstName', 'Jane')`.
Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
@@ -1145,7 +1145,7 @@ export const LoggedInEurope: Story = {
Type: `typeof import('next/navigation') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+This module exports mocked implementations of the `next/navigation` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
@@ -1194,7 +1194,7 @@ export const GoBack: Story = {
Type: `typeof import('next/router') & getRouter: () => ReturnType`
-Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
+This module exports mocked implementations of the `next/router` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
diff --git a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
index dc3347cfd980..f35bcd16f033 100644
--- a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
@@ -59,8 +59,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
index dee1f7976ec4..0138bd74920c 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
@@ -63,8 +63,8 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 2fd10eed5dec..468c1866b98c 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -67,8 +67,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
}
});
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
index 4367a53a25e9..281441a95612 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
@@ -67,8 +67,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
}
});
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
index 829ec728386a..bbe3531e64b0 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
@@ -42,8 +42,8 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 866603602f16..91e56485b7a5 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -47,8 +47,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
index d78ffb4ce4e8..cacc4af7674a 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
@@ -47,8 +47,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
index 251b119a6629..3e13c859cc70 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -45,8 +45,8 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index c4b55f47f6ef..0efceab56d6a 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -50,8 +50,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
index 6d1b27cb6946..fd23daacece7 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -50,8 +50,8 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- allFilms: {
- films,
+ AllInfo: {
+ ...TestData,
},
},
});
diff --git a/docs/toc.js b/docs/toc.js
index 7dbc4bceec4c..c872f84d2aa4 100644
--- a/docs/toc.js
+++ b/docs/toc.js
@@ -147,8 +147,8 @@ module.exports = {
type: 'menu',
children: [
{
- pathSegment: 'mocking-providers',
- title: 'Providers',
+ pathSegment: 'mocking-modules',
+ title: 'Modules',
type: 'link',
},
{
@@ -157,8 +157,8 @@ module.exports = {
type: 'link',
},
{
- pathSegment: 'mocking-modules',
- title: 'Modules',
+ pathSegment: 'mocking-providers',
+ title: 'Providers',
type: 'link',
},
],
diff --git a/docs/writing-stories/build-pages-with-storybook.md b/docs/writing-stories/build-pages-with-storybook.md
index dc7b34bd8a44..0563733e0dac 100644
--- a/docs/writing-stories/build-pages-with-storybook.md
+++ b/docs/writing-stories/build-pages-with-storybook.md
@@ -73,19 +73,19 @@ This approach is beneficial when the various subcomponents export a complex list
## Mocking connected components
-If you need to render a connected component in Storybook, you can mock the data or modules that component depends on. There are various layers in which you can do that.
+Connected components are components that depend on external data or services. For example, a full page component is often a connected component. When you render a connected component in Storybook, you need to mock the data or modules that the component depends on. There are various layers in which you can do that.
-### [Mocking providers](./mocking-providers.md)
+### [Mocking imports](./mocking-modules.md)
-Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories.
+Components can depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
### [Mocking API Services](./mocking-network-requests.md)
For components that make network requests (e.g., fetching data from a REST or GraphQL API), you can mock those requests in your stories.
-### [Mocking imports](./mocking-modules.md)
+### [Mocking providers](./mocking-providers.md)
-Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior.
+Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories.
diff --git a/docs/writing-stories/decorators.md b/docs/writing-stories/decorators.md
index da53026077e3..f673ed393c04 100644
--- a/docs/writing-stories/decorators.md
+++ b/docs/writing-stories/decorators.md
@@ -80,8 +80,12 @@ const preview: Preview = {
export default preview;
```
+
+
For another example, see the section on [configuring the mock provider](./mocking-providers.md#configuring-the-mock-provider), which demonstrates how to use the same technique to change which theme is provided to the component.
+
+
### Using decorators to provide data
If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data. Read more in the [building pages in Storybook](./build-pages-with-storybook.md) section.
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index b5d9e670f1af..fa1452b2222d 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -86,31 +86,24 @@ To configure subpath imports, you define the `imports` property in your project'
}
```
-
+There are two aspects to this configuration worth noting:
-Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
+First, **each subpath must begin with `#`**, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
-
+Second, note the **`storybook` and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
-You can then update your component file to use the subpath import:
+With the package configuration in place, you can then update your component file to use the subpath import:
```ts
// AuthButton.ts
+// ➖ Remove this line
+// import { getUserFromSession } from '../../lib/session';
+// ➕ Add this line
import { getUserFromSession } from '#lib/session';
-export const AuthButton = (props) => {
- const user = getUserFromSession();
-
- // ...
-};
+// ... rest of the file
```
-### Conditional imports
-
-Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project.
-
-The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
-
## Builder aliases
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
@@ -240,7 +233,7 @@ export const SaveFlow: Story = {
### Setting up and cleaning up
-You can use the asynchronous `beforeEach` function to perform any setup that you need before the story is rendered, eg. setting up mock behavior. It can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.js|ts`, which will run for all stories in the project) level.
+Before the story renders, you can use the asynchronous `beforeEach` function to perform any setup you need (e.g., configure the mock behavior). This function can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.js|ts`, which will run for all stories in the project).
You can also return a cleanup function from `beforeEach` which will be called after your story unmounts. This is useful for tasks like unsubscribing observers, etc.
@@ -250,7 +243,7 @@ It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Stor
-Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts.
+Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts.
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index adeec3072f67..0671ff37532e 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -2,7 +2,21 @@
title: Mocking providers
---
-
+export const SUPPORTED_RENDERERS = ['react', 'solid'];
+
+
+
+
+
+The [context provider pattern](https://react.dev/learn/passing-data-deeply-with-context) and how to mock it only applies to renderers that use JSX, like [React](?renderer=react) or [Solid](?renderer=solid).
+
+
+
+
+
+
+
+
Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context.
@@ -90,3 +104,5 @@ export const Dark: Story = {
```
This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable.
+
+
From 1dca308bf46991a0b48c88604628b8865b11b285 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 30 Apr 2024 22:21:31 -0600
Subject: [PATCH 40/46] Snippetize examples
---
docs/get-started/nextjs.md | 238 ++++--------------
.../before-each-in-meta-mock-date.ts.mdx | 31 +++
.../angular/storybook-test-fn-mock-spy.ts.mdx | 36 +++
.../storybook-test-mock-return-value.ts.mdx | 22 ++
.../before-each-in-meta-mock-date.js.mdx | 26 ++
.../before-each-in-meta-mock-date.ts-4-9.mdx | 32 +++
.../before-each-in-meta-mock-date.ts.mdx | 32 +++
.../common/module-aliases-config.vite.js.mdx | 25 ++
.../common/module-aliases-config.vite.ts.mdx | 29 +++
.../module-aliases-config.webpack.js.mdx | 25 ++
.../module-aliases-config.webpack.ts.mdx | 29 +++
.../common/msw-addon-install.npm.js.mdx | 3 +
.../common/msw-addon-install.pnpm.js.mdx | 3 +
.../common/msw-addon-install.yarn.js.mdx | 3 +
docs/snippets/common/msw-install.npm.js.mdx | 3 -
docs/snippets/common/msw-install.pnpm.js.mdx | 3 -
docs/snippets/common/msw-install.yarn.js.mdx | 3 -
.../common/storybook-test-fn-mock-spy.js.mdx | 31 +++
.../storybook-test-fn-mock-spy.ts-4-9.mdx | 37 +++
.../common/storybook-test-fn-mock-spy.ts.mdx | 37 +++
.../storybook-test-mock-file-example.ts.mdx | 8 +
.../storybook-test-mock-return-value.js.mdx | 18 ++
...torybook-test-mock-return-value.ts-4-9.mdx | 25 ++
.../storybook-test-mock-return-value.ts.mdx | 24 ++
.../common/subpath-imports-config.json.mdx | 24 ++
...-mock-provider-with-story-parameter.js.mdx | 18 ++
...k-provider-with-story-parameter.ts-4-9.mdx | 23 ++
...-mock-provider-with-story-parameter.ts.mdx | 23 ++
.../decorator-parameterized-in-preview.js.mdx | 28 +++
.../decorator-parameterized-in-preview.ts.mdx | 31 +++
.../react/mock-provider-in-preview.js.mdx | 23 ++
.../react/mock-provider-in-preview.ts.mdx | 26 ++
docs/snippets/react/nextjs-cache-mock.js.mdx | 22 ++
.../react/nextjs-cache-mock.ts-4-9.mdx | 28 +++
docs/snippets/react/nextjs-cache-mock.ts.mdx | 28 +++
.../snippets/react/nextjs-headers-mock.js.mdx | 26 ++
.../react/nextjs-headers-mock.ts-4-9.mdx | 32 +++
.../snippets/react/nextjs-headers-mock.ts.mdx | 32 +++
.../react/nextjs-navigation-mock.js.mdx | 35 +++
.../react/nextjs-navigation-mock.ts-4-9.mdx | 41 +++
.../react/nextjs-navigation-mock.ts.mdx | 41 +++
docs/snippets/react/nextjs-router-mock.js.mdx | 23 ++
.../react/nextjs-router-mock.ts-4-9.mdx | 28 +++
docs/snippets/react/nextjs-router-mock.ts.mdx | 28 +++
.../before-each-in-meta-mock-date.js.mdx | 25 ++
.../before-each-in-meta-mock-date.ts.mdx | 30 +++
.../storybook-test-fn-mock-spy.js.mdx | 30 +++
.../storybook-test-fn-mock-spy.ts.mdx | 35 +++
.../storybook-test-mock-return-value.js.mdx | 15 ++
.../storybook-test-mock-return-value.ts.mdx | 21 ++
docs/writing-stories/decorators.md | 43 +---
docs/writing-stories/mocking-modules.md | 219 +++++-----------
.../mocking-network-requests.md | 18 +-
docs/writing-stories/mocking-providers.md | 74 ++----
docs/writing-tests/interaction-testing.md | 95 ++-----
55 files changed, 1379 insertions(+), 509 deletions(-)
create mode 100644 docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
create mode 100644 docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
create mode 100644 docs/snippets/angular/storybook-test-mock-return-value.ts.mdx
create mode 100644 docs/snippets/common/before-each-in-meta-mock-date.js.mdx
create mode 100644 docs/snippets/common/before-each-in-meta-mock-date.ts-4-9.mdx
create mode 100644 docs/snippets/common/before-each-in-meta-mock-date.ts.mdx
create mode 100644 docs/snippets/common/module-aliases-config.vite.js.mdx
create mode 100644 docs/snippets/common/module-aliases-config.vite.ts.mdx
create mode 100644 docs/snippets/common/module-aliases-config.webpack.js.mdx
create mode 100644 docs/snippets/common/module-aliases-config.webpack.ts.mdx
create mode 100644 docs/snippets/common/msw-addon-install.npm.js.mdx
create mode 100644 docs/snippets/common/msw-addon-install.pnpm.js.mdx
create mode 100644 docs/snippets/common/msw-addon-install.yarn.js.mdx
delete mode 100644 docs/snippets/common/msw-install.npm.js.mdx
delete mode 100644 docs/snippets/common/msw-install.pnpm.js.mdx
delete mode 100644 docs/snippets/common/msw-install.yarn.js.mdx
create mode 100644 docs/snippets/common/storybook-test-fn-mock-spy.js.mdx
create mode 100644 docs/snippets/common/storybook-test-fn-mock-spy.ts-4-9.mdx
create mode 100644 docs/snippets/common/storybook-test-fn-mock-spy.ts.mdx
create mode 100644 docs/snippets/common/storybook-test-mock-file-example.ts.mdx
create mode 100644 docs/snippets/common/storybook-test-mock-return-value.js.mdx
create mode 100644 docs/snippets/common/storybook-test-mock-return-value.ts-4-9.mdx
create mode 100644 docs/snippets/common/storybook-test-mock-return-value.ts.mdx
create mode 100644 docs/snippets/common/subpath-imports-config.json.mdx
create mode 100644 docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
create mode 100644 docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
create mode 100644 docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
create mode 100644 docs/snippets/react/decorator-parameterized-in-preview.js.mdx
create mode 100644 docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
create mode 100644 docs/snippets/react/mock-provider-in-preview.js.mdx
create mode 100644 docs/snippets/react/mock-provider-in-preview.ts.mdx
create mode 100644 docs/snippets/react/nextjs-cache-mock.js.mdx
create mode 100644 docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
create mode 100644 docs/snippets/react/nextjs-cache-mock.ts.mdx
create mode 100644 docs/snippets/react/nextjs-headers-mock.js.mdx
create mode 100644 docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
create mode 100644 docs/snippets/react/nextjs-headers-mock.ts.mdx
create mode 100644 docs/snippets/react/nextjs-navigation-mock.js.mdx
create mode 100644 docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
create mode 100644 docs/snippets/react/nextjs-navigation-mock.ts.mdx
create mode 100644 docs/snippets/react/nextjs-router-mock.js.mdx
create mode 100644 docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
create mode 100644 docs/snippets/react/nextjs-router-mock.ts.mdx
create mode 100644 docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
create mode 100644 docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
create mode 100644 docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
create mode 100644 docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
create mode 100644 docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
create mode 100644 docs/snippets/web-components/storybook-test-mock-return-value.ts.mdx
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index 36140f9def16..957f58f8bb31 100644
--- a/docs/get-started/nextjs.md
+++ b/docs/get-started/nextjs.md
@@ -728,47 +728,29 @@ How you mock other modules in Storybook depends on how you import the module int
With either approach, the first step is to [create a mock file](../writing-stories/mocking-modules.md#mock-files). Here's an example of a mock file for a module named `session`:
-
+
-```ts
-// lib/session.mock.ts
-import { fn } from '@storybook/test';
-import * as actual from './session';
+
-export * from './session';
-export const getUserFromSession = fn(actual.getUserFromSession);
-```
+
#### With subpath imports
If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#subpath-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook:
-
+
-```jsonc
-// package.json
-{
- "imports": {
- "#api": {
- "storybook": "./api.mock.ts",
- "default": "./api.ts"
- },
- "#app/actions": {
- "storybook": "./app/actions.mock.ts",
- "default": "./app/actions.ts"
- },
- "#lib/session": {
- "storybook": "./lib/session.mock.ts",
- "default": "./lib/session.ts"
- },
- "#lib/db": {
- "storybook": "./lib/db.mock.ts",
- "default": "./lib/db.ts"
- },
- "#*": ["./*", "./*.ts", "./*.tsx"]
- }
-}
-```
+
+
+
@@ -780,27 +762,16 @@ Each subpath must begin with `#`, to differentiate it from a regular module path
If you're using [module aliases](#module-aliases), you can add a Webpack alias to your Storybook configuration to point to the mock file.
-
+
-```ts
-// .storybook/main.ts
-webpackFinal: async (config) => {
- if (config.resolve) {
- config.resolve.alias = {
- ...config.resolve.alias,
- // 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
- // 👇 Internal modules
- '@/api$': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
- }
+
- return config;
-},
-```
+
## Runtime config
@@ -1061,35 +1032,16 @@ Type: `typeof import('next/cache')`
This module exports mocked implementations of the `next/cache` module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md).
-
-
-```ts
-// MyForm.stories.ts
-import { expect, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
-// 👇 Must use this import path to have mocks typed correctly
-import { revalidatePath } from '@storybook/nextjs/cache.mock';
-import MyForm from './my-form';
-
-const meta = {
- component: MyForm,
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
+
-export const Submitted: Story = {
- async play({ canvasElement }) {
- const canvas = within(canvasElement);
+
- const submitButton = canvas.getByRole('button', { name: /submit/i });
- await userEvent.click(saveButton);
- // 👇 Use any mock assertions on the function
- await expect(revalidatePath).toHaveBeenCalledWith('/');
- },
-};
-```
+
#### `@storybook/nextjs/headers.mock`
@@ -1107,39 +1059,16 @@ For cookies, you can use the existing API to write them. E.g., `cookies().set('f
Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`.
-
-
-```ts
-// MyForm.stories.ts
-import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
-// 👇 Must use this import path to have mocks typed correctly
-import { cookies, headers } from '@storybook/nextjs/headers.mock';
-import MyForm from './my-form';
-
-const meta = {
- component: MyForm,
-} satisfies Meta;
-
-export default meta;
+
-type Story = StoryObj;
+
-export const LoggedInEurope: Story = {
- async beforeEach() {
- // 👇 Set mock cookies and headers ahead of rendering
- cookies().set('username', 'Sol');
- headers().set('timezone', 'Central European Summer Time');
- },
- async play() {
- // 👇 Assert that your component called the mocks
- await expect(cookies().get).toHaveBeenCalledOnce();
- await expect(cookies().get).toHaveBeenCalledWith('username');
- await expect(headers().get).toHaveBeenCalledOnce();
- await expect(cookies().get).toHaveBeenCalledWith('timezone');
- },
-};
-```
+
#### `@storybook/nextjs/navigation.mock`
@@ -1147,48 +1076,16 @@ Type: `typeof import('next/navigation') & getRouter: () => ReturnType
-
-```ts
-// MyForm.stories.ts
-import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
-// 👇 Must use this import path to have mocks typed correctly
-import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
-import MyForm from './my-form';
-
-const meta = {
- component: MyForm,
- parameters: {
- nextjs: {
- // 👇 As in the Next.js application, next/navigation only works using App Router
- appDirectory: true,
- },
- },
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Unauthenticated: Story = {
- async play() => {
- // 👇 Assert that your component called redirect()
- await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
- },
-};
+
-export const GoBack: Story = {
- async play({ canvasElement }) {
- const canvas = within(canvasElement);
- const backBtn = await canvas.findByText('Go back');
+
- await userEvent.click(backBtn);
- // 👇 Assert that your component called back()
- await expect(getRouter().back).toHaveBeenCalled();
- },
-};
-```
+
#### `@storybook/nextjs/router.mock`
@@ -1196,41 +1093,16 @@ Type: `typeof import('next/router') & getRouter: () => ReturnType
-
-```ts
-// MyForm.stories.ts
-import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
-// 👇 Must use this import path to have mocks typed correctly
-import { getRouter } from '@storybook/nextjs/router.mock';
-import MyForm from './my-form';
-
-const meta = {
- component: MyForm,
- parameters: {
- nextjs: {
- // 👇 As in the Next.js application, next/router only works using Pages Router
- appDirectory: false,
- },
- },
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
+
-export const GoBack: Story = {
- async play({ canvasElement }) {
- const canvas = within(canvasElement);
- const backBtn = await canvas.findByText('Go back');
+
- await userEvent.click(backBtn);
- // 👇 Assert that your component called back()
- await expect(getRouter().back).toHaveBeenCalled();
- },
-};
-```
+
### Options
diff --git a/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
new file mode 100644
index 000000000000..4d79269e028d
--- /dev/null
+++ b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
@@ -0,0 +1,31 @@
+```ts
+// Page.stories.ts
+import { Meta, StoryObj } from '@storybook/angular';
+import MockDate from 'mockdate';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
new file mode 100644
index 000000000000..b0aac94d7473
--- /dev/null
+++ b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
@@ -0,0 +1,36 @@
+```ts
+// NoteUI.stories.ts
+import { Meta, StoryObj } from '@storybook/angular';
+import { expect, userEvent, within } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+const meta: Meta = {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+};
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/angular/storybook-test-mock-return-value.ts.mdx b/docs/snippets/angular/storybook-test-mock-return-value.ts.mdx
new file mode 100644
index 000000000000..693843191081
--- /dev/null
+++ b/docs/snippets/angular/storybook-test-mock-return-value.ts.mdx
@@ -0,0 +1,22 @@
+```ts
+// Page.stories.ts
+import type { Meta, StoryObj } from '@storybook/angular';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/snippets/common/before-each-in-meta-mock-date.js.mdx b/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
new file mode 100644
index 000000000000..61151b7a4850
--- /dev/null
+++ b/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
@@ -0,0 +1,26 @@
+```ts
+// Page.stories.ts
+import MockDate from 'mockdate';
+
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+export default {
+ component: Page,
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+};
+
+export const Default: Story = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
diff --git a/docs/snippets/common/before-each-in-meta-mock-date.ts-4-9.mdx b/docs/snippets/common/before-each-in-meta-mock-date.ts-4-9.mdx
new file mode 100644
index 000000000000..521d5ac7a1d3
--- /dev/null
+++ b/docs/snippets/common/before-each-in-meta-mock-date.ts-4-9.mdx
@@ -0,0 +1,32 @@
+```ts
+// Page.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import MockDate from 'mockdate';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta = {
+ component: Page,
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
diff --git a/docs/snippets/common/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/common/before-each-in-meta-mock-date.ts.mdx
new file mode 100644
index 000000000000..35da639d0cdc
--- /dev/null
+++ b/docs/snippets/common/before-each-in-meta-mock-date.ts.mdx
@@ -0,0 +1,32 @@
+```ts
+// Page.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import MockDate from 'mockdate';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
diff --git a/docs/snippets/common/module-aliases-config.vite.js.mdx b/docs/snippets/common/module-aliases-config.vite.js.mdx
new file mode 100644
index 000000000000..f87d528689d3
--- /dev/null
+++ b/docs/snippets/common/module-aliases-config.vite.js.mdx
@@ -0,0 +1,25 @@
+```js
+// .storybook/main.js
+
+export default {
+ // Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
+ framework: '@storybook/your-framework',
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ viteFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve?.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
+ }
+ }
+
+ return config;
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/common/module-aliases-config.vite.ts.mdx b/docs/snippets/common/module-aliases-config.vite.ts.mdx
new file mode 100644
index 000000000000..57f3e02b20ce
--- /dev/null
+++ b/docs/snippets/common/module-aliases-config.vite.ts.mdx
@@ -0,0 +1,29 @@
+```ts
+// .storybook/main.ts
+
+// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
+import type { StorybookConfig } from '@storybook/your-framework';
+
+const config: StorybookConfig = {
+ framework: '@storybook/your-framework',
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ viteFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve?.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
+ }
+ }
+
+ return config;
+ },
+};
+
+export default config;
+```
\ No newline at end of file
diff --git a/docs/snippets/common/module-aliases-config.webpack.js.mdx b/docs/snippets/common/module-aliases-config.webpack.js.mdx
new file mode 100644
index 000000000000..b3b0b44c05e9
--- /dev/null
+++ b/docs/snippets/common/module-aliases-config.webpack.js.mdx
@@ -0,0 +1,25 @@
+```js
+// .storybook/main.js
+
+export default {
+ // Replace your-framework with the framework you are using (e.g., nextjs, vue3-vite)
+ framework: '@storybook/your-framework',
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ webpackFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api$': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
+ }
+ }
+
+ return config;
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/common/module-aliases-config.webpack.ts.mdx b/docs/snippets/common/module-aliases-config.webpack.ts.mdx
new file mode 100644
index 000000000000..327295007df6
--- /dev/null
+++ b/docs/snippets/common/module-aliases-config.webpack.ts.mdx
@@ -0,0 +1,29 @@
+```ts
+// .storybook/main.ts
+
+// Replace your-framework with the framework you are using (e.g., nextjs, vue3-vite)
+import type { StorybookConfig } from '@storybook/your-framework';
+
+const config: StorybookConfig = {
+ framework: '@storybook/your-framework',
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ webpackFinal: async (config) => {
+ if (config.resolve) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ // 👇 External module
+ 'lodash': require.resolve('./lodash.mock'),
+ // 👇 Internal modules
+ '@/api$': path.resolve(__dirname, "./api.mock.ts"),
+ '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
+ '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
+ '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
+ }
+ }
+
+ return config;
+ },
+};
+
+export default config;
+```
\ No newline at end of file
diff --git a/docs/snippets/common/msw-addon-install.npm.js.mdx b/docs/snippets/common/msw-addon-install.npm.js.mdx
new file mode 100644
index 000000000000..99de2511c94e
--- /dev/null
+++ b/docs/snippets/common/msw-addon-install.npm.js.mdx
@@ -0,0 +1,3 @@
+```sh
+npm install msw msw-storybook-addon --save-dev
+```
diff --git a/docs/snippets/common/msw-addon-install.pnpm.js.mdx b/docs/snippets/common/msw-addon-install.pnpm.js.mdx
new file mode 100644
index 000000000000..19ac40a7f0be
--- /dev/null
+++ b/docs/snippets/common/msw-addon-install.pnpm.js.mdx
@@ -0,0 +1,3 @@
+```sh
+pnpm add msw msw-storybook-addon --save-dev
+```
diff --git a/docs/snippets/common/msw-addon-install.yarn.js.mdx b/docs/snippets/common/msw-addon-install.yarn.js.mdx
new file mode 100644
index 000000000000..ac4eacd25629
--- /dev/null
+++ b/docs/snippets/common/msw-addon-install.yarn.js.mdx
@@ -0,0 +1,3 @@
+```sh
+yarn add msw msw-storybook-addon --save-dev
+```
diff --git a/docs/snippets/common/msw-install.npm.js.mdx b/docs/snippets/common/msw-install.npm.js.mdx
deleted file mode 100644
index cf6e91f4e0aa..000000000000
--- a/docs/snippets/common/msw-install.npm.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```sh
-npm install msw --save-dev
-```
diff --git a/docs/snippets/common/msw-install.pnpm.js.mdx b/docs/snippets/common/msw-install.pnpm.js.mdx
deleted file mode 100644
index c4332df90cd0..000000000000
--- a/docs/snippets/common/msw-install.pnpm.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```sh
-pnpm add msw --save-dev
-```
diff --git a/docs/snippets/common/msw-install.yarn.js.mdx b/docs/snippets/common/msw-install.yarn.js.mdx
deleted file mode 100644
index 6a8d76b5ff15..000000000000
--- a/docs/snippets/common/msw-install.yarn.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```sh
-yarn add msw --save-dev
-```
diff --git a/docs/snippets/common/storybook-test-fn-mock-spy.js.mdx b/docs/snippets/common/storybook-test-fn-mock-spy.js.mdx
new file mode 100644
index 000000000000..e9d26c1b36c4
--- /dev/null
+++ b/docs/snippets/common/storybook-test-fn-mock-spy.js.mdx
@@ -0,0 +1,31 @@
+```js
+// NoteUI.stories.js
+import { expect, userEvent, within } from '@storybook/test';
+
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+export default {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+};
+
+const notes = createNotes();
+
+export const SaveFlow = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/common/storybook-test-fn-mock-spy.ts-4-9.mdx b/docs/snippets/common/storybook-test-fn-mock-spy.ts-4-9.mdx
new file mode 100644
index 000000000000..11e9829b6ff1
--- /dev/null
+++ b/docs/snippets/common/storybook-test-fn-mock-spy.ts-4-9.mdx
@@ -0,0 +1,37 @@
+```ts
+// NoteUI.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import { expect, userEvent, within } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+const meta = {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/common/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/common/storybook-test-fn-mock-spy.ts.mdx
new file mode 100644
index 000000000000..3462e5a2cb7b
--- /dev/null
+++ b/docs/snippets/common/storybook-test-fn-mock-spy.ts.mdx
@@ -0,0 +1,37 @@
+```ts
+// NoteUI.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import { expect, userEvent, within } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+import NoteUI from './note-ui';
+
+const meta: Meta = {
+ title: 'Mocked/NoteUI',
+ component: NoteUI,
+};
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/common/storybook-test-mock-file-example.ts.mdx b/docs/snippets/common/storybook-test-mock-file-example.ts.mdx
new file mode 100644
index 000000000000..de9c01a2b4cc
--- /dev/null
+++ b/docs/snippets/common/storybook-test-mock-file-example.ts.mdx
@@ -0,0 +1,8 @@
+```ts
+// lib/session.mock.ts
+import { fn } from '@storybook/test';
+import * as actual from './session';
+
+export * from './session';
+export const getUserFromSession = fn(actual.getUserFromSession);
+```
diff --git a/docs/snippets/common/storybook-test-mock-return-value.js.mdx b/docs/snippets/common/storybook-test-mock-return-value.js.mdx
new file mode 100644
index 000000000000..4ace89df1aef
--- /dev/null
+++ b/docs/snippets/common/storybook-test-mock-return-value.js.mdx
@@ -0,0 +1,18 @@
+```js
+// Page.stories.js
+import { fn } from '@storybook/test';
+
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+export default {
+ component: Page,
+};
+
+export const Default = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/snippets/common/storybook-test-mock-return-value.ts-4-9.mdx b/docs/snippets/common/storybook-test-mock-return-value.ts-4-9.mdx
new file mode 100644
index 000000000000..3d2e8543d052
--- /dev/null
+++ b/docs/snippets/common/storybook-test-mock-return-value.ts-4-9.mdx
@@ -0,0 +1,25 @@
+```ts
+// Page.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import { fn } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta = {
+ component: Page,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/snippets/common/storybook-test-mock-return-value.ts.mdx b/docs/snippets/common/storybook-test-mock-return-value.ts.mdx
new file mode 100644
index 000000000000..be9a8e343194
--- /dev/null
+++ b/docs/snippets/common/storybook-test-mock-return-value.ts.mdx
@@ -0,0 +1,24 @@
+```ts
+// Page.stories.ts
+// Replace your-renderer with the name of your renderer (e.g. react, vue3)
+import type { Meta, StoryObj } from '@storybook/your-renderer';
+import { fn } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+import { Page } from './Page';
+
+const meta: Meta = {
+ component: Page,
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/snippets/common/subpath-imports-config.json.mdx b/docs/snippets/common/subpath-imports-config.json.mdx
new file mode 100644
index 000000000000..63615b528eb4
--- /dev/null
+++ b/docs/snippets/common/subpath-imports-config.json.mdx
@@ -0,0 +1,24 @@
+```jsonc
+// package.json
+{
+ "imports": {
+ "#api": {
+ "storybook": "./api.mock.ts",
+ "default": "./api.ts"
+ },
+ "#app/actions": {
+ "storybook": "./app/actions.mock.ts",
+ "default": "./app/actions.ts"
+ },
+ "#lib/session": {
+ "storybook": "./lib/session.mock.ts",
+ "default": "./lib/session.ts"
+ },
+ "#lib/db": {
+ "storybook": "./lib/db.mock.ts",
+ "default": "./lib/db.ts"
+ },
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
\ No newline at end of file
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
new file mode 100644
index 000000000000..edb87b826e1d
--- /dev/null
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
@@ -0,0 +1,18 @@
+```js
+// Button.stories.js
+import { Button } from './Button';
+
+export default {
+ component: Button,
+};
+
+// Wrapped in light theme
+export const Default = {};
+
+// Wrapped in dark theme
+export const Dark = {
+ parameters: {
+ theme: 'dark',
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
new file mode 100644
index 000000000000..f866555fc6dd
--- /dev/null
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
@@ -0,0 +1,23 @@
+```ts
+// Button.stories.ts
+import { Meta, StoryObj } from '@storybook/react';
+
+import { Button } from './Button';
+
+const meta = {
+ component: Button,
+} satisfies Meta;
+export default meta;
+
+type Story = StoryObj;
+
+// Wrapped in light theme
+export const Default: Story = {};
+
+// Wrapped in dark theme
+export const Dark: Story = {
+ parameters: {
+ theme: 'dark',
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
new file mode 100644
index 000000000000..e59575a2e767
--- /dev/null
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
@@ -0,0 +1,23 @@
+```ts
+// Button.stories.ts
+import { Meta, StoryObj } from '@storybook/react';
+
+import { Button } from './Button';
+
+const meta: Meta = {
+ component: Button,
+};
+export default meta;
+
+type Story = StoryObj;
+
+// Wrapped in light theme
+export const Default: Story = {};
+
+// Wrapped in dark theme
+export const Dark: Story = {
+ parameters: {
+ theme: 'dark',
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/decorator-parameterized-in-preview.js.mdx b/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
new file mode 100644
index 000000000000..b4c07fce70f2
--- /dev/null
+++ b/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
@@ -0,0 +1,28 @@
+```ts
+// .storybook/preview.tsx
+import React from 'react';
+
+export default {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ return (
+ // Your page layout is probably a little more complex than this ;)
+
+ );
+ case 'page-mobile':
+ return (
+
+ );
+ case default:
+ // In the default case, don't apply a layout
+ return ;
+ }
+ },
+ ],
+};
+```
diff --git a/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
new file mode 100644
index 000000000000..c46809be6b7f
--- /dev/null
+++ b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,31 @@
+```ts
+// .storybook/preview.tsx
+import React from 'react';
+import { Preview } from '@storybook/react';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ return (
+ // Your page layout is probably a little more complex than this ;)
+
+ );
+ case 'page-mobile':
+ return (
+
+ );
+ case default:
+ // In the default case, don't apply a layout
+ return ;
+ }
+ },
+ ],
+};
+
+export default preview;
+```
diff --git a/docs/snippets/react/mock-provider-in-preview.js.mdx b/docs/snippets/react/mock-provider-in-preview.js.mdx
new file mode 100644
index 000000000000..95bd0fa2d455
--- /dev/null
+++ b/docs/snippets/react/mock-provider-in-preview.js.mdx
@@ -0,0 +1,23 @@
+```jsx
+// .storybook/preview.jsx
+import React from 'react';
+import { ThemeProvider } from 'styled-components';
+
+// themes = { light, dark }
+import * as themes from '../src/themes';
+
+export default {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading the theme value from parameters
+ const { theme = 'light' } = parameters;
+ return (
+
+
+
+ );
+ },
+ ],
+};
+```
diff --git a/docs/snippets/react/mock-provider-in-preview.ts.mdx b/docs/snippets/react/mock-provider-in-preview.ts.mdx
new file mode 100644
index 000000000000..60f8635927db
--- /dev/null
+++ b/docs/snippets/react/mock-provider-in-preview.ts.mdx
@@ -0,0 +1,26 @@
+```tsx
+// .storybook/preview.tsx
+import React from 'react';
+import { Preview } from '@storybook/react';
+import { ThemeProvider } from 'styled-components';
+
+// themes = { light, dark }
+import * as themes from '../src/themes';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading the theme value from parameters
+ const { theme = 'light' } = parameters;
+ return (
+
+
+
+ );
+ },
+ ],
+};
+
+export default preview;
+```
diff --git a/docs/snippets/react/nextjs-cache-mock.js.mdx b/docs/snippets/react/nextjs-cache-mock.js.mdx
new file mode 100644
index 000000000000..b8e00ac1b414
--- /dev/null
+++ b/docs/snippets/react/nextjs-cache-mock.js.mdx
@@ -0,0 +1,22 @@
+```js
+// MyForm.stories.js
+import { expect, userEvent, within } from '@storybook/test';
+import { revalidatePath } from '@storybook/nextjs/cache.mock';
+
+import MyForm from './my-form';
+
+export default {
+ component: MyForm,
+};
+
+export const Submitted = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+
+ const submitButton = canvas.getByRole('button', { name: /submit/i });
+ await userEvent.click(saveButton);
+ // 👇 Use any mock assertions on the function
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
new file mode 100644
index 000000000000..23877ecdef4d
--- /dev/null
+++ b/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import { expect, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { revalidatePath } from '@storybook/nextjs/cache.mock';
+
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Submitted: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+
+ const submitButton = canvas.getByRole('button', { name: /submit/i });
+ await userEvent.click(saveButton);
+ // 👇 Use any mock assertions on the function
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-cache-mock.ts.mdx b/docs/snippets/react/nextjs-cache-mock.ts.mdx
new file mode 100644
index 000000000000..6f5872224ee5
--- /dev/null
+++ b/docs/snippets/react/nextjs-cache-mock.ts.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import { expect, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { revalidatePath } from '@storybook/nextjs/cache.mock';
+
+import MyForm from './my-form';
+
+const meta: Meta = {
+ component: MyForm,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Submitted: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+
+ const submitButton = canvas.getByRole('button', { name: /submit/i });
+ await userEvent.click(saveButton);
+ // 👇 Use any mock assertions on the function
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-headers-mock.js.mdx b/docs/snippets/react/nextjs-headers-mock.js.mdx
new file mode 100644
index 000000000000..1fed8238ceeb
--- /dev/null
+++ b/docs/snippets/react/nextjs-headers-mock.js.mdx
@@ -0,0 +1,26 @@
+```js
+// MyForm.stories.js
+import { expect, userEvent, within } from '@storybook/test';
+import { cookies, headers } from '@storybook/nextjs/headers.mock';
+
+import MyForm from './my-form';
+
+export default {
+ component: MyForm,
+};
+
+export const LoggedInEurope = {
+ async beforeEach() {
+ // 👇 Set mock cookies and headers ahead of rendering
+ cookies().set('username', 'Sol');
+ headers().set('timezone', 'Central European Summer Time');
+ },
+ async play() {
+ // 👇 Assert that your component called the mocks
+ await expect(cookies().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('username');
+ await expect(headers().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('timezone');
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
new file mode 100644
index 000000000000..d82d73152165
--- /dev/null
+++ b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
@@ -0,0 +1,32 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { cookies, headers } from '@storybook/nextjs/headers.mock';
+
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const LoggedInEurope: Story = {
+ async beforeEach() {
+ // 👇 Set mock cookies and headers ahead of rendering
+ cookies().set('username', 'Sol');
+ headers().set('timezone', 'Central European Summer Time');
+ },
+ async play() {
+ // 👇 Assert that your component called the mocks
+ await expect(cookies().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('username');
+ await expect(headers().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('timezone');
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/nextjs-headers-mock.ts.mdx b/docs/snippets/react/nextjs-headers-mock.ts.mdx
new file mode 100644
index 000000000000..eb6fa6aebed3
--- /dev/null
+++ b/docs/snippets/react/nextjs-headers-mock.ts.mdx
@@ -0,0 +1,32 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { cookies, headers } from '@storybook/nextjs/headers.mock';
+
+import MyForm from './my-form';
+
+const meta: Meta = {
+ component: MyForm,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const LoggedInEurope: Story = {
+ async beforeEach() {
+ // 👇 Set mock cookies and headers ahead of rendering
+ cookies().set('username', 'Sol');
+ headers().set('timezone', 'Central European Summer Time');
+ },
+ async play() {
+ // 👇 Assert that your component called the mocks
+ await expect(cookies().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('username');
+ await expect(headers().get).toHaveBeenCalledOnce();
+ await expect(cookies().get).toHaveBeenCalledWith('timezone');
+ },
+};
+```
\ No newline at end of file
diff --git a/docs/snippets/react/nextjs-navigation-mock.js.mdx b/docs/snippets/react/nextjs-navigation-mock.js.mdx
new file mode 100644
index 000000000000..ed5bf26a99f5
--- /dev/null
+++ b/docs/snippets/react/nextjs-navigation-mock.js.mdx
@@ -0,0 +1,35 @@
+```js
+// MyForm.stories.js
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
+
+import MyForm from './my-form';
+
+export default {
+ component: MyForm,
+ parameters: {
+ nextjs: {
+ // 👇 As in the Next.js application, next/navigation only works using App Router
+ appDirectory: true,
+ },
+ },
+};
+
+export const Unauthenticated = {
+ async play() => {
+ // 👇 Assert that your component called redirect()
+ await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
+ },
+};
+
+export const GoBack = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
new file mode 100644
index 000000000000..d2fcddf10693
--- /dev/null
+++ b/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
@@ -0,0 +1,41 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
+
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+ parameters: {
+ nextjs: {
+ // 👇 As in the Next.js application, next/navigation only works using App Router
+ appDirectory: true,
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Unauthenticated: Story = {
+ async play() => {
+ // 👇 Assert that your component called redirect()
+ await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
+ },
+};
+
+export const GoBack: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-navigation-mock.ts.mdx b/docs/snippets/react/nextjs-navigation-mock.ts.mdx
new file mode 100644
index 000000000000..0a549357c831
--- /dev/null
+++ b/docs/snippets/react/nextjs-navigation-mock.ts.mdx
@@ -0,0 +1,41 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
+
+import MyForm from './my-form';
+
+const meta: Meta = {
+ component: MyForm,
+ parameters: {
+ nextjs: {
+ // 👇 As in the Next.js application, next/navigation only works using App Router
+ appDirectory: true,
+ },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Unauthenticated: Story = {
+ async play() => {
+ // 👇 Assert that your component called redirect()
+ await expect(redirect).toHaveBeenCalledWith('/login', 'replace');
+ },
+};
+
+export const GoBack: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-router-mock.js.mdx b/docs/snippets/react/nextjs-router-mock.js.mdx
new file mode 100644
index 000000000000..7b23deb663ea
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.js.mdx
@@ -0,0 +1,23 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/router.mock';
+
+import MyForm from './my-form';
+
+export default {
+ component: MyForm,
+};
+
+export const GoBack = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
new file mode 100644
index 000000000000..c04067374c85
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/router.mock';
+
+import MyForm from './my-form';
+
+const meta = {
+ component: MyForm,
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const GoBack: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/react/nextjs-router-mock.ts.mdx b/docs/snippets/react/nextjs-router-mock.ts.mdx
new file mode 100644
index 000000000000..0f55cee0394d
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.ts.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+import { Meta, StoryObj } from '@storybook/react';
+// 👇 Must use this import path to have mocks typed correctly
+import { getRouter } from '@storybook/nextjs/router.mock';
+
+import MyForm from './my-form';
+
+const meta: Meta = {
+ component: MyForm,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const GoBack: Story = {
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const backBtn = await canvas.findByText('Go back');
+
+ await userEvent.click(backBtn);
+ // 👇 Assert that your component called back()
+ await expect(getRouter().back).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx b/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
new file mode 100644
index 000000000000..a53bd72eacfd
--- /dev/null
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
@@ -0,0 +1,25 @@
+```ts
+// Page.stories.ts
+import MockDate from 'mockdate';
+
+import { getUserFromSession } from '#api/session.mock';
+
+export default {
+ component: 'my-page',
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+};
+
+export const Default = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
diff --git a/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
new file mode 100644
index 000000000000..e422865271d5
--- /dev/null
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
@@ -0,0 +1,30 @@
+```ts
+// Page.stories.ts
+import { Meta, StoryObj } from '@storybook/web-components';
+import MockDate from 'mockdate';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+
+const meta: Meta = {
+ component: 'my-page',
+ // 👇 Set the value of Date for every story in the file
+ async beforeEach() {
+ MockDate.set('2024-02-14');
+
+ // 👇 Reset the Date after each story
+ return () => {
+ MockDate.reset();
+ };
+ },
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async play({ canvasElement }) {
+ // ... This will run with the mocked Date
+ },
+};
+```
diff --git a/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx b/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
new file mode 100644
index 000000000000..94bf53639ea6
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
@@ -0,0 +1,30 @@
+```ts
+// NoteUI.stories.ts
+import { expect, userEvent, within } from '@storybook/test';
+
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+
+export default {
+ title: 'Mocked/NoteUI',
+ component: 'note-ui',
+};
+
+const notes = createNotes();
+
+export const SaveFlow = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
new file mode 100644
index 000000000000..7f58a6acdcd3
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
@@ -0,0 +1,35 @@
+```ts
+// NoteUI.stories.ts
+import { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { saveNote } from '#app/actions.mock';
+import { createNotes } from '#mocks/notes';
+
+const meta: Meta = {
+ title: 'Mocked/NoteUI',
+ component: 'note-ui',
+};
+export default meta;
+
+type Story = StoryObj;
+
+const notes = createNotes();
+
+export const SaveFlow: Story = {
+ name: 'Save Flow ▶',
+ args: {
+ isEditing: true,
+ note: notes[0],
+ },
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+
+ const saveButton = canvas.getByRole('menuitem', { name: /done/i });
+ await userEvent.click(saveButton);
+ // 👇 This is the mock function, so you can assert its behavior
+ await expect(saveNote).toHaveBeenCalled();
+ },
+};
+```
diff --git a/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx b/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
new file mode 100644
index 000000000000..84131094426b
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
@@ -0,0 +1,15 @@
+```ts
+// Page.stories.ts
+import { getUserFromSession } from '#api/session.mock';
+
+export default {
+ component: 'my-page',
+};
+
+export const Default = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/snippets/web-components/storybook-test-mock-return-value.ts.mdx b/docs/snippets/web-components/storybook-test-mock-return-value.ts.mdx
new file mode 100644
index 000000000000..70566e35f583
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-mock-return-value.ts.mdx
@@ -0,0 +1,21 @@
+```ts
+// Page.stories.ts
+import type { Meta, StoryObj } from '@storybook/web-components';
+
+// 👇 Must use this import path to have mocks typed correctly
+import { getUserFromSession } from '#api/session.mock';
+
+const meta: Meta = {
+ component: 'my-page',
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ async beforeEach() {
+ // 👇 Set the return value for the getUserFromSession function
+ getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
+ },
+};
+```
diff --git a/docs/writing-stories/decorators.md b/docs/writing-stories/decorators.md
index f673ed393c04..b7a0870201cf 100644
--- a/docs/writing-stories/decorators.md
+++ b/docs/writing-stories/decorators.md
@@ -49,36 +49,19 @@ The second argument to a decorator function is the **story context** which conta
- `parameters`- the story's static metadata, most commonly used to control Storybook's behavior of features and addons.
- `viewMode`- Storybook's current active window (e.g., canvas, docs).
-This context can be used to adjust the behavior of your decorator based on the story's arguments or other metadata. For example, you could create a decorator that wraps the story in a layout, unless the `noLayout` parameter is set to `true`:
-
-
-
-```ts
-// .storybook/preview.js
-import React from 'react';
-import { Preview } from '@storybook/react';
-
-import { Layout } from '../components/Layout';
-
-const preview: Preview = {
- decorators: [
- // 👇 Defining the decorator in the preview file applies it to all stories
- (Story, { parameters }) => {
- // 👇 Make it configurable by reading from parameters
- const { noLayout } = parameters;
- return noLayout ? (
-
- ) : (
-
-
-
- );
- },
- ],
-};
-
-export default preview;
-```
+This context can be used to adjust the behavior of your decorator based on the story's arguments or other metadata. For example, you could create a decorator that allows you to optionally apply a layout to the story, by defining `parameters.pageLayout = 'page'` (or `'page-mobile'`):
+:
+
+
+
+
+
+
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index fa1452b2222d..d310feaa6e0d 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -20,16 +20,15 @@ To mock a module, create a file with the same name and in the same directory as
Here's an example of a mock file for a module named `session`:
-
+
-```ts
-// lib/session.mock.ts
-import { fn } from '@storybook/test';
-import * as actual from './session';
+
-export * from './session';
-export const getUserFromSession = fn(actual.getUserFromSession);
-```
+
### Mock files for external modules
@@ -59,32 +58,15 @@ The recommended method for mocking modules is to use [subpath imports](https://n
To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for four internal modules:
-
-
-```json
-// package.json
-{
- "imports": {
- "#api": {
- "storybook": "./api.mock.ts",
- "default": "./api.ts"
- },
- "#app/actions": {
- "storybook": "./app/actions.mock.ts",
- "default": "./app/actions.ts"
- },
- "#lib/session": {
- "storybook": "./lib/session.mock.ts",
- "default": "./lib/session.ts"
- },
- "#lib/db": {
- "storybook": "./lib/db.mock.ts",
- "default": "./lib/db.ts"
- },
- "#*": ["./*", "./*.ts", "./*.tsx"]
- }
-}
-```
+
+
+
+
+
There are two aspects to this configuration worth noting:
@@ -108,47 +90,18 @@ import { getUserFromSession } from '#lib/session';
If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories.
-
+
-```ts
-// .storybook/main.ts
-viteFinal: async (config) => {
- if (config.resolve) {
- config.resolve.alias = {
- ...config.resolve?.alias,
- // 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
- // 👇 Internal modules
- '@/api': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
- }
-
- return config;
-},
-```
+
-```ts
-// .storybook/main.ts
-webpackFinal: async (config) => {
- if (config.resolve) {
- config.resolve.alias = {
- ...config.resolve.alias,
- // 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
- // 👇 Internal modules
- '@/api$': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
- }
-
- return config;
-},
-```
+
## Using mocked modules in stories
@@ -156,30 +109,19 @@ When you use the `fn` utility to mock a module, you create full [Vitest mock fun
Here, we define `beforeEach` on a story (which will run before the story is rendered) to set a mocked return value for the `getUserFromSession` function used by the Page component:
-
-
-```ts
-// Page.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
-import { fn } from '@storybook/test';
-
-import { getUserFromSession } from '#api/session.mock';
-import { Page } from './Page';
+
-const meta: Meta = {
- component: Page,
-};
-export default meta;
+
-type Story = StoryObj;
-
-export const Default: Story = {
- async beforeEach() {
- // 👇 Set the return value for the getUserFromSession function
- getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' });
- },
-};
-```
+
@@ -193,43 +135,19 @@ The `fn` utility also spies on the original module's functions, which you can us
For example, this story checks that the `saveNote` function was called when the user clicks the save button:
-
+
-```ts
-// NoteUI.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
-import { expect, userEvent, within } from '@storybook/test';
-
-import { saveNote } from '#app/actions.mock';
-import { createNotes } from '#mocks/notes';
-import NoteUI from './note-ui';
-
-const meta = {
- title: 'Mocked/NoteUI',
- component: NoteUI,
-} satisfies Meta;
-export default meta;
-
-type Story = StoryObj;
-
-const notes = createNotes();
-
-export const SaveFlow: Story = {
- name: 'Save Flow ▶',
- args: {
- isEditing: true,
- note: notes[0],
- },
- play: async ({ canvasElement, step }) => {
- const canvas = within(canvasElement);
-
- const saveButton = canvas.getByRole('menuitem', { name: /done/i });
- await userEvent.click(saveButton);
- // 👇 This is the mock function, so you can assert its behavior
- await expect(saveNote).toHaveBeenCalled();
- },
-};
-```
+
+
+
### Setting up and cleaning up
@@ -245,31 +163,16 @@ It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Stor
Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts.
-
+
-```ts
-// Page.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
-import MockDate from 'mockdate';
-
-import { getUserFromSession } from '#api/session.mock';
-import { Page } from './Page';
-
-const meta: Meta = {
- component: Page,
- // 👇 Set the current date for every story in the file
- async beforeEach() {
- MockDate.set('2024-02-14');
-
- // 👇 Reset the date after each test
- return () => {
- MockDate.reset();
- };
- },
-};
-export default meta;
-
-type Story = StoryObj;
-
-export const Default: Story = {};
-```
+
+
+
diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md
index d27ae6766483..fc71ce849eae 100644
--- a/docs/writing-stories/mocking-network-requests.md
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -8,21 +8,21 @@ The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings thi
## Set up the MSW addon
-First, if necessary, run this command to install MSW:
+First, if necessary, run this command to install MSW and the MSW addon:
-Then generate the service worker file necessary for MSW to work:
+If you're not already using MSW, generate the service worker file necessary for MSW to work:
@@ -45,14 +45,6 @@ Angular projects will likely need to adjust the command to save the mock service
-Next, install and register the MSW addon:
-
-
-
-```sh
-npx storybook@latest add msw-storybook-addon
-```
-
Then ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file (in `/public`, by default):
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index 0671ff37532e..a20ea662373f 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -33,10 +33,20 @@ Components can receive data or configuration from context providers. For example
+
+
+Note the file extension above (`.tsx` or `.jsx`). You may need to adjust your preview file's extension to allow use of JSX, depending on your project's settings.
+
+
+
+
+
For another example, reference the [Screens](https://storybook.js.org/tutorials/intro-to-storybook/react/en/screen/) chapter of the Intro to Storybook tutorial, where we mock a Redux provider with mock data.
+
+
## Configuring the mock provider
@@ -49,59 +59,29 @@ For a better way, with much less repetition, you can use the [decorator function
For example, we can adjust the decorator from above to read from `parameters.theme` to determine which theme to provide:
-
-
-```ts
-// .storybook/preview.ts
-import React from 'react';
-import { Preview } from '@storybook/react';
-import { ThemeProvider } from 'styled-components';
-
-const preview: Preview = {
- decorators: [
- // 👇 Defining the decorator in the preview file applies it to all stories
- (Story, { parameters }) => {
- // 👇 Make it configurable by reading the theme value from parameters
- const theme = parameters.theme || 'default';
- return (
-
-
-
- );
- },
- ],
-};
-
-export default preview;
-```
-
-Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator:
-
-
+
-```ts
-// Button.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
+
-import { Button } from './Button';
+
-const meta: Meta = {
- component: Button,
-};
-export default meta;
+Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator:
-type Story = StoryObj;
+
-// Wrapped in default theme
-export const Default: Story = {};
+
-// Wrapped in dark theme
-export const Dark: Story = {
- parameters: {
- theme: 'dark',
- },
-};
-```
+
This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable.
diff --git a/docs/writing-tests/interaction-testing.md b/docs/writing-tests/interaction-testing.md
index 4a28b9258abf..cb1b3eea6918 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -104,38 +104,19 @@ It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Stor
Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts.
-
-
-```ts
-// Page.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
-import MockDate from 'mockdate';
-
-import { getUserFromSession } from '#api/session.mock';
-import { Page } from './Page';
-
-const meta: Meta = {
- component: Page,
- // 👇 Set the current date for every story in the file
- async beforeEach() {
- MockDate.set('2024-02-14');
-
- // 👇 Reset the date after each test
- return () => {
- MockDate.reset();
- };
- },
-};
-export default meta;
-
-type Story = StoryObj;
-
-export const Default: Story = {
- async play({ canvasElement }) {
- // ... This will run with the mocked date
- },
-};
-```
+
+
+
+
+
### API for user-events
@@ -205,43 +186,19 @@ If your component depends on modules that are imported into the component file,
You can then import the mocked module (which has all of the helpful methods of a [Vitest mocked function](https://vitest.dev/api/mock.html)) into your story and use it to assert on the behavior of your component:
-
-
-```ts
-// NoteUI.stories.ts|tsx
-import { Meta, StoryObj } from '@storybook/react';
-import { expect, userEvent, within } from '@storybook/test';
-
-import { saveNote } from '#app/actions.mock';
-import { createNotes } from '#mocks/notes';
-import NoteUI from './note-ui';
-
-const meta = {
- title: 'Mocked/NoteUI',
- component: NoteUI,
-} satisfies Meta;
-export default meta;
-
-type Story = StoryObj;
-
-const notes = createNotes();
-
-export const SaveFlow: Story = {
- name: 'Save Flow ▶',
- args: {
- isEditing: true,
- note: notes[0],
- },
- play: async ({ canvasElement, step }) => {
- const canvas = within(canvasElement);
-
- const saveButton = canvas.getByRole('menuitem', { name: /done/i });
- await userEvent.click(saveButton);
- // 👇 This is the mock function, so you can assert its behavior
- await expect(saveNote).toHaveBeenCalled();
- },
-};
-```
+
+
+
+
+
### Interactive debugger
From 6bf05a1707c802db2a92518b34ad67ecb9cd84ba Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Tue, 30 Apr 2024 22:31:12 -0600
Subject: [PATCH 41/46] Remove unnecessary snippets
---
docs/configure/images-and-assets.md | 4 +--
.../angular/app-story-with-mock.ts.mdx | 33 -----------------
.../common/app-story-with-mock.js.mdx | 28 ---------------
.../common/app-story-with-mock.ts-4-9.mdx | 35 -------------------
.../common/app-story-with-mock.ts.mdx | 34 ------------------
.../common/isomorphic-fetch-mock.js.mdx | 25 -------------
.../storybook-main-with-mock-decorator.js.mdx | 12 -------
...orybook-main-with-single-static-dir.js.mdx | 10 ------
...orybook-main-with-single-static-dir.ts.mdx | 14 --------
.../common/storybook-msw-install.npm.js.mdx | 3 --
.../common/storybook-msw-install.pnpm.js.mdx | 3 --
.../common/storybook-msw-install.yarn.js.mdx | 3 --
...orybook-preview-with-mock-decorator.js.mdx | 8 -----
...orybook-preview-with-mock-decorator.ts.mdx | 14 --------
.../web-components/app-story-with-mock.js.mdx | 26 --------------
.../web-components/app-story-with-mock.ts.mdx | 31 ----------------
.../portable-stories.test.tsx.snap | 2 +-
17 files changed, 3 insertions(+), 282 deletions(-)
delete mode 100644 docs/snippets/angular/app-story-with-mock.ts.mdx
delete mode 100644 docs/snippets/common/app-story-with-mock.js.mdx
delete mode 100644 docs/snippets/common/app-story-with-mock.ts-4-9.mdx
delete mode 100644 docs/snippets/common/app-story-with-mock.ts.mdx
delete mode 100644 docs/snippets/common/isomorphic-fetch-mock.js.mdx
delete mode 100644 docs/snippets/common/storybook-main-with-mock-decorator.js.mdx
delete mode 100644 docs/snippets/common/storybook-main-with-single-static-dir.js.mdx
delete mode 100644 docs/snippets/common/storybook-main-with-single-static-dir.ts.mdx
delete mode 100644 docs/snippets/common/storybook-msw-install.npm.js.mdx
delete mode 100644 docs/snippets/common/storybook-msw-install.pnpm.js.mdx
delete mode 100644 docs/snippets/common/storybook-msw-install.yarn.js.mdx
delete mode 100644 docs/snippets/common/storybook-preview-with-mock-decorator.js.mdx
delete mode 100644 docs/snippets/common/storybook-preview-with-mock-decorator.ts.mdx
delete mode 100644 docs/snippets/web-components/app-story-with-mock.js.mdx
delete mode 100644 docs/snippets/web-components/app-story-with-mock.ts.mdx
diff --git a/docs/configure/images-and-assets.md b/docs/configure/images-and-assets.md
index 77ffed4231db..02f32cac96ca 100644
--- a/docs/configure/images-and-assets.md
+++ b/docs/configure/images-and-assets.md
@@ -42,8 +42,8 @@ Configure a directory (or a list of directories) where your assets live when sta
diff --git a/docs/snippets/angular/app-story-with-mock.ts.mdx b/docs/snippets/angular/app-story-with-mock.ts.mdx
deleted file mode 100644
index 79fd261a6ef5..000000000000
--- a/docs/snippets/angular/app-story-with-mock.ts.mdx
+++ /dev/null
@@ -1,33 +0,0 @@
-```ts
-// Button.stories.ts
-
-import type { Meta, StoryObj } from '@storybook/angular';
-
-import { App } from './app.component';
-
-const meta: Meta = {
- component: App,
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Success: Story = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/common/app-story-with-mock.js.mdx b/docs/snippets/common/app-story-with-mock.js.mdx
deleted file mode 100644
index 5e9815db826f..000000000000
--- a/docs/snippets/common/app-story-with-mock.js.mdx
+++ /dev/null
@@ -1,28 +0,0 @@
-```js
-// App.stories.js|jsx
-
-import App from './App';
-
-export default {
- component: App,
-};
-
-export const Success = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/common/app-story-with-mock.ts-4-9.mdx b/docs/snippets/common/app-story-with-mock.ts-4-9.mdx
deleted file mode 100644
index 14d0a19c7719..000000000000
--- a/docs/snippets/common/app-story-with-mock.ts-4-9.mdx
+++ /dev/null
@@ -1,35 +0,0 @@
-```ts
-// App.stories.ts|tsx
-
-// Replace your-framework with the name of your framework
-import type { Meta, StoryObj } from '@storybook/your-framework';
-
-import App from './App';
-
-const meta = {
- component: App,
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Success: Story = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/common/app-story-with-mock.ts.mdx b/docs/snippets/common/app-story-with-mock.ts.mdx
deleted file mode 100644
index dfaa74bf679b..000000000000
--- a/docs/snippets/common/app-story-with-mock.ts.mdx
+++ /dev/null
@@ -1,34 +0,0 @@
-```ts
-// App.stories.ts|tsx
-
-// Replace your-framework with the name of your framework
-import type { Meta, StoryObj } from '@storybook/your-framework';
-
-import App from './App';
-
-const meta: Meta = {
- component: App,
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Success: Story = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/common/isomorphic-fetch-mock.js.mdx b/docs/snippets/common/isomorphic-fetch-mock.js.mdx
deleted file mode 100644
index 7ad937afc843..000000000000
--- a/docs/snippets/common/isomorphic-fetch-mock.js.mdx
+++ /dev/null
@@ -1,25 +0,0 @@
-```js
-// __mocks__/isomorphic-fetch.js
-
-// Your fetch implementation to be added to ./storybook/main.js.
-// In your webpackFinal configuration object.
-
-let nextJson;
-export default async function fetch() {
- if (nextJson) {
- return {
- json: () => nextJson,
- };
- }
- nextJson = null;
-}
-
-// The decorator to be used in ./storybook/preview to apply the mock to all stories
-
-export function decorator(story, { parameters }) {
- if (parameters && parameters.fetch) {
- nextJson = parameters.fetch.json;
- }
- return story();
-}
-```
diff --git a/docs/snippets/common/storybook-main-with-mock-decorator.js.mdx b/docs/snippets/common/storybook-main-with-mock-decorator.js.mdx
deleted file mode 100644
index 0e8418371b6d..000000000000
--- a/docs/snippets/common/storybook-main-with-mock-decorator.js.mdx
+++ /dev/null
@@ -1,12 +0,0 @@
-```js
-// .storybook/main.js
-
-export default {
- // Your Storybook configuration
-
- webpackFinal: async (config) => {
- config.resolve.alias['isomorphic-fetch'] = require.resolve('../__mocks__/isomorphic-fetch.js');
- return config;
- },
-};
-```
diff --git a/docs/snippets/common/storybook-main-with-single-static-dir.js.mdx b/docs/snippets/common/storybook-main-with-single-static-dir.js.mdx
deleted file mode 100644
index 50898439ff1c..000000000000
--- a/docs/snippets/common/storybook-main-with-single-static-dir.js.mdx
+++ /dev/null
@@ -1,10 +0,0 @@
-```js
-// .storybook/main.js
-
-export default {
- // Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
- framework: '@storybook/your-framework',
- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
- staticDirs: ['../public'], //👈 Configures the static asset folder in Storybook
-};
-```
diff --git a/docs/snippets/common/storybook-main-with-single-static-dir.ts.mdx b/docs/snippets/common/storybook-main-with-single-static-dir.ts.mdx
deleted file mode 100644
index 779ebee771f3..000000000000
--- a/docs/snippets/common/storybook-main-with-single-static-dir.ts.mdx
+++ /dev/null
@@ -1,14 +0,0 @@
-```ts
-// .storybook/main.ts
-
-// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
-import type { StorybookConfig } from '@storybook/your-framework';
-
-const config: StorybookConfig = {
- framework: '@storybook/your-framework',
- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
- staticDirs: ['../public'], //👈 Configures the static asset folder in Storybook
-};
-
-export default config;
-```
diff --git a/docs/snippets/common/storybook-msw-install.npm.js.mdx b/docs/snippets/common/storybook-msw-install.npm.js.mdx
deleted file mode 100644
index 9f0ed4aeed6f..000000000000
--- a/docs/snippets/common/storybook-msw-install.npm.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```shell
-npm install msw msw-storybook-addon --save-dev
-```
diff --git a/docs/snippets/common/storybook-msw-install.pnpm.js.mdx b/docs/snippets/common/storybook-msw-install.pnpm.js.mdx
deleted file mode 100644
index d18a7d9d344f..000000000000
--- a/docs/snippets/common/storybook-msw-install.pnpm.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```shell
-pnpm add --save-dev msw msw-storybook-addon
-```
diff --git a/docs/snippets/common/storybook-msw-install.yarn.js.mdx b/docs/snippets/common/storybook-msw-install.yarn.js.mdx
deleted file mode 100644
index 95e75837af80..000000000000
--- a/docs/snippets/common/storybook-msw-install.yarn.js.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-```shell
-yarn add --dev msw msw-storybook-addon
-```
diff --git a/docs/snippets/common/storybook-preview-with-mock-decorator.js.mdx b/docs/snippets/common/storybook-preview-with-mock-decorator.js.mdx
deleted file mode 100644
index 54d5c621f5b1..000000000000
--- a/docs/snippets/common/storybook-preview-with-mock-decorator.js.mdx
+++ /dev/null
@@ -1,8 +0,0 @@
-```js
-// .storybook/preview.js
-
-import { decorator } from '../__mocks__/isomorphic-fetch';
-
-// Add the decorator to all stories
-export default { decorators: [decorator] };
-```
diff --git a/docs/snippets/common/storybook-preview-with-mock-decorator.ts.mdx b/docs/snippets/common/storybook-preview-with-mock-decorator.ts.mdx
deleted file mode 100644
index fb6cdb2c68f7..000000000000
--- a/docs/snippets/common/storybook-preview-with-mock-decorator.ts.mdx
+++ /dev/null
@@ -1,14 +0,0 @@
-```ts
-// .storybook/preview.ts
-
-// Replace your-framework with the framework you are using (e.g., react, vue3)
-import { Preview } from '@storybook/your-framework';
-
-import { decorator } from '../__mocks__/isomorphic-fetch';
-
-const preview: Preview = {
- decorators: [decorator],
-};
-
-export default preview;
-```
diff --git a/docs/snippets/web-components/app-story-with-mock.js.mdx b/docs/snippets/web-components/app-story-with-mock.js.mdx
deleted file mode 100644
index 2d5b9a38f63a..000000000000
--- a/docs/snippets/web-components/app-story-with-mock.js.mdx
+++ /dev/null
@@ -1,26 +0,0 @@
-```js
-// App.stories.js
-
-export default {
- component: 'demo-app',
-};
-
-export const Success = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/docs/snippets/web-components/app-story-with-mock.ts.mdx b/docs/snippets/web-components/app-story-with-mock.ts.mdx
deleted file mode 100644
index e8de25628bd7..000000000000
--- a/docs/snippets/web-components/app-story-with-mock.ts.mdx
+++ /dev/null
@@ -1,31 +0,0 @@
-```ts
-// App.stories.ts
-
-import type { Meta, StoryObj } from '@storybook/web-components';
-
-const meta: Meta = {
- component: 'demo-app',
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Success: Story = {
- parameters: {
- fetch: {
- json: {
- JavaScript: 3390991,
- 'C++': 44974,
- TypeScript: 15530,
- CoffeeScript: 12253,
- Python: 9383,
- C: 5341,
- Shell: 5115,
- HTML: 3420,
- CSS: 3171,
- Makefile: 189,
- },
- },
- },
-};
-```
diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
index 2f6747019333..5e046e9b7a0b 100644
--- a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
+++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/__snapshots__/portable-stories.test.tsx.snap
@@ -1010,7 +1010,7 @@ exports[`renders nextHeaderStories stories renders Default 1`] = `
- firstName=Jane; ; lastName=Doe;
+ firstName=Jane; lastName=Doe
Date: Tue, 30 Apr 2024 22:32:01 -0600
Subject: [PATCH 42/46] Prettify snippets
---
.../angular/before-each-in-meta-mock-date.ts.mdx | 2 +-
.../angular/storybook-test-fn-mock-spy.ts.mdx | 2 +-
.../common/module-aliases-config.vite.js.mdx | 14 +++++++-------
.../common/module-aliases-config.vite.ts.mdx | 14 +++++++-------
.../common/module-aliases-config.webpack.js.mdx | 14 +++++++-------
.../common/module-aliases-config.webpack.ts.mdx | 14 +++++++-------
.../common/subpath-imports-config.json.mdx | 2 +-
...igure-mock-provider-with-story-parameter.js.mdx | 2 +-
...e-mock-provider-with-story-parameter.ts-4-9.mdx | 2 +-
...igure-mock-provider-with-story-parameter.ts.mdx | 2 +-
docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx | 2 +-
docs/snippets/react/nextjs-headers-mock.ts.mdx | 2 +-
12 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
index 4d79269e028d..96a21706b8a8 100644
--- a/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
+++ b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
@@ -28,4 +28,4 @@ export const Default: Story = {
// ... This will run with the mocked Date
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
index b0aac94d7473..3a106e1eb1bf 100644
--- a/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
+++ b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
@@ -33,4 +33,4 @@ export const SaveFlow: Story = {
await expect(saveNote).toHaveBeenCalled();
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/module-aliases-config.vite.js.mdx b/docs/snippets/common/module-aliases-config.vite.js.mdx
index f87d528689d3..925cee51558a 100644
--- a/docs/snippets/common/module-aliases-config.vite.js.mdx
+++ b/docs/snippets/common/module-aliases-config.vite.js.mdx
@@ -10,16 +10,16 @@ export default {
config.resolve.alias = {
...config.resolve?.alias,
// 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
+ lodash: require.resolve('./lodash.mock'),
// 👇 Internal modules
- '@/api': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
+ '@/api': path.resolve(__dirname, './api.mock.ts'),
+ '@/app/actions': path.resolve(__dirname, './app/actions.mock.ts'),
+ '@/lib/session': path.resolve(__dirname, './lib/session.mock.ts'),
+ '@/lib/db': path.resolve(__dirname, './lib/db.mock.ts'),
+ };
}
return config;
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/module-aliases-config.vite.ts.mdx b/docs/snippets/common/module-aliases-config.vite.ts.mdx
index 57f3e02b20ce..08fd48c2535d 100644
--- a/docs/snippets/common/module-aliases-config.vite.ts.mdx
+++ b/docs/snippets/common/module-aliases-config.vite.ts.mdx
@@ -12,13 +12,13 @@ const config: StorybookConfig = {
config.resolve.alias = {
...config.resolve?.alias,
// 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
+ lodash: require.resolve('./lodash.mock'),
// 👇 Internal modules
- '@/api': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
+ '@/api': path.resolve(__dirname, './api.mock.ts'),
+ '@/app/actions': path.resolve(__dirname, './app/actions.mock.ts'),
+ '@/lib/session': path.resolve(__dirname, './lib/session.mock.ts'),
+ '@/lib/db': path.resolve(__dirname, './lib/db.mock.ts'),
+ };
}
return config;
@@ -26,4 +26,4 @@ const config: StorybookConfig = {
};
export default config;
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/module-aliases-config.webpack.js.mdx b/docs/snippets/common/module-aliases-config.webpack.js.mdx
index b3b0b44c05e9..6dd020023cce 100644
--- a/docs/snippets/common/module-aliases-config.webpack.js.mdx
+++ b/docs/snippets/common/module-aliases-config.webpack.js.mdx
@@ -10,16 +10,16 @@ export default {
config.resolve.alias = {
...config.resolve.alias,
// 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
+ lodash: require.resolve('./lodash.mock'),
// 👇 Internal modules
- '@/api$': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
+ '@/api$': path.resolve(__dirname, './api.mock.ts'),
+ '@/app/actions$': path.resolve(__dirname, './app/actions.mock.ts'),
+ '@/lib/session$': path.resolve(__dirname, './lib/session.mock.ts'),
+ '@/lib/db$': path.resolve(__dirname, './lib/db.mock.ts'),
+ };
}
return config;
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/module-aliases-config.webpack.ts.mdx b/docs/snippets/common/module-aliases-config.webpack.ts.mdx
index 327295007df6..3dcbe14be825 100644
--- a/docs/snippets/common/module-aliases-config.webpack.ts.mdx
+++ b/docs/snippets/common/module-aliases-config.webpack.ts.mdx
@@ -12,13 +12,13 @@ const config: StorybookConfig = {
config.resolve.alias = {
...config.resolve.alias,
// 👇 External module
- 'lodash': require.resolve('./lodash.mock'),
+ lodash: require.resolve('./lodash.mock'),
// 👇 Internal modules
- '@/api$': path.resolve(__dirname, "./api.mock.ts"),
- '@/app/actions$': path.resolve(__dirname, "./app/actions.mock.ts"),
- '@/lib/session$': path.resolve(__dirname, "./lib/session.mock.ts"),
- '@/lib/db$': path.resolve(__dirname, "./lib/db.mock.ts"),
- }
+ '@/api$': path.resolve(__dirname, './api.mock.ts'),
+ '@/app/actions$': path.resolve(__dirname, './app/actions.mock.ts'),
+ '@/lib/session$': path.resolve(__dirname, './lib/session.mock.ts'),
+ '@/lib/db$': path.resolve(__dirname, './lib/db.mock.ts'),
+ };
}
return config;
@@ -26,4 +26,4 @@ const config: StorybookConfig = {
};
export default config;
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/common/subpath-imports-config.json.mdx b/docs/snippets/common/subpath-imports-config.json.mdx
index 63615b528eb4..02dca8840caf 100644
--- a/docs/snippets/common/subpath-imports-config.json.mdx
+++ b/docs/snippets/common/subpath-imports-config.json.mdx
@@ -21,4 +21,4 @@
"#*": ["./*", "./*.ts", "./*.tsx"]
}
}
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
index edb87b826e1d..b0d07bb65756 100644
--- a/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
@@ -15,4 +15,4 @@ export const Dark = {
theme: 'dark',
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
index f866555fc6dd..85b212f60d46 100644
--- a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
@@ -20,4 +20,4 @@ export const Dark: Story = {
theme: 'dark',
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
index e59575a2e767..aa76044dfcf5 100644
--- a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
@@ -20,4 +20,4 @@ export const Dark: Story = {
theme: 'dark',
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
index d82d73152165..f05ba295d6d1 100644
--- a/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
+++ b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
@@ -29,4 +29,4 @@ export const LoggedInEurope: Story = {
await expect(cookies().get).toHaveBeenCalledWith('timezone');
},
};
-```
\ No newline at end of file
+```
diff --git a/docs/snippets/react/nextjs-headers-mock.ts.mdx b/docs/snippets/react/nextjs-headers-mock.ts.mdx
index eb6fa6aebed3..1d9d94eda281 100644
--- a/docs/snippets/react/nextjs-headers-mock.ts.mdx
+++ b/docs/snippets/react/nextjs-headers-mock.ts.mdx
@@ -29,4 +29,4 @@ export const LoggedInEurope: Story = {
await expect(cookies().get).toHaveBeenCalledWith('timezone');
},
};
-```
\ No newline at end of file
+```
From 7be08e32cfa8e64a754cb70baaac8a107415aa3b Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Wed, 1 May 2024 09:44:47 -0600
Subject: [PATCH 43/46] Address feedback
- Make nextjs module alias usage more clear
- Update msw graphql snippets
- Add preact support for mocking providers
- Prose improvements
---
docs/api/portable-stories-jest.md | 2 +-
.../angular/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/snippets/react/document-screen-with-graphql.ts.mdx | 2 +-
docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../react/msw-addon-configure-handlers-graphql.ts-4-9.mdx | 2 +-
docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/snippets/solid/document-screen-with-graphql.ts.mdx | 2 +-
.../snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx | 2 +-
.../snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx | 2 +-
.../vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx | 2 +-
docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx | 2 +-
docs/writing-stories/mocking-modules.md | 2 +-
docs/writing-stories/mocking-providers.md | 2 +-
15 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/docs/api/portable-stories-jest.md b/docs/api/portable-stories-jest.md
index cdc92d922e26..8c016e28f1cd 100644
--- a/docs/api/portable-stories-jest.md
+++ b/docs/api/portable-stories-jest.md
@@ -30,7 +30,7 @@ Normally, Storybok composes a story and its [annotations](#annotations) automati
- Configure the [`next/jest.js` transformer](https://nextjs.org/docs/pages/building-your-application/testing/jest#manual-setup), which will handle all of the necessary Next.js configuration for you.
- Import [`composeStories`](#composestories) or [`composeStory`](#composestory) from the `@storybook/nextjs` package (e.g. `import { composeStories } from '@storybook/nextjs'`).
-- Set up [internal module aliases](../get-started/nextjs.md#storybooknextjsexport-mocks) to be able to mock and assert on them.
+- Set up [internal module aliases](../get-started/nextjs.md#storybooknextjsexport-mocks) to ensure the framework configuration works correctly and to be able to mock and assert on them.
diff --git a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
index f35bcd16f033..23599c6494de 100644
--- a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
@@ -59,7 +59,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/react/document-screen-with-graphql.ts.mdx b/docs/snippets/react/document-screen-with-graphql.ts.mdx
index 61a6cc29365e..56f5a24cdcc0 100644
--- a/docs/snippets/react/document-screen-with-graphql.ts.mdx
+++ b/docs/snippets/react/document-screen-with-graphql.ts.mdx
@@ -31,7 +31,7 @@ const AllInfoQuery = gql`
`;
interface Data {
- AllInfo: {
+ allInfo: {
user: {
userID: number;
name: string;
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
index 0138bd74920c..b7964e2d4925 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
@@ -63,7 +63,7 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 468c1866b98c..ab5786361bba 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -67,7 +67,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
}
diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
index 281441a95612..4ffb616b0c66 100644
--- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx
@@ -67,7 +67,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
}
diff --git a/docs/snippets/solid/document-screen-with-graphql.ts.mdx b/docs/snippets/solid/document-screen-with-graphql.ts.mdx
index 039bb7c64424..7b7f7f234c3d 100644
--- a/docs/snippets/solid/document-screen-with-graphql.ts.mdx
+++ b/docs/snippets/solid/document-screen-with-graphql.ts.mdx
@@ -32,7 +32,7 @@ const AllInfoQuery = gql`
`;
interface Data {
- AllInfo: {
+ allInfo: {
user: {
userID: number;
name: string;
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
index bbe3531e64b0..6c9974f74e53 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx
@@ -42,7 +42,7 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 91e56485b7a5..a099095c28f8 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -47,7 +47,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
index cacc4af7674a..cc9fef5e7cb9 100644
--- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx
@@ -47,7 +47,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
index 3e13c859cc70..137d1b9a37c4 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -45,7 +45,7 @@ export const MockedSuccess = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
index 0efceab56d6a..701fc8dddd30 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx
@@ -50,7 +50,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
index fd23daacece7..da7551e9474c 100644
--- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -50,7 +50,7 @@ export const MockedSuccess: Story = {
graphql.query('AllInfoQuery', () => {
return new HttpResponse.json({
data: {
- AllInfo: {
+ allInfo: {
...TestData,
},
},
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index d310feaa6e0d..5e3964578783 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -72,7 +72,7 @@ There are two aspects to this configuration worth noting:
First, **each subpath must begin with `#`**, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
-Second, note the **`storybook` and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
+Second, note the **`storybook` and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module when loaded in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
With the package configuration in place, you can then update your component file to use the subpath import:
diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md
index a20ea662373f..f44d148d0d55 100644
--- a/docs/writing-stories/mocking-providers.md
+++ b/docs/writing-stories/mocking-providers.md
@@ -2,7 +2,7 @@
title: Mocking providers
---
-export const SUPPORTED_RENDERERS = ['react', 'solid'];
+export const SUPPORTED_RENDERERS = ['preact', 'react', 'solid'];
From a9d9bfa464b667a7d96f798dc322958b57290c40 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Thu, 2 May 2024 22:44:18 -0600
Subject: [PATCH 44/46] Update decorator snippets
---
.../decorator-parameterized-in-preview.ts.mdx | 27 +++++++++++++++++
.../decorator-parameterized-in-preview.js.mdx | 4 +--
.../decorator-parameterized-in-preview.ts.mdx | 2 +-
.../decorator-parameterized-in-preview.js.mdx | 26 ++++++++++++++++
.../decorator-parameterized-in-preview.ts.mdx | 30 +++++++++++++++++++
.../decorator-parameterized-in-preview.js.mdx | 22 ++++++++++++++
.../decorator-parameterized-in-preview.ts.mdx | 26 ++++++++++++++++
docs/writing-stories/decorators.md | 5 ++++
8 files changed, 139 insertions(+), 3 deletions(-)
create mode 100644 docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
create mode 100644 docs/snippets/solid/decorator-parameterized-in-preview.js.mdx
create mode 100644 docs/snippets/solid/decorator-parameterized-in-preview.ts.mdx
create mode 100644 docs/snippets/vue/decorator-parameterized-in-preview.js.mdx
create mode 100644 docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
diff --git a/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
new file mode 100644
index 000000000000..9de5b88b4bef
--- /dev/null
+++ b/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,27 @@
+```tsx
+// .storybook/preview.tsx
+import type { Preview } from '@storybook/angular';
+import { componentWrapperDecorator } from '@storybook/angular';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ componentWrapperDecorator((story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ // Your page layout is probably a little more complex than this ;)
+ return `
${story}
`;
+ case 'page-mobile':
+ return `
${story}
`;
+ case default:
+ // In the default case, don't apply a layout
+ return story;
+ }
+ }),
+ ],
+};
+
+export default preview;
+```
diff --git a/docs/snippets/react/decorator-parameterized-in-preview.js.mdx b/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
index b4c07fce70f2..123175807b18 100644
--- a/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
+++ b/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// .storybook/preview.tsx
+```js
+// .storybook/preview.jsx
import React from 'react';
export default {
diff --git a/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
index c46809be6b7f..010256148114 100644
--- a/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
+++ b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
@@ -1,4 +1,4 @@
-```ts
+```tsx
// .storybook/preview.tsx
import React from 'react';
import { Preview } from '@storybook/react';
diff --git a/docs/snippets/solid/decorator-parameterized-in-preview.js.mdx b/docs/snippets/solid/decorator-parameterized-in-preview.js.mdx
new file mode 100644
index 000000000000..c2dfdbf375bf
--- /dev/null
+++ b/docs/snippets/solid/decorator-parameterized-in-preview.js.mdx
@@ -0,0 +1,26 @@
+```jsx
+// .storybook/preview.jsx
+export default {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ return (
+ // Your page layout is probably a little more complex than this ;)
+
+ );
+ case 'page-mobile':
+ return (
+
+ );
+ case default:
+ // In the default case, don't apply a layout
+ return ;
+ }
+ },
+ ],
+};
+```
diff --git a/docs/snippets/solid/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/solid/decorator-parameterized-in-preview.ts.mdx
new file mode 100644
index 000000000000..763ad428596d
--- /dev/null
+++ b/docs/snippets/solid/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,30 @@
+```tsx
+// .storybook/preview.tsx
+import { Preview } from 'storybook-solidjs';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (Story, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ return (
+ // Your page layout is probably a little more complex than this ;)
+
+ );
+ case 'page-mobile':
+ return (
+
+ );
+ case default:
+ // In the default case, don't apply a layout
+ return ;
+ }
+ },
+ ],
+};
+
+export default preview;
+```
diff --git a/docs/snippets/vue/decorator-parameterized-in-preview.js.mdx b/docs/snippets/vue/decorator-parameterized-in-preview.js.mdx
new file mode 100644
index 000000000000..be2c87827ef0
--- /dev/null
+++ b/docs/snippets/vue/decorator-parameterized-in-preview.js.mdx
@@ -0,0 +1,22 @@
+```js
+// .storybook/preview.js
+export default {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (_, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ // Your page layout is probably a little more complex than this ;)
+ return { template: '
' };
+ case 'page-mobile':
+ return { template: '
' };
+ case default:
+ // In the default case, don't apply a layout
+ return { template: '' };
+ }
+ },
+ ],
+};
+```
diff --git a/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
new file mode 100644
index 000000000000..da802b69a66f
--- /dev/null
+++ b/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,26 @@
+```ts
+// .storybook/preview.ts
+import { Preview } from '@storybook/vue3';
+
+const preview: Preview = {
+ decorators: [
+ // 👇 Defining the decorator in the preview file applies it to all stories
+ (_, { parameters }) => {
+ // 👇 Make it configurable by reading from parameters
+ const { pageLayout } = parameters;
+ switch (pageLayout) {
+ case 'page':
+ // Your page layout is probably a little more complex than this ;)
+ return { template: '
' };
+ case 'page-mobile':
+ return { template: '
' };
+ case default:
+ // In the default case, don't apply a layout
+ return { template: '' };
+ }
+ },
+ ],
+};
+
+export default preview;
+```
diff --git a/docs/writing-stories/decorators.md b/docs/writing-stories/decorators.md
index b7a0870201cf..7ada612fe8b8 100644
--- a/docs/writing-stories/decorators.md
+++ b/docs/writing-stories/decorators.md
@@ -58,6 +58,11 @@ This context can be used to adjust the behavior of your decorator based on the s
paths={[
'react/decorator-parameterized-in-preview.js.mdx',
'react/decorator-parameterized-in-preview.ts.mdx',
+ 'vue/decorator-parameterized-in-preview.js.mdx',
+ 'vue/decorator-parameterized-in-preview.ts.mdx',
+ 'angular/decorator-parameterized-in-preview.ts.mdx',
+ 'solid/decorator-parameterized-in-preview.js.mdx',
+ 'solid/decorator-parameterized-in-preview.ts.mdx',
]}
/>
From d2f7fe8448db742b6a058af20aa1822852cdf34d Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Fri, 3 May 2024 11:13:53 -0600
Subject: [PATCH 45/46] Address feedback
- Snippet consistency
- Prose tweaks
---
docs/api/parameters.md | 4 +---
docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx | 2 +-
.../angular/decorator-parameterized-in-preview.ts.mdx | 4 ++--
docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx | 2 +-
docs/snippets/common/subpath-imports-config.json.mdx | 4 +++-
.../configure-mock-provider-with-story-parameter.ts-4-9.mdx | 2 +-
.../react/configure-mock-provider-with-story-parameter.ts.mdx | 2 +-
docs/snippets/react/decorator-parameterized-in-preview.ts.mdx | 3 ++-
docs/snippets/react/mock-provider-in-preview.js.mdx | 1 +
docs/snippets/react/mock-provider-in-preview.ts.mdx | 3 ++-
docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx | 2 +-
docs/snippets/react/nextjs-cache-mock.ts.mdx | 2 +-
docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx | 2 +-
docs/snippets/react/nextjs-headers-mock.ts.mdx | 2 +-
docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx | 2 +-
docs/snippets/react/nextjs-navigation-mock.ts.mdx | 2 +-
docs/snippets/react/nextjs-router-mock.js.mdx | 4 ++--
docs/snippets/react/nextjs-router-mock.ts-4-9.mdx | 2 +-
docs/snippets/react/nextjs-router-mock.ts.mdx | 2 +-
docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx | 2 +-
.../web-components/before-each-in-meta-mock-date.js.mdx | 4 ++--
.../web-components/before-each-in-meta-mock-date.ts.mdx | 2 +-
.../snippets/web-components/storybook-test-fn-mock-spy.js.mdx | 4 ++--
.../snippets/web-components/storybook-test-fn-mock-spy.ts.mdx | 2 +-
.../web-components/storybook-test-mock-return-value.js.mdx | 4 ++--
docs/writing-stories/mocking-modules.md | 2 +-
docs/writing-tests/interaction-testing.md | 2 +-
27 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index be7b02cc4b83..f70fa3395962 100644
--- a/docs/api/parameters.md
+++ b/docs/api/parameters.md
@@ -184,9 +184,7 @@ Type: `boolean`
Default: `false`
-Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when unhandled errors are thrown during rendering or playing.
-
-Unhandled errors might cause false positive assertions. They can be ignored by setting this parameter to `true`.
+Unhandled errors might cause false positive assertions. Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when unhandled errors are thrown during execution.
---
diff --git a/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
index 96a21706b8a8..9e3dcefaf5cb 100644
--- a/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
+++ b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
@@ -1,6 +1,6 @@
```ts
// Page.stories.ts
-import { Meta, StoryObj } from '@storybook/angular';
+import type { Meta, StoryObj } from '@storybook/angular';
import MockDate from 'mockdate';
// 👇 Must use this import path to have mocks typed correctly
diff --git a/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
index 9de5b88b4bef..2200726832fe 100644
--- a/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
+++ b/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
@@ -1,5 +1,5 @@
-```tsx
-// .storybook/preview.tsx
+```ts
+// .storybook/preview.ts
import type { Preview } from '@storybook/angular';
import { componentWrapperDecorator } from '@storybook/angular';
diff --git a/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
index 3a106e1eb1bf..d11b6fa261cb 100644
--- a/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
+++ b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
@@ -1,6 +1,6 @@
```ts
// NoteUI.stories.ts
-import { Meta, StoryObj } from '@storybook/angular';
+import type { Meta, StoryObj } from '@storybook/angular';
import { expect, userEvent, within } from '@storybook/test';
// 👇 Must use this import path to have mocks typed correctly
diff --git a/docs/snippets/common/subpath-imports-config.json.mdx b/docs/snippets/common/subpath-imports-config.json.mdx
index 02dca8840caf..5450344f1178 100644
--- a/docs/snippets/common/subpath-imports-config.json.mdx
+++ b/docs/snippets/common/subpath-imports-config.json.mdx
@@ -3,6 +3,7 @@
{
"imports": {
"#api": {
+ // storybook condition applies to Storybook
"storybook": "./api.mock.ts",
"default": "./api.ts"
},
@@ -15,7 +16,8 @@
"default": "./lib/session.ts"
},
"#lib/db": {
- "storybook": "./lib/db.mock.ts",
+ // test condition applies to test environments *and* Storybook
+ "test": "./lib/db.mock.ts",
"default": "./lib/db.ts"
},
"#*": ["./*", "./*.ts", "./*.tsx"]
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
index 85b212f60d46..d164216f0994 100644
--- a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts-4-9.mdx
@@ -1,6 +1,6 @@
```ts
// Button.stories.ts
-import { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
diff --git a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
index aa76044dfcf5..0668941b5fdd 100644
--- a/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
@@ -1,6 +1,6 @@
```ts
// Button.stories.ts
-import { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
diff --git a/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
index 010256148114..9475198340d6 100644
--- a/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
+++ b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
@@ -1,7 +1,8 @@
```tsx
// .storybook/preview.tsx
import React from 'react';
-import { Preview } from '@storybook/react';
+
+import type { Preview } from '@storybook/react';
const preview: Preview = {
decorators: [
diff --git a/docs/snippets/react/mock-provider-in-preview.js.mdx b/docs/snippets/react/mock-provider-in-preview.js.mdx
index 95bd0fa2d455..e16bdcef8bcf 100644
--- a/docs/snippets/react/mock-provider-in-preview.js.mdx
+++ b/docs/snippets/react/mock-provider-in-preview.js.mdx
@@ -1,6 +1,7 @@
```jsx
// .storybook/preview.jsx
import React from 'react';
+
import { ThemeProvider } from 'styled-components';
// themes = { light, dark }
diff --git a/docs/snippets/react/mock-provider-in-preview.ts.mdx b/docs/snippets/react/mock-provider-in-preview.ts.mdx
index 60f8635927db..1dbdcdac969f 100644
--- a/docs/snippets/react/mock-provider-in-preview.ts.mdx
+++ b/docs/snippets/react/mock-provider-in-preview.ts.mdx
@@ -1,7 +1,8 @@
```tsx
// .storybook/preview.tsx
import React from 'react';
-import { Preview } from '@storybook/react';
+
+import type { Preview } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
// themes = { light, dark }
diff --git a/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
index 23877ecdef4d..67a416a6840f 100644
--- a/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
+++ b/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { revalidatePath } from '@storybook/nextjs/cache.mock';
diff --git a/docs/snippets/react/nextjs-cache-mock.ts.mdx b/docs/snippets/react/nextjs-cache-mock.ts.mdx
index 6f5872224ee5..14a2e439f0d5 100644
--- a/docs/snippets/react/nextjs-cache-mock.ts.mdx
+++ b/docs/snippets/react/nextjs-cache-mock.ts.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { revalidatePath } from '@storybook/nextjs/cache.mock';
diff --git a/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
index f05ba295d6d1..681d2efe0607 100644
--- a/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
+++ b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { cookies, headers } from '@storybook/nextjs/headers.mock';
diff --git a/docs/snippets/react/nextjs-headers-mock.ts.mdx b/docs/snippets/react/nextjs-headers-mock.ts.mdx
index 1d9d94eda281..10a25b7ead42 100644
--- a/docs/snippets/react/nextjs-headers-mock.ts.mdx
+++ b/docs/snippets/react/nextjs-headers-mock.ts.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { cookies, headers } from '@storybook/nextjs/headers.mock';
diff --git a/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
index d2fcddf10693..40ca4dce86b8 100644
--- a/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
+++ b/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
diff --git a/docs/snippets/react/nextjs-navigation-mock.ts.mdx b/docs/snippets/react/nextjs-navigation-mock.ts.mdx
index 0a549357c831..11ca27070d6c 100644
--- a/docs/snippets/react/nextjs-navigation-mock.ts.mdx
+++ b/docs/snippets/react/nextjs-navigation-mock.ts.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { redirect, getRouter } from '@storybook/nextjs/navigation.mock';
diff --git a/docs/snippets/react/nextjs-router-mock.js.mdx b/docs/snippets/react/nextjs-router-mock.js.mdx
index 7b23deb663ea..a9a4c1253968 100644
--- a/docs/snippets/react/nextjs-router-mock.js.mdx
+++ b/docs/snippets/react/nextjs-router-mock.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// MyForm.stories.ts
+```js
+// MyForm.stories.js
import { expect, fireEvent, userEvent, within } from '@storybook/test';
// 👇 Must use this import path to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/router.mock';
diff --git a/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx b/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
index c04067374c85..39e05f2ab502 100644
--- a/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
+++ b/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/router.mock';
diff --git a/docs/snippets/react/nextjs-router-mock.ts.mdx b/docs/snippets/react/nextjs-router-mock.ts.mdx
index 0f55cee0394d..8783e346f751 100644
--- a/docs/snippets/react/nextjs-router-mock.ts.mdx
+++ b/docs/snippets/react/nextjs-router-mock.ts.mdx
@@ -1,7 +1,7 @@
```ts
// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, fireEvent, userEvent, within } from '@storybook/test';
-import { Meta, StoryObj } from '@storybook/react';
// 👇 Must use this import path to have mocks typed correctly
import { getRouter } from '@storybook/nextjs/router.mock';
diff --git a/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx b/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
index da802b69a66f..c65f8be884e7 100644
--- a/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
+++ b/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
@@ -1,6 +1,6 @@
```ts
// .storybook/preview.ts
-import { Preview } from '@storybook/vue3';
+import type { Preview } from '@storybook/vue3';
const preview: Preview = {
decorators: [
diff --git a/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx b/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
index a53bd72eacfd..f6607ecc5c45 100644
--- a/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// Page.stories.ts
+```js
+// Page.stories.js
import MockDate from 'mockdate';
import { getUserFromSession } from '#api/session.mock';
diff --git a/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx b/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
index e422865271d5..cee04d87e602 100644
--- a/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
@@ -1,6 +1,6 @@
```ts
// Page.stories.ts
-import { Meta, StoryObj } from '@storybook/web-components';
+import type { Meta, StoryObj } from '@storybook/web-components';
import MockDate from 'mockdate';
// 👇 Must use this import path to have mocks typed correctly
diff --git a/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx b/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
index 94bf53639ea6..c93f3ba6b3ef 100644
--- a/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// NoteUI.stories.ts
+```js
+// NoteUI.stories.js
import { expect, userEvent, within } from '@storybook/test';
import { saveNote } from '#app/actions.mock';
diff --git a/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx b/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
index 7f58a6acdcd3..79ebde78c83a 100644
--- a/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
@@ -1,6 +1,6 @@
```ts
// NoteUI.stories.ts
-import { Meta, StoryObj } from '@storybook/react';
+import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
// 👇 Must use this import path to have mocks typed correctly
diff --git a/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx b/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
index 84131094426b..04c8c9858980 100644
--- a/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
+++ b/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// Page.stories.ts
+```js
+// Page.stories.js
import { getUserFromSession } from '#api/session.mock';
export default {
diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md
index 5e3964578783..a0df47cae700 100644
--- a/docs/writing-stories/mocking-modules.md
+++ b/docs/writing-stories/mocking-modules.md
@@ -72,7 +72,7 @@ There are two aspects to this configuration worth noting:
First, **each subpath must begin with `#`**, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory.
-Second, note the **`storybook` and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module when loaded in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests.
+Second, note the **`storybook`, `test`, and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module when loaded in your project. The `test` condition is also used within Storybook, which allows you to use the same configuration in Storybook and your other tests.
With the package configuration in place, you can then update your component file to use the subpath import:
diff --git a/docs/writing-tests/interaction-testing.md b/docs/writing-tests/interaction-testing.md
index cb1b3eea6918..9c2774d10b3c 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -102,7 +102,7 @@ It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Stor
-Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts.
+Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts.
From dfa82ab4f5adae12f467760b99ab663e4e9a5146 Mon Sep 17 00:00:00 2001
From: Kyle Gach
Date: Mon, 6 May 2024 09:08:36 -0600
Subject: [PATCH 46/46] Address feedback
---
docs/snippets/common/before-each-in-meta-mock-date.js.mdx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/snippets/common/before-each-in-meta-mock-date.js.mdx b/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
index 61151b7a4850..b39e8d8120b0 100644
--- a/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
+++ b/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
@@ -1,5 +1,5 @@
-```ts
-// Page.stories.ts
+```js
+// Page.stories.js
import MockDate from 'mockdate';
import { getUserFromSession } from '#api/session.mock';
@@ -18,7 +18,7 @@ export default {
},
};
-export const Default: Story = {
+export const Default = {
async play({ canvasElement }) {
// ... This will run with the mocked Date
},