Skip to content

Commit aff87f8

Browse files
authored
chore: add translation for nextTick part (#1475)
This resolve /#1451
1 parent e2a6986 commit aff87f8

6 files changed

+247
-44
lines changed

Diff for: docs/zh/api/wrapper/emitted.md

+19-15
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@
99
```js
1010
import { mount } from '@vue/test-utils'
1111

12-
const wrapper = mount(Component)
12+
test('emit demo', async () => {
13+
const wrapper = mount(Component)
1314

14-
wrapper.vm.$emit('foo')
15-
wrapper.vm.$emit('foo', 123)
15+
wrapper.vm.$emit('foo')
16+
wrapper.vm.$emit('foo', 123)
1617

17-
/*
18-
`wrapper.emitted() 返回如下对象:
19-
{
20-
foo: [[], [123]]
21-
}
22-
*/
18+
await wrapper.vm.$nextTick() // 等待事件处理完成
2319

24-
// 断言事件已经被触发
25-
expect(wrapper.emitted().foo).toBeTruthy()
20+
/*
21+
wrapper.emitted() 返回如下对象:
22+
{
23+
foo: [[], [123]]
24+
}
25+
*/
2626

27-
// 断言事件的数量
28-
expect(wrapper.emitted().foo.length).toBe(2)
27+
// 断言事件已经被触发
28+
expect(wrapper.emitted().foo).toBeTruthy()
2929

30-
// 断言事件的有效数据
31-
expect(wrapper.emitted().foo[1]).toEqual([123])
30+
// 断言事件的数量
31+
expect(wrapper.emitted().foo.length).toBe(2)
32+
33+
// 断言事件的数量
34+
expect(wrapper.emitted().foo[1]).toEqual([123])
35+
})
3236
```
3337

3438
你也可以把上面的代码写成这样:

Diff for: docs/zh/api/wrapper/trigger.md

+16-12
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,26 @@ import { mount } from '@vue/test-utils'
1616
import sinon from 'sinon'
1717
import Foo from './Foo'
1818

19-
const clickHandler = sinon.stub()
20-
const wrapper = mount(Foo, {
21-
propsData: { clickHandler }
22-
})
19+
test('trigger demo', async () => {
20+
const clickHandler = sinon.stub()
21+
const wrapper = mount(Foo, {
22+
propsData: { clickHandler }
23+
})
2324

24-
wrapper.trigger('click')
25+
wrapper.trigger('click')
2526

26-
wrapper.trigger('click', {
27-
button: 0
28-
})
27+
wrapper.trigger('click', {
28+
button: 0
29+
})
2930

30-
wrapper.trigger('click', {
31-
ctrlKey: true // 用于测试 @click.ctrl 处理函数
32-
})
31+
wrapper.trigger('click', {
32+
ctrlKey: true // 用于测试 @click.ctrl 处理函数
33+
})
3334

34-
expect(clickHandler.called).toBe(true)
35+
await wrapper.vm.$nextTick() // 等待事件处理完成
36+
37+
expect(clickHandler.called).toBe(true)
38+
})
3539
```
3640

3741
- **设置事件目标:**

Diff for: docs/zh/guides/common-tips.md

