Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Module mocking, and more #26858

Merged
merged 48 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1efccbe
WIP
kylegach Apr 15, 2024
0242371
Split into 3 pages
kylegach Apr 16, 2024
ff3f2e9
Add Modules section to Next.js docs
JReinhold Apr 16, 2024
92e1382
More mocking content
kylegach Apr 16, 2024
75b0820
Updates for Next.js
kylegach Apr 16, 2024
299a2c2
Simplify Building pages with Storybook guide
kylegach Apr 16, 2024
ec6742b
Update Decorators and Interaction tests pages
kylegach Apr 17, 2024
abc9b7c
Add missing TODOs
kylegach Apr 17, 2024
194a9bc
Add vite example
kylegach Apr 17, 2024
f2f4d13
clarify mock->original import limitations.
JReinhold Apr 17, 2024
e7c0676
add paragraph on mocking external modules.
JReinhold Apr 17, 2024
5d8ee28
add docs on `beforeEach`, `cleanup`, `parameters.test`
JReinhold Apr 17, 2024
4faf1ae
simplify subpath example in nextjs
JReinhold Apr 17, 2024
2ac03c4
Address feedback
kylegach Apr 18, 2024
b1da9c9
add getPackageAliases example
JReinhold Apr 18, 2024
71a4cea
add examples to next mock docs
JReinhold Apr 18, 2024
5bab73a
improve documentation for parameters.test.dangerouslyIgnoreUnhandledE…
JReinhold Apr 18, 2024
46c31eb
remove docs on default router and navigation contexts
JReinhold Apr 18, 2024
eef3290
add reasons for aliases in nextjs+jest
JReinhold Apr 18, 2024
f88bea5
Add back nextjs router/navigation override instructions
kylegach Apr 19, 2024
3f7e44c
Improve MSW snippets
kylegach Apr 19, 2024
8eaeb94
Address MSW snippets feedback
kylegach Apr 19, 2024
f4914e3
Prettify snippets
kylegach Apr 19, 2024
cf2be73
Add new actions to the bottom of the panel and auto scroll
kasperpeulen Apr 17, 2024
42773e3
Address review
kasperpeulen Apr 17, 2024
e850d36
add cache.mock entrypoint to nextjs
JReinhold Apr 17, 2024
401fd26
Try another mock naming convention
kasperpeulen Apr 16, 2024
9920353
Bind this
kasperpeulen Apr 16, 2024
b55673f
Update snapshot
kasperpeulen Apr 16, 2024
a033167
Fix name
kasperpeulen Apr 16, 2024
537fbb2
Hide some junk of next for nwo
kasperpeulen Apr 16, 2024
7cacdfa
Update code/frameworks/nextjs/src/export-mocks/navigation/index.ts
kasperpeulen Apr 16, 2024
af31a1f
Update code/frameworks/nextjs/src/export-mocks/navigation/index.ts
kasperpeulen Apr 16, 2024
789991f
Update code/frameworks/nextjs/src/export-mocks/navigation/index.ts
kasperpeulen Apr 16, 2024
17c5152
Update code/frameworks/nextjs/src/export-mocks/navigation/index.ts
kasperpeulen Apr 16, 2024
fc7e664
Hide some junk of next for now
kasperpeulen Apr 16, 2024
5fafb46
Show redirect and next/cache spies
kasperpeulen Apr 16, 2024
a9ce1d4
Apply suggestions from code review
kylegach Apr 30, 2024
01c6509
Address feedback
kylegach Apr 30, 2024
39689ac
Merge branch 'kasper/module-mocking' into module-mocking-docs
kylegach Apr 30, 2024
1dca308
Snippetize examples
kylegach May 1, 2024
6bf05a1
Remove unnecessary snippets
kylegach May 1, 2024
c715248
Prettify snippets
kylegach May 1, 2024
7be08e3
Address feedback
kylegach May 1, 2024
a9d9bfa
Update decorator snippets
kylegach May 3, 2024
d2f7fe8
Address feedback
kylegach May 3, 2024
dfa82ab
Address feedback
kylegach May 6, 2024
2a0d245
Merge branch 'next' into module-mocking-docs
kylegach May 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/api/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Setting this to `true` will prevent the [play function](../writing-stories/play-function.md) from failing and showing a warning when an uncaught exception is thrown.
kylegach marked this conversation as resolved.
Show resolved Hide resolved

---

### Essential addons
Expand Down
23 changes: 22 additions & 1 deletion docs/configure/story-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@
title: 'Story rendering'
---

In Storybook, your stories render in a particular “preview” iframe (Canvas tab) inside the larger Storybook web application. The JavaScript build configuration of the preview is controlled by a [webpack](../builders/webpack.md) config, but you also may want to directly control the rendered HTML to help your stories render correctly.
In Storybook, your stories render in a particular “preview” iframe (also called the Canvas) inside the larger Storybook web application. The JavaScript build configuration of the preview is controlled by a [builder](../builders/index.md) config, but you also may want to run some code for every story or directly control the rendered HTML to help your stories render correctly.

## Running code for every story

Code executed in the preview file (`.storybook/preview.js|ts`) runs for every story in your Storybook. This is useful for setting up global styles, initializing libraries, or anything else required to render your components.

For example, with Vue, you can extend Storybook's application and register your library (e.g., [Fontawesome](https://github.com/FortAwesome/vue-fontawesome)). Or with Angular, add the package ([localize](https://angular.io/api/localize)) into your `polyfills.ts` and import it:

<!-- prettier-ignore-start -->

