vue.jsvitest

Vitest: Triggering a keyup event does not have any effect


We have single file Vue component that has a @keyup.enter event handler on a sub-component which encapsulates a text input, like so:

<template>
    <div>
        <CustomInput @keyup.enter="onUpdate"></CustomInput>
        <!-- 
        This CustomInput encapsulates a normal input element like so:
        <template> (= of the CustomInput)
            <div>
                <input type="text />
            </div>
        </template>
        -->
    </div>
</template>

<script setup>
    const emits = defineEmits(['update']);

    function onUpdate() {
        emits('update');
    }
</script>

In the browser, this seems to be working fine. However, I don't seem to be able to trigger the keyup event in a Vitest unit test using the Vue test wrapper:

it('should react to the keyup event', async () => {
    const props = { /* ... */ }
    const wrapper = mount(MyMainComponent, {props});

    const searchField = wrapper.findComponent(CustomInput);
    // NOTE: Setting this input works!
    await searchField.find('input').setValue('My search term');
                
    // But neither this...
    await searchField.trigger('keyup', { key: 'enter' });
    // ... nor this ...
    await searchField.trigger('keyup.enter');
    // ... lead to the onUpdate method being called ...
    // ... so this expectation fails too
    expect(wrapper.emitted('update')?.length).toBeGreaterThan(0);
});

What may be causing this?


Solution

  • After a good night of sleep and a bit of tinkering with a test suite, the solution became clear:

    Just like I have to set the value of the input directly on the nested input element, I have to trigger the keyup event on the input too instead of the component, even though the keyup event handler is being bound to the component.

    So:

    await searchField.trigger('keyup.enter');
    

    has to become this:

    await searchField.find('input').trigger('keyup.enter');
    

    for the test to work.

    I would need to do some more research to give a definitive explanation for this behavior (= why you cannot trigger a keyup event on a custom component directly), but I guess input elements in Vue have the "special" ability to trigger keyup events and those events bubble up to the parent component, so binding the keyup handler to the parent component works to catch that event, even though my custom component did not specifically declare that it emits keyup events. However, because those keyup events may be specific to input elements, you cannot trigger them on custom components, at least not if you did not define them using defineEmits or something, but I did not test that.