Events on Vue components call associated methods, but don't update DOM unless awaited
See original GitHub issueHello! 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:
- Created 2 years ago
- Reactions:3
- Comments:7 (1 by maintainers)
Top GitHub Comments
@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 whereVue.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 awaitFor
doesn’t need to beawait
ed – if that makes sense.For anyone reading this issue later, here’s
waitFor
used in the test in the initial example. Both expectations pass!