<CodeSnippets
paths={[
'vue/storybook-preview-with-library-decorator.library-3.js.mdx',
'vue/storybook-preview-with-library-decorator.library-3.ts.mdx',
'vue/storybook-preview-with-hoc-component-decorator.component-3.js.mdx',
'vue/storybook-preview-with-hoc-component-decorator.component-3.ts.mdx',
'angular/add-localize-package-to-polyfills.ts.mdx',
'angular/storybook-preview-with-angular-polyfills.js.mdx',
]}
/>
kylegach marked this conversation as resolved.
Show resolved Hide resolved

<!-- prettier-ignore-end -->

## Adding to &#60;head&#62;

Expand Down
248 changes: 215 additions & 33 deletions docs/get-started/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,104 @@ import 'styles/globals.scss';
// ...
```

<Callout variant="warning">

Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information.

</Callout>

## 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 (
<>
<h1 className={styles.title}>Hello World</h1>
<Button />
</>
);
}
```

## 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).
JReinhold marked this conversation as resolved.
Show resolved Hide resolved

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
kylegach marked this conversation as resolved.
Show resolved Hide resolved
// package.json
{
"imports": {
"#*": ["./*", "./*.ts", "./*.tsx"]
}
}
```

<Callout variant="info">

Because subpath imports take the place of module aliases, you can remove the path aliases from your TypeScript configuration.
kylegach marked this conversation as resolved.
Show resolved Hide resolved

</Callout>

Which can then be used like this:

```jsx
// index.js
import Button from '#components/button';
import styles from '#styles/HomePage.module.css';

export default function HomePage() {
return (
<>
<h1 className={styles.title}>Hello World</h1>
<Button />
</>
);
}
```

## Mocking modules

Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](../writing-stories/mocking-modules.md) to control and assert their behavior.

### Built-in mocked modules

This framework provides mocks for many of Next.js' internal modules:

1. [`@storybook/nextjs/cache.mock`](#storybooknextjscachemock)
2. [`@storybook/nextjs/headers.mock`](#storybooknextjsheadersmock)
3. [`@storybook/nextjs/navigation.mock`](#storybooknextjsnavigationmock)
4. [`@storybook/nextjs/router.mock`](#storybooknextjsroutermock)

### Mocking other modules

How you mock other modules in Storybook depends on how you import the module into your component.

The first step, with either approach, is to [create a mock file](../writing-stories/mocking-modules.md#mock-files).

TK: More here?

#### With subpath imports

If you're using [subpath imports](#subpath-imports), you can adjust your configuration to apply [conditions](../writing-stories/mocking-modules.md#conditional-imports) so that the mocked module is used inside Storybook.

TK: Add example of mocking modules with subpath imports

#### With module aliases

If you're using [module aliases](#module-aliases), you can add a Webpack alias to your Storybook configuration to point to the mock file.

TK: Add example of mocking modules with module aliases

## Runtime config

Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime.
Expand Down Expand Up @@ -783,6 +881,12 @@ If your server components access data via the network, we recommend using the [M

In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions).

## Portable stories

You can test your stories in a Jest environment by using the [portable stories](../api/portable-stories-jest.md) API.

When using portable stories with Next.js, you need to mock the Next.js modules that your components depend on. You can use the [`@storybook/nextjs/export-mocks` module](#storybooknextjsexport-mocks) to generate the aliases needed to set up portable stories in a Jest environment.

## Notes for Yarn v2 and v3 users

If you're using [Yarn](https://yarnpkg.com/) v2 or v3, you may run into issues where Storybook can't resolve `style-loader` or `css-loader`. For example, you might get errors like:
Expand Down Expand Up @@ -885,54 +989,83 @@ 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](#mocking-modules) Next.js's internal behavior.
kylegach marked this conversation as resolved.
Show resolved Hide resolved

#### `appDirectory`
#### `@storybook/nextjs/export-mocks`

Type: `boolean`
Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }`

Default: `false`
`getPackageAliases` is a helper to generate the aliases needed to set up [portable stories](#portable-stories).
kylegach marked this conversation as resolved.
Show resolved Hide resolved

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
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
// 👇 import the utility function
import { getPackageAliases } from '@storybook/nextjs/export-mocks';

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
});

// Add any custom config to be passed to Jest
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
...getPackageAliases(), // 👈 add the utility as a moduleNameMapper
},
};

#### `navigation`
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config);
```
kylegach marked this conversation as resolved.
Show resolved Hide resolved

Type:
#### `@storybook/nextjs/cache.mock`

```ts
{
asPath?: string;
pathname?: string;
query?: Record<string, string>;
segments?: (string | [string, string])[];
}
```
Type: `typeof import('next/cache')`

Default value:
TK: Description

```js
{
segments: [];
}
```
TK: Example snippet

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.
#### `@storybook/nextjs/headers.mock`

#### `router`
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

Type:
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.
kylegach marked this conversation as resolved.
Show resolved Hide resolved

```ts
{
asPath?: string;
pathname?: string;
query?: Record<string, string>;
}
```
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:

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.
- **`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, eg. `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`.

TK: Example snippet

#### `@storybook/nextjs/navigation.mock`

Type: `typeof import('next/navigation')`

Exports mocks that replaces the actual implementation of `next/navigation` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.

TK: Example snippet

#### `@storybook/nextjs/router.mock`

Type: `typeof import('next/router')`

Exports mocks that replaces the actual implementation of `next/router` exports. Use these to mock implementations or assert on mock calls in a story's `play`-function.

TK: Example snippet

### Options

Expand Down Expand Up @@ -976,6 +1109,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<string, string>;
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<string, string>;
}
```

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.

<!-- End supported renderers -->

</If>
Loading