diff --git a/docs/configure/story-rendering.md b/docs/configure/story-rendering.md index 1ca2d09c76d8..87f8bc8c8d9b 100644 --- a/docs/configure/story-rendering.md +++ b/docs/configure/story-rendering.md @@ -8,6 +8,30 @@ In Storybook, your stories render in a particular “preview” iframe (also cal Code executed in the preview file (`.storybook/preview.js|ts`) runs for every story in your Storybook. This is useful for setting up global styles, initializing libraries, or anything else required to render your components. + + +Here's an example of how you might use the preview file to initialize a library that must run before your components render: + +```ts +// .storybook/preview.ts +// Replace your-renderer with the renderer you are using (e.g., react, vue3) +import { Preview } from '@storybook/your-renderer'; + +import { initialize } from '../lib/your-library'; + +initialize(); + +const preview: Preview = { + // ... +}; + +export default preview; +``` + + + + + For example, with Vue, you can extend Storybook's application and register your library (e.g., [Fontawesome](https://github.com/FortAwesome/vue-fontawesome)). Or with Angular, add the package ([localize](https://angular.io/api/localize)) into your `polyfills.ts` and import it: @@ -25,6 +49,8 @@ For example, with Vue, you can extend Storybook's application and register your + + ## Adding to <head> If you need to add extra elements to the `head` of the preview iframe, for instance, to load static stylesheets, font files, or similar, you can create a file called [`.storybook/preview-head.html`](./index.md#configure-story-rendering) and add tags like this: diff --git a/docs/get-started/nextjs.md b/docs/get-started/nextjs.md index 99314381ae23..36140f9def16 100644 --- a/docs/get-started/nextjs.md +++ b/docs/get-started/nextjs.md @@ -326,7 +326,7 @@ import { Preview } from '@storybook/react'; import { getRouter } from '@storybook/nextjs/router.mock'; const preview: Preview = { - paramters: { + parameters: { nextjs: { // 👇 Override the default router properties router: { @@ -487,7 +487,7 @@ import { Preview } from '@storybook/react'; import { getRouter } from '@storybook/nextjs/navigation.mock'; const preview: Preview = { - paramters: { + parameters: { nextjs: { // 👇 Override the default navigation properties navigation: { @@ -591,7 +591,7 @@ export default HelloWorld; You can use your own babel config too. This is an example of how you can customize styled-jsx. -```json +```jsonc // .babelrc (or whatever config file you use) { "presets": [ @@ -677,7 +677,7 @@ As an alternative to [module aliases](#module-aliases), you can use [subpath imp To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project: -```json +```jsonc // package.json { "imports": { @@ -741,11 +741,11 @@ export const getUserFromSession = fn(actual.getUserFromSession); #### With subpath imports -If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook: +If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#subpath-imports) so that the mocked module is used inside Storybook. The example below configures subpath imports for four internal modules, which are then mocked in Storybook: -```json +```jsonc // package.json { "imports": { @@ -827,7 +827,7 @@ module.exports = { Calls to `getConfig` would return the following object when called within Storybook: -```json +```jsonc // Runtime config { "serverRuntimeConfig": {}, @@ -862,7 +862,7 @@ Below is an example of how to add SVGR support to Storybook with this framework. Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box. -```json +```jsonc // tsconfig.json { "compilerOptions": { @@ -918,7 +918,7 @@ In the future we will provide better mocking support in Storybook and support fo You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API. -When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because, to replicate Next.js configuration, Storybook sets up aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well. +When using portable stories with Next.js, you need to mock the Next.js modules on which your components depend. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment. This is needed because, to replicate Next.js configuration, Storybook sets up aliases in Webpack to make testing and developing your components easier. If you make use of the advanced functionality like the built-in mocks for common Next.js modules, you need to set up this aliasing in your Jest environment as well. ## Notes for Yarn v2 and v3 users @@ -980,7 +980,7 @@ Make sure you are treating image imports the same way you treat them when using Before using this framework, image imports would import the raw path to the image (e.g. `'static/media/stories/assets/logo.svg'`). Now image imports work the "Next.js way", meaning that you now get an object when importing an image. For example: -```json +```jsonc // Image import object { "src": "static/media/stories/assets/logo.svg", @@ -1059,7 +1059,7 @@ export default createJestConfig(config); Type: `typeof import('next/cache')` -Exports mocks that replaces the actual implementation of `next/cache` exports. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). +This module exports mocked implementations of the `next/cache` module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). @@ -1095,7 +1095,7 @@ export const Submitted: Story = { Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js -Exports _writable_ mocks that replaces the actual implementation of `next/headers` exports. Use this to set up cookies or headers that are read in your story, and to later assert that they have been called. +This module exports _writable_ mocked implementations of the `next/headers` module's exports. You can use it to set up cookies or headers that are read in your story, and to later assert that they have been called. Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers: @@ -1103,7 +1103,7 @@ Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functi - **`headers().delete(name: string)`**: Deletes the header - **`headers().set(name: string, value: string)`**: Sets the header to the value provided. -For cookies, you can use the existing API to write them, eg. `cookies().set('firstName', 'Jane')`. +For cookies, you can use the existing API to write them. E.g., `cookies().set('firstName', 'Jane')`. Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`. @@ -1145,7 +1145,7 @@ export const LoggedInEurope: Story = { Type: `typeof import('next/navigation') & getRouter: () => ReturnType` -Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). +This module exports mocked implementations of the `next/navigation` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). @@ -1194,7 +1194,7 @@ export const GoBack: Story = { Type: `typeof import('next/router') & getRouter: () => ReturnType` -Exports mocks that replaces the actual implementation of `next/navigation` exports. Also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), so that the properties can be manipulated and asserted on. Use these to mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). +This module exports mocked implementations of the `next/router` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](../writing-stories/play-function.md). diff --git a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx index dc3347cfd980..f35bcd16f033 100644 --- a/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx +++ b/docs/snippets/angular/msw-addon-configure-handlers-graphql.ts.mdx @@ -59,8 +59,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx index dee1f7976ec4..0138bd74920c 100644 --- a/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx +++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.js.mdx @@ -63,8 +63,8 @@ export const MockedSuccess = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx index 2fd10eed5dec..468c1866b98c 100644 --- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx +++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts-4-9.mdx @@ -67,8 +67,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, } }); diff --git a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx index 4367a53a25e9..281441a95612 100644 --- a/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx +++ b/docs/snippets/react/msw-addon-configure-handlers-graphql.ts.mdx @@ -67,8 +67,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, } }); diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx index 829ec728386a..bbe3531e64b0 100644 --- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx +++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.js.mdx @@ -42,8 +42,8 @@ export const MockedSuccess = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx index 866603602f16..91e56485b7a5 100644 --- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx +++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts-4-9.mdx @@ -47,8 +47,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx index d78ffb4ce4e8..cacc4af7674a 100644 --- a/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx +++ b/docs/snippets/svelte/msw-addon-configure-handlers-graphql.ts.mdx @@ -47,8 +47,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx index 251b119a6629..3e13c859cc70 100644 --- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx +++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.js.mdx @@ -45,8 +45,8 @@ export const MockedSuccess = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx index c4b55f47f6ef..0efceab56d6a 100644 --- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx +++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts-4-9.mdx @@ -50,8 +50,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx index 6d1b27cb6946..fd23daacece7 100644 --- a/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx +++ b/docs/snippets/vue/msw-addon-configure-handlers-graphql.ts.mdx @@ -50,8 +50,8 @@ export const MockedSuccess: Story = { graphql.query('AllInfoQuery', () => { return new HttpResponse.json({ data: { - allFilms: { - films, + AllInfo: { + ...TestData, }, }, }); diff --git a/docs/toc.js b/docs/toc.js index 7dbc4bceec4c..c872f84d2aa4 100644 --- a/docs/toc.js +++ b/docs/toc.js @@ -147,8 +147,8 @@ module.exports = { type: 'menu', children: [ { - pathSegment: 'mocking-providers', - title: 'Providers', + pathSegment: 'mocking-modules', + title: 'Modules', type: 'link', }, { @@ -157,8 +157,8 @@ module.exports = { type: 'link', }, { - pathSegment: 'mocking-modules', - title: 'Modules', + pathSegment: 'mocking-providers', + title: 'Providers', type: 'link', }, ], diff --git a/docs/writing-stories/build-pages-with-storybook.md b/docs/writing-stories/build-pages-with-storybook.md index dc7b34bd8a44..0563733e0dac 100644 --- a/docs/writing-stories/build-pages-with-storybook.md +++ b/docs/writing-stories/build-pages-with-storybook.md @@ -73,19 +73,19 @@ This approach is beneficial when the various subcomponents export a complex list ## Mocking connected components -If you need to render a connected component in Storybook, you can mock the data or modules that component depends on. There are various layers in which you can do that. +Connected components are components that depend on external data or services. For example, a full page component is often a connected component. When you render a connected component in Storybook, you need to mock the data or modules that the component depends on. There are various layers in which you can do that. -### [Mocking providers](./mocking-providers.md) +### [Mocking imports](./mocking-modules.md) -Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories. +Components can depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. ### [Mocking API Services](./mocking-network-requests.md) For components that make network requests (e.g., fetching data from a REST or GraphQL API), you can mock those requests in your stories. -### [Mocking imports](./mocking-modules.md) +### [Mocking providers](./mocking-providers.md) -Components can also depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. +Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories. diff --git a/docs/writing-stories/decorators.md b/docs/writing-stories/decorators.md index da53026077e3..f673ed393c04 100644 --- a/docs/writing-stories/decorators.md +++ b/docs/writing-stories/decorators.md @@ -80,8 +80,12 @@ const preview: Preview = { export default preview; ``` + + For another example, see the section on [configuring the mock provider](./mocking-providers.md#configuring-the-mock-provider), which demonstrates how to use the same technique to change which theme is provided to the component. + + ### Using decorators to provide data If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data. Read more in the [building pages in Storybook](./build-pages-with-storybook.md) section. diff --git a/docs/writing-stories/mocking-modules.md b/docs/writing-stories/mocking-modules.md index b5d9e670f1af..fa1452b2222d 100644 --- a/docs/writing-stories/mocking-modules.md +++ b/docs/writing-stories/mocking-modules.md @@ -86,31 +86,24 @@ To configure subpath imports, you define the `imports` property in your project' } ``` - +There are two aspects to this configuration worth noting: -Each subpath must begin with `#`, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. +First, **each subpath must begin with `#`**, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. - +Second, note the **`storybook` and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module in your project. The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. -You can then update your component file to use the subpath import: +With the package configuration in place, you can then update your component file to use the subpath import: ```ts // AuthButton.ts +// ➖ Remove this line +// import { getUserFromSession } from '../../lib/session'; +// ➕ Add this line import { getUserFromSession } from '#lib/session'; -export const AuthButton = (props) => { - const user = getUserFromSession(); - - // ... -}; +// ... rest of the file ``` -### Conditional imports - -Note the `storybook` and `default` keys in each module's entry. The `storybook` key is used to import the mock file in Storybook, while the `default` key is used to import the original module in your project. - -The Storybook environment will match the conditions `storybook` and `test`, so you can apply the same module mapping for both Storybook and your tests. - ## Builder aliases If your project is unable to use [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the mock file. This will instruct the builder to replace the module with the mock file when bundling your Storybook stories. @@ -240,7 +233,7 @@ export const SaveFlow: Story = { ### Setting up and cleaning up -You can use the asynchronous `beforeEach` function to perform any setup that you need before the story is rendered, eg. setting up mock behavior. It can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.js|ts`, which will run for all stories in the project) level. +Before the story renders, you can use the asynchronous `beforeEach` function to perform any setup you need (e.g., configure the mock behavior). This function can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.js|ts`, which will run for all stories in the project). You can also return a cleanup function from `beforeEach` which will be called after your story unmounts. This is useful for tasks like unsubscribing observers, etc. @@ -250,7 +243,7 @@ It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Stor -Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the Date and reset it when the story unmounts. +Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts. diff --git a/docs/writing-stories/mocking-providers.md b/docs/writing-stories/mocking-providers.md index adeec3072f67..0671ff37532e 100644 --- a/docs/writing-stories/mocking-providers.md +++ b/docs/writing-stories/mocking-providers.md @@ -2,7 +2,21 @@ title: Mocking providers --- - +export const SUPPORTED_RENDERERS = ['react', 'solid']; + + + + + +The [context provider pattern](https://react.dev/learn/passing-data-deeply-with-context) and how to mock it only applies to renderers that use JSX, like [React](?renderer=react) or [Solid](?renderer=solid). + + + + + + + + Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. To mock a provider, you can wrap your component in a [decorator](./decorators.md) that includes the necessary context. @@ -90,3 +104,5 @@ export const Dark: Story = { ``` This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable. + +