3

I'm using VITE & VITEST for my VUE3 project and also Vuetify. Recently I tried to setup VITEST so that I could perform some component testing. To do that I used the library vue/test-utils as well as its mount() method. I have successfully written some tests for some components I made. But when I try to mount my Header component which use a v-app-bar from Vuetify, it displays the following error message:

Error: [Vuetify] Could not find injected layout

[Vue warn]: injection "Symbol(vuetify:layout)" not found.

I used the shallow: true property of the mount() method just to check on the DOM generated :

test dom

import { mount } from '@vue/test-utils';
import DefaultHeader from './DefaultHeader.vue';

// Mocking routes to pass these data as props
const routes = [
{
    path: '/',
    name: 'Home',
    meta: {
        header: false,
    },
},
{
    path: '/example1',
    name: 'Example1',
    meta: {
        header: true,
    },
    children: [
        {
            path: 'one',
            name: 'One',
        },
        {
            path: 'two',
            name: 'Two',
        },
    ],
},
{
    path: '/example2',
    name: 'Example2',
    meta: {
        header: true,
    },
}];
// stubs is for not mounting Login, Logout, UserMenu components as
// they are supposed to be tested independently
const stubs =  {
    UserMenu: {
        template: '<span />',
    },
    AppLogo: {
        template: '<span />',
    },
    Login: {
        template: '<span />',
    }
};

test('Properly displays the headers of a route list', async () => {
    const header = mount(DefaultHeader, {
        props : { routes },
        global: {
            stubs
        },
        attachTo: document.getElementById('app')
    });
    /* routes.forEach(route => {
        expect(document.getElementById('app')).toBe(1);
    }); */
});

Here is my header component :

<script setup>
import AppLogo from '@/components/AppLogo.vue';
import UserMenu from '@/components/menus/UserMenu.vue';
import Login from '@/components/Login.vue';

const props = defineProps({
  routes: {
    type: Array,
  }
});

</script>

<template>
  <v-app-bar
    name="app-bar"
    color="blue-grey"
    flat
    hide-on-scroll
  >
    <v-btn to="/">
      <AppLogo />
    </v-btn>

    <v-btn
      class="text-white"
      v-for="route in routes"
      :key="route"
      :to="route.path"
    >
      {{ route.name }}
    </v-btn>

    <v-spacer/>

    <Login />

    <UserMenu />
  </v-app-bar>
</template>

I also run a setup.js script before each test file :

import { install } from 'resize-observer';
import { config } from '@vue/test-utils'
import vuetify from '../plugins/vuetify';
import { enableAutoUnmount } from '@vue/test-utils';
import { afterEach } from 'vitest';

enableAutoUnmount(afterEach)

config.global = {
  plugins: [vuetify],
}

/* With jest-dom the resizeObserver seems to not be included, it is used by Vuetify so we have
   to include it somehow for tests.
*/
if (!window.ResizeObserver) install();

// // Custom container to integrate Vuetify.
// // Vuetify requires you to wrap your app with a v-app component that provides
// // a <div data-app="true"> node.
const app = document.createElement('div');
app.setAttribute('data-app', 'true')
app.setAttribute('id', 'app')
document.body.appendChild(app);

Here is the vuetify.js file used in the setup :

/**
 * plugins/vuetify.js
 *
 * Framework documentation: https://vuetifyjs.com`
 */

// Styles
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'

// Composables
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
  components,
  directives,
  theme: {
    themes: {
      light: {
        colors: {
          primary: '#1867C0',
          secondary: '#5CBBF6',
        },
      },
    },
  },
})

And finally here is my vite.config.js in which I also did the VITEST config (see the test property), even if I don't think I did an error during the config since it works perfectly for other components that don't use the layouts from Vuetify.

/// <reference types="vitest" />

// Plugins
import vue from '@vitejs/plugin-vue'
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'

// Utilities
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue({ 
      template: { transformAssetUrls }
    }),
    // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
    vuetify({
      autoImport: true,
      styles: {
        configFile: 'src/styles/settings.scss',
      },
    }),
  ],
  define: { 'process.env': {}},
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
    extensions: [
      '.js',
      '.json',
      '.jsx',
      '.mjs',
      '.ts',
      '.tsx',
      '.vue',
      '.scss'
    ],
  },
  server: {
    host: "<server host>",
    port: "<server port>",
  },
  test: {
    // https://vitest.dev/guide/#configuring-vitest
    globals: true,
    environment: 'jsdom',
    deps: {
      inline: ['vuetify'],
    },
    setupFiles: './src/tests/setup.js',
  },
})

I can't find the root of the problem, when I run my app I don't get any error or warning referencing this error. Also I still don't understand what this warning in the Vuetify doc means : Vuetify layout warning which could be the cause of the error I get.

I succeeded with the use of a custom component VuetifyWrapper that is just a v-app with a slot in it, then I use the following workaround :

import VuetifyWrapper from '../tests/VuetifyWrapper.vue';
import { h } from 'vue';

...

test('Properly displays the headers of a route list', async () => {
    /* When using v-layouts the component won't load for an unknow reason,
       we have to mount the component inside a V-APP, so we use this workaround.
    */
    const wrapper = mount(VuetifyWrapper, {
        slots: {
            default: h(DefaultHeader, {
                routes,
            }),
        },
        global: {
            stubs,
        },
    });
});

I'm not quite satisfied, Vuetify 3 isn't in beta anymore so I'd like to find a cleaner way of making this work.

1 Answer 1

1

I also puzzled over this problem.

But after looking at the Vuetify repository, I realized that this is the right approach.

cy.mount(() => (
  <VLayout>
    <VAppBar height="200" />
  </VLayout>
))
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.