测试 Teleport
Vue 3 带来了一个新的内置组件:<Teleport>,它允许组件将它们的内容“传送”到它们自己的 <template> 之外的更远的地方。大多数使用 Vue Test Utils 编写的测试都作用域到传递给 mount 的组件,这在测试被传送到的组件之外的组件时会带来一些复杂性。
以下是一些使用 <Teleport> 测试组件的策略和技巧。
提示
如果你想测试组件的其余部分,忽略传送,你可以通过在 全局存根选项 中传递 teleport: true 来存根 teleport。
示例
在这个例子中,我们正在测试一个 <Navbar> 组件。它在 <Teleport> 内渲染一个 <Signup> 组件。<Teleport> 的 target 属性是一个位于 <Navbar> 组件之外的元素。
这是 Navbar.vue 组件
<template>
<Teleport to="#modal">
<Signup />
</Teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Signup from './Signup.vue'
export default defineComponent({
components: {
Signup
}
})
</script>它只是将 <Signup> 传送到其他地方。为了这个例子的目的,它很简单。
Signup.vue 是一个表单,它验证 username 是否大于 8 个字符。如果是,则提交时,它会发出一个 signup 事件,并将 username 作为有效负载。测试它将是我们的目标。
<template>
<div>
<form @submit.prevent="submit">
<input v-model="username" />
</form>
</div>
</template>
<script>
export default {
emits: ['signup'],
data() {
return {
username: ''
}
},
computed: {
error() {
return this.username.length < 8
}
},
methods: {
submit() {
if (!this.error) {
this.$emit('signup', this.username)
}
}
}
}
</script>挂载组件
从一个最小的测试开始
import { mount } from '@vue/test-utils'
import Navbar from './Navbar.vue'
import Signup from './Signup.vue'
test('emits a signup event when valid', async () => {
const wrapper = mount(Navbar)
})运行此测试将给出警告:[Vue warn]: Failed to locate Teleport target with selector "#modal"。让我们创建它
import { mount } from '@vue/test-utils'
import Navbar from './Navbar.vue'
import Signup from './Signup.vue'
beforeEach(() => {
// create teleport target
const el = document.createElement('div')
el.id = 'modal'
document.body.appendChild(el)
})
afterEach(() => {
// clean up
document.body.innerHTML = ''
})
test('teleport', async () => {
const wrapper = mount(Navbar)
})我们在这个例子中使用 Jest,它不会在每次测试后重置 DOM。因此,最好在每次测试后使用 afterEach 进行清理。
与传送的组件交互
接下来要做的是填写用户名输入框。不幸的是,我们不能使用 wrapper.find('input')。为什么不呢?一个快速的 console.log(wrapper.html()) 向我们展示了
<!--teleport start-->
<!--teleport end-->我们看到 Vue 使用的一些注释来处理 <Teleport> - 但没有 <input>。这是因为 <Signup> 组件(及其 HTML)不再渲染在 <Navbar> 内 - 它被传送到了外面。
虽然实际的 HTML 被传送到了外面,但事实证明,与 <Navbar> 关联的虚拟 DOM 保持对原始组件的引用。这意味着你可以使用 getComponent 和 findComponent,它们作用于虚拟 DOM,而不是常规 DOM。
beforeEach(() => {
// ...
})
afterEach(() => {
// ...
})
test('teleport', async () => {
const wrapper = mount(Navbar)
wrapper.getComponent(Signup) // got it!
})getComponent 返回一个 VueWrapper。现在你可以使用 get、find 和 trigger 等方法。
让我们完成测试
test('teleport', async () => {
const wrapper = mount(Navbar)
const signup = wrapper.getComponent(Signup)
await signup.get('input').setValue('valid_username')
await signup.get('form').trigger('submit.prevent')
expect(signup.emitted().signup[0]).toEqual(['valid_username'])
})它通过了!
完整的测试
import { mount } from '@vue/test-utils'
import Navbar from './Navbar.vue'
import Signup from './Signup.vue'
beforeEach(() => {
// create teleport target
const el = document.createElement('div')
el.id = 'modal'
document.body.appendChild(el)
})
afterEach(() => {
// clean up
document.body.innerHTML = ''
})
test('teleport', async () => {
const wrapper = mount(Navbar)
const signup = wrapper.getComponent(Signup)
await signup.get('input').setValue('valid_username')
await signup.get('form').trigger('submit.prevent')
expect(signup.emitted().signup[0]).toEqual(['valid_username'])
})你可以通过使用 teleport: true 来存根传送
import { mount } from '@vue/test-utils'
import Navbar from './Navbar.vue'
test('teleport', async () => {
const wrapper = mount(Navbar, {
global: {
stubs: {
teleport: true
}
}
})
})结论
- 使用
document.createElement创建一个传送目标。 - 使用
getComponent或findComponent查找传送的组件,它们作用于虚拟 DOM 层级。