本文作者:IMWeb Terrance 原文出处:IMWeb社区 未经同意,禁止转载
Storybook is a development environment for UI components. It allows you to browse a component library, view the different states of each component, and interactively develop and test components.
2018年10月storybook发布了4.0版本,在UI层支持、构建、移动端、stroy参数等多个方面进行了升级优化。本文已React的UI组件为例,演示如何新建/集成Storybook到项目中,并对UI组件进行全方位的管理,包括发布、demo文档、测试等。
1. 新建一个Storybook React项目
- 按照官方教程使用
npx -p [@storybook](/user/storybook)/cli sb init
安装,一直会报错:
TypeError: Cannot create property 'dependencies' on boolean 'false'
- 我采用的是手动创建的方式
- 首先在React项目中手动添加@storybook/react和babel依赖和运行脚本
"scripts": {
"storybook": "start-storybook -p 9001 -c .storybook"
}
"devDependencies": {
"[@storybook](/user/storybook)/addon-actions": "^4.0.11",
"[@storybook](/user/storybook)/react": "^4.0.11",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5"
},
"dependencies": {
"react": "^16.6.3",
"react-dom": "^16.6.3"
}
PS:由于babel-loader的最新版本是v8,需要babel版本是v7,所以按照官方教程直接安装babel-core(最高版本是v6)运行会失败,这里选择安装的是babel6。
- 添加storybook配置文件
import { configure, addDecorator } from '[@storybook](/user/storybook)/react';
function loadStories() {
require('../stories/index.js');
// You can require as many stories as you need.
}
configure(loadStories, module);
- 添加story
// /stories/index.js
import React from 'react';
import { storiesOf } from '[@storybook](/user/storybook)/react';
import { Button } from '[@storybook](/user/storybook)/react/demo';
storiesOf('Button', module)
.addDecorator(story => <div style={{ textAlign: 'center' }}>{story()}</div>)
.add('with text abc', () => <Button onClick={action('clicked')}>hello world!</Button>, {
notes: { markdown: docs },
})
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
? ? ? ?
</span>
</Button>
), {
notes: { markdown: docs },
});
- 运行
npm run storybook
,这时启动一个server,并自动打开一个storybook的页面
2. 使用storybook的插件功能
storybook的很多功能都是靠插件来实现的,大多数插件都需要提前注册,在页面中有一个单独的tab来对storybook进行增强。
下面介绍几款官方插件:
代码语言:javascript复制// /.storybook/addons.js
import '[@storybook](/user/storybook)/addon-actions/register'; // 记录事件日志
import '[@storybook](/user/storybook)/addon-notes/register'; // story笔记文档,支持markdown
import '[@storybook](/user/storybook)/addon-options/register'; // storybook页面自定义
import '[@storybook](/user/storybook)/addon-links/register'; // storybook页面跳转
import '[@storybook](/user/storybook)/addon-knobs/register'; // 组件可视化配置
@storybook/addon-info插件比较特殊,不需要提前注册,它可以显示story的源码,并针对props提供一些文档。
3. 以一个分页组件为例
从团队的stoneUI组件库直接移植过来
- 将Pagination、IconV组件源码放入components目录;
- 编写story:
import React from 'react';
import { storiesOf } from '[@storybook](/user/storybook)/react';
import { withKnobs, number } from '[@storybook](/user/storybook)/addon-knobs';
import Pagination from '../components/Pagination';
import paginationDoc from '../components/Pagination/readme.md';
storiesOf('Stone UI', module)
.addDecorator(story => <div style={{ marginTop: '50px' }}>{story()}</div>)
.addDecorator(withKnobs)
.add('Pagination', () => {
const totalPage = number('totalPage', 100);
const currentPage = number('currentPage', 45);
const maxDisplayNumber = number('maxDisplayNumber', 4);
return (<Pagination totalPage={totalPage} page={currentPage - 1} maxDisplayNumber={maxDisplayNumber} />);
}, {
notes: { markdown: paginationDoc },
});
- 运行效果如下:
4. 测试UI组件
4.1 写测试用例的原因
- 找到bug
- 新修改没有改变已有的接口和功能
- 将测试用例作为文档
4.2 测试结构
使用storyshots插件来实现,其核心是使用Jest,原理是每次生成一份DOM结构文档(类似于html源码),可以无痛集成到组件测试中。
对于React项目,需额外安装如下npm包:
代码语言:javascript复制npm i -D [@storybook](/user/storybook)/addon-storyshots jest react-test-renderer
新建一个测试文件storyshots.test.js
(路径随意,以.test.js结尾即可)
import initStoryshots from '[@storybook](/user/storybook)/addon-storyshots';
initStoryshots({ /* configuration options */ });
在控制台运行npm test
即可(在package.json中配置好scripts:"test": "jest"
),测试完成后会在storyshots.test.js
生成一个stories/index.js对应的DOM快照。
PS:下次运行Jest时,只有DOM结构与上次完全一致测试才会通过,通常会有两种方法来解决这种情况:
- 找到问题,修复不同;
- 用新的DOM结构替换旧的。
4.3 测试交互
storybook交互性测试可以使用 Enzyme来模拟用户输入,然后使用Mocha or Jest来进行结果测试,storybook又一个专门的插件帮助我们集成他们:specifications。
首先,需要安装如下npm包:
代码语言:javascript复制npm i -D enzyme enzyme-adapter-react-16 expect storybook-addon-specifications
在storybook/config.js中配置enzyme
代码语言:javascript复制import { configure as enzymeConfigure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
enzymeConfigure({ adapter: new Adapter() });
在stories/test.js中编写测试用例:
代码语言:javascript复制import React from 'react';
import { storiesOf } from '[@storybook](/user/storybook)/react';
import { specs, describe, it } from 'storybook-addon-specifications';
import { mount } from 'enzyme';
import expect from 'expect';
storiesOf('Interaction test', module)
.add('Button test', () => {
const story = (
<button onClick={action('Hello World')}>
Hello World
</button>
);
specs(() => describe('Hello World', () => {
it('Should have the Hello World label', () => {
const output = mount(story);
expect(output.text()).toContain('Hello World');
});
}));
return story;
});
在组件挂载后,通过断言来测试UI组件的属性,更多使用方法可以参考specifications插件的使用。
4.4 测试样式
样式测试这里采用Puppeteer 和Jest来实现,其原理是利用Puppeteer的无头的chrome浏览器和storybook的url绑定组件特点,来渲染不同的UI组件,再进行图片快照的对比。
首先安装几个npm包:(puppeteer默认会下载Chromium,比较慢要耐心等候)
代码语言:javascript复制npm install --save-dev jest puppeteer jest-puppeteer jest-image-snapshot start-server-and-test
然后添加一些文件到integration目录下:
代码语言:javascript复制// integration/jest.config.js
module.exports = {
preset: 'jest-puppeteer',
testRegex: './*\.test\.js$',
setupTestFrameworkScriptFile: './setupTests.js',
};
// integration/setupTests.js
import { toMatchImageSnapshot } from 'jest-image-snapshot';
expect.extend({ toMatchImageSnapshot });
// integration/Button.test.js
describe('Button', () => {
it('visually looks correct', async () => {
// APIs from jest-puppeteer
await page.goto('http://localhost:9009/iframe.html?selectedKind=Button&selectedStory=with text');
const image = await page.screenshot();
// API from jest-image-snapshot
expect(image).toMatchImageSnapshot();
});
});
然后在package.json中添加两个scripts命令:
代码语言:javascript复制"jest:integration": "jest -c integration/jest.config.js",
"test:integration": "start-server-and-test storybook http-get://localhost:9009 jest:integration",
第一次运行npm run test:integration
可以生成UI组件渲染的一次快照,再次运行会将新旧快照进行对比,只有完全一致才能测试通过。
PS:测试不通过时,运行npm run jest:integration
将强制更新原有快照。
4.5 手动测试
再好的自动化测试,都和人的体验存在差距,所以发布之前还是需要经过人眼测试,因为storybook活文档的特点,我们可以直接运行体验UI组件,通过交互操作、knobs插件等来进行全面体验。
5. 包管理
使用lerna进行包管理和发布。
6. 参考链接
- Storybook 4.0 is here!
- Storybook Github源码
- Storybook 官方网站
- Jest官方文档
- puppeteer官方文档
7. 写在最后
本文是作者学习storybook的一些总结,总体感觉是接入成本不算高,但是模块包版本安装可能会有一些坑,但收获是给组件的管理、文档和测试提供了一个一体化的解决方案,还是很值得的。
PS:文中所涉及的demo已放入Github仓库storybook-react。