Skip to content

Context of 'click' event #456

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

Closed
Blackfaded opened this issue Mar 11, 2021 · 13 comments
Closed

Context of 'click' event #456

Blackfaded opened this issue Mar 11, 2021 · 13 comments
Labels
bug Something isn't working

Comments

@Blackfaded
Copy link

When I test a custom v-click-outside directive with vue-test-utils the directive does not get triggered.
Does the mount method not recognize the dom events attached vith the directive?

My test code:

import { mount } from '@vue/test-utils';
import { defineComponent, ref } from 'vue';

import { clickOutside } from '@/libs/shared/directives/clickOutside';

const App = defineComponent({
  setup() {
    const test = ref('initial');

    const onClickOutside = () => {
      console.log('outside');
      test.value = 'clickedOutside';
    };
    return {
      test,
      onClickOutside,
    };
  },
  template: `<template>
  <div data-test="outside">SomeOtherDiv</div>
  <div data-test="inside" v-click-outside="onClickOutside">{{test}}</div>
  </template>
  `,
});

describe('clickOutside', () => {
  it('should trigger function"', async () => {
    const wrapper = mount(App, {
      global: {
        directives: {
          'click-outside': clickOutside,
        },
      },
    });

    const inside = wrapper.get('[data-test=inside]');
    const outside = wrapper.get('[data-test=outside]');
    await inside.trigger('click');
    expect(wrapper.get('[data-test=inside]').text()).toContain('initial');
    await outside.trigger('click');
    
    // this fails and is still 'initial'
    expect(wrapper.get('[data-test=inside]').text()).toContain('clickedOutside');
  });
});

My directive:

export const clickOutside = {
  beforeMount: (el: any, binding: any) => {
    el.eventSetDrag = () => {
      el.setAttribute('data-dragging', 'yes');
    };
    el.eventClearDrag = () => {
      el.removeAttribute('data-dragging');
    };
    el.eventOnClick = (event: any) => {
      console.log('sdfsf');
      const dragging = el.getAttribute('data-dragging');
      // Check that the click was outside the el and its children, and wasn't a drag
      if (!(el == event.target || el.contains(event.target)) && !dragging) {
        // call method provided in attribute value
        binding.value(event);
      }
    };
    document.addEventListener('touchstart', el.eventClearDrag);
    document.addEventListener('touchmove', el.eventSetDrag);
    document.addEventListener('click', el.eventOnClick);
    document.addEventListener('touchend', el.eventOnClick);
  },
  unmounted: (el: any) => {
    document.removeEventListener('touchstart', el.eventClearDrag);
    document.removeEventListener('touchmove', el.eventSetDrag);
    document.removeEventListener('click', el.eventOnClick);
    document.removeEventListener('touchend', el.eventOnClick);
    el.removeAttribute('data-dragging');
  },
};

My problem is, that the v-click-outside method does not get called in tests, but works fine in the browser.

@nandi95
Copy link
Contributor

nandi95 commented Mar 11, 2021

Isn't that meant to be mounted as opposed to beforeMount?

I'm facing a similar/same issue by the way, except my directive is in the component:

jest.useFakeTimers();
const mockFn = jest.fn();

const wrapper = mount({
    template: '<div id="outside"><div id="inside" v-click-away="mockFn"><span id="content" /></div></div>',
    directives: { clickAway },
    setup: () => ({ mockFn })
});
jest.runAllTimers();

await wrapper.get('#content').trigger('click');
expect(mockFn).not.toHaveBeenCalled();
await wrapper.get('#inside').trigger('click');
expect(mockFn).not.toHaveBeenCalled();

// this doesn't seem to work either
// const event = new MouseEvent('click', { relatedTarget: wrapper.element });
// wrapper.element.dispatchEvent(event);
// await nextTick();

await wrapper.get('#outside').trigger('click');

expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith(MouseEvent);

jest.useRealTimers();

my directive seems to work flawless when testing manually

@lmiller1990
Copy link
Member

