返回Storybook 简介
章节
  • 开始使用
  • 简单组件
  • 复合组件
  • 数据
  • 屏幕
  • 部署
  • 可视化测试
  • 插件
  • 结论
  • 贡献

构建屏幕

从组件构建屏幕

我们专注于自下而上构建 UI,从小处着手并增加复杂性。这样做使我们能够隔离开发每个组件,弄清楚其数据需求,并在 Storybook 中进行测试。所有这些都无需启动服务器或构建屏幕!

在本章中,我们将继续提高复杂性,将组件组合到一个屏幕中,并在 Storybook 中开发该屏幕。

嵌套容器组件

由于我们的应用程序很简单,因此我们将构建的屏幕非常简单,只是将 TaskList 组件(通过 Svelte Store 提供自己的数据)包装在一些布局中,并从 store 中拉出一个顶级 error 字段(假设如果我们连接到服务器时遇到问题,我们将设置该字段)。

让我们首先更新我们的 Svelte store(在 src/store.js 中)以包含我们想要的新 error 字段

复制
src/store.js
// A simple Svelte store implementation with update methods and initial data.
// A true app would be more complex and separated into different files.

import { writable } from "svelte/store";

/*
 * The initial state of our store when the app loads.
 * Usually, you would fetch this from a server. Let's not worry about that now
 */
const defaultTasks = [
  { id: '1', title: 'Something', state: 'TASK_INBOX' },
  { id: '2', title: 'Something more', state: 'TASK_INBOX' },
  { id: '3', title: 'Something else', state: 'TASK_INBOX' },
  { id: '4', title: 'Something again', state: 'TASK_INBOX' },
];

const TaskBox = () => {
  // Creates a new writable store populated with some initial data
  const { subscribe, update } = writable({
    tasks: defaultTasks,
    status: 'idle',
    error: false,
  });
  return {
    subscribe,
    // Method to archive a task, think of a action with redux or Pinia
    archiveTask: (id) =>
      update((store) => {
        const filteredTasks = store.tasks
          .map((task) =>
            task.id === id ? { ...task, state: 'TASK_ARCHIVED' } : task
          )
          .filter((t) => t.state === 'TASK_INBOX' || t.state === 'TASK_PINNED');

        return { ...store, tasks: filteredTasks };
      }),
    // Method to archive a task, think of a action with redux or Pinia
    pinTask: (id) => {
      update((store) => {
        const task = store.tasks.find((t) => t.id === id);
        if (task) {
          task.state = 'TASK_PINNED';
        }
        return store;
      });
    },
+   isError: () => update((store) => ({ ...store, error: true })),
  };
};
export const taskStore = TaskBox();

现在我们已经更新了 store 并包含了新字段。让我们在您的 components 目录中创建 InboxScreen.svelte

复制
src/components/InboxScreen.svelte
<script>
  import TaskList from "./TaskList.svelte";
  export let error = false;
</script>

<div>
  {#if error}
    <div class="page lists-show">
      <div class="wrapper-message">
        <span class="icon-face-sad" />
        <p class="title-message">Oh no!</p>
        <p class="subtitle-message">Something went wrong</p>
      </div>
    </div>
  {:else}
    <div class="page lists-show">
      <nav>
        <h1 class="title-page">Taskbox</h1>
      </nav>
      <TaskList />
    </div>
  {/if}
</div>

我们还需要更改 App 组件以渲染 InboxScreen(最终,我们将使用路由器来选择正确的屏幕,但这里我们先不用担心)

复制
src/App.svelte
<script>
  import InboxScreen from './components/InboxScreen.svelte';
  import { taskStore } from './store';
</script>

<InboxScreen error={$taskStore.error} />

最后,是 src/main.js

复制
src/main.js
- import './app.css';
+ import './index.css';
import App from './App.svelte';

const app = new App({
  target: document.getElementById("app"),
});

export default app;

然而,有趣的地方在于在 Storybook 中渲染故事。

正如我们之前看到的,TaskList 组件是一个容器,它渲染 PureTaskList 演示组件。根据定义,容器组件不能简单地隔离渲染;它们期望传递一些上下文或连接到服务。这意味着要在 Storybook 中渲染容器,我们必须模拟(即,提供一个虚假版本)它需要的上下文或服务。

当将 TaskList 放入 Storybook 时,我们能够通过简单地渲染 PureTaskList 并避免容器来避开这个问题。我们将做类似的事情,并在 Storybook 中也渲染 InboxScreen

因此,当我们在 InboxScreen.stories.js 中设置我们的故事时

复制
src/components/InboxScreen.stories.js
import InboxScreen from './InboxScreen.svelte';

export default {
  component: InboxScreen,
  title: 'InboxScreen',
  tags: ['autodocs'],
};

export const Default = {};

export const Error = {
  args: { error: true },
};

我们看到 ErrorDefault 故事都工作正常。

💡 顺便说一句,向下传递数据层级是一种合理的方法,尤其是在使用 GraphQL 时。这就是我们构建 Chromatic 以及 800 多个故事的方式。

在 Storybook 中循环浏览状态使其易于测试我们是否正确完成了此操作

组件测试

到目前为止,我们已经能够从头开始构建一个功能齐全的应用程序,从一个简单的组件到一个屏幕,并使用我们的故事持续测试每个更改。但是,每个新故事还需要手动检查所有其他故事,以确保 UI 不会崩溃。这需要大量额外的工作。

我们能否自动化此工作流程并自动测试我们的组件交互?

使用 play 函数编写组件测试

Storybook 的 play@storybook/addon-interactions 可以帮助我们实现这一点。play 函数包含在故事渲染后运行的小代码片段。

play 函数帮助我们验证当任务更新时 UI 会发生什么。它使用与框架无关的 DOM API,这意味着我们可以编写带有 play 函数的故事来与 UI 交互并模拟人类行为,而无需考虑前端框架。

@storybook/addon-interactions 帮助我们在 Storybook 中可视化我们的测试,提供逐步流程。它还提供了一组方便的 UI 控件,用于暂停、恢复、倒带和单步执行每个交互。

让我们看看它的实际效果!更新您新创建的 InboxScreen 故事,并通过添加以下内容来设置组件交互

复制
src/components/InboxScreen.stories.js
import InboxScreen from './InboxScreen.svelte';

+ import { fireEvent, within } from '@storybook/test';

export default {
  component: InboxScreen,
  title: 'InboxScreen',
  tags: ['autodocs'],
};

export const Default = {};

export const Error = {
  args: { error: true },
};

+ export const WithInteractions = {
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+    // Simulates pinning the first task
+    await fireEvent.click(canvas.getByLabelText('pinTask-1'));
+    // Simulates pinning the third task
+    await fireEvent.click(canvas.getByLabelText('pinTask-3'));
+  },
+ };

