diff --git a/docs/api/parameters.md b/docs/api/parameters.md
index 506dc051b3df..f70fa3395962 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`
+
+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.
+
---
### Essential addons
diff --git a/docs/api/portable-stories-jest.md b/docs/api/portable-stories-jest.md
index 3abe9798112c..21e35920ac5f 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 ensure the framework configuration works correctly and to be able to mock and assert on them.
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/configure/story-rendering.md b/docs/configure/story-rendering.md
index fff549e8ef4c..87f8bc8c8d9b 100644
--- a/docs/configure/story-rendering.md
+++ b/docs/configure/story-rendering.md
@@ -2,7 +2,54 @@
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.
+
+
+
+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:
+
+
+
+
+
+
+
+
## Adding to <head>
diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md
index b17cf19661b3..957f58f8bb31 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() {
@@ -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,33 @@ 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):
-
-
+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 = {
+ parameters: {
+ nextjs: {
+ // ๐ Override the default router properties
+ router: {
+ basePath: '/app/',
+ },
+ },
+ },
+ async beforeEach() {
+ // ๐ Manipulate the default router method mocks
+ getRouter().push.mockImplementation(() => {
+ /* ... */
+ });
+ },
+};
+```
## Next.js navigation
@@ -493,43 +471,38 @@ 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):
-
-
+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 = {
+ parameters: {
+ nextjs: {
+ // ๐ Override the default navigation properties
+ navigation: {
+ pathname: '/app/',
+ },
+ },
+ },
+ async beforeEach() {
+ // ๐ Manipulate the default navigation method mocks
+ getRouter().push.mockImplementation(() => {
+ /* ... */
+ });
+ },
+};
+```
## Next.js Head
@@ -618,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": [
@@ -645,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!
@@ -671,6 +644,135 @@ 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.jsx
+// 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 all modules in the project:
+
+```jsonc
+// package.json
+{
+ "imports": {
+ "#*": ["./*", "./*.ts", "./*.tsx"]
+ }
+}
+```
+
+
+
+Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration.
+
+
+
+Which can then be used like this:
+
+```jsx
+// index.jsx
+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.
+
+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`:
+
+
+
+
+
+
+
+#### 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:
+
+
+
+
+
+
+
+
+
+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.
+
+
+
+
+
+
+
## 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.
@@ -696,7 +798,7 @@ module.exports = {
Calls to `getConfig` would return the following object when called within Storybook:
-```json
+```jsonc
// Runtime config
{
"serverRuntimeConfig": {},
@@ -731,7 +833,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": {
@@ -783,6 +885,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 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
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:
@@ -843,7 +951,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",
@@ -885,54 +993,116 @@ 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ย several modules that enableย you to [mock](#mocking-modules) Next.js's internal behavior.
-#### `appDirectory`
+#### `@storybook/nextjs/export-mocks`
-Type: `boolean`
+Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`
-Default: `false`
+`getPackageAliases` is a helper for generating the aliases needed to set up [portable stories](#portable-stories).
-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.
+```ts
+// jest.config.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: './',
+});
+
+const config: Config = {
+ testEnvironment: 'jsdom',
+ // ... rest of Jest config
+ moduleNameMapper: {
+ ...getPackageAliases(), // ๐ Add the utility as mapped module names
+ },
+};
-#### `navigation`
+export default createJestConfig(config);
+```
-Type:
+#### `@storybook/nextjs/cache.mock`
-```ts
-{
- asPath?: string;
- pathname?: string;
- query?: Record;
- segments?: (string | [string, string])[];
-}
-```
+Type: `typeof import('next/cache')`
-Default value:
+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).
-```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:
+#### `@storybook/nextjs/headers.mock`
-```ts
-{
- asPath?: string;
- pathname?: string;
- query?: Record;
-}
-```
+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
-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.
+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:
+
+- **`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.
+
+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`.
+
+
+
+
+
+
+
+#### `@storybook/nextjs/navigation.mock`
+
+Type: `typeof import('next/navigation') & getRouter: () => ReturnType`
+
+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).
+
+
+
+
+
+
+
+#### `@storybook/nextjs/router.mock`
+
+Type: `typeof import('next/router') & getRouter: () => ReturnType`
+
+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).
+
+
+
+
+
+
### Options
@@ -976,6 +1146,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.
+
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/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..9e3dcefaf5cb
--- /dev/null
+++ b/docs/snippets/angular/before-each-in-meta-mock-date.ts.mdx
@@ -0,0 +1,31 @@
+```ts
+// Page.stories.ts
+import type { 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
+ },
+};
+```
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..2200726832fe
--- /dev/null
+++ b/docs/snippets/angular/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,27 @@
+```ts
+// .storybook/preview.ts
+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/angular/documentscreen-story-msw-graphql-query.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx
similarity index 71%
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..23599c6494de 100644
--- a/docs/snippets/angular/documentscreen-story-msw-graphql-query.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 = {
@@ -58,28 +54,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: {
+ allInfo: {
+ ...TestData,
+ },
+ },
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await 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-http.ts.mdx
similarity index 68%
rename from docs/snippets/angular/documentscreen-story-msw-rest-request.ts.mdx
rename to docs/snippets/angular/msw-addon-configure-handlers-http.ts.mdx
index 271ad00de38f..820857b94b48 100644
--- a/docs/snippets/angular/documentscreen-story-msw-rest-request.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';
@@ -41,21 +40,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/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..d11b6fa261cb
--- /dev/null
+++ b/docs/snippets/angular/storybook-test-fn-mock-spy.ts.mdx
@@ -0,0 +1,36 @@
+```ts
+// NoteUI.stories.ts
+import type { 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();
+ },
+};
+```
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/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/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..b39e8d8120b0
--- /dev/null
+++ b/docs/snippets/common/before-each-in-meta-mock-date.js.mdx
@@ -0,0 +1,26 @@
+```js
+// Page.stories.js
+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 = {
+ 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/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/module-aliases-config.vite.js.mdx b/docs/snippets/common/module-aliases-config.vite.js.mdx
new file mode 100644
index 000000000000..925cee51558a
--- /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;
+ },
+};
+```
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..08fd48c2535d
--- /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;
+```
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..6dd020023cce
--- /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;
+ },
+};
+```
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..3dcbe14be825
--- /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;
+```
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..cd2138c50a0f 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,6 @@
```js
// YourPage.stories.js|jsx
-
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
import { DocumentScreen } from './YourPage';
@@ -36,21 +35,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 64%
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..663a9fd0f1f2 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
@@ -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';
@@ -42,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.ts.mdx b/docs/snippets/common/msw-addon-configure-handlers-http.ts.mdx
similarity index 64%
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..26d7ffaae623 100644
--- a/docs/snippets/common/documentscreen-story-msw-rest-request.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';
@@ -42,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/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/msw-addon-initialize.ts.mdx b/docs/snippets/common/msw-addon-initialize.ts.mdx
new file mode 100644
index 000000000000..e0fa098e8490
--- /dev/null
+++ b/docs/snippets/common/msw-addon-initialize.ts.mdx
@@ -0,0 +1,22 @@
+```ts
+// .storybook/preview.ts
+
+// 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';
+
+/*
+ * Initializes MSW
+ * See https://github.com/mswjs/msw-storybook-addon#configuring-msw
+ * to learn how to customize it
+ */
+initialize();
+
+const preview: Preview = {
+ // ... rest of preview configuration
+ loaders: [mswLoader], // ๐ Add the MSW loader to all stories
+};
+
+export default preview;
+```
diff --git a/docs/snippets/common/storybook-msw-install.npm.js.mdx b/docs/snippets/common/msw-addon-install.npm.js.mdx
similarity index 85%
rename from docs/snippets/common/storybook-msw-install.npm.js.mdx
rename to docs/snippets/common/msw-addon-install.npm.js.mdx
index 9f0ed4aeed6f..99de2511c94e 100644
--- a/docs/snippets/common/storybook-msw-install.npm.js.mdx
+++ b/docs/snippets/common/msw-addon-install.npm.js.mdx
@@ -1,3 +1,3 @@
-```shell
+```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/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/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.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-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/common/storybook-preview-register-msw-addon.ts.mdx b/docs/snippets/common/storybook-preview-register-msw-addon.ts.mdx
deleted file mode 100644
index d7c3ba6e0991..000000000000
--- a/docs/snippets/common/storybook-preview-register-msw-addon.ts.mdx
+++ /dev/null
@@ -1,29 +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 { 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();
-
-const preview: Preview = {
- decorators: [mswDecorator],
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
- },
-};
-
-export default preview;
-```
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/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..5450344f1178
--- /dev/null
+++ b/docs/snippets/common/subpath-imports-config.json.mdx
@@ -0,0 +1,26 @@
+```jsonc
+// package.json
+{
+ "imports": {
+ "#api": {
+ // storybook condition applies to Storybook
+ "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": {
+ // 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.js.mdx b/docs/snippets/react/configure-mock-provider-with-story-parameter.js.mdx
new file mode 100644
index 000000000000..b0d07bb65756
--- /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',
+ },
+};
+```
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..d164216f0994
--- /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 type { 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',
+ },
+};
+```
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..0668941b5fdd
--- /dev/null
+++ b/docs/snippets/react/configure-mock-provider-with-story-parameter.ts.mdx
@@ -0,0 +1,23 @@
+```ts
+// Button.stories.ts
+import type { 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',
+ },
+};
+```
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..123175807b18
--- /dev/null
+++ b/docs/snippets/react/decorator-parameterized-in-preview.js.mdx
@@ -0,0 +1,28 @@
+```js
+// .storybook/preview.jsx
+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..9475198340d6
--- /dev/null
+++ b/docs/snippets/react/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,32 @@
+```tsx
+// .storybook/preview.tsx
+import React from 'react';
+
+import type { 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/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/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/mock-provider-in-preview.js.mdx b/docs/snippets/react/mock-provider-in-preview.js.mdx
new file mode 100644
index 000000000000..e16bdcef8bcf
--- /dev/null
+++ b/docs/snippets/react/mock-provider-in-preview.js.mdx
@@ -0,0 +1,24 @@
+```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..1dbdcdac969f
--- /dev/null
+++ b/docs/snippets/react/mock-provider-in-preview.ts.mdx
@@ -0,0 +1,27 @@
+```tsx
+// .storybook/preview.tsx
+import React from 'react';
+
+import type { 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/documentscreen-story-msw-graphql-query.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx
similarity index 68%
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..b7964e2d4925 100644
--- a/docs/snippets/react/documentscreen-story-msw-graphql-query.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';
@@ -60,28 +58,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: {
+ allInfo: {
+ ...TestData,
+ },
+ },
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await 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 70%
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..ab5786361bba 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
@@ -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';
@@ -64,34 +62,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: {
+ allInfo: {
+ ...TestData,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await 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..4ffb616b0c66 100644
--- a/docs/snippets/react/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.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';
@@ -64,34 +62,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: {
+ allInfo: {
+ ...TestData,
+ },
+ }
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
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..67a416a6840f
--- /dev/null
+++ b/docs/snippets/react/nextjs-cache-mock.ts-4-9.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+// ๐ 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..14a2e439f0d5
--- /dev/null
+++ b/docs/snippets/react/nextjs-cache-mock.ts.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+// ๐ 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..681d2efe0607
--- /dev/null
+++ b/docs/snippets/react/nextjs-headers-mock.ts-4-9.mdx
@@ -0,0 +1,32 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+// ๐ 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');
+ },
+};
+```
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..10a25b7ead42
--- /dev/null
+++ b/docs/snippets/react/nextjs-headers-mock.ts.mdx
@@ -0,0 +1,32 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+// ๐ 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');
+ },
+};
+```
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..40ca4dce86b8
--- /dev/null
+++ b/docs/snippets/react/nextjs-navigation-mock.ts-4-9.mdx
@@ -0,0 +1,41 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+// ๐ 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..11ca27070d6c
--- /dev/null
+++ b/docs/snippets/react/nextjs-navigation-mock.ts.mdx
@@ -0,0 +1,41 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, fireEvent, userEvent, within } from '@storybook/test';
+// ๐ 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-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-mock.js.mdx b/docs/snippets/react/nextjs-router-mock.js.mdx
new file mode 100644
index 000000000000..a9a4c1253968
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.js.mdx
@@ -0,0 +1,23 @@
+```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';
+
+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..39e05f2ab502
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.ts-4-9.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+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';
+
+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..8783e346f751
--- /dev/null
+++ b/docs/snippets/react/nextjs-router-mock.ts.mdx
@@ -0,0 +1,28 @@
+```ts
+// MyForm.stories.ts
+import type { Meta, StoryObj } from '@storybook/react';
+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';
+
+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/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;
-```
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 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..c65f8be884e7
--- /dev/null
+++ b/docs/snippets/vue/decorator-parameterized-in-preview.ts.mdx
@@ -0,0 +1,26 @@
+```ts
+// .storybook/preview.ts
+import type { 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/snippets/vue/documentscreen-story-msw-graphql-query.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
similarity index 57%
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..137d1b9a37c4 100644
--- a/docs/snippets/vue/documentscreen-story-msw-graphql-query.js.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx
@@ -1,14 +1,16 @@
```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,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
};
//๐The mocked data that will be used in the story
@@ -36,43 +38,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: {
+ allInfo: {
+ ...TestData,
+ },
+ },
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await 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 61%
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..701fc8dddd30 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
@@ -1,16 +1,18 @@
```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,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
} satisfies Meta;
//๐The mocked data that will be used in the story
@@ -41,43 +43,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: {
+ allInfo: {
+ ...TestData,
+ },
+ },
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await 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 61%
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..da7551e9474c 100644
--- a/docs/snippets/vue/documentscreen-story-msw-graphql-query.ts.mdx
+++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx
@@ -1,16 +1,18 @@
```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,
+ render: () => ({
+ components: { DocumentScreen, WrapperComponent },
+ template: '',
+ }),
};
//๐The mocked data that will be used in the story
@@ -41,43 +43,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: {
+ allInfo: {
+ ...TestData,
+ },
+ },
+ });
+ }),
+ ],
+ },
},
};
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 () => {
+ await delay(800);
+ return new HttpResponse.json({
+ errors: [
+ {
+ message: 'Access denied',
+ },
+ ],
+ });
+ }),
+ ],
+ },
},
};
```
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/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..f6607ecc5c45
--- /dev/null
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.js.mdx
@@ -0,0 +1,25 @@
+```js
+// Page.stories.js
+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..cee04d87e602
--- /dev/null
+++ b/docs/snippets/web-components/before-each-in-meta-mock-date.ts.mdx
@@ -0,0 +1,30 @@
+```ts
+// Page.stories.ts
+import type { 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/documentscreen-story-msw-rest-request.js.mdx b/docs/snippets/web-components/msw-addon-configure-handlers-http.js.mdx
similarity index 63%
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..d1c3cda8bc9f 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
@@ -1,7 +1,6 @@
```js
// YourPage.stories.js
-
-import { rest } from 'msw';
+import { http, HttpResponse, delay } from 'msw';
export default {
component: 'demo-document-screen',
@@ -34,21 +33,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 66%
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..b12fd8f93a41 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
@@ -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',
@@ -39,21 +38,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/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..c93f3ba6b3ef
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.js.mdx
@@ -0,0 +1,30 @@
+```js
+// NoteUI.stories.js
+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..79ebde78c83a
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-fn-mock-spy.ts.mdx
@@ -0,0 +1,35 @@
+```ts
+// NoteUI.stories.ts
+import type { 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..04c8c9858980
--- /dev/null
+++ b/docs/snippets/web-components/storybook-test-mock-return-value.js.mdx
@@ -0,0 +1,15 @@
+```js
+// Page.stories.js
+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/toc.js b/docs/toc.js
index a4d3e85cf6be..c872f84d2aa4 100644
--- a/docs/toc.js
+++ b/docs/toc.js
@@ -141,6 +141,28 @@ module.exports = {
title: 'Naming components and hierarchy',
type: 'link',
},
+ {
+ pathSegment: '',
+ title: 'Mocking data and modules',
+ type: 'menu',
+ children: [
+ {
+ pathSegment: 'mocking-modules',
+ title: 'Modules',
+ type: 'link',
+ },
+ {
+ pathSegment: 'mocking-network-requests',
+ title: 'Network requests',
+ type: 'link',
+ },
+ {
+ pathSegment: 'mocking-providers',
+ title: 'Providers',
+ type: 'link',
+ },
+ ],
+ },
{
pathSegment: 'build-pages-with-storybook',
title: 'Build pages and screens',
diff --git a/docs/writing-stories/build-pages-with-storybook.md b/docs/writing-stories/build-pages-with-storybook.md
index aeb293e09fc8..0563733e0dac 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.
+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 imports](./mocking-modules.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 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 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 providers](./mocking-providers.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 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 8c3160302cf8..7ada612fe8b8 100644
--- a/docs/writing-stories/decorators.md
+++ b/docs/writing-stories/decorators.md
@@ -40,49 +40,43 @@ 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.
+The second argument to a decorator function is the **story context** which contains the properties:
-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:
+- `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).
+- `globals` - Storybook-wide [globals](../essentials/toolbars-and-globals.md#globals). In particular you can use the [toolbars feature](../essentials/toolbars-and-globals.md#global-types-toolbar-annotations) to allow you to change these values using Storybookโs UI.
+- `hooks` - Storybook's API hooks (e.g., useArgs).
+- `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 allows you to optionally apply a layout to the story, by defining `parameters.pageLayout = 'page'` (or `'page-mobile'`):
+:
-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:
-
-- `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).
-- `globals` - Storybook-wide [globals](../essentials/toolbars-and-globals.md#globals). In particular you can use the [toolbars feature](../essentials/toolbars-and-globals.md#global-types-toolbar-annotations) to allow you to change these values using Storybookโs UI.
-- `hooks` - Storybook's API hooks (e.g., useArgs).
-- `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 pattern can also be applied to your own stories. Some of Storybook's supported frameworks already use it (e.g., Vue 3).
+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
new file mode 100644
index 000000000000..a0df47cae700
--- /dev/null
+++ b/docs/writing-stories/mocking-modules.md
@@ -0,0 +1,178 @@
+---
+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 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 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.
+
+Here's an example of a mock file for a module named `session`:
+
+
+
+
+
+
+
+### Mock files for external modules
+
+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
+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:
+
+
+
+
+
+
+
+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`, `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:
+
+```ts
+// AuthButton.ts
+// โ Remove this line
+// import { getUserFromSession } from '../../lib/session';
+// โ Add this line
+import { getUserFromSession } from '#lib/session';
+
+// ... rest of the file
+```
+
+## 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.
+
+
+
+
+
+
+
+## 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.
+
+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:
+
+
+
+
+
+
+
+
+
+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.
+
+
+
+### 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:
+
+
+
+
+
+
+
+### Setting up and cleaning up
+
+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.
+
+
+
+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`](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-network-requests.md b/docs/writing-stories/mocking-network-requests.md
new file mode 100644
index 000000000000..fc71ce849eae
--- /dev/null
+++ b/docs/writing-stories/mocking-network-requests.md
@@ -0,0 +1,172 @@
+---
+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 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.
+
+## Set up the MSW addon
+
+First, if necessary, run this command to install MSW and the MSW addon:
+
+
+
+
+
+
+
+If you're not already using MSW, 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`).
+
+
+
+
+
+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):
+
+
+
+
+
+
+
+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..f44d148d0d55
--- /dev/null
+++ b/docs/writing-stories/mocking-providers.md
@@ -0,0 +1,88 @@
+---
+title: Mocking providers
+---
+
+export const SUPPORTED_RENDERERS = ['preact', '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.
+
+
+
+
+
+
+
+
+
+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
+
+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:
+
+
+
+
+
+
+
+Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator:
+
+
+
+
+
+
+
+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 b949aa284a8a..9c2774d10b3c 100644
--- a/docs/writing-tests/interaction-testing.md
+++ b/docs/writing-tests/interaction-testing.md
@@ -90,6 +90,34 @@ 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 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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts.
+
+
+
+
+
+
+
### 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 +180,26 @@ 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:
+
+
+
+
+
+
+
### 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.