+144
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,50 @@ const wrapper = shallowMount(Component)
2727
wrapper.vm // 挂载的 Vue 实例
2828
```
2929

30+
### 生命周期钩子
31+
32+
在使用 `mount``shallowMount` 方法时,你可以期望你的组件响应 Vue 所有生命周期事件。但是请务必注意的是,除非使用 `Wrapper.destory()`,否则 `beforeDestroy``destroyed` _将不会触发_
33+
34+
此外组件在每个测试规范结束时并不会被自动销毁,并且将由用户来决定是否要存根或手动清理那些在测试规范结束前继续运行的任务( 例如 `setInterval` 或者 `setTimeout`)。
35+
36+
### 使用 `nextTick` 编写异步测试代码 (新)
37+
38+
默认情况下 Vue 会异步地批量执行更新(在下一轮 tick),以避免不必要的 DOM 重绘或者是观察者计算([查看文档](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue) 了解更多信息)。
39+
40+
这意味着你在更新会引发 DOM 变化的属性后**必须**等待一下。你可以使用 `Vue.nextTick()`
41+
42+
```js
43+
it('updates text', async () => {
44+
const wrapper = mount(Component)
45+
wrapper.trigger('click')
46+
await Vue.nextTick()
47+
expect(wrapper.text()).toContain('updated')
48+
})
49+
50+
// 或者你不希望使用async/await
51+
it('render text', done => {
52+
const wrapper = mount(TestComponent)
53+
wrapper.trigger('click')
54+
Vue.nextTick(() => {
55+
wrapper.text().toContain('some text')
56+
wrapper.trigger('click')
57+
Vue.nextTick(() => {
58+
wrapper.text().toContain('some different text')
59+
done()
60+
})
61+
})
62+
})
63+
```
64+
65+
下面的方法通常会导致观察者更新,你需要等待下一轮 tick:
66+
67+
- `setChecked`
68+
- `setData`
69+
- `setSelected`
70+
- `setProps`
71+
- `setValue`
72+
- `trigger`
73+
3074
### 断言触发的事件
3175

3276
每个挂载的包裹器都会通过其背后的 Vue 实例自动记录所有被触发的事件。你可以用 `wrapper.emitted()` 方法取回这些事件记录。
@@ -136,6 +180,106 @@ mount(Component, {
136180

137181
_想查阅所有选项的完整列表,请移步该文档的[挂载选项](../api/options.md)章节。_
138182

183+
### 仿造 Transitions
184+
185+
尽管在大多数情况下使用 `await Vue.nextTick()` 效果很好,但是在某些情况下还需要一些额外的工作。这些问题将在 `vue-test-utils` 移出 beta 版本之前解决。其中一个例子是 Vue 提供的带有 `<transition>` 包装器的单元测试组件。
186+
187+
```vue
188+
<template>
189+
<div>
190+
<transition>
191+
<p v-if="show">Foo</p>
192+
</transition>
193+
</div>
194+
</template>
195+
196+
<script>
197+
export default {
198+
data() {
199+
return {
200+
show: true
201+
}
202+
}
203+
}
204+
</script>
205+
```
206+
207+
您可能想编写一个测试用例来验证是否显示了文本 Foo ,在将 `show` 设置为 `false` 时,不再显示文本 Foo。测试用例可以这么写:
208+
209+
```js
210+
test('should render Foo, then hide it', async () => {
211+
const wrapper = mount(Foo)
212+
expect(wrapper.text()).toMatch(/Foo/)
213+
214+
wrapper.setData({
215+
show: false
216+
})
217+
await wrapper.vm.$nextTick()
218+
219+
expect(wrapper.text()).not.toMatch(/Foo/)
220+
})
221+
```
222+
223+
实际上,尽管我们调用了 `setData` 方法,然后等待 `nextTick` 来确保 DOM 被更新,但是该测试用例仍然失败了。这是一个已知的问题,与 Vue 中 `<transition>` 组件的实现有关,我们希望在 1.0 版之前解决该问题。在目前情况下,有一些解决方案:
224+
225+
#### 使用 `transitionStub`
226+
227+
```js
228+
const transitionStub = () => ({
229+
render: function(h) {
230+
return this.$options._renderChildren
231+
}
232+
})
233+
234+
test('should render Foo, then hide it', async () => {
235+
const wrapper = mount(Foo, {
236+
stubs: {
237+
transition: transitionStub()
238+
}
239+
})
240+
expect(wrapper.text()).toMatch(/Foo/)
241+
242+
wrapper.setData({
243+
show: false
244+
})
245+
await wrapper.vm.$nextTick()
246+
247+
expect(wrapper.text()).not.toMatch(/Foo/)
248+
})
249+
```
250+
251+
上面的代码重写了 `<transition>` 组件的默认行为,并在在条件发生变化时立即呈现子元素。这与 Vue 中 `<transition>` 组件应用 CSS 类的实现是相反的。
252+
253+
#### 避免 `setData`
254+
255+
另一种选择是通过编写两个测试来简单地避免使用 `setData`,这要求我们在使用 `mount` 或者 `shallowMount` 时需要指定一些  选项:
256+
257+
```js
258+
test('should render Foo', async () => {
259+
const wrapper = mount(Foo, {
260+
data() {
261+
return {
262+
show: true
263+
}
264+
}
265+
})
266+
267+
expect(wrapper.text()).toMatch(/Foo/)
268+
})
269+
270+
test('should not render Foo', async () => {
271+
const wrapper = mount(Foo, {
272+
data() {
273+
return {
274+
show: false
275+
}
276+
}
277+
})
278+
279+
expect(wrapper.text()).not.toMatch(/Foo/)
280+
})
281+
```
282+
139283
### 应用全局的插件和混入
140284

141285
有些组件可能依赖一个全局插件或混入 (mixin) 的功能注入,比如 `vuex``vue-router`

Diff for: docs/zh/guides/getting-started.md

+25-6
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,29 @@ it('button click should increment the count', () => {
102102
})
103103
```
104104

