Search code examples
javascriptvue.jsjestjsvee-validatevue-testing-library

VeeValidate4. How to test custom field Component which uses useField?


I tried to test my custom input component. But I don't know how to do it right, because I didn't find any info how to test it. The only thing I found was from this Mocking Form and Field Contexts

My custom component:

<script setup>
import { useField } from 'vee-validate';
import { watch, toRef } from 'vue';

const props = defineProps({
  modelValue: {
    type: undefined,
    default: undefined,
  },

  type: {
    type: String,
    default: 'text',
  },

  label: {
    type: String,
    default: '',
  },

  name: {
    type: String,
    required: true,
  },
});

const emit = defineEmits(['update:modelValue']);

const { value, setValue, errorMessage } = useField(toRef(props, 'name'));

watch(
  () => value.value,
  (newValue) => {
    emit('update:modelValue', newValue);
  }
);

watch(
  () => props.modelValue,
  (newValue) => {
    setValue(newValue);
  }
);
</script>

<template>
  <div class="form-group">
    <label v-if="props.label" :for="props.label">{{ props.label }}</label>

    <input
      :id="props.label"
      v-model="value"
      :name="props.name"
      :type="props.type"
      class="form-input"
    />

    <span v-if="errorMessage" class="form-error" role="alert">{{ errorMessage }}</span>
  </div>
</template>

My test file:

import { render, screen, waitFor } from '@testing-library/vue';
import AppField from './AppField.vue';
import { FormContextKey, useForm } from 'vee-validate';
import userEvent from '@testing-library/user-event';

const rednerField = ({ label, type, ...rest } = {}) => {
  const options = { props: { name: 'inputName', label, type }, ...rest };

  render(AppField, options);
};

const user = userEvent.setup();

const MockedForm = useForm({
  validationSchema: {
    inputName(value) {
      if (value && value.trim()) {
        return true;
      }

      return 'This is required';
    },
  },
});

describe('showing error', () => {
  it('show error if field is invalid', async () => {
    rednerField({ label: 'labelName', global: { provide: { [FormContextKey]: MockedForm } } });
    const input = screen.getByLabelText('labelName');

    user.type(input, ' ');

    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent('This is required');
    });
  });
});

This test works correctly, but there are warnings in the console: console log from jest

Please tell me if this test is written correctly and how can I get rid of warnings or is this normal ?


Solution

  • I just figured out how to do this myself. This issue may be 2 years old but the vee-validate documentation doesn't really explain how to deal with these issue so this might help the next person who runs into this problem.

    I ended up making a helper function that extends the Component setup() so the provide within useForm will run in the right context. I hate to couple to the library like this but I wasn't able to get a mock form context working without it.

    With this setup you should be able to get working tests against any expected validationSchema

    Helper function

    export function getExtendedComponent(
      inputComponent: ReturnType<typeof defineComponent>,
      options: { mockFormData: FormData }
    ): Component {
      return {
        ...inputComponent,
        setup(props, ctx) {
          let result = {};
    
          if (options.mockFormData) {
            const useFormResult = useForm(options.mockFormData);
            /** @ts-ignore non-problematic type differences */
            provide(FormContextKey, useFormResult);
          }
    
          if (inputComponent.setup) {
            const setupResult = inputComponent.setup(props, ctx);
            result = { ...result, ...setupResult };
          }
    
          return result;
        },
      };
    }
    

    Test file setup

    const mockFormData = {
      validationSchema: {
        username(value: string) {
          if (value && value.trim()) {
            return true;
          }
    
          return "required";
        },
      },
    };
    
    const InputComponentWrapped: Component = getExtendedComponent(InputComponent, {
      mockFormData,
    });