vuetify
vuetify copied to clipboard
[Bug Report][3.6.7] Can't test components that require the v-app component as their parent.
Environment
Vuetify Version: 3.6.7 Vue Version: 3.4.27 Browsers: Chrome 125.0.0.0 OS: Windows 10
Steps to reproduce
Continuing from the discussion (https://github.com/vuetifyjs/vuetify/discussions/18076), components like VMain, VNavigationDrawer, VBottomNavigation, VAppBar, and VLayoutItem do not render in tests using @vue/test-utils, making it impossible to test components that contain these components.
For example, the following test:
import { mount } from '@vue/test-utils'
import { describe, test } from 'vitest'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const vuetify = createVuetify({
components,
directives
})
const mountComponent = (props?: any) => mount(
{
template: `
<v-app>
<v-app-bar>
<div>123</div>
</v-app-bar>
</v-app>
`,
setup() {
return { props }
}
},
{
global: {
plugins: [vuetify]
},
props
}
)
describe('should', () => {
test('', () => {
const wrapper = mountComponent({
mainPage: true,
title: 'main'
})
console.log(wrapper.html())
})
})
Will have the following markup:
<div class="v-application v-theme--light v-layout v-layout--full-height v-locale--is-ltr" mainpage="true" title="main">
<div class="v-application__wrap">
<!---->
</div>
</div>
The following variant also does not work:
import { mount } from '@vue/test-utils'
import { describe, test } from 'vitest'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const vuetify = createVuetify({
components,
directives
})
const mountComponent = (props?: any) => mount(
components.VApp,
{
slots: {
default: components.VLayoutItem
},
global: {
plugins: [vuetify]
},
props
}
)
describe('should', () => {
test('', () => {
const wrapper = mountComponent({
mainPage: true,
title: 'main'
})
console.log(wrapper.html())
})
})
Expected Behavior
Components must rendering
Actual Behavior
Components are not rendering
Reproduction Link
https://play.vuetifyjs.com/#...
Other comments
Can’t give a link to the repository, since the work is being done in a private repository
UPD: I managed to fix it using this before the first expect:
await new Promise((res) => {
setTimeout(() => res(''), 1)
})
I'm not sure it's very obvious. It might be worth describing this in the documentation if it can't be fixed.
I believe I can confirm this behaviour is present since 3.6.0.
We encountered the same issue in component testing a component with v-app-bar with Playwright, none of the fixes suggested above or in the discussion (#18076) seemed to work. As a workaround we've created a template with v-app and a nested dynamic component that can be passed in as a prop. Posting in case it helps anyone else out.
This is the template:
<template>
<v-app>
<component :is=componentToTest></component>
</v-app>
</template>
<script lang="ts" setup>
defineProps(['componentToTest'])
</script>
And the test:
import { test, expect } from '@playwright/experimental-ct-vue'
import AppBar from '../../src/components/AppBar.vue'
import ComponentTestAppTemplate from './templates/ComponentTestAppTemplate.vue'
test('app bar title is correct', async ({ mount }) => {
const appBar = await mount(ComponentTestAppTemplate, {
props: {
componentToTest: AppBar
}
})
await expect(appBar).toContainText('App Bar Title')
})
Where AppBar.vue is:
<template>
<v-app-bar>
<template #prepend>
</template>
<v-app-bar-title>
App Bar Title
</v-app-bar-title>
<template #append>
</template>
</v-app-bar>
</template>
<script lang="ts" setup>
</script>
Is this still an issue? As far as I'm aware, you can use VLayout instead of VApp for the purposes of tests. Basically, those components are layout specific and are looking for a parent instance to bind with.
I think this is still an issue, especially when combined with certain interactions with VOverlays and VMenus as well, I'll try to make a reproduction when I'm able.
Is this still an issue? As far as I'm aware, you can use VLayout instead of VApp for the purposes of tests. Basically, those components are layout specific and are looking for a parent instance to bind with.
This was from when we were using Suspense so none of the layout components were rendered initially, it doesn't exist in 3.7.
VOverlay will never show up in wrapper.html() as it is teleported to the document body, and it won't be interactive for a while after opening - in our tests we use webdriverio's waitForStable between opening the menu and trying to click on it.
Ah! Thank you for the update. We've managed to work around VOverlay and VMenu in our unit tests by stubbing them where we need to, though it does lose a lot of the interactivity as you mention. Looking forward to trying 3.7 out.
This was from when we were using Suspense so none of the layout components were rendered initially, it doesn't exist in 3.7.
VOverlay will never show up in wrapper.html() as it is teleported to the document body, and it won't be interactive for a while after opening - in our tests we use webdriverio's waitForStable between opening the menu and trying to click on it.
It's definitely still an issue. Still needs to be used like @jl094 solution.
It's definitely still an issue. Still needs to be used like @jl094 solution.
Can you show a reproduction? The offending code that caused the bug has been removed entirely from the framework.
Hi @johnleider I have a component that holds a v-app-bar without having a direct v-app as its parent. I have to test this component without building the entire hierarchy, i.e., the v-app component, the error will be there. Therefore, I have to provide a wrapper component that builds the v-app and plug it with my component that has v-app-bar. It'll work if you build the entire component's hierarchy though.
"vite": "^6.2.2", "vite-plugin-vuetify": "^2.1.0", "vue": "^3.5.13", "vuetify": "^3.7.18",
MyBar.vue
<v-card class="model-bar" data-testid="my-bar">
<v-app-bar elevation="1" class="px-11">
<v-app-bar-title class="text-primary text-h6 font-weight-bold">
Auto Register
</v-app-bar-title>
...
MyBar.cy.ts
import { markRaw } from 'vue';
import MyBar from '../Components/MyBar.vue';
import ComponentTestAppTemplate from '@/.../ComponentAppTestTemplate.vue';
// If I try to mount it directly without the wrapper, it'll break.
const mountComponent = () => {
return cy.mount(ComponentTestAppTemplate, {
props: {
componentToTest: markRaw(MyBar),
},
});
};
describe('<MyBar />', () => {
...
ComponentAppTestTemplate.vue
<template>
<v-app>
<component :is="componentToTest"></component>
</v-app>
</template>
<script lang="ts" setup>
defineProps(['componentToTest']);
</script>
Use a toolbar instead of an appbar for this use-case.
@johnleider Gotcha. Thanks.