Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transition javascript hooks firing? #1603

Closed
joelworsham opened this issue Jul 1, 2020 · 8 comments
Closed

Transition javascript hooks firing? #1603

joelworsham opened this issue Jul 1, 2020 · 8 comments

Comments

@joelworsham
Copy link

Subject of the issue

I have a component which is entirely wrapped in a transition. This component begins with isShowing as false, waits a tick, then sets it to true, thus kicking off the transition.

In all visual testing and building of the component, this works great. They fire, all is well. In my test, however, they don't seem to fire.

Steps to reproduce

  1. Take any component that has a transition expected to fire, and add emitters to those hooks. E.G. @enter="$emit('show').
  2. Test this in jest/vue-test-utils. In your test, make sure you await a $nextTick or something, even a timeout, so that the event would hav efired
  3. Test with: expect(wrapper.emitted('show')).toBeTruthy()

This will not pass (for me)

Expected behaviour

Test should pass, becuase the transition @enter hook should have fired, which should have emitted the show event

Actual behaviour

wrapper.emitted() is totally blank. Even after a manual, 5second timeout, just to be sure. Nothing was emitted. I even tested against this by tossing the emitter right in mounted(), and then it worked and was seen. So I know it isn't the emitting logic.

Possible Solution

Not sure. Does vue test utils "silence" these transition hooks? Or perhaps not account for them?

@lmiller1990
Copy link
Member

Hi!

You are right - Vue Test Utils stubs out <Transition> and <TransitionGroup> by default, because they have caused problems with the DOM not updating the past. This mostly isn't a problem, but can be for people relying on testing with transition hooks.

This change was made in #1411. You should be able to get them to trigger like this, I think: #1411 (comment)

If you can share your test, I might be able to suggest some strategies to test it, if that doesn't work.

@joelworsham
Copy link
Author

Thanks for the response! I checked, and that solution does appear to fix most issues, but still doesn't fire any hooks. If this is simply isn't possible right now, totally fine. But if it is, I'd love to hear how :)

Here's the test suite:

/* eslint-disable no-unused-expressions, no-shadow, import/extensions */
/* global describe, it, expect */
import { mount } from '@vue/test-utils';
import defaultContext from '../../../../test/unit/util/context';
import LdsToast from '../LdsToast';

const suiteStubs = {
  transition: false,
};

describe('Component : Toast : LdsToast.vue', () => {
  describe('Default state', () => {
    const context = {
      ...defaultContext,
      propsData: {},
      stubs: {
        ...defaultContext.stubs,
        ...suiteStubs,
      }
    };

    const wrapper = mount(LdsToast, context);

    it('component LdsToast has been mounted', () => {
      expect(wrapper).toBeTruthy();
    });

    it('has a root element of kind "undefined", because it has not yet shown', () => {
      expect(wrapper.is('div')).toBe(undefined);
    });

    it('should match the snapshot where it is hidden', () => {
      expect(wrapper.html()).toMatchSnapshot();
    });

    it('should begin not showing, in efforts to trigger the transition', () => {
      expect(wrapper.vm.isShowing).toBe(false);
    });

    it('should show after 1 "window" tick', async () => {
      await wrapper.vm.windowNextTick();
      expect(wrapper.vm.isShowing).toBe(true);
    });

    it('should initialize the auto-dismissal', () => {
      expect(wrapper.vm.autoDismissalTimeout).toBeTruthy();
    });

    it('should match the snapshot where it is shown', () => {
      expect(wrapper.html()).toMatchSnapshot();
    });

    it('should have "offsetTop" as "0px" (no sticky)', () => {
      expect(wrapper.vm.offsetTop).toBe('0px');
    });

    it('should have "top" style set with proper offset', () => {
      expect(wrapper.vm.$el.style.top).toBe(wrapper.vm.offsetTop);
    });

    it('should be initialized with the proper theming classes', () => {
      expect(wrapper.classes('theme-informative')).toBe(true);
      expect(wrapper.classes('position-top')).toBe(true);
      expect(wrapper.classes('align-center')).toBe(true);
      expect(wrapper.classes('inline')).toBe(false);
      expect(wrapper.classes('light')).toBe(false);
    });

    // TODO test these once figured out
    // @see https://github.com/vuejs/vue-test-utils/issues/1603
    it('should have emitted transition event "show", to signal initializing show transition', () => {
      expect(wrapper.emitted('dismissed')).toBeTruthy()
    })
  });

  describe('Test auto-hide timeout', () => {
    const context = {
      ...defaultContext,
      propsData: {},
      stubs: {
        ...defaultContext.stubs,
        ...suiteStubs,
      }
    };

    const wrapper = mount(LdsToast, context);

    it('should not be hidden before the timeout', async () => {
      await new Promise(resolve => setTimeout(resolve, 4000));
      expect(wrapper.vm.isShowing).toBe(true);
    });

    it('should hide after the timeout', async () => {
      // 1000ms accommodates for previous test (just before timeout, by 1000ms)
      await new Promise(resolve => setTimeout(resolve, 1000));
      expect(wrapper.vm.isShowing).toBe(false);
    });
  });

  describe('Alternate props 1 (position/align/variant/light/inline)', () => {
    const context = {
      ...defaultContext,
      propsData: {
        position: 'bottom',
        align: 'left',
        variant: 'error',
        light: true,
        inline: true,
      },
      stubs: {
        ...defaultContext.stubs,
        ...suiteStubs,
      }
    };

    const wrapper = mount(LdsToast, context);

    it('should show after 1 "window" tick', async () => {
      await wrapper.vm.windowNextTick();
      expect(wrapper.vm.isShowing).toBe(true);
    });

    it('should match the snapshot', () => {
      expect(wrapper.html()).toMatchSnapshot();
    });

    it('should be initialized with the proper theming classes', () => {
      expect(wrapper.classes('theme-error')).toBe(true);
      expect(wrapper.classes('position-bottom')).toBe(true);
      expect(wrapper.classes('align-left')).toBe(true);
      expect(wrapper.classes('inline')).toBe(true);
      expect(wrapper.classes('light')).toBe(true);
    });

    it('should not initialize the auto-dismissal when it is "inline"', () => {
      expect(wrapper.vm.autoDismissalTimeout).toBe(undefined);
    });

    it('should have the icon "warning-filled-circle" for the variant "error', () => {
      expect(wrapper.vm.icon).toBe('warning-filled-circle');
    });
  });
});

