Skip to content

Commit

Permalink
Merge pull request #28444 from storybookjs/kasper/mount-docs
Browse files Browse the repository at this point in the history
Docs: Add docs for mounting inside the play function
  • Loading branch information
kylegach committed Jul 10, 2024
1 parent 6b9200a commit 858f1b6
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 34 deletions.
28 changes: 12 additions & 16 deletions code/core/src/preview-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,27 +210,23 @@ export class StoryStoreAccessedBeforeInitializationError extends StorybookError

export class MountMustBeDestructuredError extends StorybookError {
constructor(public data: { playFunction: string }) {
const transpiled =
/function\s*\*|regeneratorRuntime|asyncToGenerator|_ref|param|_0|__async/.test(
data.playFunction
);

super({
category: Category.PREVIEW_API,
code: 12,
message: dedent`
To use mount in the play function, you must use object destructuring, e.g. play: ({ mount }) => {}.
${
!transpiled
? ''
: dedent`
It seems that your builder is configured to transpile destructuring.
To use the mount prop of the story context, you must configure your builder to transpile to no earlier than ES2017.
`
}
More info: https://storybook.js.org/docs/writing-tests/interaction-testing#run-code-before-each-test
To use mount in the play function, you must satisfy the following two requirements:
1. You *must* destructure the mount property from the \`context\` (the argument passed to your play function).
This makes sure that Storybook does not start rendering the story before the play function begins.
2. Your Storybook framework or builder must be configured to transpile to ES2017 or newer.
This is because destructuring statements and async/await usages are otherwise transpiled away,
which prevents Storybook from recognizing your usage of \`mount\`.
Note that Angular is not supported. As async/await is transpiled to support the zone.js polyfill.
More info: https://storybook.js.org/docs/writing-tests/interaction-testing#run-code-before-the-component-gets-rendered
Received the following play function:
${data.playFunction}`,
Expand Down
24 changes: 24 additions & 0 deletions docs/_snippets/before-all-in-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```js filename=".storybook/preview.js" renderer="common" language="js"
import { init } from '../project-bootstrap';

export default {
async beforeAll() {
await init();
},
};
```

```ts filename=".storybook/preview.ts" renderer="common" language="ts"
// Replace your-renderer with the renderer you are using (e.g., react, vue3, angular, etc.)
import { Preview } from '@storybook/your-renderer';

import { init } from '../project-bootstrap';

const preview: Preview = {
async beforeAll() {
await init();
},
};

export default preview;
```
24 changes: 24 additions & 0 deletions docs/_snippets/before-each-in-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```js filename=".storybook/preview.js" renderer="common" language="js"
import MockDate from 'mockdate';

export default {
async beforeEach() {
MockDate.reset()
}
};
```

```ts filename=".storybook/preview.ts" renderer="common" language="ts"
// Replace your-renderer with the renderer you are using (e.g., react, vue3, angular, etc.)
import { Preview } from '@storybook/your-renderer';
import MockDate from 'mockdate';

const preview: Preview = {
async beforeEach() {
MockDate.reset()
}
};

export default preview;
```

135 changes: 135 additions & 0 deletions docs/_snippets/mount-advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
```tsx filename="Page.stories.tsx" renderer="react" language="ts"
export const Default: Story = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
<Page {...args} params={{ id: String(note.id) }} />
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```

```jsx filename="Page.stories.jsx" renderer="react" language="js"
export const Default = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
<Page {...args} params={{ id: String(note.id) }} />
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```

```ts filename="Page.stories.ts" renderer="svelte" language="ts"
export const Default: Story = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
Page,
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
{ props: { ...args, params: { id: String(note.id) } } }
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```

```js filename="Page.stories.js" renderer="svelte" language="js"
export const Default = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
Page,
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
{ props: { ...args, params: { id: String(note.id) } } }
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```

```ts filename="Page.stories.ts" renderer="vue3" language="ts"
export const Default: Story = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
Page,
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
{ props: { ...args, params: { id: String(note.id) } } }
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```

```js filename="Page.stories.js" renderer="vue3" language="js"
export const Default = {
play: async ({ mount, args }) => {
const note = await db.note.create({
data: { title: 'Mount inside of play' },
});

const canvas = await mount(
Page,
// 👇 Pass data that is created inside of the play function to the component
// For example, a just-generated UUID
{ props: { ...args, params: { id: String(note.id) } } }
);

await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i }));
},
argTypes: {
// 👇 Make the params prop un-controllable, as the value is always overriden in the play function.
params: { control: { disable: true } },
}
};
```
44 changes: 44 additions & 0 deletions docs/_snippets/mount-basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
```js filename="Page.stories.js" renderer="common" language="js"
import MockDate from 'mockdate';

// ...rest of story file

export const ChristmasUI = {
async play({ mount }) {
MockDate.set('2024-12-25');
// 👇 Render the component with the mocked date
await mount();
// ...rest of test
},
};
```

```ts filename="Page.stories.ts" renderer="common" language="ts-4-9"
import MockDate from 'mockdate';

// ...rest of story file

export const ChristmasUI: Story = {
async play({ mount }) {
MockDate.set('2024-12-25');
// 👇 Render the component with the mocked date
await mount();
// ...rest of test
},
};
```

```ts filename="Page.stories.ts" renderer="common" language="ts"
import MockDate from 'mockdate';

// ...rest of story file

export const ChristmasUI: Story = {
async play({ mount }) {
MockDate.set('2024-12-25');
// 👇 Render the component with the mocked date
await mount();
// ...rest of test
},
};
```
4 changes: 4 additions & 0 deletions docs/api/doc-blocks/doc-block-story.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ Because all stories render simultaneously in docs entries, play functions can pe

However, if you know your play function is “safe” to run in docs, you can use this prop to run it automatically.

<Callout variant="info">
If a story uses [`mount` in its play function](../../writing-tests/interaction-testing.mdx#run-code-before-the-component-gets-rendered), it will not render in docs unless `autoplay` is set to `true`.
</Callout>

### `height`

Type: `string`
Expand Down
Loading

0 comments on commit 858f1b6

Please sign in to comment.