105-
### 关于 `nextTick` 怎么办?
105+
为了测试计数器中的文本是否已经更新,我们需要了解 `nextTick`
106106

107-
Vue 会异步的将未生效的 DOM 更新批量应用,以避免因数据反复突变而导致的无谓的重渲染。这也是为什么在实践过程中我们经常在触发状态改变后用 `Vue.nextTick` 来等待 Vue 把实际的 DOM 更新做完的原因。
107+
### 使用 `nextTick`
108108

109-
为了简化用法,Vue Test Utils 同步应用了所有的更新,所以你不需要在测试中使用 `Vue.nextTick` 来等待 DOM 更新
109+
Vue 会异步的将未生效的 DOM 批量更新,避免因数据反复变化而导致不必要的渲染
110110

111-
_注意:当你需要为诸如异步回调或 Promise 解析等操作显性改进为事件循环的时候,`nextTick` 仍然是必要的_
111+
_你可以阅读[Vue 文档](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)了解更多关于异步指更新的信息_
112112

113-
如果你仍然需要在自己的测试文件中使用 `nextTick`,注意任何在其内部被抛出的错误可能都不会被测试运行器捕获,因为其内部使用了 Promise。关于这个问题有两个建议:要么你可以在测试的一开始将 Vue 的全局错误处理器设置为 `done` 回调,要么你可以在调用 `nextTick` 时不带参数让其作为一个 Promise 返回:
113+
更新会引发 DOM 变化的属性后,我们需要使用 `Vue.nextTick()` 等待 Vue 完成 DOM 更新。
114+
115+
在编写测试代码时,我们可以在异步函数里使用`await` `Vue.nextTick()`
116+
117+
```js
118+
it('button click should increment the count text', async () => {
119+
expect(wrapper.text()).toContain('0')
120+
const button = wrapper.find('button')
121+
button.trigger('click')
122+
await Vue.nextTick()
123+
expect(wrapper.text()).toContain('1')
124+
})
125+
```
126+
127+
当你在测试代码中使用 `nextTick` 时,请注意任何在其内部被抛出的错误可能都不会被测试运行器捕获,因为其内部使用了 Promise。关于这个问题有两个建议:要么你可以在测试的一开始将 Vue 的全局错误处理器设置为 `done` 回调,要么你可以在调用 `nextTick` 时不带参数让其作为一个 Promise 返回:
114128

115129
```js
116130
// 这不会被捕获
@@ -121,7 +135,7 @@ it('will time out', done => {
121135
})
122136
})
123137

124-
// 接下来的两项测试都会如预期工作
138+
// 接下来的三项测试都会如预期工作
125139
it('will catch the error using done', done => {
126140
Vue.config.errorHandler = done
127141
Vue.nextTick(() => {
@@ -135,6 +149,11 @@ it('will catch the error using a promise', () => {
135149
expect(true).toBe(false)
136150
})
137151
})
152+
153+
it('will catch the error using async/await', async () => {
154+
await Vue.nextTick()
155+
expect(true).toBe(false)
156+
})
138157
```
139158

140159
### 下一步是什么

Diff for: docs/zh/guides/testing-async-components.md

+41-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,49 @@
11
## 测试异步行为
22

3-
为了让测试变得简单,`@vue/test-utils` *同步*应用 DOM 更新。不过当测试一个带有回调或 Promise 等异步行为的组件时,你需要留意一些技巧。
3+
在编写测试代码时你将会遇到两种异步行为:
44

