使用storybook管理React元件
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"> :grinning: :sunglasses: :+1: :100: </span> </Button> ), { notes: { markdown: docs }, });
- 執行
npm run storybook
,這時啟動一個server,並自動開啟一個storybook的頁面
2. 使用storybook的外掛功能
storybook的很多功能都是靠外掛來實現的,大多數外掛都需要提前註冊,在頁面中有一個單獨的tab來對storybook進行增強。
下面介紹幾款官方外掛:
// /.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外掛來實現,其核心是使用 ofollow,noindex">Jest ,原理是每次生成一份DOM結構文件(類似於html原始碼),可以無痛整合到元件測試中。
對於React專案,需額外安裝如下npm包:
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包:
npm i -D enzyme enzyme-adapter-react-16 expect storybook-addon-specifications
在 storybook/config.js 中配置enzyme
import { configure as enzymeConfigure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; enzymeConfigure({ adapter: new Adapter() });
在 stories/test.js 中編寫測試用例:
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,比較慢要耐心等候)
npm install --save-dev jest puppeteer jest-puppeteer jest-image-snapshot start-server-and-test
然後新增一些檔案到 integration 目錄下:
// 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命令:
"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. 參考連結
7. 寫在最後
本文是作者學習storybook的一些總結,總體感覺是接入成本不算高,但是模組包版本安裝可能會有一些坑,但收穫是給元件的管理、文件和測試提供了一個一體化的解決方案,還是很值得的。
PS:文中所涉及的demo已放入Github倉庫 storybook-react 。