Akryum / floating-vue

💬 Easy tooltips, popovers, dropdown, menus... for Vue
https://floating-vue.starpad.dev/
MIT License
3.26k stars 333 forks source link

VDropdown unit testing with vue-test-utils #996

Closed dirago closed 5 months ago

dirago commented 8 months ago

Hello all 👋

I'm struggling a lot with unit testing VDropdown, I can't access what's inside <template #popper> when I trigger a click on the dropdown trigger.

My dependencies:

Here's a simple example where my test fails:

NavBar.vue


<template>
<div class="nav-bar">
<VDropdown
data-test="nav-bar.dropdown"
:distance="12"
no-auto-focus
placement="top-start"
skidding="-6"

<div data-test="nav-bar.navigation-trigger">
...
</div>
  <template #popper="{ hide }"> // I don't use v-close-popper since it does not always work
    <ul data-test="nav-bar.navigation-dropdown" @click="hide">
      ...
    </ul>
  </template>
</VDropdown>


> NavBar.spec.ts
```ts
import NavBar from '@/ui/components/nav-bar/NavBar.vue';
import { mount } from '@vue/test-utils';
import { Dropdown } from 'floating-vue';

describe('NavBar', () => {
  it('should display dropdown when trigger is clicked', async () => {
    expect.assertions(1)
    const wrapper = mount(NavBar, {
      global: {
        stubs: {
          VDropdown: Dropdown
        }
      }
    });

    await wrapper.find('[data-test="nav-bar.navigation-trigger"]').trigger('click');
    // fails also with `await wrapper.vm.$nextTick()`
    // fails also with `await nextTick()`

    expect(wrapper.find('[data-test="nav-bar.navigation-dropdown"]').exists()).toBeTruthy(); // fails
  });
});

main.ts

...
const app = createApp(App);
app.component('VDropdown', Dropdown);
...

Has anybody faced this? Do you have any working solution? 🙏

dirago commented 8 months ago

Nobody?

ItMaga commented 8 months ago

Hi @dirago!

I'm not sure, because I didn't test it. But I would suggest checking the content not inside the wrapper but in document.body for the reason of appending the popper to the body. And you should use utility waitFor.

Dino-Kupinic commented 7 months ago

@dirago have you found a solution?

dirago commented 7 months ago

@dirago have you found a solution?

not yet...

jjaramillom commented 7 months ago

@dirago @Dino-Kupinic I'm also struggling with this and haven't been able to find a solution. Could any of you make it work?

Dino-Kupinic commented 7 months ago

@dirago @Dino-Kupinic I'm also struggling with this and haven't been able to find a solution. Could any of you make it work?

Sadly no, would be nice if testing was included in the docs

byv55f5f5 commented 6 months ago

I also face this problem too. It was fine when I used v-tooltip v2.

dirago commented 6 months ago

I also face this problem too. It was fine when I used v-tooltip v2.

I guess we're all using v-tooltip v2 (renamed to floating-vue), can you share some code to understand how you figured it out?

byv55f5f5 commented 6 months ago

I also face this problem too. It was fine when I used v-tooltip v2.

I guess we're all using v-tooltip v2 (renamed to floating-vue), can you share some code to understand how you figured it out?

I mean the old version was fine. After I upgrade v-tooltip to floating-vue, all of my unit-test related to floating-vue are broken too. The test-utils can not find the element in popper after click event is triggered.

Akryum commented 6 months ago

Content inside the #popper slot will be rendered on body by default, so it might explain why you can't access it through wrapper.

Dino-Kupinic commented 6 months ago

Content inside the #popper slot will be rendered on body by default, so it might explain why you can't access it through wrapper.

yes. How should we go about testing then?

dirago commented 6 months ago

Content inside the #popper slot will be rendered on body by default, so it might explain why you can't access it through wrapper.

Indeed, I'll test with attachTo and check on document.querySelector.

Keeping you in touch @Dino-Kupinic @jjaramillom @byv55f5f5

dirago commented 6 months ago

Still no satisfactory result, here's my test:

import NavBar from '@/ui/components/nav-bar/NavBar.vue';
import { mount } from '@vue/test-utils';
import { Dropdown } from 'floating-vue';

describe('NavBar', () => {
  it('should display dropdown when trigger is clicked', async () => {
    expect.assertions(1);
    const wrapper = mount(NavBar, {
      attachTo: document.body,
      global: {
        stubs: {
          VDropdown: Dropdown
        }
      }
  })

    await wrapper.find('[data-test="nav-bar.navigation-trigger"]').trigger('click');
    console.log(document.documentElement.innerHTML); // see below

    expect(document.querySelector('[data-test="nav-bar.navigation-dropdown"]')).not.toBeNull(); // fails
    wrapper.unmount(); // obligatory if you do not want to pollute your next unit tests
  });
});

Here's what @vue/test-utils renders with attachTo (before AND after clicking the trigger)

<head></head>
<body>
  <div data-v-app="">
    <div class="nav-bar" data-test="nav-bar">
      <div class="v-popper v-popper--theme-dropdown" data-test="nav-bar.dropdown">
        <div class="nav-bar-element nav-bar-element-nav text-13-medium" data-test="nav-bar.navigation-trigger">
          I am the trigger
        </div>
      </div>
    </div>
  </div>
</body>

You can notice <body> doesn't have the class v-popper--some-open and the dropdown content is not rendered.

I'm going to give up for now, I'm probably missing something but I've already spent way too much time on this. The problem could come from @vue/test-utils or happy-dom (I didn't test with jsdom and it will break all my UT)

Akryum commented 6 months ago

Maybe you can stub the dropdown component to test the popper content?

NeoLSN commented 6 months ago

Need to install floating-vue as a plugin, but still don't have a good way to trigger it in test case.

import NavBar from '@/ui/components/nav-bar/NavBar.vue';
import { config, mount } from '@vue/test-utils';
import FloatingVue from 'floating-vue';

describe('NavBar', () => {
  beforeAll(() => {
    config.plugins.VueWrapper.install(FloatingVue);
  });

  it('should display dropdown when trigger is clicked', async () => {
    expect.assertions(1);
    const wrapper = mount(NavBar, {
      attachTo: document.body,
    })

    await wrapper.find('[data-test="nav-bar.navigation-trigger"]').trigger('click');
    console.log(document.documentElement.innerHTML); // see below

    expect(document.querySelector('[data-test="nav-bar.navigation-dropdown"]')).not.toBeNull(); // fails
    wrapper.unmount(); // obligatory if you do not want to pollute your next unit tests
  });
});
dirago commented 6 months ago

@Akryum VDropdown is already stubbed in mount, isn't it?

import { Dropdown } from 'floating-vue';

const wrapper = mount(NavBar, {
    attachTo: document.body,
    global: {
        stubs: {
            VDropdown: Dropdown
        }
    }
})

@NeoLSN why do you want to install floating-vue since we stub Dropdown? Maybe I miss something but I might only want to use Dropdown and instantiate it in main.ts with:

import { Dropdown } from 'floating-vue';

app.component('VDropdown', Dropdown);
NeoLSN commented 6 months ago

I used this solution before, but it's not working for me.

Akryum commented 6 months ago

By stubbing I mean stubbing using a dummy component with a popper slot

dirago commented 6 months ago

@Akryum oh ok, I would have preferred it to work without having to stub it all but let's try

edit: ok it works with:

const VDropdownStub = defineComponent({
  setup() {
    const showPopper = ref(false);

    return { showPopper };
  },
  template: `
    <div @click="$emit('update:shown', !showPopper); showPopper = !showPopper">
      <slot></slot>

      <div v-if="showPopper">
        <slot name="popper"></slot>
      </div>
    </div>
  `,
});

config.global = {
  stubs: {
    VDropdown: VDropdownStub,
  },
};

I decided to put this on global config but you can also do the same in mount @NeoLSN @jjaramillom @Dino-Kupinic @byv55f5f5

jjaramillom commented 6 months ago

@dirago Working like a charm. Thanks a lot for the snippet pal.