Skip to content

测试传送

¥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 维护了对原始组件的引用。这意味着你可以使用 getComponentfindComponent,它们在虚拟 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。现在你可以使用 getfindtrigger 等方法。

¥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.

  • 查找使用 getComponentfindComponent 在虚拟 DOM 级别上运行的传送组件。

    ¥Find teleported components using getComponent or findComponent which operate on the Virtual DOM level.

Vue Test Utils 中文网 - 粤ICP备13048890号