I don't see any obvious problem with your code. I can't see why this would work any differently in test utils, though.

You could try running test utils in the browser and see if it's a jsdom problem, or a test-utils specific problem. Other than that, someone (maybe me) will need to debug it.

@lmiller1990 lmiller1990 added the bug Something isn't working label Mar 12, 2021
@Blackfaded
Copy link
Author

Will try to run that in the Browser on monday.
Thanks for your Help.

@lmiller1990
Copy link
Member

This is not passing a browser for me, but as you said, it does work when testing manually. Hm.

@lmiller1990
Copy link
Member

Ohhhh, I found the problem. At least, I got it to pass in a browser (using test utils).

The problem is the elements are not actually mounted in the document by default. They are just hanging out in memory. You need to create an element and use the attachTo method. I guess it makes sense, you cannot really click outside something that isn't mounted in the document to begin with.

Give that a try. I saw it pass, at least in a browser. If you can't get it working, please ping me again, but for now I'll close this since I think we have a solution.

@Blackfaded
Copy link
Author

Nice catch! It worked with the following:

  const elem = document.createElement('div');
      if (document.body) {
        document.body.appendChild(elem);
      }
      const wrapper = mount(App, {
        global: {
          directives: {
            'click-outside': clickOutside,
          },
        },
        attachTo: elem,
      });
      expect(wrapper).toBeDefined();

      const inside = wrapper.get('[data-test=inside]');
      const outside = wrapper.get('[data-test=outside]');
      await inside.trigger('click');
      expect(wrapper.get('[data-test=inside]').text()).toContain('initial');
      await outside.trigger('click');
      expect(wrapper.get('[data-test=inside]').text()).toContain('clickedOutside');

Thanks you so much! And yes, if you think about it it makes sense, but it still is not obvious ;)

@WingDust
Copy link

@lmiller1990 , How to using test-utils in browser,I can't find in Doc.

@lmiller1990
Copy link
Member

lmiller1990 commented May 17, 2021

Do you mean from a cdn (using <script src="/vue-test-utils.js">) or with a bundler like Vite (bundle, the run in the browser?)

@ziaadini
Copy link

i'm facing same issue and attachTo is not working for me, here is my test code :

test('test close on click outside', async () => {
    const wrapperMenu={
      template:'<div><div id="outside">outSide of menu</div><t-menu v-model="model"></t-menu></div>',
      components:{TMenu},
      data(){
        return {model:true}
      }
    }
    const wrapper = mount(wrapperMenu, {
      provide: { 'TSettings': {} },
      attachTo:document.body,
    })
    await nextTick()
    await wrapper.find('#outside').trigger('click')
    await nextTick()
    expect(wrapper.find("[data-name='menu-items']").isVisible()).toBe(false)
    wrapper.destroy()
  })

am using import { onClickOutside } from '@vueuse/core' for detect click outside and my test is failing and my console.log is not showing, this code is inside TMenu:

    onClickOutside(menuRef, () => {
      console.log("click outside called ************************")
      triggerMenu(false)
    })```
and am sure i passed menuRef to  root level of TMenu component

@lmiller1990
Copy link
Member

@ziaadini can you provide a minimal reproduction we can run? You could upload a repo, or use https://stackblitz.com/edit/vitest-dev-vitest-qoiwy3?file=package.json&initialPath=__vitest.

@unematiii
Copy link

@ziaadini @lmiller1990 It looks onClickOutside from @vueuse/core attaches events to window object. I was able to get the test passing using global.dispatchEvent(new Event('click')); instead of triggering event(s) on mounted nodes.

@ziaadini
Copy link

ziaadini commented Jan 5, 2023

@unematiii it loooks nice thanks, for your solution

@jacobtshirt
Copy link

@ziaadini @lmiller1990 It looks onClickOutside from @vueuse/core attaches events to window object. I was able to get the test passing using global.dispatchEvent(new Event('click')); instead of triggering event(s) on mounted nodes.

this worked for me as well. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

7 participants