'How to test transition function in Vue 3 test utils?

I have a problem testing transiton functions with jest and vue test utlis. This is my component code:

<div class="disclosure">
    <button
        class="label"
        type="button"
        @click="toggle"
    >
        <span class="label-text">
            <slot name="disclosure-title" />
        </span>
    </button>
    <Transition
        name="disclosure"
        @before-enter="transitionBaseState"
        @enter="transitionEndState"
        @before-leave="transitionEndState"
        @leave="transitionBaseState"
    >
        <div
            v-show="open"
            class="panel"
        >
            <div class="panel-content">
                <slot />
            </div>
        </div>
    </Transition>
</div>

I have a problem with transition functions: transitionBaseState and transitionEndState. When I run ejst spec for the component, coverage tab shows that those functions are not covered. Do you know what is the best way to test those functions? My test for showing element is this:

it('can be changed to opened by clicking the panel', async () => {
    await wrapper.find(buttonSelector).trigger('click');
    expect(wrapper.find(panelSelector).isVisible()).toBe(true);
});

Functions:

function transitionBaseState(el: HTMLElement): void {
    el.style.height = '0';
}
function transitionEndState(el: HTMLElement): void {
    el.style.height = `${el.scrollHeight}px`;
}


Solution 1:[1]

Ok. This is the tough one. Vue test utils stubs transitions by default. And this behavior is not covered by docs. You need to mock it globally by hardcoded false value.

import { config, mount } from '@vue/test-utils'
...

// before Mount
config.global.stubs = {
  transition: false
}
jest.useFakeTimers() // to deal with durations
// mount
// trigger click

jest.advanceTimersByTime(duration + 1)
// expect whatever you need from before-enter, enter, after-enter callbacks
// trigger click

jest.advanceTimersByTime(duration + 1)
// expect whatever you need from before-leave, leave, after-leave callbacks

As you can see, I added workaround with fake timers to deal with transition duration.

In fact, in most cases you just don't need to test what transition does. This is already tested by vue core team, but maybe there are some cases where it should be tested. As you can see, the number of line in tests grows just to avoid default behavior. It also uses undocumented mocks and you should to keep that working after every update of Vue/vue-test-utils with some breaking changes.

Anyway I've tested this code above. Here is my attempt

it('should call all the callbacks', async () => {
    expect.hasAssertions()
    const callbacks = {
      onBeforeEnter: jest.fn(),
      onEnter: jest.fn(),
      onAfterEnter: jest.fn(),
      onBeforeLeave: jest.fn(),
      onLeave: jest.fn(),
      onAfterLeave: jest.fn(),
    }

    const component = defineComponent({
      name: 'Component',
      data() {
        return {
          flag: false
        }
      },
      template: `<div>
        <transition
          @before-enter="onBeforeEnter"
          @enter="onEnter"
          @after-enter="onAfterEnter"
          @before-leave="onBeforeLeave"
          @leave="onLeave"
          @after-leave="onAfterLeave">
          <span v-if="flag"></span>
        </transition>
      </div>`,
      methods: {
        onBeforeEnter: callbacks.onBeforeEnter,
        onEnter(e: Event, done: Function) {
          callbacks.onEnter()
          done()
        },
        onAfterEnter() {
          callbacks.onAfterEnter()
        },
        onBeforeLeave() {
          callbacks.onBeforeLeave()
        },
        onLeave(e: Event, done: Function) {
          callbacks.onLeave();
          done()
        },
        onAfterLeave() {
          callbacks.onAfterLeave()
        }
      }
    })

    config.global.stubs = {
      transition: false
    }

    jest.useFakeTimers()
    const wrapper = mount(component)

    await wrapper.setData({ flag: true })

    expect(callbacks.onBeforeEnter).toHaveBeenCalled()
    expect(callbacks.onEnter).toHaveBeenCalled()
    expect(callbacks.onAfterEnter).toHaveBeenCalled()

    await wrapper.setData({ flag: false })

    jest.advanceTimersByTime(1)

    expect(callbacks.onBeforeLeave).toHaveBeenCalled()
    expect(callbacks.onLeave).toHaveBeenCalled()
    expect(callbacks.onAfterLeave).toHaveBeenCalled()
  })

Sources

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

Source: Stack Overflow

Solution Source
Solution 1 Andrey Nelubin