Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to test teleport, if subcomponents not a vue component #798

Closed
Yijx opened this issue Jul 27, 2021 · 30 comments
Closed

How to test teleport, if subcomponents not a vue component #798

Yijx opened this issue Jul 27, 2021 · 30 comments
Labels
discussion enhancement New feature or request

Comments

@Yijx
Copy link

Yijx commented Jul 27, 2021

I have a vue component like this

<template>
  <teleport to="body">
     <div class="hello">
    </div>
  </teleport>
</template>

It is different from https://next.vue-test-utils.vuejs.org/guide/advanced/teleport.html
I can use document.body.querySelect('.hello'), it is work.
but wrapper.find or wrapper.findComponent, they are not work.
so, how can i test this component, it is a same question in stackoverflow https://stackoverflow.com/questions/66923194/how-to-make-vue3-test-utils-work-with-teleport

@lmiller1990
Copy link
Member

Good question, I posted some of my ideas on that SO thread: https://stackoverflow.com/questions/66923194/how-to-make-vue3-test-utils-work-with-teleport/68552979#68552979

I don't have a great solution right now. You could make an assertion against the document.body, that should work. It's not the cleanest solution, though.

I'll think about this a bit more and try to add something to Test Utils to handle this scenario. Do you have any ideas?

@lmiller1990 lmiller1990 added discussion enhancement New feature or request labels Jul 28, 2021
@dospunk
Copy link

dospunk commented Aug 11, 2021

I have a similar issue right now, I'm trying to test a component using headlessui's Dialog component which uses Teleport, but Dialog does not expose its target, so there is no way to manually create a target for it as suggested in the docs

@lmiller1990
Copy link
Member

I tried to find a solution for teleport and headlessui. They hav some tests, which are kind of complex but worth looking at: https://github.com/tailwindlabs/headlessui/blob/d25f80ae9d896446c2b7119a70cfd5d99e250050/packages/%40headlessui-vue/src/components/dialog/dialog.ts#L98

Due to how they manage the <teleport> internally, I think we need to use attachTo and query document, no wrapper.html().

Here's a quick example:

  it.only('works with headlessui', async () => {
    // @ts-ignore
    window.IntersectionObserver = class {
    // @ts-ignore
      observe () {}
    }
    const Comp = defineComponent({
      components: { Dialog, DialogOverlay, DialogTitle, PortalGroup },
      template: `
        <button @click="isOpen = true">Open</button>
        <Dialog :open="isOpen" @close="close" to="#id">
          <DialogTitle>Dialog title</DialogTitle>
          <DialogOverlay />
          <input />
        </Dialog>
      `,
      setup () {
        const isOpen = ref(true)
        return {
          isOpen,
          close: () => isOpen.value = false
        }
      }
    })
    const destination = document.createElement('div')
    destination.id = 'somewhere'
    document.body.appendChild(destination)

    const wrapper = mount(Comp, {
      attachTo: destination
    })
    console.log(wrapper.html())
    console.log(document.body.outerHTML)
    await wrapper.find('button').trigger('click')
  })

This logs:

<body><div id="somewhere"><div data-v-app=""><button>Open</button><!--teleport start--><!--teleport end--></div></div><div id="headlessui-portal-root"><div><div to="#id" id="headlessui-dialog-1" role="dialog" aria-modal="true"><h2 id="headlessui-dialog-title-2">Dialog title</h2><div id="headlessui-dialog-overlay-3" aria-hidden="true"></div><input></div></div></div></body>

So the content is there.

See this doc or this video for some ideas on how you can continue using the VTU wrapper API.

This solution is kind of complex and hard to understand, I would like something a bit more ergonomic, but I'm not sure what. I think a utility function could probably abstract away a lot of this complexity.

@AndreasNasman
Copy link

Hi!

I'm writing a similar test to the one described in the teleport docs and the accompanying video, but with a slight variation.

The child component which I need to test (and causing the issue) is a Dialog component from PrimeVue.

<template>
  <h5>Parent</h5>
  <Dialog :visible="true">
    ...
  </Dialog>
</template
...

Looking at the Dialog's source code, I noticed that the component itself uses the built-in <Teleport> component, which causes some unexpected behavior for me. This differs from the documentation where the <Teleport> component is in the parent.

