'After test is run, Error "TypeError: Cannot read property '$model' of undefined..." appears

UPDATE: I've found out that i have to mock certain functions but it is still a hell of a job :)

I've been stuck on this particular problem for almost 3 weeks now and I haven't been able to find a similar case.

I am trying to run vue test utils with vuelidate in Vue3, but everytime I run the test it gives me this error:

src/components/Input/Input.test.ts > Input.vue > looks for error message
TypeError: Cannot read property '$model' of undefined
 ❯ Proxy._sfc_render src/components/Input/Input.vue:103:61
    104| 
    105|   computed: {
    106|     // If error prop is passed then the class name for the error with the corresponding styling will be used, else the default class name will be used
       |                                         ^
    107|     errorInputStyling(): string {
    108|       return this.v$.$errors.length === 0 ? 'input-field' : 'input-error';
 ❯ renderComponentRoot ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:887:44
 ❯ ReactiveEffect.componentUpdateFn [as fn] ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4963:57
 ❯ ReactiveEffect.run ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:171:25
 ❯ setupRenderEffect ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5089:9
 ❯ mountComponent ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4872:9
 ❯ processComponent ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4830:17
 ❯ patch ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4433:21
 ❯ ReactiveEffect.componentUpdateFn [as fn] ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4970:21
 ❯ ReactiveEffect.run ../../node_modules/.pnpm/@[email protected]/node_modules/@vue/reactivity/dist/reactivity.cjs.js:171:25

Here is the input component:

<template> 
<div class="form-group">
 <label class="input-label" v-if="label" :for="name">{{
      formattedLabel
    }}</label>
    <input
      :class="errorInputStyling"
      @input="$emit('update:modelValue', state.value)"
      :name="name"
      :type="type"
      v-bind="$attrs"
      v-model="v$.value.$model"
    />
    <p class="error-message" v-for="error of v$.$errors" :key="error.$uid">
      <strong>{{ error.$message }}</strong>
    </p>
  </div>
  <slot />
</template>

<script lang="ts">
import { defineComponent, reactive, computed, PropType } from 'vue';

import useVuelidate from '@vuelidate/core';

import * as v from '@vuelidate/validators';

export default defineComponent({
  name: 'Input',
  inheritAttrs: false,
  emits: ['update:modelValue'],
  props: {
    label: {
      type: String,
    },
    modelValue: {
      type: String,
      required: false,
    },
    name: {
      type: String,
      required: true,
    },
    rules: {
      type: Object as PropType<Record<string, any>>,
      required: false,
    },
    type: {
      default: 'text',
      validator: (value: unknown) => {
        return (
          typeof value == 'string' &&
          ['email', 'number', 'password', 'text'].indexOf(value) !== -1
        );
      },
    },
  },

  setup: function (props) {
    const state = reactive({
      value: props.modelValue,
    });

    const rules = computed(() => {
      if (!props.rules) {
        return {};
      }

      const values = {} as Record<string, any>;

      // we map the rules object to the validators from vuelidate
      for (const [key, value] of Object.entries(props.rules)) {
        if (typeof value === 'boolean') {
          if (value) {
            values[key] = (v as any)[key];
            console.log(key, value);
          }
        } else {
          values[key] = (v as any)[key](value);
          console.log(key, value);
        }
      }
      // after this we append some default validators based on the type
      if (props.type === 'email') {
        values.email = v.email;
      } else if (props.type === 'number') {
        values.numeric = v.numeric;
      } else if (props.type === 'password') {
        values.required = v.required;
      }

      return { value: values };
    });

    //@ts-ignore
    const v$ = useVuelidate(rules, state, {
      $lazy: true,
    });
    return {
      state,
      v$,
    };
  },

And here is the test file:

import { describe, expect, test } from 'vitest';
import { mount } from '@vue/test-utils';
import Input from './Input.vue';
import useVuelidate from '@vuelidate/core';

describe('Input.vue',  () => {
    test('looks for error message', async () => {
        const wrapper = mount(Input, {
            global: {
                plugins: [useVuelidate],
            },
            sync: false,
        });
        const input = wrapper.find('input')
        const text = wrapper.find('strong')

        await input.setValue('test')
        expect(input.element.value).toBe('test')

        // input.setValue('Test')
        // expect(text.text()).toContain('');
        // await wrapper.vm.$nextTick()
        // expect(text.text()).toContain('Value is required');
        // wrapper.vm.$v.$model();
        // wrapper.vm.$v.$nextTick();
        // await wrapper.find('input').trigger('keyup');
        // await wrapper.vm.$nextTick()
        // expect(wrapper.text()).toContain('Test');
    });
});

I've come to a wits' end and I would appreciate any help. Thanks in advance!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source