Marco Cano
Marco Cano

Reputation: 427

How to make Vue3 test utils work with teleport

I have a component that uses teleport to , the test html doesn't seem to be working as expected. I can't find any documentation on this particular use. Here's my test:

describe('MetaHead', () => {
  it('dynamic metadata tags contain custom text', () => {

    let title = 'My Page';
    let description = 'Some description about my page';
    // This component uses Vue3's teleport to tag <head>
    // we must modify wrapper to contain such tag
    document.body.innerHTML = `
      <head>
        <div id="app"></div>
      </head>
    `

    const wrapper = mount(MetaHead, {
      attachTo: document.getElementById('app'),
      props: { 
        title,
        description
      },
      global:{
        mocks: {
          $route:{fullPath: 'full/path'}
        } 
      }
    })
    expect(wrapper.html()).toContain(title)
    expect(wrapper.html()).toContain(description)
  })
})

and the minimal component looks like this:

<template>
  <teleport to="head">
    <title>{{title}}</title>
    <meta property="og:site_name" :content="title">
    <meta name="description" :content="description">
  </teleport>
</template>

Am I missing something?

Upvotes: 7

Views: 4758

Answers (3)

Federico Sosa
Federico Sosa

Reputation: 1

There is official documentaiton how to handle teleport. It basically adds a beforeEach and afterEach for target element of your teleport.

https://test-utils.vuejs.org/guide/advanced/teleport

  beforeEach(() => {
    // create teleport target
    const el = document.createElement('div')
    el.id = 'target-id'
    document.body.appendChild(el)
  })
  
  afterEach(() => {
    // clean up
    document.body.innerHTML = ''
  })

Upvotes: 0

Ferri Adi Prasetya
Ferri Adi Prasetya

Reputation: 339

Another approach is, you can get element of your destination of teleport in your test.

From your case, you can just get element with id 'app', and get the innerHtml

e.g:

it('dynamic metadata tags contain custom text', () => {

let title = 'My Page';
let description = 'Some description about my page';
// This component uses Vue3's teleport to tag <head>
// we must modify wrapper to contain such tag
document.body.innerHTML = `
  <head>
    <div id="app"></div>
  </head>
`

const wrapper = mount(MetaHead, {
  attachTo: document.getElementById('app'),
  props: { 
    title,
    description
  },
  global:{
    mocks: {
      $route:{fullPath: 'full/path'}
    } 
  }
})
expect(document.getElementById('app')?.innerHTML).toContain(title)
expect(document.getElementById('app')?.innerHTML).toContain(description)
})

Upvotes: 2

lmiller1990
lmiller1990

Reputation: 945

the problem here is wrapper.html() only returns HTML in your component - since you are teleporting outside your component, that markup won't show up when you call wrapper.html().

You have a few options. One would be making an assertion against document.body.outerHTML. Another would be using a neat trick with findComponent, I wrote about it here and posted a video about it here.

Another thing you could try that I just thought of (but have not tested) would be:

mount({
  template: `
    <div id="app" />
    <MetaHead />
  `,
  components: { MetaHead }
})

I don't know if that will work, but worth a try.

Upvotes: 4

Related Questions