Appearance
测试传送
¥Testing Teleport
Vue 3 带有一个新的内置组件:<Teleport>
,它允许组件对其内容进行 "teleport" 远远超出其自己的 <template>
。使用 Vue Test Utils 编写的大多数测试的范围都是传递给 mount
的组件,这在测试被传送到最初渲染的组件之外的组件时会带来一些复杂性。
¥Vue 3 comes with a new built-in component: <Teleport>
, which allows components to "teleport" their content far outside of their own <template>
. Most tests written with Vue Test Utils are scoped to the component passed to mount
, which introduces some complexity when it comes to testing a component that is teleported outside of the component where it is initially rendered.
以下是使用 <Teleport>
测试组件的一些策略和技术。
¥Here are some strategies and techniques for testing components using <Teleport>
.
提示
如果你想测试组件的其余部分,忽略传送,你可以通过在 全局存根选项 中传递 teleport: true
来对 teleport
进行存根。
¥If you want to test the rest of your component, ignoring teleport, you can stub teleport
by passing teleport: true
in the global stubs option.
示例
¥Example
在此示例中,我们正在测试 <Navbar>
组件。它在 <Teleport>
内部渲染 <Signup>
组件。<Teleport>
的 target
属性是位于 <Navbar>
组件外部的元素。
¥In this example we are testing a <Navbar>
component. It renders a <Signup>
component inside of a <Teleport>
. The target
prop of <Teleport>
is an element located outside of the <Navbar>
component.
这是 Navbar.vue
组件:
¥This is the Navbar.vue
component:
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>
传送到其他地方。对于本示例来说,这很简单。
¥It simply teleports a <Signup>
somewhere else. It's simple for the purpose of this example.
Signup.vue
是一种验证 username
是否大于 8 个字符的形式。如果是,则在提交时,它会触发 signup
事件,并将 username
作为有效负载。测试这将是我们的目标。
¥Signup.vue
is a form that validates if username
is greater than 8 characters. If it is, when submitted, it emits a signup
event with the username
as the payload. Testing that will be our goal.
vue
<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>
挂载组件
¥Mounting the Component
从最小的测试开始:
¥Starting with a minimal test:
ts
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"
。让我们创建它:
¥Running this test will give you a warning: [Vue warn]: Failed to locate Teleport target with selector "#modal"
. Let's create it:
ts
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
进行清理。
¥We are using Jest for this example, which does not reset the DOM every test. For this reason, it's good to clean up after each test with afterEach
.
与传送组件交互
¥Interacting with the Teleported Component
接下来要做的就是填写用户名输入。不幸的是,我们不能使用 wrapper.find('input')
。为什么不?快速的 console.log(wrapper.html())
向我们展示了:
¥The next thing to do is fill out the username input. Unfortunately, we cannot use wrapper.find('input')
. Why not? A quick console.log(wrapper.html())
shows us:
html
<!--teleport start-->
<!--teleport end-->
我们看到 Vue 使用一些注释来处理 <Teleport>
- 但没有 <input>
。这是因为 <Signup>
组件(及其 HTML)不再在 <Navbar>
内部渲染 - 它被传送到了外面。
¥We see some comments used by Vue to handle <Teleport>
- but no <input>
. That's because the <Signup>
component (and its HTML) are not rendered inside of <Navbar>
anymore - it was teleported outside.
尽管实际的 HTML 被传送到外部,但事实证明与 <Navbar>
关联的虚拟 DOM 维护了对原始组件的引用。这意味着你可以使用 getComponent
和 findComponent
,它们在虚拟 DOM 上操作,而不是在常规 DOM 上操作。
¥Although the actual HTML is teleported outside, it turns out the Virtual DOM associated with <Navbar>
maintains a reference to the original component. This means you can use getComponent
and findComponent
, which operate on the Virtual DOM, not the regular DOM.
ts
beforeEach(() => {
// ...
})
afterEach(() => {
// ...
})
test('teleport', async () => {
const wrapper = mount(Navbar)
wrapper.getComponent(Signup) // got it!
})
getComponent
返回 VueWrapper
。现在你可以使用 get
、find
和 trigger
等方法。
¥getComponent
returns a VueWrapper
. Now you can use methods like get
, find
and trigger
.
让我们完成测试:
¥Let's finish the test:
ts
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'])
})
它过去了!
¥It passes!
完整测试:
¥The full test:
ts
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
进行传送:
¥You can stub teleport by using teleport: true
:
ts
import { mount } from '@vue/test-utils'
import Navbar from './Navbar.vue'
test('teleport', async () => {
const wrapper = mount(Navbar, {
global: {
stubs: {
teleport: true
}
}
})
})
结论
¥Conclusion
使用
document.createElement
创建传送目标。¥Create a teleport target with
document.createElement
.查找使用
getComponent
或findComponent
在虚拟 DOM 级别上运行的传送组件。¥Find teleported components using
getComponent
orfindComponent
which operate on the Virtual DOM level.