vuetify icon indicating copy to clipboard operation
vuetify copied to clipboard

[Bug Report][3.6.7] Can't test components that require the v-app component as their parent.

Open Yamadetta opened this issue 1 year ago • 1 comments

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

Yamadetta avatar May 26 '24 17:05 Yamadetta

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.

Yamadetta avatar May 26 '24 18:05 Yamadetta

I believe I can confirm this behaviour is present since 3.6.0.

thopiddock avatar Jul 11 '24 13:07 thopiddock

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>

jl094 avatar Dec 17 '24 15:12 jl094

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.

johnleider avatar Feb 10 '25 22:02 johnleider

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.

thopiddock avatar Feb 13 '25 11:02 thopiddock

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.

KaelWD avatar Feb 13 '25 11:02 KaelWD

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.

thopiddock avatar Feb 13 '25 12:02 thopiddock

It's definitely still an issue. Still needs to be used like @jl094 solution.

thiagosaife avatar Mar 20 '25 19:03 thiagosaife

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.

johnleider avatar Mar 21 '25 15:03 johnleider

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>

thiagosaife avatar Mar 22 '25 01:03 thiagosaife

Use a toolbar instead of an appbar for this use-case.

johnleider avatar Mar 26 '25 02:03 johnleider

@johnleider Gotcha. Thanks.

thiagosaife avatar Mar 26 '25 10:03 thiagosaife