Having difficulties getting Vuetify form submission to work with the submit button in Jest

I'm currently testing the functionality of a Vue form that utilizes Vuetify components using Jest and Avoriaz.
I have successfully triggered submit.prevent on the form, which resulted in the expected behavior.
However, I encountered an issue when attempting to trigger click on the submit button.

Below is the component structure:

<template>
  <v-form
    ref="form"
    data-cy="form"
    @submit.prevent="login"
  >
    <v-text-field
      id="email"
      v-model="email"
      label="Email"
      name="email"
      prepend-icon="mdi-at"
      type="text"
      required
      autofocus
      data-cy="email-text"
    />
    <v-btn
      color="primary"
      type="submit"
      data-cy="login-btn"
    >
      Login
    </v-btn>
  </v-form>
</template>

<script>

export default {
  data () {
    return {
      email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aedacbdddaeedacbddda80cdc1c3">[email protected]</a>',
    }
  },
  computed: {},
  methods: {
    login: function () {
      console.log('Logging in')
    }
  }
}
</script>

Currently setting up the test environment:

import vuetify from '@/plugins/vuetify'
import { mount } from 'avoriaz'
import Form from '@/views/Form'

describe('Form', () => {
  const mountFunction = options => {
    return mount(Form, {
      vuetify,
      ...options
    })
  }

The Vue and Vuetify configuration can be found in @/plugins/vuetify:

import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

export default new Vuetify({
})

This particular test case passes with the mocked setup:

  it('can trigger form directly', () => {
    const login = jest.fn()
    const wrapper = mountFunction()
    wrapper.setData({ 'email': 'test@com' })
    wrapper.setMethods({ login })

    let element = wrapper.first('[data-cy=form]')
    element.trigger('submit.prevent')

    expect(login).toHaveBeenCalledTimes(1)
  })

But when trying to test the submit button, the test fails:

  it('can trigger form through button', () => {
    const login = jest.fn()
    const wrapper = mountFunction()
    wrapper.setData({ 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aadecfd9deeadecfd9de84c9c5c7">[email protected]</a>' })
    wrapper.setMethods({ login })

    const button = wrapper.first('[type=submit]')
    button.trigger('click')

    expect(login).toHaveBeenCalledTimes(1)
  })

Additional dependencies mentioned in package.json:

{
  ..
  "dependencies": {
    "axios": "^0.19.1",
    "core-js": "^3.4.4",
    "vue": "^2.6.11",
    "vue-router": "^3.1.3",
    "vuetify": "^2.1.0",
    "vuex": "^3.1.2"
  },
  "devDependencies": {
    ..
    "avoriaz": "^6.3.0",
    "vue-jest": "^3.0.5",
    "vuetify-loader": "^1.3.0"
  }
}

Update: Testing with non-Vuetify components (<form> and <btn) using test-utils succeeds:

const localVue = createLocalVue()

localVue.use(Vuetify)
describe('Form', () => {
  const mountFunction = options => {
    return shallowMount(Form, {
      localVue,
      vuetify,
      ...options
    })
  }
  it('can trigger form through button alternative', async () => {
    const login = jest.fn()
    const wrapper = mountFunction({ attachToDocument: true })
    try {
      wrapper.setData({ 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="790d1c0a0d390d1c0a0d571a1614">[email protected]</a>' })
      wrapper.setMethods({ login })

      const button = wrapper.find('[type=submit]')
      expect(button).toBeDefined()
      button.trigger('click')
      await Vue.nextTick()

      expect(login).toHaveBeenCalledTimes(1)
    } finally {
      wrapper.destroy()
    }
  })
})

Switching back to Vuetify components results in a failed test.

Answer №1

It seems that when working with Vuetify components like <v-form> and <v-btn>, there are specific steps required to ensure they function properly:

  • Utilize Vue's test-utils instead of avoriaz
  • Use mount rather than shallowMount
  • Include attachToDocument, but remember to clean up afterwards with wrapper.destroy()
  • As pointed out by @Husam Ibrahim, make sure to use async and await Vue.nextTick()
  • In some cases, you may need an additional await Vue.nextTick() for setData to take full effect. For instance, in scenarios involving form validation with rules on the input and applying v-model to the form along with binding the button's :disabled to the v-model data element, two nextTick()s were necessary.

The provided code snippet works effectively (assuming '@/plugins/vuetify' stays consistent with what was initially asked):

import vuetify from '@/plugins/vuetify'
import Vue from 'vue'
import { createLocalVue, mount } from '@vue/test-utils'
import Form from '@/views/Form'
import Vuetify from 'vuetify/lib'

const localVue = createLocalVue()

localVue.use(Vuetify)

describe('Form', () => {
  const mountFunction = options => {
    return mount(Form, {
      localVue,
      vuetify,
      ...options
    })
  }
  it('can trigger form through button alternative', async () => {
    const login = jest.fn()
    const wrapper = mountFunction({ attachToDocument: true })
    try {
      wrapper.setData({ 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e793829493a793829493c984888a">[email protected]</a>' })
      await Vue.nextTick() # might require multiple
      wrapper.setMethods({ login })

      const button = wrapper.find('[type=submit]')
      button.trigger('click')
      await Vue.nextTick()

      expect(login).toHaveBeenCalledTimes(1)
    } finally {
      wrapper.destroy()
    }
  })
})

Answer №2

It's possible that the issue is related to asynchronous behavior. One potential solution could be to include Vue.nextTick() after initiating the click event and prior to making the assertion.

  it('can activate form using button', async () => {
    const login = jest.fn()
    const wrapper = mountFunction()
    wrapper.setData({ 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0c78697f784c78697f78226f63661">[email protected]</a>' })
    wrapper.setMethods({ login })

    const button = wrapper.first('[type=submit]')
    button.trigger('click')

    await Vue.nextTick()
    expect(login).toHaveBeenCalledTimes(1)
  })

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

Material Design Lite and jQuery experiencing issues with smooth scrolling feature

I am facing an issue with using the .animate method of jQuery in conjunction with Google's Material Design Lite (MDL) framework. I implemented MDL to create a navigation bar, but encountered problems with achieving smooth scrolling. Here is an exampl ...

Explore the world of Laravel and Vue with the powerful Fullcalendar feature, enhanced with

I integrated Fullcalendar into my school project after following the instructions from this Video (https://www.youtube.com/watch?v=x_WMkIKztRQ). The calendar functionality is working fine, but I am facing an issue with displaying more than one table in the ...

Value definition: The object may be 'undefined'. Tips on preventing the need to manually reassert types with Zod

I encountered a type error in VS Code while working on this function. The error specifically points to board[0]. It states that Object is possibly 'undefined'. export default function validPosition(board: Grid | Board, position: Position) { con ...

The Issue with AngularJS ng-repeat Function Not Functioning

I am attempting to utilize angularJS to display div cards in rows of 3, but it's not working as expected. Instead of showing the cards in rows, it's displaying pure HTML where the object keywords in {{ }} are appearing as plain text. Below is all ...

React router error: wrong redirect executed

In the react.js code provided below, you can see a table with the component from the table.js file. This enables viewing a table with saved data, and clicking on the button in each row generated in the table displays the new EditClient component located at ...

Create a JavaScript button that increases a progress bar value by 1 and adjusts its width style by 10 units

Here is the code for managing a progress bar using JavaScript: function getProgress() { return document.getElementById("progressbar").getAttribute("aria-valuenow"); } function setProgress(value) { document.getElementById("progressbar").setAttri ...

Creating multiple div elements with changing content dynamically

I am facing an issue with a div named 'red' on my website where user messages overflow the 40px length of the div. To prevent this, I want to duplicate the 'red' div every time a message is sent so that the messages stay within the boun ...

The server node proxy is failing to trigger the API call

update 1: After modifying the api path, I am now able to initiate the api call. However, I encountered the following error: (node:13480) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): RangeError: Invalid status code: res ...

Cross-project AntiForgeryToken validation doesn't work within the same solution's sub-projects

Currently, I am working on implementing CSRF in an MVC application. In order to validate the token for inputs that are JSON encoded and called via Ajax, I have created a custom attribute. The validation works perfectly within the same project. However, whe ...

"Filtering a JSON File Based on Button Data Attributes: A Step-by-

I am working with a set of buttons that have specific data-map attributes as shown below: <button class="btn btn-default mapper" data-map="2015-11-13">Monday</button> <button class="btn btn-default mapper" data-map="2015-11-14">Tuesday&l ...

Preventing the need for reassessing ng-options

I have been utilizing ng-options to create a dropdown menu for values that may change infrequently. In a larger example, I have approximately 50 options in the array, and I noticed a decrease in performance each time I made a selection. It seems that ng-op ...

Using the setTimeout function with asynchronous tasks

I have a situation where I need to introduce a 5000ms delay before firing an asynchronous function. To accomplish this, I attempted to utilize the setTimeout() method. This async function is called within a loop that runs multiple times, and each time it i ...

Utilizing JavaScript and HTML5 to add and delete the 'mandatory' attribute

Seeking assistance with a function that involves hiding and showing input fields based on the selection of radio buttons. When the first radio button is chosen, specific fields should be hidden and not required by the form. Conversely, when the second ra ...

Only a single list is visible in the Vue.js template

Currently, I am in the learning phase of vue.js and wanted to share my index.html code with you: <html> <head> <link rel="stylesheet" href="index.css"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"> ...

Error in React: Attempting to access the 'year' property of an undefined value

I'm currently working on building a calendar using react hooks, but I've encountered an error that reads: Store.js:3661 Uncaught TypeError: Cannot read property 'year' of undefined at Function.diff (Store.js:3661) at Function.i ...

Ticket system bot using Discord.js reactions

I am currently working on building a ticket system, but I have encountered an issue. My goal is to implement a cooldown feature where users must wait for a ticket to close before they can react again. Below is the code snippet that I have been working wit ...

How can I merge disabled dates in react-day-picker-input?

Currently I am exploring the NPM package known as react-day-picker, however, I have encountered a significant lack of documentation regarding the react-day-picker-input element. The documentation briefly mentions a prop named dayPickerProps which requires ...

An array filled with unique and non-repeating elements

I want to display random country flags next to each other, making sure they do not match. However, my specific case requires a unique solution for dealing with arrays: function displayRandomFlags() { var flagurls = ["ZPlo8tpmp/chi","cJBo8tpk6/sov","QyLo ...

Rendering a React component conditionally within the same line

I'm trying to conditionally render a Home component based on a certain condition. I attempted to utilize the Inline If-Else with Conditional Operator recommended by React, as explained in this source. The code snippet I have looks like this: import ...

Generating unique spreadsheet identifiers in an HTML file

When it comes to displaying a chart using Google Sheets, I encounter an issue where the data is sourced from the same spreadsheet: function updateChart() { var ui = HtmlService.createHtmlOutputFromFile('ChartLine').setWidth(1350).setHeight(550) ...