vue.jsvuejs3vue-composition-apivitestvuelidate

Component does not emit event after async vuelidate validation line


I'm using Vue 3.3.* and Vuelidate 2.0.*. I'm using setup + composition API, TypeScript and TailwindCSS.

I'm creating unit tests using vitest and have a form in a component that I want to check if emits a submit event from that component when the submit button is clicked. In my unit test however, the emit function doesn't work if it emits the event after this line of code: const isValid = await v$.value.$validate();

This is the vitest:

    it("should emit submit event when submit button is clicked", async () => {
        const nameInput = wrapper.find("input#name");
        const emailInput = wrapper.find("input#email");
        const messageInput = wrapper.find("textarea#message");

        // First fill out the inputs with valid data
        await nameInput.setValue("John Doe");
        await emailInput.setValue("john.doe@example.com");
        await messageInput.setValue("A 10+ character long message.");

        await wrapper.find("form").trigger("submit");
        expect(wrapper.emitted()).toHaveProperty("submit");
    });

Here is my form:

        <form ref="formElement" id="contact-form" @submit.prevent="handleSubmit" class="flex flex-col gap-4">
            <div class="flex flex-col">
                <label for="name" class="text-lg mb-2">Name</label>
                <input
                    type="text"
                    id="name"
                    name="name"
                    @change="v$.name.$reset()"
                    :class="inputCss + ' ' + (v$.name.$error ? 'border-red-500' : 'border-white')"
                />
                <span v-if="v$.name.$error" class="ml-2 italic text-red-500">{{ v$.name.$errors[0].$message }}</span>
            </div>
            <div class="flex flex-col">
                <label for="email" class="text-lg mb-2">Email</label>
                <input
                    type="email"
                    id="email"
                    name="email"
                    @change="v$.email.$reset()"
                    :class="inputCss + ' ' + (v$.email.$error ? 'border-red-500' : 'border-white')"
                />
                <span v-if="v$.email.$error" class="ml-2 italic text-red-500">{{ v$.email.$errors[0].$message }}</span>
            </div>
            <div class="flex flex-col">
                <label for="message" class="text-lg mb-2">Message</label>
                <textarea
                    id="message"
                    name="message"
                    @change="v$.message.$reset()"
                    :class="inputCss + ' ' + (v$.message.$error ? 'border-red-500' : 'border-white')"
                ></textarea>
                <span v-if="v$.message.$error" class="ml-2 italic text-red-500">{{
                    v$.message.$errors[0].$message
                }}</span>
            </div>
            <div class="flex justify-center">
                <button type="submit" class="hover:bg-gray-50 hover:text-gray-900 duration-75" :class="inputCss">
                    Submit
                </button>
            </div>
        </form>

and the handleSubmit function:

async function handleSubmit() {
    if (!formElement.value) return;
    
    const newFormData = new FormData(formElement.value);
    const data = Object.fromEntries(newFormData.entries());
    Object.assign(formData, data);

    // This works:
    // emit("submit", toRaw(formData));
    const isValid = await v$.value.$validate();

    // This doesn't work:
    // emit("submit", toRaw(formData));
    if (!isValid) return;
    
    // This is how it should be, but it's not working
    emit("submit", toRaw(formData));

    // This prints out fine when running the test:
    console.log("Should have submitted now, but hasn't!")
}

So I have located the problem to this line: const isValid = await v$.value.$validate(); and I checked if the values are correct (they are. isValid is true). I have also tried adding await wrapper.vm.$nextTick() in different places to no avail.

What is going on here and why doesn't it work?


Solution

  • As @EstusFlask said in their comment to the post, I need to flush the promises which can either be done with the flush-promises package, or with @vue/test-utils: https://test-utils.vuejs.org/api/#flushPromises