0

I'm trying to call the close() method in my Test component, but it only gets fired when clicking outside the div that the directive is on. What should I do in my test to make sure that that method gets fired? I'm using the v-click-outside npm package in my component.

Component

<script>
  import vClickOutside from 'v-click-outside';

  export default {
    name: 'Test',

    directives: {
      vClickOutside,
    },

    data: () => ({
      isOpen: false,
    }),
    methods: {
      close() {
        this.isOpen = false;
      },
  };
</script>

<template>
  <div
    v-click-outside="close"
    class="test-class"
  >
    <OtherComponent />
  </div>
</template>

This is my test file.

const clickOutsidelDirective = jest.fn();

describe('Test.vue', () => {
  const wrapper = shallowMount(Component, {
   directives: {
      clickOutside: clickOutsidelDirective,
    },
  });
   wrapper.find('.test-class').trigger('click');
   //not sure what i have to do to mock the close() function 

   //This doesn't get called
   expect(clickOutsidelDirective).toHaveBeenCalled();
}
tony19
  • 125,647
  • 18
  • 229
  • 307
Mazzie01
  • 25
  • 1
  • 6

2 Answers2

2

The directive is not setup properly in your component:

import vClickOutside from 'v-click-outside'

export default {
  directives: {
    // BEFORE: ❌ 
    vClickOutside,

    // AFTER: ✅
    clickOutside: vClickOutside.directive
  },
}

To verify that close() is called when you click outside the component:

  1. Mock the close method with jest.spyOn.
  2. Create a div for the test component, and attach the mounted wrapper to it.
  3. v-click-directive adds its event listeners on the next macro-tick (using setTimeout with no timeout), so the test also needs to wait a macro-tick for the directive to initialize.
  4. Trigger a click event on the wrapper, and await the result. Then, assert that close() was called.

The test should look like this:

it('click directive', async () => {
  1️⃣
  const closeFn = jest.spyOn(HelloWorld.methods, 'close')

  2️⃣ 
  const div = document.createElement('div')
  document.body.appendChild(div)

  const wrapper = mount({
    template: `<div><HelloWorld /></div>`,
    components: {
      HelloWorld
    },
  }, { attachTo: div })

  try {
    3️⃣
    await new Promise(r => setTimeout(r))

    4️⃣
    await wrapper.trigger('click')
    expect(closeFn).toHaveBeenCalled() ✅

  } finally {
    wrapper.destroy()
  }
})
tony19
  • 125,647
  • 18
  • 229
  • 307
  • @FirzokNadeem This particular directive's implementation checks for clicks outside an element, so we wrap that element inside a `div` that can be clicked in the test. The target element needs to be attached to the `div` for the test to work. – tony19 Jan 12 '21 at 00:00
0

In your main component, the directive was imported explicitly. So, in your test, you do not need to define it again. v-click-outside has a consequence, you should test it. It means, the close method should fire, mock it not the entire directive. Something like it:

EDIT: Your definition to include the directive is wrong:

<template>
  <div>
    <div
        v-click-outside="close"
        class="test-class"
    >
      <h1>H1</h1>
    </div>
    <div>
      <h1 class="outside-class">Outside</h1>
    </div>
  </div>
</template>
<script>
import clickOutside from 'v-click-outside';

export default {
  name: 'Test',

  directives: {
    clickOutside: clickOutside.directive,
  },

  data() {
    return {
      isOpen: false,
    };
  },
  methods: {
    close() {
      this.isOpen = true;
    },
  }
}
</script>

By the new version of Vue-test-utils, method overriding is going to be deprecated, So, something like this should work:

const wrapper = shallowMount(HelloWorld)
wrapper.find('.test-class').trigger('click')
expect(wrapper.vm.isOpen).toBeTruthy()
wrapper.find('.outside-class').trigger('click')
expect(wrapper.vm.isOpen).toBeFalsy()

But it does not. It's related to the internal v-click-outside implementation. I think there is a problem with the directive and shallowMount.

Raeisi
  • 1,647
  • 1
  • 6
  • 18
  • Seems like even doing it this way doesn't trigger the method/directive to being called – Mazzie01 Nov 12 '20 at 19:25
  • I've tested this template in the browser and everything went well. But in the unit test didn't work. It should be about the environment that the directive works in. – Raeisi Nov 12 '20 at 22:12