5-
API 调用和 Vuex action 都是最常见的异步行为之一。下列例子展示了如何测试一个会调用到 API 的方法。这个例子使用 Jest 运行测试用例同时模拟了 HTTP 库 `axios`。更多关于 Jest 的手动模拟的介绍可移步[这里](https://jestjs.io/docs/zh-Hans/manual-mocks)
5+
1. 来自 Vue 的更新
6+
2. 来自外部行为的更新
67

7-
`axios` 的模拟实现大概是这个样子的:
8+
## 来自 Vue 的更新
9+
10+
Vue 会异步的将未生效的 DOM 批量更新,避免因数据反复变化而导致不必要的渲染。
11+
12+
_你可以阅读[Vue 文档](https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)了解更多关于异步指更新的信息。_
13+
14+
在实践中,往往意味着你在更新会引发 DOM 变化的属性后必须使用 `Vue.nextTick()` 来等待 Vue 完成 DOM 更新。
15+
16+
使用 `Vue.nextTick()` 最简单的方法是在你的测试代码中使用异步函数:
17+
18+
```js
19+
// 在文件头部引用Vue库
20+
import Vue from 'vue'
21+
22+
// 其它的代码片断...
23+
24+
// 在测试框架中,编写一个测试用例
25+
it('button click should increment the count text', async () => {
26+
expect(wrapper.text()).toContain('0')
27+
const button = wrapper.find('button')
28+
button.trigger('click')
29+
await Vue.nextTick()
30+
expect(wrapper.text()).toContain('1')
31+
})
32+
```
33+
34+
## 来自外部行为的更新
35+
36+
在 Vue 之外最常见的一种异步行为就是在 Vuex 中进行 API 调用。以下示例将展示如何测试在 Vuex 中进行 API 调用的方法。本示例使用 Jest 运行测试并模拟 HTTP 库`axios`。可以在[这里](https://jestjs.io/docs/en/manual-mocks.html#content)找到有关 Jest Mock 的更多信息。
37+
38+
`axios` Mock 的实现如下所示:
839

940
```js
1041
export default {
1142
get: () => Promise.resolve({ data: 'value' })
1243
}
1344
```
1445

15-
下面的组件在按钮被点击的时候会调用一个 API,然后将响应的值赋给 `value`
46+
当按钮被点击时,组件将会产生一个 API 调用,并且将响应的返回内容赋值给 `value`
1647

1748
```html
1849
<template>
@@ -39,7 +70,7 @@ export default {
3970
</script>
4071
```
4172

42-
测试用例可以写成像这样
73+
可以这样编写测试
4374

4475
```js
4576
import { shallowMount } from '@vue/test-utils'
@@ -53,7 +84,7 @@ it('fetches async when a button is clicked', () => {
5384
})
5485
```
5586

56-
现在这则测试用例会失败,因为断言在 `fetchResults` 中的 Promise 完成之前就被调用了。大多数单元测试库都提供一个回调来使得运行期知道测试用例的完成时机。Jest 和 Mocha 都是用了 `done`。我们可以和 `$nextTick``setTimeout` 结合使用 `done` 来确保任何 Promise 都会在断言之前完成
87+
上面的代码代码会执行失败,这是因为我们在 `fetchResults` 方法执行完毕前就对结果进行断言。绝大多数单元测试框架都会提供一个回调来通知你测试将在何时完成。Jest 和 Mocha 都使用`done` 这个方法。我们可以将 `done``$nextTick``setTimeout` 结合使用,以确保在进行断言前已经处理完所有的 Promise 回调
5788

5889
```js
5990
it('fetches async when a button is clicked', done => {
@@ -66,11 +97,11 @@ it('fetches async when a button is clicked', done => {
6697
})
6798
```
6899

69-
`setTimeout` 允许测试通过的原因是 Promise 回调的 microtask 队列会在处理 `setTimeout` 的回调的任务队列之前先被处理。也就是说在 `setTimeout` 的回调运行的时候,任何 microtask 队列上的 Promise 回调都已经执行过了。另一方面 `$nextTick` 会安排一个 microtask,但是因为 microtask 队列的处理方式是先进先出,所以也会保证回调在作出断言时已经被执行。更多的解释请移步[这里](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/)
100+
setTimeout 也可以使测试通过的原因是,Promise 回调的微任务队列会排在 setTimeout 回调的微任务队列之前。这意味着当 setTimeout 回调执行时,微任务队列上的所有 Promise 回调已经被执行过了。另一方面`$nextTick` 也存在调度微任务的情况,但是由于微任务队列是先进先出的,因此也保证了在进行断言时已经处理完所有的 Promise 回调。请参阅[此处]https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/)了解更多详细说明
70101

71-
另一个解决方案是使用一个 `async` 函数配合 npm `flush-promises``flush-promises` 会清除所有等待完成的 Promise 句柄。你可以 `await` `flushPromiese` 调用,以此清除等待中的 Promise 并改进你的测试用例的可读性
102+
另外一个使用 `async` 方法的解决方案是使用 npm 仓库中的 `flush-promises``flush-promises` 会刷新所有处于 pending 状态或 resolved 状态的 Promise。你可以用 `await` 语句来等待 `flushPromises` 刷新 Promise 的状态,这样可以提升你代码的可读性
72103

73-
更新后的测试看起来像这样
104+
修改以后的测试代码
74105

75106
```js
76107
import { shallowMount } from '@vue/test-utils'
@@ -86,4 +117,4 @@ it('fetches async when a button is clicked', async () => {
86117
})
87118
```
88119

89-
相同的技巧可以被运用在同样默认返回一个 Promise 的 Vuex action 中
120+
相同的技术细节也可以应用在处理 Vue Actions 上,默认情况下,它也会返回一个 Promise

0 commit comments

Comments
 (0)