💡 @storybook/test 包取代了 @storybook/jest@storybook/testing-library 测试包,提供更小的捆绑包大小和基于 Vitest 包的更直接的 API。

检查您新创建的故事。单击 Interactions 面板以查看故事的 play 函数内部的交互列表。

使用测试运行器自动化测试

借助 Storybook 的 play 函数,我们能够避开问题,使我们能够与 UI 交互并快速检查如果我们更新任务,UI 会如何响应——在无需额外手动操作的情况下保持 UI 的一致性。

但是,如果我们仔细查看我们的 Storybook,我们可以看到它仅在查看故事时才运行交互测试。因此,如果我们进行更改,我们仍然必须遍历每个故事才能运行所有检查。我们能否自动化它?

好消息是我们可以!Storybook 的 test runner 允许我们做到这一点。它是一个独立的实用程序——由 Playwright 提供支持——它运行我们所有的交互测试并捕获损坏的故事。

让我们看看它是如何工作的!运行以下命令进行安装

复制
yarn add --dev @storybook/test-runner

接下来,更新您的 package.json scripts 并添加新的测试任务

{
  "scripts": {
    "test-storybook": "test-storybook"
  }
}

最后,在您的 Storybook 运行时,打开一个新的终端窗口并运行以下命令

复制
yarn test-storybook --watch

💡 使用 play 函数进行组件测试是测试 UI 组件的绝佳方法。它可以做的事情远不止我们在这里看到的;我们建议阅读官方文档以了解更多信息。

要更深入地了解测试,请查看测试手册。它涵盖了规模化前端团队用于增强您的开发工作流程的测试策略。

Storybook test runner successfully runs all tests

成功!现在我们有了一个工具,可以帮助我们验证是否所有故事都已渲染且没有错误,并且所有断言都自动通过。更重要的是,如果测试失败,它将为我们提供一个链接,该链接会在浏览器中打开失败的故事。

组件驱动开发

我们从底层的 Task 开始,然后逐步发展到 TaskList,现在我们已经到了整个屏幕 UI。我们的 InboxScreen 容纳了一个嵌套容器组件,并包含随附的故事。

组件驱动开发 允许您在组件层次结构中向上移动时逐步扩展复杂性。好处包括更集中的开发过程和所有可能的 UI 排列组合的更高覆盖率。简而言之,CDD 帮助您构建更高质量和更复杂的用户界面。

我们还没有完成 - 当 UI 构建完成时,工作并没有结束。我们还需要确保它随着时间的推移保持持久性。

💡 不要忘记使用 git 提交您的更改!
这个免费指南对您有帮助吗?发推文以示赞赏并帮助其他开发人员找到它。
下一章
部署
了解如何在线部署 Storybook
✍️ 在 GitHub 上编辑 – 欢迎 PR!
加入社区
6,721位开发者及更多
为什么为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

由以下机构维护
Chromatic - Storybook 中文
特别感谢 Netlify CircleCI