文档
Storybook 文档

Interactions

Storybook 中的 play 函数允许您模拟用户交互,以便在 story 渲染后运行。借助 Interactions 插件,您可以可视化和调试这些交互。

用于交互的 Play 函数

Stories 以结构化的方式隔离和捕获组件状态。在开发组件时,您可以快速循环浏览 stories 以验证外观和感觉。每个 story 都指定了重现特定状态所需的所有输入。您甚至可以模拟上下文和 API 调用,从而使您能够处理组件的大多数用例。但是,对于需要用户交互的状态又该如何处理呢?

例如,单击按钮以打开/关闭对话框、拖动列表项以重新排序或填写表单以检查验证错误。要测试这些行为,您必须像用户一样与组件交互。交互式 story 使您可以使用 play 函数自动执行这些交互。它们是小段代码,在 story 完成渲染后运行,模拟用户与组件交互的确切步骤。

由 Testing Library 和 Vitest 提供支持

这些交互是使用名为 @storybook/test 的包编写的。它提供了 Storybook 工具化的 Testing LibraryVitest 版本。这为您提供了熟悉的、对开发者友好的语法来与 DOM 交互并进行断言,但具有额外的遥测功能来帮助进行调试。

设置 Interactions 插件

默认情况下,如果您为新项目添加 Storybook,则 @storybook/addon-interactions 已安装并配置。如果您是从以前版本的 Storybook 迁移,则需要手动安装。

运行以下命令以安装 Interactions 插件和相关依赖项。

npm install @storybook/test @storybook/addon-interactions --save-dev

接下来,更新 .storybook/main.js|ts 为以下内容

.storybook/main.ts
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    // Other Storybook addons
    '@storybook/addon-interactions', // 👈 Register the addon
  ],
};
 
export default config;

确保在 @storybook/addon-essentials 插件(或 @storybook/addon-actions 插件,如果您单独安装了它)之后列出 @storybook/addon-interactions

现在,当您运行 Storybook 时,Interactions 插件将启用。

Storybook Interactions installed and registered

编写组件测试

交互作为 stories 的 play 函数的一部分运行。我们依赖 Testing Library 来完成繁重的工作。

确保通过 @storybook/test 导入 Vitest 和 Testing Library 的 Storybook 包装器,而不是直接导入原始包。

Form.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { userEvent, waitFor, within, expect, fn } from '@storybook/test';
 
import { Form } from './Form';
 
const meta: Meta<typeof Form> = {
  component: Form,
  args: {
    // 👇 Use `fn` to spy on the onSubmit arg
    onSubmit: fn(),
  },
};
 
export default meta;
type Story = StoryObj<typeof Form>;
 
/*
 * See https://storybook.org.cn/docs/writing-stories/play-function#working-with-the-canvas
 * to learn more about using the canvasElement to query the DOM
 */
export const Submitted: Story = {
  play: async ({ args, canvasElement, step }) => {
    const canvas = within(canvasElement);
 
    await step('Enter credentials', async () => {
      await userEvent.type(canvas.getByTestId('email'), 'hi@example.com');
      await userEvent.type(canvas.getByTestId('password'), 'supersecret');
    });
 
    await step('Submit form', async () => {
      await userEvent.click(canvas.getByRole('button'));
    });
 
    // 👇 Now we can assert that the onSubmit arg was called
    await waitFor(() => expect(args.onSubmit).toHaveBeenCalled());
  },
};

上面的示例使用 canvasElement 将元素查询范围限定为当前 story。如果您希望您的 play 函数最终与 Storybook 文档兼容,这将至关重要,Storybook 文档会在同一页面上渲染多个组件。此外,step 函数可用于创建带标签的交互组。

虽然您可以参考 Testing Library 文档 以了解有关如何使用它的详细信息,但在使用 Storybook 包装器时,有一个重要的细节有所不同:方法调用必须 await。它允许您使用调试器在交互之间来回步进。

任何已标记为 Action 的 args,无论是使用 argTypes 注释还是 argTypesRegex,都将自动转换为 Jest 模拟函数 (spy)。这允许您对这些函数的调用进行断言。

要在 Storybook stories 中模拟函数以进行可靠且隔离的组件测试,请使用来自 @storybook/test 的命名 fn 导入。