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

docs: add missing testing doc #2132

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion docs/guide/auto-api-table.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: 自动 API 表格
group: 进阶
order: 2
order: 4
---

# 自动 API 表格<Badge>实验性</Badge>
Expand All @@ -12,6 +12,7 @@ order: 2
- API 表格仅支持展示解析出的顶层属性
- `@description` 不支持多语言配置
- Windows 操作系统上会工作不正常,目前不建议使用

:::

dumi 支持基于 JSDoc 及 TypeScript 类型定义自动为 React 组件生成 API 表格,降低 API 文档的维护成本。
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/mobile-library.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
group: 进阶
order: 2
order: 3
---

# 移动端组件研发
Expand All @@ -10,7 +10,7 @@ order: 2
与 dumi 1 一样,只需要安装移动端组件研发主题即可切换到移动端组件研发模式:

```bash
$ npm i dumi-theme-mobile@^2.0.0 -D
npm i dumi-theme-mobile@^2.0.0 -D
```

然后可以通过 `themeConfig` 配置该主题包的行为:
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/page-tab.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
group: 进阶
order: 1
order: 2
---

# 页面 Tab
Expand Down
227 changes: 227 additions & 0 deletions docs/guide/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
---
title: 单元测试
group: 进阶
order: 1
---

## 单元测试

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

```text
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>
```

当然也可以增加类似 npm 的 badge:

```text
[![dumi](https://img.shields.io/badge/test%20-passing-green)](https://github.com/umijs/dumi)
```
Loading