9

Does anyone know how to do component testing in @vue/test-utils v2 library, while using Vuetify 3 Beta version? I am also using Vue3 composition API to define my components. The documentation that i found here https://vuetifyjs.com/en/getting-started/unit-testing/#spec-tests is not working, since it is for Vuetify v2 and @vue/test-utils v1

This is my plugins/vuetify.js

import '@mdi/font/css/materialdesignicons.css';
import 'vuetify/styles';
import { createVuetify } from 'vuetify';

export default createVuetify();

This is my code in NavBar.spec.js

import { mount } from '@vue/test-utils';
import NavBar from '../NavBar.vue';
import Vuetify from '../../plugins/vuetify';

describe('NavBar', () => {
    it('No properties [correct]', () => {
        mount(NavBar, {
            props: {},
            global: {
                plugins: [Vuetify]
            }
        });
        // check if navbar renders
        cy.get('.main')
            .first();

    });
});

This is the error i receive: Could not find injected Vuetify layout

Bogdan
  • 372
  • 3
  • 12
  • I'm stuck with the same problem, did you find a solution yet? Both answers don't work for me. – dev0 Feb 27 '23 at 13:48

4 Answers4

4

I was having the same issue, someone answered a similar question saying they fixed the error by moving their component within the <v-app> tag.

If like me however your component was already within your top level <v-app> tag, other people online had success by adding

let el = document.createElement("div");
el.setAttribute("data-app", "true");
document.body.appendChild(el);

before your test, but this also didn't work for my setup.

(I found wrapping my component in a <v-app> tag got my tests to work, but you shouldn't have multiple of those on your page and you don't want to have to add extra code to every component just for your tests to pass.)

If that solution doesn't work for you either though, I eventually worked around it by mounting the VApp component itself and passing my component to it as the default slot content, like so:

import { h } from "vue";
import { VApp } from "vuetify/components";
import MyComponent from "./MyComponent.vue";

describe("Test", () => {
  it("should", () => {
    const wrapper = mount(VApp, {
      slots: {
        default: h(MyComponent),
      }
    })
  )}
)}

(thanks to this GitHub thread for the assist)

Hopefully this is just an issue as Vuetify 3 is still in beta, and once they formalise best testing practice we should be able to remove this custom fix.

Hope this helps!

Fergmux
  • 932
  • 9
  • 13
  • Thanks for the reply, this is a very specific problem. I managed to get it working by creating a VuetifyWrapper component that has the as top element and it loads a component dynamically with . So it's something similar to what you did with the slot. – Bogdan Jun 03 '22 at 09:38
  • I had the same issue, but when I try to wrap it inside the `VApp`, I get the error "Cannot find module 'vuetify/lib/components' from 'tests/unit/example.spec.ts'". Do you have extra configuration in your setup or babel files? – Marco Arruda Jul 11 '22 at 17:05
  • 1
    @MarcoArruda, please see my answer. hope it helps. – ikhvjs Jul 12 '22 at 15:32
3

I have the same issue and I am using vitest.

Thanks for this GitHub issue

I solved it by adding global.plugin in each test file as below.

ToDoList.test.js

import { createVuetify } from "vuetify";
import { mount } from "@vue/test-utils";
import ToDoList from "./ToDoList.vue";

describe("ToDoList.vue", () => {
    const vuetify = createVuetify()

    it("renders", () => {
        const wrapper = mount(ToDoList, {
            props: {
                title: "testTitle",
                text: "testText",
            },
            global: {
                plugins: [vuetify],
            },
        });
    });
});

I also need to add some configuration in vite.config.js as below

vite.config.js

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

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vuetify from "vite-plugin-vuetify";

const path = require("path");

export default defineConfig({
    //...other config...
    test: {
        globals: true,
        environment: "jsdom",
        setupFiles: "vuetify.config.js",
        deps: {
            inline: ["vuetify"],
        },
    },
});

Finally, I create a vuetify.config.js file in the root directory referenced from the setupFiles properties of vite.config.js

vuetify.config.js

global.CSS = { supports: () => false };
ikhvjs
  • 5,316
  • 2
  • 13
  • 36
  • Thanks, it helped a lot. I also found two repositories that can be helpful as templates: https://github.com/peshanghiwa/Vue3-Vite-Vuetify3-Typescript-Template and https://github.com/logue/vite-vuetify-ts-starter – Marco Arruda Jul 12 '22 at 21:45
