Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update advanced.zh-CN.md add testing docs #2131

Open
wants to merge 4 commits into
base: 1.x
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 222 additions & 8 deletions docs/guide/advanced.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ toc: menu
### 1. 初始化 dumi 组件开发项目

```bash
$ mkdir dumi-lib && cd dumi-lib
$ npx @umijs/create-dumi-lib
mkdir dumi-lib && cd dumi-lib
npx @umijs/create-dumi-lib
```

### 2. 为 demo 添加资产元信息
Expand Down Expand Up @@ -134,19 +134,19 @@ export default () => <Foo title="First Demo" />;
如果只是用于测试,可以用 `npm version` 来代替 `npm publish`,随后用 link 进行本地玩耍:

```bash
$ npm run build
$ npm version patch -m "build: bump version to %s"
npm run build
npm version patch -m "build: bump version to %s"
```

### 5. 在 Umi UI 中使用

初始化 Umi 应用,安装 Umi UI 并 link 我们刚刚的组件库:

```bash
$ mkdir umi-app && cd umi-app
$ npx @umijs/create-dumi-app
$ npm i @umijs/preset-ui -D
$ npm link path/to/dumi/lib
mkdir umi-app && cd umi-app
npx @umijs/create-dumi-app
npm i @umijs/preset-ui -D
npm link path/to/dumi/lib
```

在 Umi 应用的 `package.json` 中,手动添加组件库为依赖:
Expand Down Expand Up @@ -239,3 +239,217 @@ dumi 背后的类型解析工具是 `react-docgen-typescript`,更多类型和

和其他内置组件一样,`API` 组件也支持通过 theme API 进行复写,只需要创建 `.dumi/theme/builtins/API.tsx`(本地主题)或者创建一个包含 `API.tsx` 的主题包,结合 `dumi/theme` 暴露的 `useApiData` hook,即可自行控制 API 表格的渲染,可参考 dumi 默认主题的 [API 组件实现](https://github.com/umijs/dumi/blob/1.x/packages/theme-default/src/builtins/API.tsx)。

## 单元测试

采用 Vitest + react-testing-library (RTL) 做为我们的测试框架。目录结构如下:

```
tests
setup.mjs # 测试初始化脚本
src
ComponentA
index.tsx
index.test.tsx # 建议和源文件放一起,好处是让新加入的成员能快速感知到 TDD 意识
...
```

### 环境准备

安装依赖:

```sh
npm install vitest jsdom @testing-library/react @testing-library/jest-dom --save-dev
```

新增文件 tests/setup.mjs,写入以下内容:

```js
// tests/setup.js
// @ts-check
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from "@testing-library/jest-dom/matchers";

expect.extend(matchers); // 对 expect 的能力增强,不用可注释

// 清屏
// 解决单个文件内多个 test 多次 render,后面的 render 会累积前面 render 产生的 DOM 节点问题
afterEach(() => {
cleanup();
});

// 如果遇到 window.matchMedia undefined is not a function 可以开启
// window.matchMedia = vi.fn().mockImplementation((query) => ({
// matches: false,
// media: query,
// onchange: null,
// addListener: vi.fn(),
// removeListener: vi.fn(),
// }))
```

新增 vitest.config.mjs 写入以下内容:

```js
// @ts-check
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
// @ts-expect-error
plugins: [react()],
test: {
setupFiles: './tests/setup.mjs',
environment: 'jsdom',
coverage: {
thresholds: {
branches: 20, // 自行设置合理值
functions: 20,
lines: 20,
},
include: ['src/'], // 只计算 src 内文件覆盖率
},
},

resolve: {
alias: [
{
find: '@',
replacement: '/src', // 如果有设置 tsconfig.json paths 比如 `@`
},
{
find: 'name-in-package.json', // 组件名,package.json 的 name。目的是文档测试
replacement: '/src',
},
],
},
})
```

更新 package.json,新增以下 script:

```json
"test": "vitest",
"ci": "vitest run --coverage",
```

- `test`:本地写单测会用到,将 watch 单测和配置文件达到热更新的效果。
- `ci`:ci 流程会用到或在发布前进行自动化测试,此处会读取 vitest.config.mjs 中设置的 coverage 阈值,如果低于阈值 ci 将失败。

### 书写单测

下面结合例子来说明如何写一个单测。

比如有如下组件,我们想测试是否能正常展示 `Hello React`。

```tsx
// src/App.tsx
import * as React from 'react';

const title = 'Hello React';

function App() {
return <div>{title}</div>;
}

export default App;
```

新增测试文件 `src/App.test.tsx`:

```tsx {9}
import * as React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () => {
it('renders App component', () => {
render(<App />);

expect(screen.getByText('Hello React')).toBeDefined();
});
});
```

再比如我们有一个登录组件,我们想测试在没有输入任何内容时,点击登录按钮会出现提示。

```tsx
expect(screen.queryByText('Please Enter Username / Email.')).toBeNull()
expect(screen.queryByText('Please Enter Password.')).toBeNull()

fireEvent.click(screen.getByRole('button'))

expect(screen.queryByText('Please Enter Username / Email.')).toBeDefined()
expect(screen.queryByText('Please Enter Password.')).toBeDefined()
```

> 注意我们此处用的是 `queryByText`,因为 `getByText` 找不到文本将报错。

更多示例以及关于 `get|queryByText` 等 API 使用和选择可以参考 <https://www.robinwieruch.de/react-testing-library/>。
此处不再详述。

#### 文档测试

如果我们能对 demo 进行单测,那该多好。相当于对用户契约有了自动化保障,类比 Rust 的 documentation test。
> Nothing is better than documentation with examples. But nothing is worse than examples that don't work because the code has changed since the documentation was written.
>
> 来自 <https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#documentation-comments-as-tests>

“没有什么比带有示例的文档更好了。但是,没有什么比示例不起作用更糟糕了,因为自从编写文档以来,代码已经发生了变化。” 给我们的启发是文档中的示例可以帮助理解,但需要确保示例的代码与文档一致,传统做法是定期检查代码与文档的匹配度。我们是否还有更好的手段?那就是**文档测试**。

针对 dumi 而言,文档测试是指我们在 markdown 中写的示例。还记得我们在 vitest.config.mjs 中配置的 alias 吗?

```js
{
find: 'name-in-package.json', // 组件名,package.json 的 name。目的是文档测试
replacement: '/src',
},
```

该配置是让示例代码中 `import { ComponentA } from 'name-in-package.json'` 能正确解析的关键。
举例说明,若我们有如下文档:

```md
// index.md
<code src="./demo/app.tsx"></code>
```

demo 内容为:

```tsx
// demo/app.tsx
import React from 'react'
import { ComponentA } from 'name-in-package.json'

export default () => {
return <ComponentA ... />
}
```

在同目录 demo/ 下新增测试文件 demo/app.`test`.tsx:

```tsx
// demo/app.test.tsx
import * as React from 'react';
import { render, screen } from '@testing-library/react';

import Demo from './demo';

describe('Demo', () => {
it('renders Demo component', () => {
render(<Demo />);

expect(screen.getByText('Hello React')).toBeDefined();
});
});
```

至此我们已完成对一个组件的**单测**和**文档测试**,可以在该组件的 index.md 标题添加单测通过的 tag!

index.md:

```diff
- # ComponentA / 中文标题
+ # ComponentA / 中文标题 <Badge type="success">test passing</Badge>
```