I'm able to correctly locate the Dialog component using getComponent/findComponent as you @lmiller1990 described in the docs and video,

const dialog = parent.getComponent(Dialog);

but invoking e.g. get on it

const input = dialog.get('input');

fails. The reason seems to be that get still searches in the context of the parent – the parent component in my example – although the context should be the dialog component, at least to my understanding.

Running html() on the Dialog component verifies the context still being the parent:

console.log('parent\n', parent.html());
console.log('dialog\n', dialog.html());

// ...
// console.log tests/unit/DialogTest.spec.ts:27
//   parent
//    <h5>Parent</h5>
//   <!--teleport start-->
//   <!--teleport end-->
// 
// console.log tests/unit/DialogTest.spec.ts:28
//   dialog
//    <h5>Parent</h5>
//   <!--teleport start-->
//   <!--teleport end-->
// ...

In the actual test, I have a button that opens the dialog and an input field I want to modify, but that should be irrelevant for this issue.


I was able to replicate this with some minimal changes to Navbar.vue and its test file in the vuejs-composition-api-course project. I basically moved the <teleport to="#modal"> part to the Signup component itself to replicate the problem (attaching a .patch file with the changes).
0001-Make-breaking-changes.patch.zip


Is there some concept I'm missing or some modifications I need to make to adapt to the child containing <Teleport>? Or, could there be a bug with contexts when using get/findComponent for components using teleport?

Hopefully, I came across somewhat clear! 😄

– Andreas

@AndreasNasman
Copy link

Any comments on this @lmiller1990?

@TheDutchCoder
Copy link
Contributor

I wonder if we could stub out the teleport all together?
I don't see a real purpose for it in testing anyway, because it just moves pieces of the DOM around.

Would that be an option?

@lmiller1990
Copy link
Member

lmiller1990 commented Nov 18, 2021

We could definitely stub out <teleport> - we do that what with <transition>. I like this idea!

We have that logic here, you'd just need to copy-paste it for <teleport>. I'm not sure if we want to default to this or not, but we could definitely add support for

mount(Foo, {
  global: {
    stubs: {
      teleport: true
    }
  }
})

Which won't be a breaking change.

Would someone like to make this PR? I could do it otherwise, but I'd really like to grow the contributors to this repo, since it's only a few people right now. This should be quite easy, since we have the concept already for transitions.

@TheDutchCoder
Copy link
Contributor

TheDutchCoder commented Nov 18, 2021

I'll see if I can clone the repo and maybe PR this!

Edit: it's not a simple copy/paste unfortunately, because Teleport doesn't have a compatible type with const type = nodeType as VNodeTypes and I'm not sure how to solve it tbh.

I'll keep plugging away, but TS is not my strong suit.

@TheDutchCoder
Copy link
Contributor

I created a PR for this with some notes/questions: #1087

@lmiller1990
Copy link
Member

Added some review comments 👍

@lmiller1990
Copy link
Member

@AndreasNasman @Yijx we released rc.17, which has support for stubbing teleport: https://github.com/vuejs/vue-test-utils-next/releases/tag/v2.0.0-rc.17

mount(Comp, {
  global: {
    stubs: {
      teleport: true
    }
  }
})

This might be useful? Just stub teleport, and hopefully business as usual. I didn't test this on the reproduction you posted yet @AndreasNasman, but let me know if this helps.

@AndreasNasman
Copy link

Thanks, great work! 🥳
I'll test it later today and let you know how it went! 👍

@AndreasNasman
Copy link

AndreasNasman commented Nov 22, 2021

Works perfectly for our needs, thank you! @TheDutchCoder @lmiller1990 🙏

@dospunk
Copy link

dospunk commented Jan 7, 2022

This is a good solution, though I just wanted to chime in and say it doesn't work for headlessui. I suspect this is a headlessui problem though, not a VTU problem. For some reason the Dialog's FocusTrap can't find the contents of the Dialog when teleport is stubbed out causing an infinite loop? I don't really get it. Anyway, this is just for anyone else in the future running across this issue who's having the same issues with headlessui that I did.

@d0peCode
Copy link

d0peCode commented Sep 2, 2022

How to use same solution (stubs) if I have my teleport like this:

<component :is="teleport" to="body">

I must do teleport like this because of vite building issue: vuejs/core#2855 (comment) .

