构建一个简单的组件

    是我们的应用程序的核心组件. 每个任务的显示略有不同,具体取决于它所处的状态-state. 我们显示一个选中 (或未选中) 复选框,一些有关任务的信息,以及一个"pin"按钮,允许我们在列表中上下移动任务. 为了把各个它们摆在一起,我们需要下面的 props:

    • title - 描述任务的字符串

    • state - 哪个列表是当前的任务,是否已检查?

    在我们开始构建Task时,我们首先编写 与 上面草图中不同类型的任务 相对应的测试状态. 然后我们使用 Storybook 模拟数据 隔离对应状态组件. 我们将"视觉测试"组件在每个状态下的外观.

    这个过程类似于,我们可以称之为"Visual-虚拟 TDD"

    获取设置

    首先,让我们创建任务组件 及 其附带的 storybook-故事 文件:

    src/components/Task.jssrc/components/Task.stories.js

    我们将从基本实现开始,简单传入我们需要的属性-props 以及 您可以对任务执行的两个on操作 (在列表之间移动它) :

    上面,我们基于 Todos应用程序现有HTML结构 为 Task提供简单的 markup .

    下面, 我们在 故事文件中 构建 Task的 三个测试状态:

    1. import React from 'react';
    2. import { storiesOf } from '@storybook/react';
    3. import { action } from '@storybook/addon-actions';
    4. import Task from './Task';
    5. export const task = {
    6. id: '1',
    7. title: 'Test Task',
    8. state: 'TASK_INBOX',
    9. updatedAt: new Date(2018, 0, 1, 9, 0),
    10. export const actions = {
    11. onPinTask: action('onPinTask'),
    12. onArchiveTask: action('onArchiveTask'),
    13. };
    14. .add('default', () => <Task task={task} {...actions} />)
    15. .add('pinned', () => <Task task={{ ...task, state: 'TASK_PINNED' }} {...actions} />)
    16. .add('archived', () => <Task task={{ ...task, state: 'TASK_ARCHIVED' }} {...actions} />);

    Storybook中有两个基本的组织级别.

    将每个故事 视为组件的排列. 您可以根据需要为每个组件 创建 尽可能多的故事.

    • 组件

      • 故事
      • 故事
      • 故事要开始 Storybook,我们先运行 注册组件的storiesOf()`函数. 我们为组件添加 显示名称 - Storybook应用程序侧栏上显示的名称.

    action()允许我们创建一个回调, 当在Storybook UI的面板中 单击这个 action 时 回调触发. 因此,当我们构建一个pin按钮 时,我们将能够在 测试UI中 确定按钮单击 是否成功.

    由于我们需要将 相同的一组操作 传递给 组件的所有排列,因此将它们捆绑到actions变量 并 使用React{…actions}porps扩展以立即传递它们. <Task {…actions}>相当于<Task onPinTask={actions.onPinTask} onArchiveTask={actions.onArchiveTask}>.

    关于捆绑actions的另一个好处就是,你可以export-暴露它们,用于重用该组件的组件,我们稍后会看到.

    为了定义我们的故事,我们用add(),一次一个为我们的每个测试状态生成一个故事. add第二个参数是一个函数,它返回一个给定状态的渲染元素 (即带有一组props的组件类) - 就像一个React.

    在创建故事时,我们使用基本任务 (task) 构建组件期望的 任务的形状. 这通常是 根据真实数据的模型建模的. 再次,正如我们所看到的,export这种形状将使我们能够在以后的故事中重复使用它.

    Actions 帮助您在隔离构建UI组件时 验证交互. 通常,您无法访问应用程序上下文中的函数和状态。 使用 action() 将它们存入.

    我们还必须对 Storybook的配置设置 (.storybook/config.js) 做一个小改动,让它注意到我们的.stories.js文件并使用我们的CSS文件. 默认情况下, Storybook 会查找故事/stories目录; 本教程使用类似于.test.js的命名方案, 这个命令是 CRA 赞成的用于自动化测试的方案.

    完成此操作后,重新启动 Storybook服务器 应该会产生 三个任务状态的测试用例:

    建立状态

    现在我们有 Storybook设置,导入的样式和构建的测试用例,我们可以快速开始实现组件的HTML,以匹配设计.

    该组件目前仍然是基本的. 首先编写实现设计的代码,不用过多细节:

    1. import React from 'react';
    2. export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
    3. return (
    4. <div className={`list-item ${state}`}>
    5. <input
    6. defaultChecked={state === 'TASK_ARCHIVED'}
    7. disabled={true}
    8. name="checked"
    9. />
    10. <span className="checkbox-custom" onClick={() => onArchiveTask(id)} />
    11. </label>
    12. <div className="title">
    13. <input type="text" value={title} readOnly={true} placeholder="Input title" />
    14. </div>
    15. <div className="actions" onClick={event => event.stopPropagation()}>
    16. {state !== 'TASK_ARCHIVED' && (
    17. <a onClick={() => onPinTask(id)}>
    18. <span className={`icon-star`} />
    19. </a>
    20. )}
    21. </div>
    22. </div>
    23. );
    24. }

    最好的做法是propTypes在React中 指定组件所需的 数据形状. 它不仅可以自我记录,还有助于及早发现问题.

    现在,如果任务组件被滥用,则会出现开发警告.

    另一种实现方法是使用 类似TypeScript的JavaScript类型系统 来为组件属性 创建类型。

    组件构建!

    我们现在已成功构建了一个组件,没用到服务器或运行整个前端应用程序. 下一步是以类似的方式逐个构建剩余的 Taskbox组件.

    如您所见,开始单独构建组件非常简单快捷. 我们可以期望生成更高质量的UI,减少错误和更多打磨,因为它可以挖掘并测试每个可能的状态.

    Storybook 为我们提供了一种在施工期间,可视化测试我们的应用程序.在我们继续开发应用程序时,"故事"将有助于确保我们不会在视觉上打破我们的任务.但是,在这个阶段,这是一个完全手动的过程,有人必须努力点击每个测试状态,并确保它呈现良好且没有错误或警告.我们不能自动这样做吗?

    快照测试是指,记录 带一定输入的组件的"已知良好"输出,然后,将来 输出发生变化时标记组件 的做法.这补充了 Storybook,因为快照 是查看 组件新版本 并 检查更改的快速方法.

    确保您的组件呈现 不变 的数据,以便每次快照测试都不会失败。 注意日期或随机生成的值等内容。

    需要为每个故事创建 快照测试.通过添加开发依赖项来使用它:

    1. yarn add --dev @storybook/addon-storyshots react-test-renderer

    然后创建一个src/storybook.test.js文件中包含以下内容:

    完成上述操作后,我们就可以运行了yarn test并看到以下输出:

    Task test runner