Jest 中的可移植 Story
如果您正在使用实验性的 CSF Factories 格式,则无需使用可移植 story API。相反,您可以直接导入和使用您的 story。
可移植 story 是 Storybook story,可以在外部环境中使用,例如 Jest。
通常,Storybook 会自动组合 story 及其注解,作为 story 管道的一部分。在 Jest 测试中使用 story 时,您必须自己处理 story 管道,这正是 composeStories
和 composeStory
函数的作用。
此处指定的 API 在 Storybook 8.2.7
及更高版本中可用。如果您使用的是旧版本的 Storybook,您可以升级到最新版本 (npx storybook@latest upgrade
) 以使用此 API。如果您无法升级,可以使用之前的 API,它使用 .play()
方法而不是 .run()
,但在其他方面是相同的。
使用 Next.js
?在 Next.js 项目中将可移植 story 用于 Jest 时,您需要进行三项不同的操作
- 配置
next/jest.js
转换器,它将为您处理所有必要的 Next.js 配置。 - 从
@storybook/nextjs
包中导入composeStories
或composeStory
(例如import { composeStories } from '@storybook/nextjs'
)。 - 设置内部模块别名,以确保框架配置正常工作,并能够模拟和断言它们。
composeStories
composeStories
将处理您指定的组件 story,将每个 story 与必要的注解组合,并返回一个包含组合 story 的对象。
默认情况下,组合 story 将使用 story 中定义的 args 渲染组件。您还可以在测试中将任何 props 传递给组件,这些 props 将覆盖 story 的 args 中传递的值。
import { test, expect } from '@jest/globals';
import { render, screen } from '@testing-library/react';
// 👉 Using Next.js? Import from @storybook/nextjs instead
import { composeStories } from '@storybook/react';
// Import all stories and the component annotations from the stories file
import * as stories from './Button.stories';
// Every component that is returned maps 1:1 with the stories,
// but they already contain all annotations from story, meta, and project levels
const { Primary, Secondary } = composeStories(stories);
test('renders primary button with default args', () => {
render(<Primary />);
const buttonElement = screen.getByText('Text coming from args in stories file!');
expect(buttonElement).not.toBeNull();
});
test('renders primary button with overridden props', () => {
// You can override props and they will get merged with values from the story's args
render(<Primary>Hello world</Primary>);
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});
类型
(
csfExports: CSF file exports,
projectAnnotations?: ProjectAnnotations
) => Record<string, ComposedStoryFn>
参数
csfExports
(必需)
类型:CSF 文件导出
指定要组合哪个组件的 story。传递 CSF 文件的完整导出集(不是默认导出!)。例如 import * as stories from './Button.stories'
projectAnnotations
类型:ProjectAnnotation | ProjectAnnotation[]
指定要应用于组合 story 的项目注解。
提供此参数是为了方便起见。您可能应该使用 setProjectAnnotations
代替。有关 ProjectAnnotation
类型的详细信息,请参阅该函数的 projectAnnotations
参数。
此参数可用于覆盖通过 setProjectAnnotations
应用的项目注解。
返回
类型:Record<string, ComposedStoryFn>
一个对象,其中键是 story 的名称,值是组合 story。
此外,组合 story 将具有以下属性
属性 | 类型 | 描述 |
---|---|---|
args | Record<string, any> | story 的 args |
argTypes | ArgType | story 的 argTypes |
id | string | story 的 id |
parameters | Record<string, any> | story 的 parameters |
play | (context) => Promise<void> | undefined | 执行给定 story 的 play 函数 |
run | (context) => Promise<void> | undefined | 挂载并执行 play 函数给定 story 的 play 函数 |
storyName | string | story 的名称 |
tags | string[] | story 的 tags |
composeStory
如果您希望为组件组合单个 story,则可以使用 composeStory
。
import { jest, test, expect } from '@jest/globals';
import { render, screen } from '@testing-library/react';
// 👉 Using Next.js? Import from @storybook/nextjs instead
import { composeStory } from '@storybook/react';
import meta, { Primary as PrimaryStory } from './Button.stories';
test('onclick handler is called', () => {
// Returns a story which already contains all annotations from story, meta and global levels
const Primary = composeStory(PrimaryStory, meta);
const onClickSpy = jest.fn();
await Primary.run({ args: { ...Primary.args, onClick: onClickSpy } });
const buttonElement = screen.getByRole('button');
buttonElement.click();
expect(onClickSpy).toHaveBeenCalled();
});
类型
(
story: Story export,
componentAnnotations: Meta,
projectAnnotations?: ProjectAnnotations,
exportsName?: string
) => ComposedStoryFn
参数
story
(必需)
类型:Story export
指定要组合的 story。
componentAnnotations
(必需)
类型:Meta
包含 story
的 story 文件的默认导出。
projectAnnotations
类型:ProjectAnnotation | ProjectAnnotation[]
指定要应用于组合 story 的项目注解。
提供此参数是为了方便起见。您可能应该使用 setProjectAnnotations
代替。有关 ProjectAnnotation
类型的详细信息,请参阅该函数的 projectAnnotations
参数。
此参数可用于覆盖通过 setProjectAnnotations
应用的项目注解。
exportsName
类型:string
您可能不需要这个。因为 composeStory
接受单个 story,所以它无法访问该 story 在文件中的导出名称(如 composeStories
那样)。如果您必须确保测试中 story 名称的唯一性,并且不能使用 composeStories
,则可以在此处传递 story 的导出名称。
返回
类型:ComposedStoryFn
一个组合 story。
setProjectAnnotations
此 API 应在测试运行之前调用一次,通常在 setup 文件中。这将确保在调用 composeStories
或 composeStory
时,也会考虑项目注解。
这些是在 setup 文件中需要的配置
- 预览注解:在
.storybook/preview.ts
中定义的那些 - 插件注解(可选):由插件导出的那些
- beforeAll:在所有测试之前运行的代码 (更多信息)
import { beforeAll } from '@jest/globals';
import { setProjectAnnotations } from '@storybook/react';
// 👇 Import the exported annotations, if any, from the addons you're using; otherwise remove this
import * as addonAnnotations from 'my-addon/preview';
import * as previewAnnotations from './.storybook/preview';
const annotations = setProjectAnnotations([previewAnnotations, addonAnnotations]);
// Supports beforeAll hook from Storybook
beforeAll(annotations.beforeAll);
有时,story 可能需要插件的装饰器或加载器才能正确渲染。例如,插件可以应用一个装饰器,将您的 story 包裹在必要的路由器上下文中。在这种情况下,您必须在项目注解集中包含该插件的 preview
导出。请参阅上面示例中的 addonAnnotations
。
注意:如果插件没有自动应用装饰器或加载器本身,而是导出它们供您在 .storybook/preview.js|ts
中手动应用(例如,使用来自 @storybook/addon-themes 的 withThemeFromJSXProvider
),那么您无需执行任何其他操作。它们已经包含在上面示例中的 previewAnnotations
中。
如果您需要配置 Testing Library 的 render
或使用不同的渲染函数,请在此讨论中告知我们,以便我们了解更多关于您的需求。
类型
(projectAnnotations: ProjectAnnotation | ProjectAnnotation[]) => ProjectAnnotation
参数
projectAnnotations
(必需)
类型:ProjectAnnotation | ProjectAnnotation[]
一组项目注解(在 .storybook/preview.js|ts
中定义的那些)或项目注解集的数组,这些注解将应用于所有组合 story。
注解
注解是应用于 story 的元数据,例如 args、装饰器、加载器和 play 函数。它们可以为特定的 story、组件的所有 story 或项目中的所有 story 定义。
Story 管道
为了在 Storybook 中预览您的 story,Storybook 运行一个 story 管道,其中包括应用项目注解、加载数据、渲染 story 和播放交互。这是管道的简化版本
但是,当您想在不同的环境中重用 story 时,至关重要的是要了解所有这些步骤构成了一个 story。可移植 story API 为您提供了在外部环境中重建该 story 管道的机制
1. 应用项目级注解
注解来自 story 本身、该 story 的组件和项目。项目级注解是在您的 .storybook/preview.js
文件中定义的那些以及您正在使用的插件定义的那些。在可移植 story 中,这些注解不会自动应用,您必须自己应用它们。
👉 为此,您可以使用 setProjectAnnotations
API。
2. 组合
通过运行 composeStories
或 composeStory
来准备 story。结果是一个可渲染的组件,它代表 story 的渲染函数。
3. 运行
最后,story 可以在渲染之前准备它们需要的数据(例如,设置一些模拟或获取数据),方法是定义加载器、beforeEach 或在使用 mount 时将所有 story 代码放在 play 函数中。在可移植 story 中,当您调用组合 story 的 run 方法时,将执行所有这些步骤。
👉 为此,您可以使用 composeStories
或 composeStory
API。组合 story 将返回要调用的 run
方法。
import { test } from '@jest/globals';
// 👉 Using Next.js? Import from @storybook/nextjs instead
import { composeStories } from '@storybook/react';
import * as stories from './Button.stories';
const { Primary } = composeStories(stories);
test('renders and executes the play function', async () => {
// Mount story and run interactions
await Primary.run();
});
如果您的 play 函数包含断言(例如 expect
调用),当这些断言失败时,您的测试将失败。
覆盖全局变量
如果您的 story 的行为因全局变量而异(例如,以英语或西班牙语渲染文本),您可以通过在组合 story 时覆盖项目注解,在可移植 story 中定义这些全局值
import { test } from '@jest/globals';
// 👉 Using Next.js? Import from @storybook/nextjs instead
import { composeStory } from '@storybook/react';
import meta, { Primary as PrimaryStory } from './Button.stories';
test('renders in English', async () => {
const Primary = composeStory(
PrimaryStory,
meta,
{ globals: { locale: 'en' } }, // 👈 Project annotations to override the locale
);
await Primary.run();
});
test('renders in Spanish', async () => {
const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } });
await Primary.run();
});