question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Events on Vue components call associated methods, but don't update DOM unless awaited

See original GitHub issue

Hello! I’m a big fan of user-event in my React projects, and I know that’s the primary framework for this library! I’ve started learning Vue and want to use userEvent in my component tests here too, but weird stuff happens. I’ll do my best to describe what I think is going on; please be patient with me!

  • @testing-library/user-event version: 13.1.5
  • Testing Framework and version: Jest 26.6.3 (with jest-dom matchers 5.11.10)
  • DOM Environment: jsdom 16.5.2
  • Vue version: 2.6.12

Relevant code or config

Component

// Header.vue
<template>
<div>
    <h1 v-if="checked">
        Checked
    </h1>
    <h1 v-else>
        Unchecked
    </h1>
    <fieldset>
        <legend class="question">Check this box to make the header change!</legend>
        <input
            id="box"
            type="checkbox"
            v-on:change="toggleBox()"
        >
        <label for="box">Click me!</label>
    </fieldset>
</div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
    data() {
        return {
            checked: false
        }
    },

    methods: {
        toggleBox() {
            this.checked = !this.checked
        }
    }
})
</script>

Failing test

// Header.test.ts
import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'

import Header from './Header.vue'

test('changes the header when checkbox checked', () => {
    render(Header)

    userEvent.click(screen.getByLabelText('Click me!'))
    
    // passes
    expect(screen.getByLabelText('Click me!')).toBeChecked()

    // fails
    expect(screen.getByText('Checked')).toBeInTheDocument()
})

// screen.debug() output
<body>
  <div>
    <div>
      <h1>
        Unchecked!
      </h1>
      <fieldset>
        <legend
          class="question"
        >
          Check this box to make the header change!
        </legend>
        <input
          id="box"
          type="checkbox"
        />
        <label
          for="box"
        >
          Click me!
        </label>
      </fieldset>
    </div>
  </div>
</body>

Working test without user-event

// Header.test.ts
import { fireEvent, render, screen } from '@testing-library/vue'

import Header from './Header.vue'

test('changes the header when checkbox checked', async () => {
    render(Header)

    await fireEvent.click(screen.getByLabelText('Click me!'))

    // passes
    expect(screen.getByLabelText('Click me!')).toBeChecked()
    expect(screen.getByText('Checked')).toBeInTheDocument()
})

// screen.debug
<body>
  <div>
    <div>
      <h1>
        Checked!
      </h1>
      <fieldset>
        <legend
          class="question"
        >
          Check this box to make the header change!
        </legend>
        <input
          id="box"
          type="checkbox"
        />
        <label
          for="box"
        >
          Click me!
        </label>
      </fieldset>
    </div>
  </div>
</body>

Working test, unexpectedly, with user-event

// Header.test.ts
import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'

import Header from './Header.vue'

test('changes the header when checkbox checked', async () => {
    render(Header)

    // user-event should be synchronous, so why does this work?
    await userEvent.click(screen.getByLabelText('Click me!'))

    // passes
    expect(screen.getByLabelText('Click me!')).toBeChecked()
    expect(screen.getByText('Checked')).toBeInTheDocument()
})

// screen.debug output same as previous test

Reproduction repository: I can try to get one together if the above code is not enough! It’ll take me some time, though.

Problem description: user-event properly interacts with DOM elements and triggers a Vue component’s methods, but testing-library’s representation of the DOM after that method is called doesn’t change unless userEvent.[method] is awaited, despite the click method here being synchronous.

I’m not sure how to keep exploring this, and fireEvent is a fine substitute for now, but I figured you’d like to know this is happening with Vue!

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
ITenthusiasmcommented, Jul 27, 2021

@rydash You may be able to get a clearer idea of how the problem arises from here.

For your immediate purposes, I’d recommend using waitFor instead. There are peculiar circumstances where Vue.nextTick doesn’t do what one would expect or hope for. By contrast, waitFor is guaranteed to work in all circumstances (because you’re waiting for a condition to be met). And the last assertion in your test that appears in a waitFor doesn’t need to be awaited – if that makes sense.

0reactions
rydashcommented, Jul 30, 2021

For anyone reading this issue later, here’s waitFor used in the test in the initial example. Both expectations pass!

// Header.test.ts
import { render, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'

import Header from './Header.vue'

test('changes the header when checkbox checked', async () => {
    render(Header)

    userEvent.click(screen.getByLabelText('Click me!'))

    expect(screen.getByLabelText('Click me!')).toBeChecked()
    await waitFor(() => expect(screen.getByText('Checked')).toBeInTheDocument())
})
Read more comments on GitHub >

github_iconTop Results From Across the Web

Trouble getting DOM update in Vue.js while waiting for ...
This means that everything in Vue is event(data)-driven. Your function only defines behavior that has no data binding to the V-DOM.
Read more >
Asynchronous Behavior | Vue Test Utils
Surprisingly, this fails! The reason is although count is increased, Vue will not update the DOM until the next event loop tick.
Read more >
Focus management with Vue refs - Learn web development
Instead, we need to wait until after Vue undergoes the next DOM update cycle. To do that, Vue components have a special method...
Read more >
Understanding Vue.js nextTick - Telerik
These callbacks can be handy when you want to execute something while ensuring that the props, the data or the computed of a...
Read more >
Beginner Vue.js Tutorial with User Login - Auth0
If you already have a Vue application and just want to add authentication, jump to this section to learn how. Vue events authentication...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found