'../../../../test/unit/util/context';

import { createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
import LDS from '../../../src';
import { LdsButton, LdsButtonBase, LdsButtonLoading, LdsLink } from '../../../src/components';

const localVue = createLocalVue();
localVue.use(LDS);
localVue.use(VueRouter);

export default {
  localVue,
  stubs:{
    'lds-button': LdsButton,
    'lds-button-base': LdsButtonBase,
    'lds-button-loading': LdsButtonLoading,
    'lds-link': LdsLink,
  }
};

@joelworsham
Copy link
Author

Oh!!! I just realized I was mucking around with that trying to get it to work, and I changed the emitter name.

So, transition: false does appear to work! 🎉

thanks so much :)

@joelworsham
Copy link
Author

well, actually, it appears the @enter and @after-enter fire, but @dismiss and @after-dismiss are not firing.

from above, here's the updated test suite that is failing the dismiss/dismissed triggers:

  describe('Test auto-hide timeout and emitters', () => {
    const context = {
      ...defaultContext,
      propsData: {},
      stubs: {
        ...defaultContext.stubs,
        ...suiteStubs,
      }
    };

    const wrapper = mount(LdsToast, context);

    it('should have emitted transition event "show", to signal initializing show transition', async () => {
      await wrapper.vm.$nextTick();
      expect(wrapper.emitted('show')).toBeTruthy();
    });

    it('should have emitted transition event "shown", to signal it has shown (transition complete)', async () => {
      await new Promise(resolve => setTimeout(resolve, transitionLength)); // transition length
      expect(wrapper.emitted('shown')).toBeTruthy();
    });

    it('should not be hidden before the timeout', async () => {
      await new Promise(resolve => setTimeout(resolve, defaultAutoTimeoutLength - transitionLength - 1000));
      expect(wrapper.vm.isShowing).toBe(true);
    });

    it('should hide after the timeout', async () => {
      // 1000ms accommodates for previous test (just before timeout, by 1000ms)
      await new Promise(resolve => setTimeout(resolve, 1000));
      expect(wrapper.vm.isShowing).toBe(false);
    });

    it('should have emitted transition event "dismiss", to signal initializing dismiss transition', async () => {
      await new Promise(resolve => setTimeout(resolve, 2000));
      console.log(wrapper.emitted())
      expect(wrapper.emitted('dismiss')).toBeTruthy();
    });

    it('should have emitted transition event "dismissed", to signal it has dismissed (transition complete)', async () => {
      await new Promise(resolve => setTimeout(resolve, transitionLength)); // transition length
      expect(wrapper.emitted('dismissed')).toBeTruthy();
    });
  });

And the beginning of the component that fires these transitions:

  <transition
    name="toast"
    @enter="$emit('show')"
    @after-enter="$emit('shown')"
    @leave="$emit('dismiss')"
    @after-leave="$emit('dismissed')"
  >
    <div
      v-if="isShowing"
      class="lds-toast"
      :class="[
        `theme-${variant}`,
        `position-${position}`,
        `align-${align}`,
        { light, inline },
      ]"
      :style="{ top: offsetTop }"
    >
...

@joelworsham joelworsham reopened this Jul 6, 2020
@lmiller1990
Copy link
Member

I have never tested those transition hooks. I don't see why they wouldn't trigger, though. Maybe you can wrap it in a setTimeout with a done callback for some initial debugging (maybe a race condition, test finishing before transition?)

@justforuse
Copy link

Do you check the code coverage? It seems the hook's handler of transition not be covered, even it actually called and passed in test file.

image

@Exotelis
Copy link

Exotelis commented Oct 8, 2022

Do you check the code coverage? It seems the hook's handler of transition not be covered, even it actually called and passed in test file.

image

I'm observing the same. Even if events are fired, test coverage seem not to work

@ebisbe
Copy link
Collaborator

ebisbe commented Jan 26, 2023

Closing again as the coverage issues are covered at #1977

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants