Search code examples
vue.jsjestjsvuetify.jsvue-test-utils

Cannot read property 'querySelector' of undefined - Vue test utils


I testing Vuetify navigation drawer component because I must change it a little bit and I have very weird problem. Additionally I add that I use TypeScript

TypeError: Cannot read property 'querySelector' of undefined

  28 | 
  29 |   private setBorderWidth(): void {
> 30 |     const border = this.drawer.$el.querySelector(
     | ^
  31 |       ".v-navigation-drawer__border"
  32 |     );
  33 | 

  at VueComponent.setBorderWidth (src/components/Navigation/NavigationDrawer.vue:30:1)
  at VueComponent.mounted (src/components/Navigation/NavigationDrawer.vue:90:1)
  at invokeWithErrorHandling (node_modules/vue/dist/vue.runtime.common.dev.js:1850:57)
  at callHook (node_modules/vue/dist/vue.runtime.common.dev.js:4207:7)
  at Object.insert (node_modules/vue/dist/vue.runtime.common.dev.js:3133:7)
  at invokeInsertHook (node_modules/vue/dist/vue.runtime.common.dev.js:6326:28)
  at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6543:5)
  at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3933:19)
  at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4054:10)
  at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
  at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
  at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
  at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
  at mount (node_modules/@vue/test-utils/dist/vue-test-utils.js:13855:21)
  at shallowMount (node_modules/@vue/test-utils/dist/vue-test-utils.js:13881:10)
  at Object.<anonymous> (tests/unit/components/Navigation/NavigationDrawer.spec.ts:20:21)

any suggestions whats is wrong bellow my test code I am open to all advicess

import { shallowMount, createLocalVue } from "@vue/test-utils";
import Vuetify from "vuetify";
import NavigationDrawer from "@/components/Navigation/NavigationDrawer.vue";

const localVue = createLocalVue();

describe("NavigationDrawer.vue", () => {
  let vuetify: any;

  beforeEach(() => {
    vuetify = new Vuetify();
  });

  test("Render props correctly when passed", () => {
    const props = {
      width: 300,
      visible: true
    };

    const wrapper = shallowMount(NavigationDrawer, {
      localVue,
      vuetify,
      propsData: props,
    });

    expect(wrapper.props()).toStrictEqual(props);
    expect(wrapper.vm.$data.navigation.show).toBeTruthy();
  });
});

Navigation drawer component I added possibility to resize this drawer

<template>
  <v-navigation-drawer
    ref="drawer"
    v-bind="$attrs"
    :width="navigation.width"
    v-model="navigation.visible"
  >
    <slot></slot>
  </v-navigation-drawer>
</template>

<script lang="ts">
import { Component, Vue, Prop, Ref } from "vue-property-decorator";

@Component
export default class NavigationDrawer extends Vue {
  @Prop({ required: true, type: [Number], default: 256 })
  private width!: number;
  @Prop({ required: true, type: Boolean, default: true })
  private visible!: boolean;

  @Ref("drawer") private drawer!: any;

  private navigation = {
    show: this.visible,
    width: this.width,
  };

  private setBorderWidth(): void {
    const border = this.drawer.$el.querySelector(
      ".v-navigation-drawer__border"
    );

    border.style.width = "5px";
    border.style.cursor = "col-resize";
  }

  private setEvents(): void {
    const minSize = 5;
    const el: HTMLElement = this.drawer.$el;

    const border = el.querySelector(
      ".v-navigation-drawer__border"
    )! as HTMLElement;
    const direction: string = el.classList.contains(
      "v-navigation-drawer--right"
    )
      ? "right"
      : "left";

    function resize(e: MouseEvent) {
      let clientX: number =
        direction === "right"
          ? document.body.scrollWidth - e.clientX
          : e.clientX;

      if (clientX <= 5) {
        clientX = 5;
      }

      el.style.width = `${clientX}px`;
    }

    border.addEventListener(
      "mousedown",
      (e: MouseEvent) => {
        if (e.offsetX < minSize) {
          el.style.transition = "initial";
          document.body.style.userSelect = "none";
          document.addEventListener("mousemove", resize, false);
        }
      },
      false
    );

    document.addEventListener(
      "mouseup",
      () => {
        el.style.transition = "";
        this.navigation.width = Number(el.style.width.replace("px", ""));
        document.body.style.cursor = "";
        document.body.style.userSelect = "";
        document.removeEventListener("mousemove", resize, false);
      },
      false
    );
  }

  private mounted() {
    this.setBorderWidth();
    this.setEvents();
  }
}
</script>

Solution

  • Replace shallowMount with mount and it should be ok.

    Remember that vuetify components needs to be mounted to to access them.

    EDIT:

    It seems that you have invalid v-model directive value. I suppose it should be navigation.show instead of navigation.visible

    EDIT 2: Try calling localVue.use(Vuetify) just right after const localVue = createLocalVue()

    It could result in Multiple instances of Vue detected. Then i would suggest to do it like so:

    ...
    import Vue from 'vue'
    import Vuetify from 'vuetify'
    ... 
    
    Vue.use(Vuetify);
    
    ...
    
    const wrapper = mount(NavigationDrawer, {
      vuetify,
      propsData: props,
    });
    
    

    EDIT 3 (Added my code):

    import { createLocalVue, mount } from "@vue/test-utils";
    
    import Vuetify from "vuetify";
    import NavigationDrawer from "@/components/NavigationDrawer.vue";
    
    const localVue = createLocalVue()
    localVue.use(Vuetify)
    
    describe("NavigationDrawer.vue", () => {
      let vuetify: any;
    
      beforeEach(() => {
        vuetify = new Vuetify();
      });
    
      test("Render props correctly when passed", () => {
        const props = {
          width: 300,
          visible: true
        };
    
        const wrapper = mount(NavigationDrawer, {
          localVue,
          vuetify,
          propsData: props,
        });
    
        expect(wrapper.props()).toStrictEqual(props);
        expect(wrapper.vm.$data.navigation.show).toBeTruthy();
      });
    });
    
    import { createLocalVue, mount } from "@vue/test-utils";
    import Vue from 'vue'
    import Vuetify from "vuetify";
    import NavigationDrawer from "@/components/NavigationDrawer.vue";
    
    Vue.use(Vuetify)
    
    describe("NavigationDrawer.vue", () => {
      let vuetify: any;
    
      beforeEach(() => {
        vuetify = new Vuetify();
      });
    
      test("Render props correctly when passed", () => {
        const props = {
          width: 300,
          visible: true
        };
    
        const wrapper = mount(NavigationDrawer, {
          vuetify,
          propsData: props,
        });
    
        expect(wrapper.props()).toStrictEqual(props);
        expect(wrapper.vm.$data.navigation.show).toBeTruthy();
      });
    });