vuejs / vue-test-utils

Component Test Utils for Vue 2
https://vue-test-utils.vuejs.org
MIT License
3.57k stars 668 forks source link

Trigger not calling method with dynamic listener name #1955

Open alexmccabe opened 2 years ago

alexmccabe commented 2 years ago

Subject of the issue

Vue supports dynamic listener names, here's a contrived example of what I mean:

<MyElement @[clickListenerName]="onClick" />
// ...

computed: {
  clickListenerName() {
    return this.someCondition ? 'click.native' : 'click';
  }
}

This works in the real world code running in a web browser. However inside vue-test-utils when calling wrapper.trigger('click') on the element, the handler is never called. Replacing the template with <MyElement @click.native="onClick" /> works as expected.

This isn't due to a timing issue since logging the return value of the computed property elsewhere in the component, dumping to the DOM and using a whole bunch of await wrapper.vm.nextTick() in the test yields no result with the dynamic listener name.

Just to note that we are using the @vue/composition-api helpers in a Vue 2 application.

Steps to reproduce

CustomComponent.vue

<script lang="ts">
import { defineComponent } from '@vue/composition-api';

export default defineComponent({
    setup(_props, context) {
        return () => context.slots.default?.();
    },
});
</script>

TestComponent.vue

<template>
    <Component :is="tag" @[customEventName]="onClick" />
</template>

<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import CustomComponent from './CustomComponent.vue';

export default defineComponent({
    components: { CustomComponent },
    setup(_props, context) {
        const condition = true;

        return {
            onClick() {
                console.log('event called');
                context.emit('click');
            },
            customEventName: condition ? 'click.native' : 'click',
            condition,
            tag: condition ? 'CustomComponent' : 'button',
        };
    },
});
</script>

TestComponent.test.ts

import { shallowMount } from '@vue/test-utils';
import TestComponent from './Test.vue';

describe('<TestComponent />', () => {
    it('calls the onClickMethod', async () => {
        const wrapper = shallowMount(TestComponent);

        await wrapper.trigger('click');
        expect(wrapper.emitted().click).toBeTruthy();
    });
});

Expected behaviour

I would expect the tested expectation to pass and there to be a console.log

Actual behaviour

CleanShot 2022-02-02 at 14 37 56@2x

Changing the template to below yields a test pass

<template>
    <Component :is="tag" @click.native="onClick" />
</template>
CleanShot 2022-02-02 at 14 40 51@2x

Possible Solution

Since it's not valid to attach .native modifiers to non-Vue elements, this doesn't work as intended.

alexmccabe commented 2 years ago

I tried digging through the source and as far as I can tell it creates and dispatches the event, so it should work, but the method is never called

alexmccabe commented 2 years ago

Workaround is to call the listener handler directly, but that feels wrong to me: wrapper.vm.onClick({} as MouseEvent)