When I'm doing like @lmiller1990 suggested I don't see content of mounted component:

image

Plus I'm getting warn which I think might be because teleport actually worked (stub didn't replace in time) there is no target (<body>)?

@lmiller1990
Copy link
Member

What is your testing stack? Is this Vitest specific?

What is the goal - to avoid teleporting your content from your component? Eg, can you share your entire test and intention?

@Made-of-Clay
Copy link

Stubbing teleport works great - thanks so much!

@Made-of-Clay
Copy link

5 days in and I'm finding the stubbed teleport isn't reactive 😕 Maybe I'm missing something.

@dpogue
Copy link

dpogue commented Apr 17, 2023

I've not been able to access the contents of a stubbed teleport in my tests ever since 2.2.0. I keep intending to build a sample test case to demonstrate the bug, but it's quicker to just stay on 2.1.0 for now 😓

@lmiller1990
Copy link
Member

5 days in and I'm finding the stubbed teleport isn't reactive 😕 Maybe I'm missing something.

I'm not sure if the current state of this library lends itself well to testing teleport in general - I wonder if we need better docs, a better/new API for this, or something else.

Can you share a minimal reproduction?

I am not exactly what you mean, but I wouldn't expected a stubbed component to do much at all.

@Made-of-Clay
Copy link

I'm amazed at how much of a pain it is to just spin up a quick Vue project after Vite. I love Vite and think it was the right decision. However, I really miss the simplicity of Vue CLI and how quickly it set up a basic project with Jest testing.

Anyways, I've got a repo up and get the expected bug behavior. HelloWorld.vue and HelloWorld.spec.ts are the relevant files. Once installed, just run npm test and it'll show you the failing test (there's only 1) and it also logs the markup, like so.

image

@lmiller1990
Copy link
Member

Ah sorry, for reproductions, people often use https://stackblitz.com/edit/vitest-dev-vitest-2cimwj?file=test/basic.test.ts (ready to go). Or you can fork this repo and add a test case.

I will make a new issue for this.

@lmiller1990
Copy link
Member

#2033

@lmiller1990
Copy link
Member

@Made-of-Clay would you be interested in trying to fix this? You could add a test case to this test suite, and try to fix it. The stubbing stuff for Teleport is here: https://github.com/vuejs/test-utils/blob/main/src/vnodeTransformers/stubComponentsTransformer.ts#L123-L127

@Made-of-Clay
Copy link

Made-of-Clay commented Apr 18, 2023

@lmiller1990 I'll take a look when I can make time for it 👍

Ah sorry, for reproductions, people often use https://stackblitz.com/edit/vitest-dev-vitest-2cimwj?file=test/basic.test.ts (ready to go). Or you can fork this repo and add a test case.

I will make a new issue for this.

If that's specified somewhere and I missed it, my bad. I admit I didn't look thru docs for contribution notes. Thanks for making the issue/bug for tracking.

@lmiller1990
Copy link
Member

I had a quick look but I'm having trouble providing a custom stub for Teleport. It's been a while since I looked at this code, I don't have any ideas for a quick fix right now - might require some digging and exploration.

@vincerubinetti
Copy link

Hi all.

I was using the teleport stub solution mentioned above previously, with test utils version 2.0.2. Upgrading to the latest version, 2.3.2, it breaks. I have a dropdown component where the dropdown is teleported to body, and with the latest version, inspecting the wrapper HTML, there is no dropdown there.

Could someone consider looking into this or re-opening this issue as a possible regression? I'm assuming since it's the same major version there wouldn't be any breaking changes with this particular functionality.

@lmiller1990
Copy link
Member

Can you try previous versions (maybe 2.3.1, 2.3.0) and isolate the issue?

How about making a new one describing the version with the breakage? I think it's better to track a new regression in a new issue. Thanks!

@dpogue
Copy link

dpogue commented May 2, 2023

Can you try previous versions (maybe 2.3.1, 2.3.0) and isolate the issue?

How about making a new one describing the version with the breakage? I think it's better to track a new regression in a new issue. Thanks!

This sounds like the same issue I'm facing, where the behaviour of subbing teleport broke in 2.2.0.

@lmiller1990
Copy link
Member

Oh, so this one would be the issue to follow: #2033

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

9 participants