UNPKG

@maxpike/vue

Version:

Vue VariantJS: Fully configurable Vue 3 components styled with TailwindCSS

1,541 lines (1,099 loc) 37.5 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { mount, VueWrapper } from '@vue/test-utils'; import { TDropdownConfig, TDropdownPopperDefaultOptions } from '@variantjs/core'; import { h } from 'vue'; import TDropdown from '@/components/TDropdown.vue'; import { scopedParamsAsString, parseScopedParams } from '../testUtils'; describe('TDropdown.vue', () => { let updatePopperMock: jest.Mock<any, any>; const originalUpdatePopperMethod = TDropdown.methods!.updatePopper; beforeEach(() => { updatePopperMock = jest.fn().mockReturnValue(Promise.resolve()); TDropdown.methods!.updatePopper = updatePopperMock; }); afterEach(() => { updatePopperMock.mockRestore(); TDropdown.methods!.updatePopper = originalUpdatePopperMethod; }); it('renders the component', () => { const wrapper = mount(TDropdown); expect(wrapper.find('button').exists()).toBe(true); expect(wrapper.find('button').isVisible()).toBe(true); expect(wrapper.find('div').exists()).toBe(true); expect(wrapper.vm.$refs.trigger).toBeTruthy(); expect(wrapper.vm.$refs.dropdown).toBeTruthy(); }); it('has default classes', () => { const wrapper = mount(TDropdown); const { trigger, dropdown } = wrapper.vm.$refs; expect(trigger.className).toBe(TDropdownConfig.classes.trigger); expect(dropdown.className).toBe(TDropdownConfig.classes.dropdown); expect(wrapper.vm.configuration.classesList).toEqual(TDropdownConfig.classes); }); it('initializes the dropdown', async () => { const wrapper = mount(TDropdown); expect(wrapper.vm.shown).toBe(false); expect(wrapper.vm.popperIsAdjusted).toBe(false); expect(wrapper.vm.popper).toBe(null); }); it('uses the content of the trigger slot inside the trigger button', () => { const wrapper = mount(TDropdown, { slots: { trigger: 'Press me!', }, }); expect(wrapper.find('button').text()).toBe('Press me!'); }); it('uses the content of the text prop inside the trigger button', () => { const wrapper = mount(TDropdown, { props: { text: 'Press me!', }, }); expect(wrapper.find('button').text()).toBe('Press me!'); }); it('exposes the `isShow` variable and the configuration ', () => { const wrapper = mount(TDropdown, { slots: { trigger: (params) => scopedParamsAsString(params), }, }); const scopeParamKeys = parseScopedParams(wrapper.text()); expect(scopeParamKeys).toEqual({ isShow: 'boolean', configuration: 'object', popper: 'object', }); }); it('exposes the `toggle`, `show` and `hide` methods and the configuration to the dropdown slot ', () => { const wrapper = mount(TDropdown, { slots: { default: (params) => scopedParamsAsString(params), }, }); const scopeParamKeys = parseScopedParams(wrapper.text()); expect(scopeParamKeys).toEqual({ show: 'function', hide: 'function', toggle: 'function', configuration: 'object', popper: 'object', }); }); it('renders an empty button if no slot or text', () => { const wrapper = mount(TDropdown); expect(wrapper.find('button').text()).toBe(''); }); it('prioritizes the slot over the text prop', () => { const wrapper = mount(TDropdown, { props: { text: 'Press me2!', }, slots: { trigger: 'Press me!', }, }); expect(wrapper.find('button').text()).toBe('Press me!'); }); it('uses the content of the default slot inside the dropdown', () => { const wrapper = mount(TDropdown, { slots: { default: 'Dropdown stuffy', }, }); const { dropdown } = wrapper.vm.$refs; expect(dropdown.innerHTML).toBe('Dropdown stuffy'); }); it('shows the dropdown when the trigger is pressed', async () => { const wrapper = mount(TDropdown, { props: { toggleOnClick: true, toggleOnFocus: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(true); }); it('hides the dropdown when the trigger is pressed and is openede ', async () => { const wrapper = mount(TDropdown, { props: { toggleOnClick: true, show: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(false); }); it('doesnt teleports the dropdown by default', () => { const wrapper = mount(TDropdown); expect(wrapper.find('div').exists()).toBe(true); }); it('teleports the dropdown to the body if teleport option is set', () => { mount(TDropdown, { props: { teleport: true, }, slots: { default: 'The body', }, }); expect(document.body.children[0].textContent).toBe('The body'); }); it('teleports the dropdown to the selector in the teleportTo prop', () => { const div = document.createElement('div'); div.id = 'teleport-here'; document.body.appendChild(div); mount(TDropdown, { props: { teleport: true, teleportTo: '#teleport-here', }, slots: { default: 'The body', }, }); expect(document.querySelector('#teleport-here')!.textContent).toBe('The body'); }); it('teleports the dropdown to the element in the teleportTo prop', () => { const div = document.createElement('div'); div.id = 'dont-teleport-here'; document.body.appendChild(div); mount(TDropdown, { props: { teleport: true, teleportTo: div, }, slots: { default: 'The body', }, }); expect(document.querySelector('#teleport-here')!.textContent).toBe('The body'); }); it('the trigger is a button with type `button`', async () => { const wrapper = mount(TDropdown); const trigger = wrapper.get('button'); expect(trigger.attributes().type).toBe('button'); expect(trigger.element.tagName).toBe('BUTTON'); }); it('disables the dropdown', async () => { const wrapper = mount(TDropdown, { props: { disabled: true, }, }); const trigger = wrapper.get('button'); const { dropdown } = wrapper.vm.$refs; expect(trigger.attributes().disabled).toBeDefined(); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(false); expect(dropdown.style.display).toBe('none'); }); it('applies the dropdownAttributes to the dropdown', () => { const wrapper = mount(TDropdown, { props: { dropdownAttributes: { id: 'my-id', 'data-foo': 'bar', }, }, }); const { dropdown } = wrapper.vm.$refs; expect(dropdown.getAttribute('id')).toBe('my-id'); expect(dropdown.getAttribute('data-foo')).toBe('bar'); }); it('applies the attributes to the trigger button', () => { const wrapper = mount(TDropdown, { attrs: { id: 'my-id', 'data-foo': 'bar', }, }); const trigger = wrapper.get('button'); expect(trigger.attributes().id).toBe('my-id'); expect(trigger.attributes()['data-foo']).toBe('bar'); }); it('applies the attributes that comes from the configuration the trigger button', () => { const wrapper = mount(TDropdown, { global: { provide: { configuration: { TDropdown: { id: 'my-id', 'data-foo': 'bar', }, }, }, }, }); const trigger = wrapper.get('button'); expect(trigger.attributes().id).toBe('my-id'); expect(trigger.attributes()['data-foo']).toBe('bar'); }); it('prioritizes the local attributes', () => { const wrapper = mount(TDropdown, { global: { provide: { configuration: { TDropdown: { id: 'my-id', }, }, }, }, attrs: { id: 'my-local-id', }, }); const trigger = wrapper.get('button'); expect(trigger.attributes().id).toBe('my-local-id'); }); it('uses the set tagName ', () => { const wrapper = mount(TDropdown, { props: { tagName: 'a', }, }); const { trigger } = wrapper.vm.$refs; expect(trigger.tagName).toBe('A'); }); it('uses the set dropdownTagName ', () => { const wrapper = mount(TDropdown, { props: { dropdownTagName: 'ul', }, }); const { dropdown } = wrapper.vm.$refs; expect(dropdown.tagName).toBe('UL'); }); it('doesnt toggle the dropdown on click if toggleOnClick is set to `false`', async () => { const wrapper = mount(TDropdown, { props: { toggleOnClick: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(false); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(false); }); it('shows the dropdown on click by default', async () => { const wrapper = mount(TDropdown, { props: { // To avoid false positives toggleOnFocus: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(true); }); it('hides the dropdown on click by default', async () => { const wrapper = mount(TDropdown, { props: { // To avoid false positives toggleOnFocus: false, show: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(false); }); it('shows the dropdown on focus by default', async () => { const wrapper = mount(TDropdown, { props: { // To avoid false positives toggleOnClick: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('focus'); expect(wrapper.vm.shown).toBe(true); }); it('doesnt hide the dropdown when focus when doesnt have the `toggleOnFocus` options', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('focus'); expect(wrapper.vm.shown).toBe(true); }); it('hides the dropdown on blur by default', async () => { const wrapper = mount(TDropdown, { props: { show: true, // To avoid false positives toggleOnClick: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('doesnt shows the dropdown on hover by default', async () => { const wrapper = mount(TDropdown); const trigger = wrapper.get('button'); await trigger.trigger('hover'); expect(wrapper.vm.shown).toBe(false); }); it('doesnt hides the drodown on hoverout by default', async () => { const wrapper = mount(TDropdown, { props: { show: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('hover'); expect(wrapper.vm.shown).toBe(true); }); it('toggles the dropdown on focus if option is set', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('focus'); expect(wrapper.vm.shown).toBe(true); await trigger.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('doesnt hides the dropdown if blur in the the dropdown', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, }); const triggerButton = wrapper.get('button'); const { dropdown } = wrapper.vm.$refs; await triggerButton.trigger('blur', { relatedTarget: dropdown, }); expect(wrapper.vm.shown).toBe(true); }); it('adds and remove blur listener to focusable elements inside the dropdown when shown', async () => { const addEventListenerMock = jest.fn(); const removeEventListenerMock = jest.fn(); window.HTMLInputElement.prototype.addEventListener = addEventListenerMock; window.HTMLInputElement.prototype.removeEventListener = removeEventListenerMock; const wrapper = mount(TDropdown, { slots: { default: h('input'), }, }); wrapper.vm.doShow(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); expect(addEventListenerMock).toHaveBeenCalled(); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); expect(removeEventListenerMock).toHaveBeenCalled(); }); it('doesnt hides the dropdown if blur in the the trigger', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, }); const triggerButton = wrapper.get('button'); const { trigger } = wrapper.vm.$refs; await triggerButton.trigger('blur', { relatedTarget: trigger, }); expect(wrapper.vm.shown).toBe(true); }); it('doesnt hides the dropdown if blur in an element inside the dropdown', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, slots: { default: h('button', 'focus me'), }, }); const triggerButton = wrapper.get('button'); const { dropdown } = wrapper.vm.$refs; const button = dropdown.querySelector('button'); await triggerButton.trigger('blur', { relatedTarget: button, }); expect(wrapper.vm.shown).toBe(true); }); it('hides the dropdown on trigger blur', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('hides the dropdown on dropdown blur', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, }); const dropdown = wrapper.get('div'); await dropdown.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('hides the dropdown if blur on an element that is not a child', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, slots: { default: h('button', {}, 'blur me'), }, }); const button = wrapper.find('button') as any; button.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('removes the child element blur handler if toggleOnFocus changes', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnFocus: true, }, slots: { default: h('button', {}, 'blur me'), }, }); wrapper.setProps({ toggleOnFocus: false, }); const { dropdown } = wrapper.vm.$refs; const button = dropdown.querySelector('button') as HTMLButtonElement; button.dispatchEvent(new FocusEvent('blur')); expect(wrapper.vm.shown).toBe(true); expect(wrapper.vm.focusableElements).toEqual([]); }); it('doesnt toggle the dropdown on hover by default', async () => { const wrapper = mount(TDropdown); const trigger = wrapper.get('button'); await trigger.trigger('hover'); expect(wrapper.vm.shown).toBe(false); await trigger.trigger('blur'); expect(wrapper.vm.shown).toBe(false); }); it('toggles the dropdown on hover immediatly if option is set', async () => { const wrapper = mount(TDropdown, { props: { toggleOnHover: true, hideOnLeaveTimeout: null, }, }); const trigger = wrapper.get('button'); await trigger.trigger('mouseover'); expect(wrapper.vm.shown).toBe(true); await trigger.trigger('mouseleave'); expect(wrapper.vm.shown).toBe(false); }); it('doesnt toggles the dropdown on hover if option is not set', async () => { const wrapper = mount(TDropdown, { props: { toggleOnHover: false, hideOnLeaveTimeout: null, }, }); const trigger = wrapper.get('button'); await trigger.trigger('mouseover'); expect(wrapper.vm.shown).toBe(false); }); it('hides the dropdown if mouseleave the dropdown', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnHover: true, hideOnLeaveTimeout: null, }, }); const dropdown = wrapper.get('div'); await dropdown.trigger('mouseleave'); expect(wrapper.vm.shown).toBe(false); }); it('hides the dropdown after the timeout', async () => { jest.useFakeTimers(); const wrapper = mount(TDropdown, { props: { show: true, toggleOnHover: true, hideOnLeaveTimeout: 500, }, }); const dropdown = wrapper.get('div'); await dropdown.trigger('mouseleave'); expect(wrapper.vm.shown).toBe(true); jest.advanceTimersByTime(499); expect(wrapper.vm.shown).toBe(true); jest.advanceTimersByTime(1); expect(wrapper.vm.shown).toBe(false); jest.useRealTimers(); }); it('clears the hidetimeout if something receives mouseover ', async () => { jest.useFakeTimers(); const wrapper = mount(TDropdown, { props: { show: true, toggleOnHover: true, hideOnLeaveTimeout: 500, }, }); const button = wrapper.get('button'); const dropdown = wrapper.get('div'); await dropdown.trigger('mouseleave'); expect(wrapper.vm.shown).toBe(true); jest.advanceTimersByTime(499); await button.trigger('mouseover'); expect(wrapper.vm.shown).toBe(true); jest.advanceTimersByTime(500); expect(wrapper.vm.shown).toBe(true); jest.useRealTimers(); }); it('doesnt hides the dropdown if mouseleave the dropdown', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnHover: true, hideOnLeaveTimeout: null, }, }); const triggerButton = wrapper.get('button'); const { dropdown } = wrapper.vm.$refs; await triggerButton.trigger('mouseleave', { relatedTarget: dropdown, }); expect(wrapper.vm.shown).toBe(true); }); it('doesnt hides the dropdown if mouseleave the trigger', async () => { const wrapper = mount(TDropdown, { props: { show: true, toggleOnHover: true, hideOnLeaveTimeout: null, }, }); const triggerButton = wrapper.get('button'); const { trigger } = wrapper.vm.$refs; await triggerButton.trigger('mouseleave', { relatedTarget: trigger, }); expect(wrapper.vm.shown).toBe(true); }); it('has a focus method that focus the trigger', async () => { const wrapper = mount(TDropdown); const { trigger } = wrapper.vm.$refs; const focusMock = jest.fn(); trigger.focus = focusMock; wrapper.vm.focus(); expect(focusMock).toHaveBeenCalled(); }); it('emits native button events', () => { const onClick = jest.fn(); const onBlur = jest.fn(); const onFocus = jest.fn(); const wrapper = mount(TDropdown, { attrs: { onClick, onBlur, onFocus, }, }); const { trigger } = wrapper.vm.$refs; trigger.dispatchEvent(new MouseEvent('click')); expect(onClick).toHaveBeenCalled(); trigger.dispatchEvent(new FocusEvent('focus')); expect(onFocus).toHaveBeenCalled(); trigger.dispatchEvent(new FocusEvent('blur')); expect(onBlur).toHaveBeenCalled(); }); it('triggers custom events', async () => { const onCustom = jest.fn(); const wrapper = mount(TDropdown, { attrs: { onCustom, }, }); const { trigger } = wrapper.vm.$refs; const evt = new CustomEvent('custom', { detail: 'my-custom-event' }); trigger.dispatchEvent(evt); expect(onCustom).toHaveBeenCalled(); }); it('display the dropdown if `show` prop is set ', async () => { const wrapper = mount(TDropdown, { props: { show: true, }, }); const { dropdown } = wrapper.vm.$refs; expect(wrapper.vm.shown).toBe(true); expect(dropdown.style.display).toBe(''); }); it('emits `update:show` when show property is updated', async () => { const wrapper = mount(TDropdown); wrapper.vm.doShow(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); // assert event has been emitted expect(wrapper.emitted()['update:show']).toBeTruthy(); // assert event payload expect(wrapper.emitted()['update:show']).toEqual([[true]]); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); // assert event has been emitted expect(wrapper.emitted()['update:show']).toBeTruthy(); // assert event payload expect(wrapper.emitted()['update:show']).toEqual([[true], [false]]); }); it('shows the modal if the `show` props changes', async () => { const wrapper = mount(TDropdown); await wrapper.setProps({ show: true, }); expect(wrapper.vm.shown).toBe(true); }); it('hides the modal if the `show` props changes', async () => { const wrapper = mount(TDropdown, { props: { show: true, }, }); await wrapper.setProps({ show: false, }); expect(wrapper.vm.shown).toBe(false); }); it('clears the hidetimeout when unmounted', async () => { jest.useFakeTimers(); const jestMock = jest.fn(); const wrapper = mount(TDropdown); wrapper.vm.hideTimeout = setTimeout(jestMock, 100); wrapper.unmount(); jest.advanceTimersByTime(100); expect(jestMock).not.toHaveBeenCalled(); jest.useRealTimers(); }); it('invalidates invalid dropdown placements', () => { const { validator } = TDropdown.props.placement; expect(validator('invalid')).toBe(false); }); it.each([ 'auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end', ])('accept valid dropdown placements', (placement) => { const { validator } = TDropdown.props.placement; expect(validator(placement)).toBe(true); }); it('has a default the popper configuration', async () => { const wrapper = mount(TDropdown); expect(wrapper.vm.popperOptions).toEqual(TDropdownPopperDefaultOptions); }); it('assigns the default popper configuration if undefined', async () => { const wrapper = mount(TDropdown, { props: { popperOptions: undefined, }, }); expect(wrapper.vm.popperOptions).toEqual(TDropdownPopperDefaultOptions); }); it('the dropdownAfterLeave method removes the `visibility` property', async () => { const wrapper = mount(TDropdown); const { dropdown } = wrapper.vm.$refs; dropdown.style.visibility = 'hidden'; expect(dropdown.style.visibility).toBe('hidden'); wrapper.vm.dropdownAfterLeave(); expect(dropdown.style.visibility).toBe(''); }); describe('touch-only devices', () => { let windowSpy: any; beforeAll(() => { windowSpy = jest.spyOn(window, 'window', 'get'); const windowImplementation = Object.assign(window, { matchMedia: () => ({ matches: true, }), }); windowSpy.mockImplementation(() => windowImplementation); }); afterAll(() => { windowSpy.mockRestore(); }); it('detects touch only devces', () => { expect(window.matchMedia('(any-hover: none)')).toEqual({ matches: true, }); const wrapper = mount(TDropdown); expect(wrapper.vm.isTouchOnlyDevice).toBe(true); }); it('ignores mouseoverHandler action in touch-only devices', async () => { const wrapper = mount(TDropdown, { props: { toggleOnHover: true, }, }); wrapper.vm.isTouchOnlyDevice = true; const action = jest.spyOn(wrapper.vm, 'doShow'); const trigger = wrapper.get('button'); await trigger.trigger('mouseover'); expect(action).not.toHaveBeenCalled(); }); it('ignores mouseleaveHandler action in touch-only devices', async () => { const wrapper = mount(TDropdown, { props: { toggleOnHover: true, }, }); wrapper.vm.isTouchOnlyDevice = true; const action = jest.spyOn(wrapper.vm, 'targetIsChild'); const trigger = wrapper.get('button'); await trigger.trigger('mouseleave'); expect(action).not.toHaveBeenCalled(); }); it('ignores focusHandler action in touch-only devices', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, }, }); const trigger = wrapper.get('button'); await trigger.trigger('focus'); expect(wrapper.vm.shown).toBe(false); }); it('ignores blurHandler action in touch-only devices', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, }, }); // doHide should not be called const action = jest.spyOn(wrapper.vm, 'doHide'); const trigger = wrapper.get('button'); await trigger.trigger('blur'); expect(action).not.toHaveBeenCalled(); }); it('shows the dropdown when clicked on touch-only devices if `toggleOnFocus` is set even if `toggleOnClick` is false', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, toggleOnClick: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(true); }); it('shows the dropdown when clicked on touch-only devices if `toggleOnHover` is set even if `toggleOnClick` is false', async () => { const wrapper = mount(TDropdown, { props: { toggleOnHover: true, toggleOnFocus: false, toggleOnClick: false, }, }); const trigger = wrapper.get('button'); await trigger.trigger('click'); expect(wrapper.vm.shown).toBe(true); }); it('adds the `touchstartHandler` to the current window when dropdown is shown and is isTouchOnlyDevice', async () => { const wrapper = mount(TDropdown); const addSpy = jest.spyOn(window, 'addEventListener'); const removeSpy = jest.spyOn(window, 'removeEventListener'); wrapper.vm.doShow(); await wrapper.vm.$nextTick(); expect(addSpy).toHaveBeenCalledWith('touchstart', wrapper.vm.touchstartHandler); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); expect(removeSpy).toHaveBeenCalledWith('touchstart', wrapper.vm.touchstartHandler); }); it('adds the `touchstartHandler` if the component is shown when mounted', async () => { const addSpy = jest.spyOn(window, 'addEventListener'); const wrapper = mount(TDropdown, { props: { show: true, }, }); expect(addSpy).toHaveBeenCalledWith('touchstart', wrapper.vm.touchstartHandler); }); it('removes the `touchstartHandler` if the component when component is unmounted', async () => { const removeSpy = jest.spyOn(window, 'removeEventListener'); const wrapper = mount(TDropdown, { props: { show: true, }, }); wrapper.unmount(); expect(removeSpy).toHaveBeenCalledWith('touchstart', wrapper.vm.touchstartHandler); }); it('hides the dropdown if toggle on focus is set and when touch outside', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, toggleOnClick: false, show: true, }, }); window.dispatchEvent(new TouchEvent('touchstart')); await wrapper.vm.$nextTick(); expect(wrapper.vm.shown).toBe(false); }); it('doesnt hides the dropdown if touch a children even if toggle on focus is set', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: true, toggleOnClick: false, show: true, }, }); window.dispatchEvent(new TouchEvent('touchstart', { targetTouches: [ { identifier: 1, target: wrapper.vm.$refs.dropdown as EventTarget, } as Touch, ], })); await wrapper.vm.$nextTick(); expect(wrapper.vm.shown).toBe(true); }); it('hides the dropdown if toggle on hover is set and when touch outside', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: false, toggleOnClick: false, toggleOnHover: true, show: true, }, }); window.dispatchEvent(new TouchEvent('touchstart')); await wrapper.vm.$nextTick(); expect(wrapper.vm.shown).toBe(false); }); it('doesnt hides the dropdown if toggle on hover and toggle on focus is not set when touch outside', async () => { const wrapper = mount(TDropdown, { props: { toggleOnFocus: false, toggleOnClick: false, toggleOnHover: false, show: true, }, }); window.dispatchEvent(new TouchEvent('touchstart')); await wrapper.vm.$nextTick(); expect(wrapper.vm.shown).toBe(true); }); it('calls the `enablePopperNeedsAdjustmentListener` when popperIsAdjusted is set', async () => { const wrapper = mount(TDropdown); const enablePopperNeedsAdjustmentListenerSpy = jest.spyOn(wrapper.vm, 'enablePopperNeedsAdjustmentListener'); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); expect(enablePopperNeedsAdjustmentListenerSpy).toHaveBeenCalled(); }); it('calls the `disablePopperNeedsAdjustmentListener` when popperIsAdjusted is set to false', async () => { const wrapper = mount(TDropdown); const disablePopperNeedsAdjustmentListenerSpy = jest.spyOn(wrapper.vm, 'disablePopperNeedsAdjustmentListener'); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); wrapper.vm.popperIsAdjusted = false; await wrapper.vm.$nextTick(); expect(disablePopperNeedsAdjustmentListenerSpy).toHaveBeenCalled(); }); it('set popperIsAdjusted to false when scroll event after is adjusted', async () => { jest.useFakeTimers(); const wrapper = mount(TDropdown); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); window.dispatchEvent(new Event('scroll')); expect(wrapper.vm.popperIsAdjusted).toBe(true); // need to wait because is throttled jest.advanceTimersByTime(200); expect(wrapper.vm.popperIsAdjusted).toBe(false); jest.useRealTimers(); }); it('set popperIsAdjusted to false when resize event after is adjusted', async () => { jest.useFakeTimers(); const wrapper = mount(TDropdown); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); window.dispatchEvent(new Event('resize')); expect(wrapper.vm.popperIsAdjusted).toBe(true); // need to wait because is throttled jest.advanceTimersByTime(200); expect(wrapper.vm.popperIsAdjusted).toBe(false); jest.useRealTimers(); }); it('removes the listener to resize and scroll after component is unmounted', async () => { const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); const wrapper = mount(TDropdown); wrapper.unmount(); expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', wrapper.vm.popperAdjusterListener); expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', wrapper.vm.popperAdjusterListener); }); }); }); describe('TDropdown popper instance', () => { const popperWasCreated = async (wrapper: VueWrapper<any>) => { do { // eslint-disable-next-line no-await-in-loop await wrapper.vm.$nextTick(); } while (wrapper.vm.popper === null); return Promise.resolve(); }; const popperIsAdjusted = async (wrapper: VueWrapper<any>) => { do { // eslint-disable-next-line no-await-in-loop await wrapper.vm.$nextTick(); } while (wrapper.vm.popperIsAdjusted === false); return Promise.resolve(); }; it('creates a popper instance when shown', async () => { const wrapper = mount(TDropdown); expect(wrapper.vm.popper).toBeNull(); wrapper.vm.doShow(); await popperWasCreated(wrapper); expect(wrapper.vm.popper).toBeTruthy(); await popperIsAdjusted(wrapper); expect(wrapper.vm.popperIsAdjusted).toBe(true); }); it('doesnt create popper if already exists', async () => { const wrapper = mount(TDropdown); const createPopperSpy = jest.spyOn(wrapper.vm, 'createPopper'); wrapper.vm.doShow(); await popperWasCreated(wrapper); expect(createPopperSpy).toHaveBeenCalledTimes(1); expect(wrapper.vm.popper).toBeTruthy(); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); expect(wrapper.vm.shown).toBe(false); expect(createPopperSpy).toHaveBeenCalledTimes(1); }); it('adjust popper if marked as not adjusted', async () => { const wrapper = mount(TDropdown); wrapper.vm.doShow(); await popperWasCreated(wrapper); const popperUpdateSpy = jest.spyOn(wrapper.vm.popper, 'update'); wrapper.vm.doHide(); wrapper.vm.popperIsAdjusted = false; wrapper.vm.doShow(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).toHaveBeenCalled(); }); it('doesnt adjust popper if marked as adjusted', async () => { const wrapper = mount(TDropdown); wrapper.vm.doShow(); await popperWasCreated(wrapper); const popperUpdateSpy = jest.spyOn(wrapper.vm.popper, 'update'); wrapper.vm.doHide(); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); expect(popperUpdateSpy).not.toHaveBeenCalled(); wrapper.vm.doShow(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).not.toHaveBeenCalled(); }); it('doesnt adjust popper if marked as adjusted (method check)', async () => { const wrapper = mount(TDropdown); wrapper.vm.doShow(); await popperWasCreated(wrapper); const popperUpdateSpy = jest.spyOn(wrapper.vm, 'adjustPopper'); wrapper.vm.doHide(); wrapper.vm.popperIsAdjusted = true; await wrapper.vm.$nextTick(); expect(popperUpdateSpy).not.toHaveBeenCalled(); wrapper.vm.doShow(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).not.toHaveBeenCalled(); }); it('doesnt adjust popper if is hidden and adjustingPopper is false', async () => { const wrapper = mount(TDropdown); // Show the dropdown to initialize popper wrapper.vm.doShow(); await popperWasCreated(wrapper); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); wrapper.vm.adjustingPopper = false; const popperUpdateSpy = jest.spyOn(wrapper.vm.popper, 'update'); await wrapper.vm.adjustPopper(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).not.toHaveBeenCalled(); }); it('adjust popper if is hidden but adjustingPopper is true', async () => { const wrapper = mount(TDropdown); // Show the dropdown to initialize popper wrapper.vm.doShow(); await popperWasCreated(wrapper); wrapper.vm.doHide(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); wrapper.vm.adjustingPopper = true; const popperUpdateSpy = jest.spyOn(wrapper.vm.popper, 'update'); await wrapper.vm.adjustPopper(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).toHaveBeenCalled(); }); it('adjust popper if not is hidden and adjustingPopper is false', async () => { const wrapper = mount(TDropdown); // Show the dropdown to initialize popper wrapper.vm.doShow(); await popperWasCreated(wrapper); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); wrapper.vm.adjustingPopper = true; const popperUpdateSpy = jest.spyOn(wrapper.vm.popper, 'update'); await wrapper.vm.adjustPopper(); await wrapper.vm.$nextTick(); expect(popperUpdateSpy).toHaveBeenCalled(); }); it('accepts undefined as the placement', async () => { const wrapper = mount(TDropdown, { props: { placement: undefined, }, }); wrapper.vm.doShow(); await popperWasCreated(wrapper); expect(wrapper.vm.popper).toBeTruthy(); expect(wrapper.vm.popper.state.placement).toBe(TDropdownPopperDefaultOptions.placement); }); it('overrides the popper placement if placement is set', async () => { const wrapper = mount(TDropdown, { props: { placement: 'top', }, }); wrapper.vm.doShow(); await popperWasCreated(wrapper); expect(wrapper.vm.popper).toBeTruthy(); expect(wrapper.vm.popper.state.placement).toBe('top'); }); });