From 1efccbe46433eea26e92065b4e198614efba1926 Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Mon, 15 Apr 2024 12:24:59 -0600 Subject: [PATCH 01/46] WIP --- ...ch-ts.mdx => document-screen-fetch.ts.mdx} | 0 docs/toc.js | 5 + docs/writing-stories/mocks.md | 309 ++++++++++++++++++ 3 files changed, 314 insertions(+) rename docs/snippets/react/{document-screen-fetch-ts.mdx => document-screen-fetch.ts.mdx} (100%) create mode 100644 docs/writing-stories/mocks.md diff --git a/docs/snippets/react/document-screen-fetch-ts.mdx b/docs/snippets/react/document-screen-fetch.ts.mdx similarity index 100% rename from docs/snippets/react/document-screen-fetch-ts.mdx rename to docs/snippets/react/document-screen-fetch.ts.mdx diff --git a/docs/toc.js b/docs/toc.js index a4d3e85cf6be..1a8530bbc6bd 100644 --- a/docs/toc.js +++ b/docs/toc.js @@ -141,6 +141,11 @@ module.exports = { title: 'Naming components and hierarchy', type: 'link', }, + { + pathSegment: 'mocks', + title: 'Mocking data and modules', + type: 'link', + }, { pathSegment: 'build-pages-with-storybook', title: 'Build pages and screens', diff --git a/docs/writing-stories/mocks.md b/docs/writing-stories/mocks.md new file mode 100644 index 000000000000..67b354eff23c --- /dev/null +++ b/docs/writing-stories/mocks.md @@ -0,0 +1,309 @@ +--- +title: 'Mocking data and modules' +--- + +1. Intro +2. Providers + 1. Decorators +3. Network requests + 1. MSW +4. Modules + 1. Mock files + 1. Naming convention + 2. Cannot use subpath imports in a mocked file; must use relative + 3. Watch out for side effects + 2. Subpath imports + 1. Relative imports not supported + 2. Fallback to builder aliases + 3. Next.js callout + 3. Configuring mocks + 1. `fn.mock` and friends + 2. `beforeEach` + +Components that rely on external data or modules can be difficult to use in isolation. Mocking is a way to replace these dependencies with fake data or modules that you control. This allows you to develop and test your components without worrying about the behavior or stability of the external dependencies. + +Storybook provides different tools and techniques to help you mock data and modules. + + + +## Providers + +Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context. + + + + + + + + + +## Network requests + +For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data. + +The MSW addon adds this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. + +
+Set up MSW in Storybook + +First, run this command to install MSW: + + + +```sh +npm install msw --save-dev +``` + +Next, install and register the MSW addon: + + + +```sh +npx storybook@latest add msw-storybook-addon +``` + +Then, generate the service worker file necessary for MSW to work: + + + + + + + + + + + + +Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`). + + + + + +Initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders): + + + + + + + + +Finally, ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file: + + + + + + + + +
+ +Let's use an example of a document screen component that requests data from an API to demonstrate how to mock network requests in Storybook. The following snippets show an example implementation using `fetch` for a REST API and/or GraphQL: + + + + + + + + + + + + + + + + +These examples use the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) or GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/), [URQL](https://formidable.com/open-source/urql/), or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook. + + + +### Mocking REST requests + +To mock a REST request, you can use the `msw` library to intercept the request and respond with mock data. Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that fetches data successfully and another that fails. + + + + + + + + +Note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level. + +### Mocking GraphQL requests + +In addition to mocking RESTful requests, the MSW addon can also mock GraphQL requests. + +Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that queries data successfully and another that fails. + + + + + + + + +## Modules + +Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. + +There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component. + +For either approach, relative imports of the mocked module are not supported. + +### Mock files + +To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior. + +Here's an example of a mock file for a module named `api`: + +```js +// api.mock.js +TK: Add snippet +``` + + + +When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking. + +Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component. + + + +### Subpath imports + +The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md). + +To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules: + +TK: External module example? + +```json +// package.json +{ + "imports": { + "#api": { + "storybook": "./api.mock.ts", + "default": "./api.ts" + }, + "#prisma/prisma": { + "storybook": "./prisma/prisma.mock.ts", + "default": "./prisma/prisma.ts" + }, + "#*": ["./*", "./*.ts", "./*.tsx"] + } +} +``` + + + +Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. + + + +You can then update your component file to use the subpath import: + +```ts +TK: Component snippet +``` + +#### Conditional imports + +Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project. + +The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. + +### Builder aliases + +If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. + +```js +// .storybook/main.ts + +webpackFinal: async (config) => { + if (config.resolve) { + config.resolve.alias = { + ...config.resolve.alias, + 'next/headers': require.resolve('./next-headers'), + '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'), + } + } + + return config +}, +``` From 024237154fc718da4c186273eeb283db5d8ed744 Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Mon, 15 Apr 2024 23:28:21 -0600 Subject: [PATCH 02/46] Split into 3 pages --- docs/toc.js | 21 +- docs/writing-stories/mocking-modules.md | 93 ++++++ .../mocking-network-requests.md | 178 ++++++++++ docs/writing-stories/mocking-providers.md | 24 ++ docs/writing-stories/mocks.md | 309 ------------------ 5 files changed, 314 insertions(+), 311 deletions(-) create mode 100644 docs/writing-stories/mocking-modules.md create mode 100644 docs/writing-stories/mocking-network-requests.md create mode 100644 docs/writing-stories/mocking-providers.md delete mode 100644 docs/writing-stories/mocks.md diff --git a/docs/toc.js b/docs/toc.js index 1a8530bbc6bd..7dbc4bceec4c 100644 --- a/docs/toc.js +++ b/docs/toc.js @@ -142,9 +142,26 @@ module.exports = { type: 'link', }, { - pathSegment: 'mocks', + pathSegment: '', title: 'Mocking data and modules', - type: 'link', + type: 'menu', + children: [ + { + pathSegment: 'mocking-providers', + title: 'Providers', + type: 'link', + }, + { + pathSegment: 'mocking-network-requests', + title: 'Network requests', + type: 'link', + }, + { + pathSegment: 'mocking-modules', + title: 'Modules', + type: 'link', + }, + ], }, { pathSegment: 'build-pages-with-storybook', diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md new file mode 100644 index 000000000000..3c0365382b3a --- /dev/null +++ b/docs/writing-stories/mocking-modules.md @@ -0,0 +1,93 @@ +--- +title: Mocking modules +--- + +Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. + +There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component. + +For either approach, relative imports of the mocked module are not supported. + +### Mock files + +To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior. + +Here's an example of a mock file for a module named `api`: + +```js +// api.mock.js +TK: Add snippet +``` + + + +When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking. + +Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component. + + + +### Subpath imports + +The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md). + +To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules: + +TK: External module example? + +```json +// package.json +{ + "imports": { + "#api": { + "storybook": "./api.mock.ts", + "default": "./api.ts" + }, + "#prisma/prisma": { + "storybook": "./prisma/prisma.mock.ts", + "default": "./prisma/prisma.ts" + }, + "#*": ["./*", "./*.ts", "./*.tsx"] + } +} +``` + + + +Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. + + + +You can then update your component file to use the subpath import: + +```ts +TK: Component snippet +``` + +#### Conditional imports + +Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project. + +The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. + +### Builder aliases + +If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. + + + +```js +// .storybook/main.ts + +webpackFinal: async (config) => { + if (config.resolve) { + config.resolve.alias = { + ...config.resolve.alias, + 'next/headers': require.resolve('./next-headers'), + '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'), + } + } + + return config +}, +``` diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md new file mode 100644 index 000000000000..28afb5e12697 --- /dev/null +++ b/docs/writing-stories/mocking-network-requests.md @@ -0,0 +1,178 @@ +--- +title: Mocking network requests +--- + +For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data. + +The [MSW addon]https://storybook.js.org/addons/msw-storybook-addon/ brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. + +## Set up the MSW addon + +First, if necessary, run this command to install MSW: + + + +```sh +npm install msw --save-dev +``` + +Then generate the service worker file necessary for MSW to work: + + + + + + + + + + + + +Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`). + + + + + +Next, install and register the MSW addon: + + + +```sh +npx storybook@latest add msw-storybook-addon +``` + +Then ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file: + + + + + + + + +Finally, initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders): + + + + + + + + +## Mocking REST requests + +If your component fetches data from a REST API, you can use MSW to mock those requests in Storybook. As an example, consider this document screen component: + + + + + + + + + +This example uses the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/)), you can apply the same principles to mock network requests in Storybook. + + + +With the MSW addon, we can write stories that use MSW to mock the REST requests. Here's an example of two stories for the document screen component: one that fetches data successfully and another that fails. + + + + + + + + +## Mocking GraphQL requests + +GraphQL is another common way to fetch data in components. You can use MSW to mock GraphQL requests in Storybook. Here's an example of a document screen component that fetches data from a GraphQL API: + + + + + + + + + +This example uses GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [URQL](https://formidable.com/open-source/urql/) or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook. + + + +The MSW addon allows you to write stories that use MSW to mock the GraphQL requests. Here's an example demonstrating two stories for the document screen component. The first story fetches data successfully, while the second story fails. + + + + + + + + +## Configuring MSW for stories + +In the examples above, note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level, allowing you to share the same mock server configuration across multiple stories. diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md new file mode 100644 index 000000000000..a6000ff59558 --- /dev/null +++ b/docs/writing-stories/mocking-providers.md @@ -0,0 +1,24 @@ +--- +title: Mocking providers +--- + + + +## Providers + +Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context. + + + + + + + +TK: Something about the `context` argument? diff --git a/docs/writing-stories/mocks.md b/docs/writing-stories/mocks.md deleted file mode 100644 index 67b354eff23c..000000000000 --- a/docs/writing-stories/mocks.md +++ /dev/null @@ -1,309 +0,0 @@ ---- -title: 'Mocking data and modules' ---- - -1. Intro -2. Providers - 1. Decorators -3. Network requests - 1. MSW -4. Modules - 1. Mock files - 1. Naming convention - 2. Cannot use subpath imports in a mocked file; must use relative - 3. Watch out for side effects - 2. Subpath imports - 1. Relative imports not supported - 2. Fallback to builder aliases - 3. Next.js callout - 3. Configuring mocks - 1. `fn.mock` and friends - 2. `beforeEach` - -Components that rely on external data or modules can be difficult to use in isolation. Mocking is a way to replace these dependencies with fake data or modules that you control. This allows you to develop and test your components without worrying about the behavior or stability of the external dependencies. - -Storybook provides different tools and techniques to help you mock data and modules. - - - -## Providers - -Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context. - - - - - - - - - -## Network requests - -For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data. - -The MSW addon adds this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. - -
-Set up MSW in Storybook - -First, run this command to install MSW: - - - -```sh -npm install msw --save-dev -``` - -Next, install and register the MSW addon: - - - -```sh -npx storybook@latest add msw-storybook-addon -``` - -Then, generate the service worker file necessary for MSW to work: - - - - - - - - - - - - -Angular projects will likely need to adjust the command to save the mock service worker file in a different directory (e.g., `src`). - - - - - -Initialize the addon and apply it to all stories with a [project-level loader](./loaders.md#global-loaders): - - - - - - - - -Finally, ensure the [`staticDirs`](../api/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file: - - - - - - - - -
- -Let's use an example of a document screen component that requests data from an API to demonstrate how to mock network requests in Storybook. The following snippets show an example implementation using `fetch` for a REST API and/or GraphQL: - - - - - - - - - - - - - - - - -These examples use the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) or GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/), [URQL](https://formidable.com/open-source/urql/), or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook. - - - -### Mocking REST requests - -To mock a REST request, you can use the `msw` library to intercept the request and respond with mock data. Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that fetches data successfully and another that fails. - - - - - - - - -Note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](./parameters.md#component-parameters) or even [project](./parameters.md#global-parameters) level. - -### Mocking GraphQL requests - -In addition to mocking RESTful requests, the MSW addon can also mock GraphQL requests. - -Here's an example showing two stories for the document screen component. They are each configured to use mock data: one that queries data successfully and another that fails. - - - - - - - - -## Modules - -Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. - -There are two primary approaches to mocking modules in Storybook. They both involve creating a mock file to replace the original module. The difference between the two approaches is how you import the mock file into your component. - -For either approach, relative imports of the mocked module are not supported. - -### Mock files - -To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior. - -Here's an example of a mock file for a module named `api`: - -```js -// api.mock.js -TK: Add snippet -``` - - - -When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking. - -Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component. - - - -### Subpath imports - -The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md). - -To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for the `api` and `prisma` internal modules: - -TK: External module example? - -```json -// package.json -{ - "imports": { - "#api": { - "storybook": "./api.mock.ts", - "default": "./api.ts" - }, - "#prisma/prisma": { - "storybook": "./prisma/prisma.mock.ts", - "default": "./prisma/prisma.ts" - }, - "#*": ["./*", "./*.ts", "./*.tsx"] - } -} -``` - - - -Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. - - - -You can then update your component file to use the subpath import: - -```ts -TK: Component snippet -``` - -#### Conditional imports - -Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project. - -The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. - -### Builder aliases - -If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. - -```js -// .storybook/main.ts - -webpackFinal: async (config) => { - if (config.resolve) { - config.resolve.alias = { - ...config.resolve.alias, - 'next/headers': require.resolve('./next-headers'), - '@/api/todo$': path.resolve(__dirname, './api/todo.mock.ts'), - } - } - - return config -}, -``` From ff3f2e9f966f4580d7b8a23552ba03af72646800 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Apr 2024 15:06:44 +0200 Subject: [PATCH 03/46] Add Modules section to Next.js docs --- docs/get-started/nextjs.md | 104 +++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md index b17cf19661b3..f1e024ef6049 100644 --- a/docs/get-started/nextjs.md +++ b/docs/get-started/nextjs.md @@ -885,54 +885,43 @@ You can refer to the [Install `sharp` to Use Built-In Image Optimization](https: ## API -### Parameters +### Modules -This framework contributes the following [parameters](../writing-stories/parameters.md) to Storybook, under the `nextjs` namespace: +The `@storybook/nextjs` package exports a number of modules that enables you to [mock](/TODO_LINK_TO_MOCKING) Next.js's internal behavior. -#### `appDirectory` +#### `@storybook/nextjs/headers.mock` -Type: `boolean` +Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js -Default: `false` +Exports _writable_ mocks that replaces the actual implementation of `next/headers` exports. Use this to set up cookies or headers that are read in your story, and to later assert that they have been called. -If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](../api/parameters.md#story-parameters), [all stories for a component](../api/parameters.md#meta-parameters), or [every story in your Storybook](../api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details. +Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers: -#### `navigation` +- **`headers().append(name: string, value: string)`**: Appends the value to the header if it exists already. +- **`headers().delete(name: string)`**: Deletes the header +- **`headers().set(name: string, value: string)`**: Sets the header to the value provided. -Type: +For cookies, you can use the existing API to write them, eg. `cookies().set('firstName', 'Jane')`. -```ts -{ - asPath?: string; - pathname?: string; - query?: Record; - segments?: (string | [string, string])[]; -} -``` +Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`. -Default value: +#### `@storybook/nextjs/navigation.mock` -```js -{ - segments: []; -} -``` +Type: `typeof import('next/navigation')` -The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details. +Exports mocks that replaces the actual implementation of `next/navigation` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function. -#### `router` +#### `@storybook/nextjs/router.mock` -Type: +Type: `typeof import('next/router')` -```ts -{ - asPath?: string; - pathname?: string; - query?: Record; -} -``` +Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function. -The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details. +#### `@storybook/nextjs/export-mocks` + +Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }` + +`getPackageAliases` returns the aliases needed to set up Portable Stories in a Jest environment. See the [Portable Stories](TODO) section. ### Options @@ -976,6 +965,55 @@ Type: `string` The absolute path to the `next.config.js` file. This is necessary if you have a custom `next.config.js` file that is not in the root directory of your project. +### Parameters + +This framework contributes the following [parameters](../writing-stories/parameters.md) to Storybook, under the `nextjs` namespace: + +#### `appDirectory` + +Type: `boolean` + +Default: `false` + +If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](../api/parameters.md#story-parameters), [all stories for a component](../api/parameters.md#meta-parameters), or [every story in your Storybook](../api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details. + +#### `navigation` + +Type: + +```ts +{ + asPath?: string; + pathname?: string; + query?: Record; + segments?: (string | [string, string])[]; +} +``` + +Default value: + +```js +{ + segments: []; +} +``` + +The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details. + +#### `router` + +Type: + +```ts +{ + asPath?: string; + pathname?: string; + query?: Record; +} +``` + +The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details. + From 92e13821355628eb7a383e9058ee9d539b360afa Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Tue, 16 Apr 2024 13:17:37 -0600 Subject: [PATCH 04/46] More mocking content - Providers - Configuring mocked providers with parameters - Network requests - Bad link - Modules - Better mock files section - Using mocked files in stories --- docs/writing-stories/mocking-modules.md | 148 +++++++++++++++--- .../mocking-network-requests.md | 2 +- docs/writing-stories/mocking-providers.md | 68 +++++++- 3 files changed, 196 insertions(+), 22 deletions(-) diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md index 3c0365382b3a..e59f0f7c2b59 100644 --- a/docs/writing-stories/mocking-modules.md +++ b/docs/writing-stories/mocking-modules.md @@ -8,26 +8,27 @@ There are two primary approaches to mocking modules in Storybook. They both invo For either approach, relative imports of the mocked module are not supported. -### Mock files +## Mock files -To mock a module, create a file with the same name as the module you want to mock. For example, if you want to mock a module named `api`, create a file named `api.mock.js|ts` in the same directory as the original module. This file should match the exports of the original module but with fake data or behavior. +To mock a module, create a file with the same name and in the same directory as the module you want to mock. For example, to mock a module named `session`, create a file next to it named `session.mock.js|ts`, with a few characteristics: -Here's an example of a mock file for a module named `api`: +- It should re-export all exports from the original module. +- It should use the `fn` utility to mock any necessary functionality from the original module. +- It cannot use subpath imports or builder aliases for its dependencies. +- It should not introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the module they are mocking. -```js -// api.mock.js -TK: Add snippet -``` - - - -When creating a mock file, be careful not to introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the component they are mocking. +Here's an example of a mock file for a module named `session`: -Additionally, you must use absolute paths to import any dependencies in the mock file. Relative imports can cause issues when importing the mock file into your component. +```js +// session.mock.js +import { fn } from '@storybook/test'; +import * as actual from './session'; - +export * from './session'; +export const getUserFromSession = fn(actual.getUserFromSession); +``` -### Subpath imports +## Subpath imports The recommended method for mocking modules is to use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a feature of Node packages that is supported by both [Vite](../builders/vite.md) and [Webpack](../builders/webpack.md). @@ -64,17 +65,17 @@ You can then update your component file to use the subpath import: TK: Component snippet ``` -#### Conditional imports +### Conditional imports Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. -### Builder aliases +## Builder aliases -If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. +If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories. - +TK: Vite version, too ```js // .storybook/main.ts @@ -91,3 +92,114 @@ webpackFinal: async (config) => { return config }, ``` + +## Using mocked modules in stories + +When you use the `fn` utility to mock a module, you create full [mock functions](https://vitest.dev/api/mock.html) which have many useful methods. For example, you can use the [`mockReturnValue`](https://vitest.dev/api/mock.html#mockreturnvalue) method to set a return value for the mocked function or [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) to define a custom implementation. + +Here, we define `beforeEach` on a story (which will run before the story is rendered) to set a mocked return value for the `getUserFromSession` function used by the Page component: + + + +```js +// Page.stories.tsx +import { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { getUserFromSession } from '#api/session.mock'; +import { Page } from './Page'; + +const meta: Meta = { + component: Page, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + async beforeEach() { + // 👇 Set the return value for the getUserFromSession function + getUserFromSession.mockReturnValue({ id: '1', name: 'Alice' }); + }, +}; +``` + + + +If you are [writing your stories in TypeScript](./typescript.md), you will need to import your mock modules using the full mocked file name to have the mocked function correctly typed in your stories. You do **not** need do this in your component files, that's what the [subpath import](#subpath-imports) or [builder alias](#builder-aliases) is for. + + + +### Spying on mocked modules + +The `fn` utility also spies on the original module's functions, which you can use to assert their behavior in your tests. For example, you can use [interaction tests](../writing-tests/interaction-testing.md) to verify that a function was called with specific arguments. + +For example, this story checks that the `saveNote` function was called when the user clicks the save button: + +```ts +// NoteUI.stories.tsx +import { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent, within } from '@storybook/test'; + +import { saveNote } from '#app/actions.mock'; +import { createNotes } from '#mocks/notes'; +import NoteUI from './note-ui'; + +const meta = { + title: 'Mocked/NoteUI', + component: NoteUI, +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +const notes = createNotes(); + +export const SaveFlow: Story = { + name: 'Save Flow ▶', + args: { + isEditing: true, + note: notes[0], + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + const saveButton = canvas.getByRole('menuitem', { name: /done/i }); + await userEvent.click(saveButton); + // 👇 This is the mock function, so you can assert its behavior + await expect(saveNote).toHaveBeenCalled(); + }, +}; +``` + +### Shared setup and clearing mocks + +You can use `beforeEach` at the component level to perform shared setup or clear the mocks between stories. This ensures that each test starts with a clean slate and is not affected by the mocks from previous stories. + + + +```js +// Page.stories.tsx +import { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { getUserFromSession } from '#api/session.mock'; +import { Page } from './Page'; + +const meta: Meta = { + component: Page, + async beforeEach() { + // 👇 Do this for each story + // TK + // 👇 Clear the mock between stories + getUserFromSession.mockClear(); + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + // TK +}; +``` diff --git a/docs/writing-stories/mocking-network-requests.md b/docs/writing-stories/mocking-network-requests.md index 28afb5e12697..461574f7c856 100644 --- a/docs/writing-stories/mocking-network-requests.md +++ b/docs/writing-stories/mocking-network-requests.md @@ -4,7 +4,7 @@ title: Mocking network requests For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW allows you to intercept requests made by your components and respond with fake data. -The [MSW addon]https://storybook.js.org/addons/msw-storybook-addon/ brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. +The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. ## Set up the MSW addon diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md index a6000ff59558..4c5a8b20a496 100644 --- a/docs/writing-stories/mocking-providers.md +++ b/docs/writing-stories/mocking-providers.md @@ -4,8 +4,6 @@ title: Mocking providers -## Providers - Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context. @@ -21,4 +19,68 @@ Components can receive data or configuration from context providers. For example -TK: Something about the `context` argument? +## Configuring the mock provider + +When mocking a provider, it may be necessary to configure the provider to supply a different value for individual stories. For example, you might want to test a component with different themes or user roles. + +One way to do this is to define the decorator for each story individually. But if you imagine a scenario where you wish to create stories for each of your components in both light and dark themes, this approach can quickly become cumbersome. + +For a better way, with much less repetition, you can use the [decorator function's second "context" argument](./decorators.md#context-for-mocking) to access a story's [`parameters`](./parameters.md) and adjust the provided value. This way, you can define the provider once and adjust its value for each story. + +For example, we can adjust the decorator from above to read from `parameters.theme` to determine which theme to provide: + + + +```ts +// .storybook/preview.js +import React from 'react'; +import { Preview } from '@storybook/react'; +import { ThemeProvider } from 'styled-components'; + +const preview: Preview = { + decorators: [ + // 👇 Defining the decorator in the preview file applies it to all stories + (Story, { parameters }) => { + // 👇 Make it configurable by reading the theme value from parameters + const theme = parameters.theme || 'default'; + return ( + + + + ); + }, + ], +}; + +export default preview; +``` + +Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator: + + + +```ts +// Button.stories.tsx +import { Meta, StoryObj } from '@storybook/react'; + +import { Button } from './Button'; + +const meta: Meta = { + component: Button, +}; +export default meta; + +type Story = StoryObj; + +// Wrapped in default theme +export const Default: Story = {}; + +// Wrapped in dark theme +export const Dark: Story = { + parameters: { + theme: 'dark', + }, +}; +``` + +This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable. From 75b0820ab13048ef27d47a967d77c745b9e50aeb Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Tue, 16 Apr 2024 14:28:54 -0600 Subject: [PATCH 05/46] Updates for Next.js - Warning for absolute imports & mocking - Module aliases - Subpath imports - Mocking modules - `@storybook/nextjs/cache.mock` module API --- docs/get-started/nextjs.md | 122 ++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md index f1e024ef6049..8f109ff3202c 100644 --- a/docs/get-started/nextjs.md +++ b/docs/get-started/nextjs.md @@ -671,6 +671,106 @@ import 'styles/globals.scss'; // ... ``` + + +Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information. + + + +## Module aliases + +[Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported. + +```jsx +// index.js +// All good! +import Button from '@/components/button'; +// Also good! +import styles from '@/styles/HomePage.module.css'; + +export default function HomePage() { + return ( + <> +

Hello World

+