Skip to content

Commit

Permalink
Merge pull request #28452 from storybookjs/yann/document-portable-sto…
Browse files Browse the repository at this point in the history
…ries

Docs: Document portable stories usage
  • Loading branch information
kylegach committed Jul 10, 2024
1 parent 9c3d891 commit 6b9200a
Show file tree
Hide file tree
Showing 37 changed files with 611 additions and 897 deletions.
Binary file removed docs/_assets/api/story-pipeline-playwright-ct.png
Binary file not shown.
Binary file added docs/_assets/api/story-pipeline-playwright.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_assets/api/story-pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 21 additions & 12 deletions docs/_snippets/button-snapshot-test-portable-stories.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
```js filename="test/Button.test.js|ts" renderer="react" language="js" tabTitle="jest"
import { render } from '@testing-library/react';

import { composeStories } from '@storybook/react';

import * as stories from '../stories/Button.stories';

const { Primary } = composeStories(stories);
test('Button snapshot', async () => {
const mounted = render(<Primary />);
expect(mounted.container).toMatchSnapshot();
await Primary.play();
expect(document.body.firstChild).toMatchSnapshot();
});
```

Expand All @@ -17,16 +15,14 @@ test('Button snapshot', async () => {

import { expect, test } from 'vitest';

import { render } from '@testing-library/react';

import { composeStories } from '@storybook/react';

import * as stories from '../stories/Button.stories';

const { Primary } = composeStories(stories);
test('Button snapshot', async () => {
const mounted = render(Primary());
expect(mounted.container).toMatchSnapshot();
await Primary.play();
expect(document.body.firstChild).toMatchSnapshot();
});
```

Expand All @@ -35,16 +31,29 @@ test('Button snapshot', async () => {

import { expect, test } from 'vitest';

import { render } from '@testing-library/vue';

import { composeStories } from '@storybook/vue3';

import * as stories from '../stories/Button.stories';

const { Primary } = composeStories(stories);
test('Button snapshot', async () => {
const mounted = render(Primary());
expect(mounted.container).toMatchSnapshot();
await Primary.play();
expect(document.body.firstChild).toMatchSnapshot();
});
```

```js filename="__tests__/Button.spec.js|ts" renderer="svelte" language="js"
// @vitest-environment jsdom

import { expect, test } from 'vitest';

import { composeStories } from '@storybook/svelte';

import * as stories from '../stories/Button.stories';

const { Primary } = composeStories(stories);
test('Button snapshot', async () => {
await Primary.play();
expect(document.body.firstChild).toMatchSnapshot();
});
```
66 changes: 35 additions & 31 deletions docs/_snippets/component-test-with-testing-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { render, fireEvent } from '@testing-library/preact';

import { InvalidForm } from './LoginForm.stories'; //👈 Our stories imported here.

it('Checks if the form is valid', () => {
it('Checks if the form is valid', async () => {
const { getByTestId, getByText } = render(<InvalidForm {...InvalidForm.args} />);

fireEvent.click(getByText('Submit'));
Expand All @@ -39,14 +39,15 @@ it('Checks if the form is valid', () => {
```js filename="Form.test.js|jsx" renderer="react" language="js"
import { fireEvent, render, screen } from '@testing-library/react';

import { composeStory } from '@storybook/react';
import { composeStories } from '@storybook/react';

import Meta, { InvalidForm as InvalidFormStory } from './LoginForm.stories'; //👈 Our stories imported here.
import * as stories from './LoginForm.stories'; // 👈 Our stories imported here.

const FormError = composeStory(InvalidFormStory, Meta);
const { InvalidForm } = composeStories(stories);

test('Checks if the form is valid', () => {
render(<FormError />);
test('Checks if the form is valid', async () => {
// Renders the composed story
await InvalidForm.play();

const buttonElement = screen.getByRole('button', {
name: 'Submit',
Expand All @@ -62,14 +63,15 @@ test('Checks if the form is valid', () => {
```ts filename="Form.test.ts|tsx" renderer="react" language="ts"
import { fireEvent, render, screen } from '@testing-library/react';

import { composeStory } from '@storybook/react';
import { composeStories } from '@storybook/react';

import Meta, { InvalidForm as InvalidFormStory } from './LoginForm.stories'; //👈 Our stories imported here.
import * as stories from './LoginForm.stories'; // 👈 Our stories imported here.

const FormError = composeStory(InvalidFormStory, Meta);
const { InvalidForm } = composeStories(stories);

test('Checks if the form is valid', () => {
render(<FormError />);
test('Checks if the form is valid', async () => {
// Renders the composed story
await InvalidForm.play();

const buttonElement = screen.getByRole('button', {
name: 'Submit',
Expand All @@ -82,36 +84,38 @@ test('Checks if the form is valid', () => {
});
```

```js filename="Form.test.js|ts" renderer="svelte" language="js"
import { render, fireEvent } from '@testing-library/svelte';
```js filename="Form.test.js" renderer="svelte" language="js"
import { fireEvent, render, screen } from '@testing-library/svelte';

import LoginForm from './LoginForm.svelte';
import { composeStories } from '@storybook/svelte';

import { InvalidForm } from './LoginForm.stories'; //👈 Our stories imported here.
import * as stories from './LoginForm.stories'; // 👈 Our stories imported here.

const { InvalidForm } = composeStories(stories);

it('Checks if the form is valid', async () => {
const { getByTestId, getByText } = render(LoginForm, {
props: InvalidForm.args,
});
// Renders the composed story
await InvalidForm.play();

await fireEvent.click(getByText('Submit'));
await fireEvent.click(screen.getByText('Submit'));

const isFormValid = getByTestId('invalid-form');
const isFormValid = screen.getByTestId('invalid-form');
expect(isFormValid).toBeInTheDocument();
});
```

```js filename="tests/Form.test.js" renderer="vue" language="js" tabTitle="3"
import { fireEvent, render, screen } from '@testing-library/vue';

import { composeStory } from '@storybook/vue3';
import { composeStories } from '@storybook/vue3';

import Meta, { InvalidForm as InvalidFormStory } from './LoginForm.stories'; //👈 Our stories imported here.
import * as stories from './LoginForm.stories'; // 👈 Our stories imported here.

const FormError = composeStory(InvalidFormStory, Meta);
const { InvalidForm } = composeStories(stories);

test('Checks if the form is valid', () => {
render(FormError());
test('Checks if the form is valid', async () => {
// Renders the composed story
await InvalidForm.play();

const buttonElement = screen.getByRole('button', {
name: 'Submit',
Expand All @@ -127,14 +131,15 @@ test('Checks if the form is valid', () => {
```ts filename="tests/Form.test.ts" renderer="vue" language="ts" tabTitle="3"
import { fireEvent, render, screen } from '@testing-library/vue';

import { composeStory } from '@storybook/vue3';
import { composeStories } from '@storybook/vue3';

import Meta, { InvalidForm as InvalidFormStory } from './LoginForm.stories'; //👈 Our stories imported here.
import * as stories from './LoginForm.stories'; // 👈 Our stories imported here.

const FormError = composeStory(InvalidFormStory, Meta);
const { InvalidForm } = composeStories(stories);

test('Checks if the form is valid', () => {
render(FormError());
test('Checks if the form is valid', async () => {
// Renders the composed story
await InvalidForm.play();

const buttonElement = screen.getByRole('button', {
name: 'Submit',
Expand All @@ -146,4 +151,3 @@ test('Checks if the form is valid', () => {
expect(isFormValid).toBeInTheDocument();
});
```

63 changes: 25 additions & 38 deletions docs/_snippets/individual-snapshot-tests-portable-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,23 @@ import 'jest-specific-snapshot';

import { describe, test, expect } from '@jest/globals';

// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue)
import { render } from '@testing-library/your-testing-library';

// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3)
import { composeStories } from '@storybook/your-framework';
// Replace your-renderer with the renderer you are using (e.g., react, vue3, svelte, etc.)
import { composeStories } from '@storybook/your-renderer';

const compose = (entry) => {
try {
return composeStories(entry);
} catch (e) {
throw new Error(
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`,
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`
);
}
};

function getAllStoryFiles() {
// Place the glob you want to match your stories files
const storyFiles = glob.sync(
path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}'),
path.join(__dirname, 'stories/**/*.{stories,story}.{js,jsx,mjs,ts,tsx}')
);

return storyFiles.map((filePath) => {
Expand All @@ -48,18 +45,18 @@ describe('Stories Snapshots', () => {

if (stories.length <= 0) {
throw new Error(
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`,
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`
);
}

stories.forEach(({ name, story }) => {
test(name, async () => {
const mounted = render(story());
await story.play();
// Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot.
await new Promise((resolve) => setTimeout(resolve, 1));
// Defines the custom snapshot path location and file name
const customSnapshotPath = `./__snapshots__/${componentName}.test.js.snap`;
expect(mounted.container).toMatchSpecificSnapshot(customSnapshotPath);
expect(document.body.firstChild).toMatchSpecificSnapshot(customSnapshotPath);
});
});
});
Expand All @@ -79,11 +76,8 @@ import "jest-specific-snapshot";

import { describe, test, expect } from "@jest/globals";

// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue)
import { render } from '@testing-library/your-testing-library';

// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3)
import { composeStories } from '@storybook/your-framework';
// Replace your-renderer with the renderer you are using (e.g., react, vue3, svelte, etc.)
import { composeStories } from '@storybook/your-renderer';

type StoryFile = {
default: Meta;
Expand Down Expand Up @@ -137,12 +131,12 @@ describe("Stories Snapshots", () => {

stories.forEach(({ name, story }) => {
test(name, async () => {
const mounted = render(story());
await story.play();
// Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot.
await new Promise((resolve) => setTimeout(resolve, 1));
// Defines the custom snapshot path location and file name
const customSnapshotPath = `./__snapshots__/${componentName}.test.ts.snap`;
expect(mounted.container).toMatchSpecificSnapshot(customSnapshotPath);
expect(document.body.firstChild).toMatchSpecificSnapshot(customSnapshotPath);
});
});
});
Expand All @@ -155,18 +149,15 @@ describe("Stories Snapshots", () => {
import path from 'path';
import { describe, expect, test } from 'vitest';

// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue)
import { render } from '@testing-library/your-testing-library';

// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3)
import { composeStories } from '@storybook/your-framework';
// Replace your-renderer with the renderer you are using (e.g., react, vue3, svelte, etc.)
import { composeStories } from '@storybook/your-renderer';

const compose = (entry) => {
try {
return composeStories(entry);
} catch (error) {
throw new Error(
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${error}`,
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${error}`
);
}
};
Expand All @@ -175,7 +166,7 @@ function getAllStoryFiles() {
const storyFiles = Object.entries(
import.meta.glob('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', {
eager: true,
}),
})
);

return storyFiles.map(([filePath, storyFile]) => {
Expand All @@ -194,18 +185,18 @@ describe('Stories Snapshots', () => {

if (stories.length <= 0) {
throw new Error(
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`,
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`
);
}

stories.forEach(({ name, story }) => {
test(name, async () => {
const mounted = render(story());
await story.play();
// Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot.
await new Promise((resolve) => setTimeout(resolve, 1));
// Defines the custom snapshot path location and file name
const customSnapshotPath = `./__snapshots__/${componentName}.spec.js.snap`;
expect(mounted.container).toMatchFileSnapshot(customSnapshotPath);
expect(document.body.firstChild).toMatchFileSnapshot(customSnapshotPath);
});
});
});
Expand All @@ -222,11 +213,8 @@ import type { Meta, StoryFn } from '@storybook/your-framework';
import path from 'path';
import { describe, expect, test } from 'vitest';

// Replace your-testing-library with one of the supported testing libraries (e.g., react, vue)
import { render } from '@testing-library/your-testing-library';

// Adjust the import based on the supported framework or Storybook's testing libraries (e.g., react, vue3)
import { composeStories } from '@storybook/your-framework';
// Replace your-renderer with the renderer you are using (e.g., react, vue3, svelte, etc.)
import { composeStories } from '@storybook/your-renderer';

type StoryFile = {
default: Meta;
Expand All @@ -238,7 +226,7 @@ const compose = (entry: StoryFile): ReturnType<typeof composeStories<StoryFile>>
return composeStories(entry);
} catch (e) {
throw new Error(
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`,
`There was an issue composing stories for the module: ${JSON.stringify(entry)}, ${e}`
);
}
};
Expand All @@ -248,7 +236,7 @@ function getAllStoryFiles() {
const storyFiles = Object.entries(
import.meta.glob<StoryFile>('./stories/**/*.(stories|story).@(js|jsx|mjs|ts|tsx)', {
eager: true,
}),
})
);

return storyFiles.map(([filePath, storyFile]) => {
Expand All @@ -268,22 +256,21 @@ describe('Stories Snapshots', () => {

if (stories.length <= 0) {
throw new Error(
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`,
`No stories found for this module: ${title}. Make sure there is at least one valid story for this module.`
);
}

stories.forEach(({ name, story }) => {
test(name, async () => {
const mounted = render(story());
await story.play();
// Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot.
await new Promise((resolve) => setTimeout(resolve, 1));
// Defines the custom snapshot path location and file name
const customSnapshotPath = `./__snapshots__/${componentName}.spec.ts.snap`;
expect(mounted.container).toMatchFileSnapshot(customSnapshotPath);
expect(document.body.firstChild).toMatchFileSnapshot(customSnapshotPath);
});
});
});
});
});
```
Loading

0 comments on commit 6b9200a

Please sign in to comment.