Description
In writing some small apps and the VTU docs, I encounted some caveats when testing components using Vue Router. This is mainly because the navigation is now all async (which I think it is a great design decision, it fits into Vue's async update model perfectly).
This happens because navigations are all asynchronous now.
When I first tried writing some tests using Vue Router, I didn't realize I had to do await router.isReady
, or the initial navigation may not be completed before the component renders.
Also, I triggered a click on a <router-link>
I got an error. It turns out that you need to wait for the navigation to complete (makes sense with the new async navigation model).
Here is a full snippet demonstrating what is currently required when using VueRouter in a jsdom environment (sans VTU).
it('avoids several caveats to succesfully click an <a> tag", async () => {
const el = document.createElement('div')
el.id = 'app'
document.body.appendChild(el)
const app = createApp(App)
app.use(router)
router.push('/')
await router.isReady() // <- need to do this
app.mount(el)
const event = document.createEvent('Event')
event.initEvent('click')
let promise = new Promise(resolve => {
const remove = router.afterEach(() => {
remove() // <- need to do this
resolve() // <- need to do this
})
})
document.getElementById('post')!.dispatchEvent(event)
await promise // <- need to do this
console.log(document.body.outerHTML)
})
The user must await isReady
, and also await resolve
using the afterEach
callback to ensure the navigation triggered when clicking on the <a>
tag (from <router-link>
) before continuing their tests. Kind of like how you need to do await nextTick
for Vue to update the DOM.
We should bundle some goodies to make testing Vue Router a more smooth experience. @posva suggested await routerMock.pendingNavigation()
. I think we need both good utils for testing with the actual VueRouter, and a mocked VueRouter.
This is the kind of thing you would previously write in VTU 1.x with Vue 2.x and Vue Router 3.x:
const router = createRouter()
const wrapper = mount(App. {
global: {
plugins: [router]
}
})
await wrapper.find('a').trigger('click') // it "just works"
I see a few options, but I think the best and most simple is some additional methods on wrapper
. Here are some ideas I had:
// react testing library style, like act(() => ...
await doNavigation(() => {
wrapper.find('a').trigger('click')
})
// or a more VTU-like like API
await wrapper.navigate(wrapper.find('a'))
// or with a sneaky implicit find/findComponent
await wrapper.navigate('a')
I like the latter the best. Tight integration with Vue Router, and I think it's really readable.
Any ideas or feedback would be great 👍