Is there a way to simulate a call to a method triggered by a Vue directive?

I am having an issue with the close() method in my Test component. It seems to only work when clicking outside of the div that the directive is applied to. How can I ensure that the method is triggered appropriately in my test? The component utilizes the v-click-outside npm package.

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>

Test File

const clickOutsidelDirective = jest.fn();

describe('Test.vue', () => {
  const wrapper = shallowMount(Component, {
   directives: {
      clickOutside: clickOutsidelDirective,
    },
  });
   // Not sure how to mock the close() function 
   wrapper.find('.test-class').trigger('click');
   
   // This assertion does not pass as expected
   expect(clickOutsidelDirective).toHaveBeenCalled();
}

Answer №1

The directive in your component is not properly configured:

import vClickOutside from 'v-click-outside'

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

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

To confirm that the close() function is invoked when clicking outside the component:

  1. Mock the close method using jest.spyOn.
  2. Create a div element 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. Simulate a click event on the wrapper, and await the result. Then, verify that close() was called.

The test should be structured 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()
  }
})

Answer №2

Within your primary component, the directive was explicitly imported. Therefore, there is no need to redefine it in your test.

It is essential to test the consequences of using v-click-outside. Specifically, ensure that the close method triggers correctly rather than mocking the entire directive. Consider the following approach:

UPDATE: Your implementation of the directive is incorrect:

<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>

In the latest version of Vue-test-utils, method overriding will be deprecated. A revised method should function like so:

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()

However, unexpected behavior may occur due to the internal implementation of v-click-outside. It seems there might be an issue with the directive and shallowMount compatibility.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing various AngularJS filters with multiple input sources

Looking to enhance my user array filtering process with two input boxes. Here's how the array is structured: $scope.users = [{ id: 1, fname: 'Sophia', lname: 'Smith', email: '<a href="/cdn-cgi/l/email ...

Troubleshooting ASP.NET MVC5: Partial view not loading dynamic JavaScript reference

In an effort to update a partial View containing an interactive world map, my objective is to refresh it with new data. I have a Controller action public JavaScriptResult DynMap(int id, List<int> relevantCountries = null){} This action returns the ...

"Resolving the issue of Django request.FILE returning a null value

HTML Template: <form method="post" action="" enctype="multipart/form-data" class="form-group"> {% csrf_token %} <h1 class="m-3 text-center">New Post</h1> <div id="tui-image-e ...

Retrieve the origin of the copied text

While working on a Vue application, I'm curious to know if it's possible to access the source of pasted text. For example, whether the pasted text originated from your own application, Word, or Notepad? I attempted the code below but was unable ...

How can we utilize Javascript to add both days and years to the current date?

Is there a way to get the current date, add 1 day to it and then also add 1 year? If so, how can this be done? ...

locate the following div using an accordion view

Progress: https://jsfiddle.net/zigzag/jstuq9ok/4/ There are various methods to achieve this, but one approach is by using a CSS class called sub to hide a 'nested' div and then using jQuery to toggle the Glyphicon while displaying the 'nest ...

Changing the text color of the Vuetify Table header is a simple way to customize the

I am currently working on a Vuetify table with the class condition-table. I have applied the following CSS styling: .condition-table { background-color: #e1f5fe70; } The styling seems to work fine so far. However, when I added this additional CSS: .co ...

Conflict arising from duplicated directive names in AngularJS

Hey there, I've got a question for the experts. How can I prevent conflicts with directive names when using external modules? Right now, I'm utilizing the angular bootstrap module, but I also downloaded another module specifically for its carouse ...

Transform JSON data into a Google Sheet using Google Apps Script

Having trouble inserting the JSON response into Google Sheet using Google Apps Script with the code below. Running into errors, even though I can't seem to pinpoint the issue. Take a look at the screenshot and code snippet provided: function myF ...

What techniques can I use to adjust the size of an image through zooming in and out?

In my custom gallery component, the crucial code section looks like this: <Gallery> <Header> <img src={galleryIcon} alt='Galley icon' /> <h1>My Gallery</h1> </Header> ...

Incorporating database coordinates into Marker React Leaflet: A Step-by-Step Guide

When I retrieve coordinates from the database, they are structured as "lat" and "lon" fields. In my mapping application, I have multiple markers to display. How can I combine these two fields to pass coordinates (coord.lat and coord.lon) to the Marker comp ...

Angular's lazy evaluation technique for single-shot binding on expressions

Since AngularJS version 1.3.0-beta.10, a new feature has been introduced called the "lazy one-time binding". To implement this feature, you can prefix simple expressions with ::, which instructs AngularJS to stop watching the expression after it has been ...

Having trouble incorporating choreographer-js, an external JavaScript library, into my Nuxt project as it is not functioning correctly

Just starting out with vue.js and nuxt.js, I wanted to experiment with integrating an external JavaScript library into my nuxt.js project. My goal is to test out the functionality provided by this library: The library in question can be found here: https: ...

SyntaxError: The token + was not anticipated

I encountered an error while using this code even though the php id variable is properly set. Why am I receiving the unexpected token error? var current_page = 1; var id = <?php echo $id; ?>; $(document).ready(function(){ $.ajax({ &apos ...

Paragraph Separated by Bullets Without Bullets At End of Line

I need assistance in creating a unique type of list that I call a "bullet separated paragraph list" by using a javascript array of strings. For example: item • item • item • item • item     item • item • item • item item • ...

applying various conditions to JavaScript arrays for filtering

After spending countless hours trying to solve my filtering issue, I'm still struggling. I'm in the middle of creating a react marketplace where users need to be able to apply multiple filters on one page. Here's an example of my product lis ...

Preventing scrolling in one div while focusing on another div

I am currently in the process of creating a website that functions as a single page site. The main feature of the site is a masonry grid of images. When a user clicks on an item, a detailed panel slides in from the left. Once the panel is closed, it slide ...

Having trouble getting the Bootstrap 5 Modal to function properly within an Electron app

Facing an issue with my web app using Bootstrap 5, as the modal is not displaying properly. Below is the HTML code for the modal and the button that triggers it: <div class="modal" tabindex="-1" id=&quo ...

Clicking on "li" to activate and deactivate

Currently utilizing: $('#btnEmpresarial').attr('onclick','').unbind('click'); In order to deactivate a read using javascript.. However, I now require enabling the onclick after the function has completed. Is ther ...

Error encountered in Node.js: Attempting to modify headers after they have already been sent to the client

When attempting to create a login function and sending post requests using Postman, everything works fine with the correct email and password. However, if I try to send the wrong password, I encounter an error message stating that either the email or passw ...