2

I post my minimal configuration that worked for me at the time of writing for the combination
Vue 3, Vite 4, Vuetify 3, Typescript 4.

It's based on the answers from this page and from other sources I had to investigate to make it work.
The code is based on the default template built with the Vue bootstrap tool, so it has a separate vitest.config.ts which extends the main vite.config which doesn't need changes.

The ResizeObserverStub is needed to resolve ReferenceError: ResizeObserver is not defined mentioned here: https://github.com/vuetifyjs/vuetify/issues/14749#issuecomment-1481141623.

According to the suggestions from that Github issue, the vite-plugin-vuetify is not needed, so the solution works without it.

package.json (excerpt)

"dependencies": {
  "vue": "^3.2.47",
  "vuetify": "^3.1.10"
},
"devDependencies": {
  "@vue/test-utils": "^2.3.0",
  "vite": "^4.1.4",
  "vitest": "^0.29.1",
}

vitest.config.ts

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

import { fileURLToPath } from 'node:url'
import { mergeConfig } from 'vite'
import { configDefaults, defineConfig } from 'vitest/config'

import viteConfig from './vite.config'

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      environment: 'jsdom',
      exclude: [...configDefaults.exclude, 'e2e/*'],
      root: fileURLToPath(new URL('./', import.meta.url)),
      deps: {
        inline: ["vuetify"]
      },
      setupFiles: ['./vitest/setup.ts']
    }
  })
)

vitest/setup.ts

// Fix undefined ResizeObserver error
class ResizeObserverStub {
  observe () { }
  unobserve () { }
  disconnect () { }
}

window.ResizeObserver = window.ResizeObserver || ResizeObserverStub;

HomeView.spec.ts

import { describe, test } from 'vitest'
import { mount } from '@vue/test-utils'

import { createVuetify } from "vuetify"
import * as components from "vuetify/components"
import * as directives from "vuetify/directives"

import HomeView from '@/views/HomeView.vue'


const vuetify = createVuetify({ components, directives })

describe('HomeView', () => {
  test('init', async () => {
    const view = mount(HomeView, { global: {plugins: [vuetify]} })
    console.log(view.html())  // check the Vuetify components are rendered to HTML correctly
    // ... test code
  })
})
mortalis
  • 2,060
  • 24
  • 34
  • Thanks for the minimalist answer using an example straight of the scaffolding tool. What fixed it for me was the deps: { inline: ["vuetify"] }, before that I encountered issues looking like Unknown file extension ".css" – Charles G May 04 '23 at 11:50
0

I have fixed this issue in Vuetify 3 + @testing-library/vue (should also work with @vue/test-utils and mount):

import { VuetifyLayoutKey } from "vuetify/lib/composables/layout.mjs"

//..

global: {
  // fix: [Vuetify] Could not find injected layout
  provide: {
    [VuetifyLayoutKey]: {
       mainStyles: { value: "" },
       register: () => ({
         layoutItemStyles: { value: { top: 0 } },
       }),
       unregister: () => ({}),
     },
  },
} 

Full render function:


// test-utils.js

import { render } from "@testing-library/vue"
import { createVuetify } from "vuetify"
import * as components from "vuetify/components"
import * as directives from "vuetify/directives"
import { VuetifyLayoutKey } from "vuetify/lib/composables/layout.mjs"

const vuetify = createVuetify({
  components,
  directives,
})

const customRender: typeof render = (ui, options?) => {
  // should also work with `mount` and `shallowMount` instead of `render`
  return render(ui, {
    global: {
      ...options?.global,
      plugins: [vuetify, ...(options?.global?.plugins ?? [])],
      // fix: [Vuetify] Could not find injected layout
      provide: {
        [VuetifyLayoutKey]: {
          mainStyles: { value: "" },
          register: () => ({
            layoutItemStyles: { value: { top: 0 } },
          }),
          unregister: () => ({}),
        },
        ...options?.global?.provide,
      },
    ...options,
  })
}

// re-export everything
export * from "@testing-library/vue"
export { default as userEvent } from "@testing-library/user-event"
export { axe } from "vitest-axe"
// override render method
export { customRender as render }

The error only happens in components that use useLayout from Vuetify composables. In my case I had a v-main and v-navigation-drawer component. Other components might require more mocks.

oemera
  • 3,223
  • 1
  • 19
  • 33