UNPKG

@maxpike/vue

Version:

Vue VariantJS: Fully configurable Vue 3 components styled with TailwindCSS

1,575 lines (1,273 loc) 58.9 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import { NormalizedOption, normalizeOptions } from '@variantjs/core'; import { shallowMount } from '@vue/test-utils'; import TRichSelect from '../../components/TRichSelect.vue'; import { componentHasAttributeWithInlineHandlerAndParameter, componentHasAttributeWithValue, getChildComponentNameByRef } from '../testUtils'; describe('TRichSelect.vue', () => { const focusDropdownTriggerMock = jest.fn(); const adjustPopperMock = jest.fn(); const dropdownDoHideMock = jest.fn(); const dropdownDoShowMock = jest.fn(); const TDropdownComponentMock = { template: '<div />', methods: { focus: focusDropdownTriggerMock, adjustPopper: adjustPopperMock, doHide: dropdownDoHideMock, doShow: dropdownDoShowMock, }, }; afterEach(() => { focusDropdownTriggerMock.mockReset(); adjustPopperMock.mockReset(); dropdownDoHideMock.mockReset(); dropdownDoShowMock.mockReset(); }); it('renders the component', () => { const wrapper = shallowMount(TRichSelect); expect(wrapper.get('div')).toBeTruthy(); }); it('hides the dropdown and focus the trigger when the vmodel value changes and the `closeOnSelect` option is set', async () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, closeOnSelect: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; await wrapper.vm.$nextTick(); await wrapper.setProps({ modelValue: 2, }); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(focusDropdownTriggerMock).toHaveBeenCalled(); }); it('hides the dropdown and focus the trigger when the vmodel value changes and the `closeOnSelect` is undefined and is not multiple', async () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, closeOnSelect: undefined, multiple: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; await wrapper.vm.$nextTick(); await wrapper.setProps({ modelValue: 2, }); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(focusDropdownTriggerMock).toHaveBeenCalled(); }); it('doesnt hide the dropdown and focus the trigger when the vmodel value changes and the `closeOnSelect` is undefined but is multiple', async () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, closeOnSelect: undefined, multiple: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; await wrapper.vm.$nextTick(); await wrapper.setProps({ modelValue: 2, }); expect(dropdownDoHideMock).not.toHaveBeenCalled(); expect(focusDropdownTriggerMock).not.toHaveBeenCalled(); }); it('doesnt hide the dropdown and focus the trigger when the vmodel value changes, is not multiple but closeOnSelect is `false`', async () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, closeOnSelect: false, multiple: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; await wrapper.vm.$nextTick(); await wrapper.setProps({ modelValue: 2, }); expect(dropdownDoHideMock).not.toHaveBeenCalled(); expect(focusDropdownTriggerMock).not.toHaveBeenCalled(); }); it('has default dropdownPopperOptions', () => { const wrapper = shallowMount(TRichSelect); expect(Object.keys(wrapper.vm.dropdownPopperOptions)).toEqual(['placement', 'modifiers', 'strategy', 'onFirstUpdate']); }); describe('provides the data needed on the child elements', () => { it('provides the configuration', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.configuration).toEqual((wrapper.vm.$ as any).setupState.configuration); }); it('provides the options', () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, }, }); expect((wrapper.vm.$ as any).provides.options.value).toEqual(normalizeOptions(options)); }); it('provides the shown state', () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, }, }); expect((wrapper.vm.$ as any).provides.state).toEqual((wrapper.vm.$ as any).setupState.state); }); it('provides the `selectedOption` state', () => { const options = [1]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, }, }); expect((wrapper.vm.$ as any).provides.selectedOption.value).toEqual({ value: 1, text: 1, raw: 1, }); }); it('provides the `hasSelectedOption` computed property', () => { const options = [1]; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue: 1, }, }); expect((wrapper.vm.$ as any).provides.hasSelectedOption).toBeDefined(); }); it('provides the `toggleOption` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.toggleOption[0]).toEqual((wrapper.vm.$ as any).setupState.toggleOption); }); it('provides the `optionIsActive` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.optionIsActive[0]).toEqual((wrapper.vm.$ as any).setupState.optionIsActive); }); it('provides the `setActiveOption` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.setActiveOption[0]).toEqual((wrapper.vm.$ as any).setupState.setActiveOption); }); it('provides the `dropdownBottomReachedHandler` method', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.dropdownBottomReachedHandler[0]).toEqual((wrapper.vm.$ as any).setupState.dropdownBottomReachedHandler); }); it('provides the `optionIsSelected` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.optionIsSelected).toEqual((wrapper.vm.$ as any).setupState.optionIsSelected); }); it('provides the `keydownDownHandler` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.keydownDownHandler).toEqual((wrapper.vm.$ as any).setupState.keydownDownHandler); }); it('provides the `keydownUpHandler` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.keydownUpHandler).toEqual((wrapper.vm.$ as any).setupState.keydownUpHandler); }); it('provides the `keydownEnterHandler` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.keydownEnterHandler).toEqual((wrapper.vm.$ as any).setupState.keydownEnterHandler); }); it('provides the `keydownEscHandler` method`', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.keydownEscHandler).toEqual((wrapper.vm.$ as any).setupState.keydownEscHandler); }); it('provides the `showSearchInput` computed property', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.showSearchInput.value).toEqual((wrapper.vm.$ as any).setupState.showSearchInput); }); it('provides the `needsMoreCharsMessage` computed property', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.needsMoreCharsMessage.value).toEqual('Please enter undefined or more characters'); }); it('provides the `searchQuery` ref', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.searchQuery.value).toEqual((wrapper.vm.$ as any).setupState.searchQuery); }); it('provides the `fetchingOptions` ref', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.fetchingOptions.value).toBe(false); }); it('provides the `fetchingMoreOptions` ref', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.fetchingMoreOptions.value).toBe(false); }); it('provides the `usesTags` ref', () => { const wrapper = shallowMount(TRichSelect); expect((wrapper.vm.$ as any).provides.usesTags.value).toBe(false); }); }); describe('usesTags', () => { it('determines it uses tags if tags is set and is multiple', () => { const wrapper = shallowMount(TRichSelect, { props: { tags: true, multiple: true, }, }); expect(wrapper.vm.usesTags).toBe(true); }); it('determines it doesnt uses tags if tags is set but is no multiple', () => { const wrapper = shallowMount(TRichSelect, { props: { tags: true, multiple: false, }, }); expect(wrapper.vm.usesTags).toBe(false); }); it('determines it doesnt uses tags if tags is not set even if is multiple', () => { const wrapper = shallowMount(TRichSelect, { props: { tags: false, multiple: true, }, }); expect(wrapper.vm.usesTags).toBe(false); }); it('uses a button for the dropdown trigger if not uses tags', () => { const wrapper = shallowMount(TRichSelect, { props: { tags: false, }, }); expect((wrapper.vm.$refs.dropdownComponent as any).tagName).toBe('button'); }); it('uses a div for the dropdown trigger if uses tags', () => { const wrapper = shallowMount(TRichSelect, { props: { tags: true, multiple: true, }, }); expect((wrapper.vm.$refs.dropdownComponent as any).tagName).toBe('div'); }); }); describe('selectedOption', () => { it('sets the selectedOption from the initial v-model ', () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue, }, }); expect((wrapper.vm.$ as any).provides.selectedOption.value).toEqual({ value: 2, text: 2, raw: 2, }); }); it('sets the selectedOption from the initial v-model when not defined', () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, }, }); expect((wrapper.vm.$ as any).provides.selectedOption.value).toBeUndefined(); }); it('updates the selectedOption within the v-model ', async () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue, }, }); await wrapper.setProps({ modelValue: 3, }); expect((wrapper.vm.$ as any).provides.selectedOption.value).toEqual({ value: 3, text: 3, raw: 3, }); }); it('updates the selectedOption within the v-model when set to null ', async () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { options, modelValue, }, }); await wrapper.setProps({ modelValue: null, }); expect((wrapper.vm.$ as any).provides.selectedOption.value).toBeUndefined(); }); }); describe('ClearButton', () => { it('set showClearButton as `true` if `selectedOption` is clearable and is not multiple', () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { clearable: true, multiple: false, options, modelValue, }, }); expect(wrapper.vm.showClearButton).toBe(true); }); it('set showClearButton as `false` if `selectedOption`, is clearable but is multiple', () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { clearable: true, multiple: true, options, modelValue, }, }); expect(wrapper.vm.showClearButton).toBe(false); }); it('set showClearButton as `false` if `selectedOption` is multiple but is not clearable', () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { clearable: false, multiple: false, options, modelValue, }, }); expect(wrapper.vm.showClearButton).toBe(false); }); it('set showClearButton as `false` if no selectedOption even if is clearable and is not multiple', () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { clearable: true, multiple: false, options, }, }); expect(wrapper.vm.showClearButton).toBe(false); }); it('shows the clearButton if showClearButton is `true`', () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { clearable: true, multiple: false, options, modelValue, }, }); expect(wrapper.vm.$refs.clearButton).toBeDefined(); expect(getChildComponentNameByRef(wrapper, 'clearButton')).toEqual('RichSelectClearButton'); }); it('hides the clearButton if showClearButton is `false`', () => { const wrapper = shallowMount(TRichSelect); expect(wrapper.vm.$refs.clearButton).not.toBeDefined(); }); it('contains a `click` handler that calls the `clearValue` method', async () => { const options = [1, 2, 3]; const modelValue = 2; const wrapper = shallowMount(TRichSelect, { props: { clearable: true, multiple: false, options, modelValue, }, }); const component = wrapper.vm.$refs.clearButton; expect(componentHasAttributeWithValue(component, 'onClick', wrapper.vm.clearValue)).toBe(true); }); }); describe('event handlers', () => { it.each([ ['onShown', 'shownHandler'], ['onHidden', 'hiddenHandler'], ['onBeforeShow', 'beforeShowHandler'], ['onBeforeHide', 'beforeHideHandler'], ['onBlur', 'blurHandler'], ['onFocus', 'focusHandler'], ['onMousedown', 'mousedownHandler'], ['onBlurOnChild', 'blurOnChildHandler'], ])('has the `%s` event handler pointing to `%s`', (eventName, eventHandlerName) => { const wrapper = shallowMount(TRichSelect); const component = wrapper.vm.$refs.dropdownComponent as any; expect(componentHasAttributeWithValue(component, eventName, (wrapper.vm as any)[eventHandlerName])).toBe(true); }); it('hides the dropdown with blurHandler', () => { const wrapper = shallowMount(TRichSelect, { global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; const event = new FocusEvent('blur'); wrapper.vm.blurHandler(event); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(wrapper.emitted().blur).toEqual([[event]]); }); describe('onOptionSelected', () => { it('calls the `onOptionSelected` method when the localValue changes', async () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { modelValue: 1, options, }, }); const onOptionSelectedSpy = jest.spyOn(wrapper.vm, 'onOptionSelected'); expect(onOptionSelectedSpy).not.toHaveBeenCalled(); await wrapper.setProps({ modelValue: 2, }); expect(onOptionSelectedSpy).toHaveBeenCalled(); }); it('hides the dropdown and focus the dropdown if `closeOnSelect` is set to `true`', async () => { const wrapper = shallowMount(TRichSelect, { props: { closeOnSelect: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; wrapper.vm.onOptionSelected(); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(focusDropdownTriggerMock).toHaveBeenCalled(); }); it('hides the dropdown if `closeOnSelect` is `undefined` and is not multiple', async () => { const wrapper = shallowMount(TRichSelect, { props: { closeOnSelect: undefined, multiple: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; wrapper.vm.onOptionSelected(); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(focusDropdownTriggerMock).toHaveBeenCalled(); }); it('doesnt hides the dropdown if `closeOnSelect` is `undefined` and is multiple', async () => { const wrapper = shallowMount(TRichSelect, { props: { closeOnSelect: undefined, multiple: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; wrapper.vm.onOptionSelected(); expect(dropdownDoHideMock).not.toHaveBeenCalled(); expect(focusDropdownTriggerMock).not.toHaveBeenCalled(); }); it('doesnt hides the dropdown if is already hidden', async () => { const wrapper = shallowMount(TRichSelect, { props: { closeOnSelect: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.onOptionSelected(); expect(dropdownDoHideMock).not.toHaveBeenCalled(); expect(focusDropdownTriggerMock).not.toHaveBeenCalled(); }); }); describe('focusHandler', () => { it('shows the dropdown with focusHandler when `toggleOnFocus` is set', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnFocus: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const event = new FocusEvent('focus'); wrapper.vm.focusHandler(event); expect(dropdownDoShowMock).toHaveBeenCalled(); expect(wrapper.emitted().focus).toEqual([[event]]); }); it('doesnt shows the dropdown with focusHandler when `toggleOnFocus` is set to `false`', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnFocus: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const event = new FocusEvent('focus'); wrapper.vm.focusHandler(event); expect(wrapper.vm.shown).toBe(false); expect(dropdownDoShowMock).not.toHaveBeenCalled(); expect(wrapper.emitted().focus).toEqual([[event]]); }); }); describe('mousedownHandler', () => { it('shows the dropdown when `toggleOnClick` is set', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const event = new MouseEvent('click'); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); wrapper.vm.mousedownHandler(event); expect(dropdownDoShowMock).toHaveBeenCalled(); expect(wrapper.emitted().mousedown).toEqual([[event]]); expect(preventDefaultSpy).toHaveBeenCalled(); }); it('doesnt shows the dropdown when `toggleOnClick` is not set', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const event = new MouseEvent('click'); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); wrapper.vm.mousedownHandler(event); expect(dropdownDoShowMock).not.toHaveBeenCalled(); expect(wrapper.emitted().mousedown).toEqual([[event]]); expect(preventDefaultSpy).not.toHaveBeenCalled(); }); it('doesnt call the `preventDefault` method if search box is hidden', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, hideSearchBox: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const event = new MouseEvent('click'); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); wrapper.vm.mousedownHandler(event); expect(preventDefaultSpy).not.toHaveBeenCalled(); }); it('hides the dropdown when `toggleOnClick` is set and dropdown is shown', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; const event = new MouseEvent('click'); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); wrapper.vm.mousedownHandler(event); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(wrapper.emitted().mousedown).toEqual([[event]]); expect(preventDefaultSpy).not.toHaveBeenCalled(); }); it('doesnt hides the dropdown when `toggleOnClick` is not set', () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: false, }, }); wrapper.vm.shown = true; const event = new MouseEvent('click'); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); wrapper.vm.mousedownHandler(event); expect(wrapper.vm.shown).toBe(true); expect(dropdownDoHideMock).not.toHaveBeenCalled(); expect(wrapper.emitted().mousedown).toEqual([[event]]); expect(preventDefaultSpy).not.toHaveBeenCalled(); }); }); describe('blurOnChildHandler', () => { it('will restore the original focus when blurred from the focusable item to a child not focusable element', () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; const target = document.createElement('DIV'); const relatedTarget = document.createElement('DIV'); target.setAttribute('data-rich-select-focusable', 'true'); const focusSpy = jest.spyOn(target, 'focus'); wrapper.vm.blurOnChildHandler({ target, relatedTarget } as unknown as FocusEvent); expect(focusSpy).toHaveBeenCalled(); expect(wrapper.vm.shown).toBe(true); }); it('will not restore the original focus when blurred between two focusable childs', () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; const target = document.createElement('DIV'); target.setAttribute('data-rich-select-focusable', 'true'); const relatedTarget = document.createElement('DIV'); relatedTarget.setAttribute('data-rich-select-focusable', 'true'); const focusSpy = jest.spyOn(target, 'focus'); wrapper.vm.blurOnChildHandler({ target, relatedTarget } as unknown as FocusEvent); expect(focusSpy).not.toHaveBeenCalled(); expect(wrapper.vm.shown).toBe(true); }); it('will not restore the original focus when blurred to an undefined item', () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; const target = document.createElement('DIV'); target.setAttribute('data-rich-select-focusable', 'true'); const focusSpy = jest.spyOn(target, 'focus'); wrapper.vm.blurOnChildHandler({ target, relatedTarget: undefined } as unknown as FocusEvent); expect(focusSpy).not.toHaveBeenCalled(); expect(wrapper.vm.shown).toBe(true); }); }); describe('shownHandler', () => { it('sets shown as true when called', () => { const wrapper = shallowMount(TRichSelect); expect(wrapper.vm.shown).toBe(false); wrapper.vm.shownHandler(); expect(wrapper.vm.shown).toBe(true); expect(wrapper.emitted()).toHaveProperty('shown'); }); }); describe('hiddenHandler', () => { it('sets shown as false when called', () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; wrapper.vm.hiddenHandler(); expect(wrapper.vm.shown).toBe(false); expect(wrapper.emitted()).toHaveProperty('hidden'); }); }); describe('beforeShowHandler', () => { it('sets active option when is about to show the dropdown', () => { const wrapper = shallowMount(TRichSelect); const initActiveOptionSpy = jest.spyOn((wrapper.vm.$ as any).setupState, 'initActiveOption'); wrapper.vm.beforeShowHandler(); expect(initActiveOptionSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('before-show'); }); }); describe('beforeHideHandler', () => { it('selects the active option if `selectOnclose` is set and active option is different to selected option', () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { modelValue: 1, options, selectOnClose: true, }, }); wrapper.vm.activeOption = { value: 2, text: 2, raw: 2 } as NormalizedOption; const selectOptionFromActiveOptionSpy = jest.spyOn((wrapper.vm.$ as any).setupState, 'selectOptionFromActiveOption'); wrapper.vm.beforeHideHandler(); expect(selectOptionFromActiveOptionSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('before-hide'); }); it('selects the active option if `selectOnclose` when active option is null', () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { modelValue: 1, options, selectOnClose: true, }, }); wrapper.vm.activeOption = null; const selectOptionFromActiveOptionSpy = jest.spyOn((wrapper.vm.$ as any).setupState, 'selectOptionFromActiveOption'); wrapper.vm.beforeHideHandler(); expect(selectOptionFromActiveOptionSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('before-hide'); }); it('doesnt selects the active option if `selectOnclose` is set to `false`', () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { modelValue: 1, options, selectOnClose: false, }, }); wrapper.vm.activeOption = { value: 2, text: 2, raw: 2 } as NormalizedOption; const selectOptionFromActiveOptionSpy = jest.spyOn((wrapper.vm.$ as any).setupState, 'selectOptionFromActiveOption'); wrapper.vm.beforeHideHandler(); expect(selectOptionFromActiveOptionSpy).not.toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('before-hide'); }); it('doesnt selects the active option when `selectOnclose` is set if active the same as the selected option', () => { const options = [1, 2]; const wrapper = shallowMount(TRichSelect, { props: { modelValue: 2, options, selectOnClose: true, }, }); wrapper.vm.activeOption = { value: 2, text: 2, raw: 2 } as NormalizedOption; const selectOptionFromActiveOptionSpy = jest.spyOn((wrapper.vm.$ as any).setupState, 'selectOptionFromActiveOption'); wrapper.vm.beforeHideHandler(); expect(selectOptionFromActiveOptionSpy).not.toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('before-hide'); }); }); }); describe('Dropdown stuff', () => { it('invalidates invalid dropdown placements', () => { const { validator } = TRichSelect.props.dropdownPlacement; 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 } = TRichSelect.props.dropdownPlacement; expect(validator(placement)).toBe(true); }); it.each([ ['onMouseover', 'mouseover'], ['onMouseleave', 'mouseleave'], ['onTouchstart', 'touchstart'], ])('re-emits the event `%s`', (eventName, parameterName) => { const wrapper = shallowMount(TRichSelect); const component = wrapper.vm.$refs.dropdownComponent as any; expect(componentHasAttributeWithInlineHandlerAndParameter(component, eventName, parameterName)).toBe(true); }); it.each([ 'mouseover', 'mouseleave', ])('re-emits dropdown events', (eventName) => { const wrapper = shallowMount(TRichSelect); const component = wrapper.vm.$refs.dropdownComponent as any; const event = new MouseEvent(eventName); component.$el.dispatchEvent(event); expect(wrapper.emitted()).toHaveProperty(eventName); expect(wrapper.emitted()[eventName][0]).toEqual([event]); }); it('re-emits dropdown touchstart event', () => { const wrapper = shallowMount(TRichSelect); const component = wrapper.vm.$refs.dropdownComponent as any; const event = new TouchEvent('touchstart'); component.$el.dispatchEvent(event); expect(wrapper.emitted()).toHaveProperty('touchstart'); expect(wrapper.emitted().touchstart[0]).toEqual([event]); }); describe('esc key', () => { it('hides the dropdown and focus the trigger when pressed esc', async () => { const wrapper = shallowMount(TRichSelect, { global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Escape' }); dropdownComponent.$el.dispatchEvent(event); expect(dropdownDoHideMock).toHaveBeenCalled(); expect(focusDropdownTriggerMock).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('only re-emits the keyboard event when dropdown is closed ', async () => { const wrapper = shallowMount(TRichSelect, { global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Escape' }); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(false); expect(focusDropdownTriggerMock).not.toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); }); describe('space key', () => { it('shows the dropdown if `toggleOnClick` is set', async () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Space', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(dropdownDoShowMock).toHaveBeenCalled(); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('doesnt shows the dropdown if `toggleOnClick` is not set', async () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: false, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Space', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(false); expect(dropdownDoShowMock).not.toHaveBeenCalled(); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('toggles the active option if dropdown is shown', async () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Space', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); expect((wrapper.vm.$ as any).setupState.localValue).toBe(undefined); dropdownComponent.$el.dispatchEvent(event); expect((wrapper.vm.$ as any).setupState.localValue).toBe(1); expect(wrapper.vm.shown).toBe(true); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); dropdownComponent.$el.dispatchEvent(event); expect((wrapper.vm.$ as any).setupState.localValue).toBe(undefined); expect(wrapper.vm.shown).toBe(true); }); it('toggles the active option if dropdown is shown when no active option', async () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Space', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(true); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); }); describe('enter key', () => { it('doesnt shows the dropdown when hidden', async () => { const wrapper = shallowMount(TRichSelect, { global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Enter', }); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(false); expect(dropdownDoShowMock).not.toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('toggles the active option if dropdown is shown', async () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Enter', }); expect((wrapper.vm.$ as any).setupState.localValue).toBe(undefined); dropdownComponent.$el.dispatchEvent(event); expect((wrapper.vm.$ as any).setupState.localValue).toBe(1); expect(wrapper.vm.shown).toBe(true); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); dropdownComponent.$el.dispatchEvent(event); expect((wrapper.vm.$ as any).setupState.localValue).toBe(undefined); expect(wrapper.vm.shown).toBe(true); }); it('toggles the active option if dropdown is shown when no active option', async () => { const wrapper = shallowMount(TRichSelect); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'Enter', }); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(true); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); }); describe('key up key', () => { it('shows the dropdown if hidden', async () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowUp', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(dropdownDoShowMock).toHaveBeenCalled(); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('activates the the prev option if dropdown is shown', async () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, toggleOnClick: true, }, }); wrapper.vm.shown = true; wrapper.vm.activeOption = { value: 3, text: 3, raw: 3 } as NormalizedOption; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowUp', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(true); expect(wrapper.vm.activeOption).toEqual({ value: 2, text: 2, raw: 2 } as NormalizedOption); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); }); describe('key down key', () => { it('shows the dropdown if hidden', async () => { const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, }, global: { stubs: { TDropdown: TDropdownComponentMock, }, }, }); const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowDown', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(dropdownDoShowMock).toHaveBeenCalled(); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('activates the the next option if dropdown is shown', async () => { const options = [1, 2, 3]; const wrapper = shallowMount(TRichSelect, { props: { options, toggleOnClick: true, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowDown', }); const preventDefaultSpy = jest.spyOn(event, 'preventDefault'); dropdownComponent.$el.dispatchEvent(event); expect(wrapper.vm.shown).toBe(true); expect(wrapper.vm.activeOption).toEqual({ value: 2, text: 2, raw: 2 } as NormalizedOption); expect(preventDefaultSpy).toHaveBeenCalled(); expect(wrapper.emitted()).toHaveProperty('keydown'); expect(wrapper.emitted().keydown[0]).toEqual([event]); }); it('calls the fetchMoreOptions method if press down, is last item and have more options to fetch', async () => { const responsePromise = new Promise((resolve) => { resolve({ results: [1, 2], hasMorePages: true, }); }); const fetchOptionsMock = jest.fn().mockReturnValue(responsePromise); const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, fetchOptions: fetchOptionsMock, delay: 0, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowDown', }); // So it calls the fetchOptions method wrapper.vm.beforeShowHandler(); // Should be called with `undefined` search query and `undefined` next page. expect(fetchOptionsMock).toHaveBeenLastCalledWith(undefined, undefined); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); // Wait until options were fetched. await wrapper.vm.$nextTick(); // Wait until options were stored in the state await wrapper.vm.$nextTick(); // Active option is first one expect(wrapper.vm.activeOption!.value).toEqual(1); // Not called again yet await wrapper.vm.$nextTick(); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); // Press down one time dropdownComponent.$el.dispatchEvent(event); // now the active option is last loaded one expect(wrapper.vm.activeOption!.value).toEqual(2); await wrapper.vm.$nextTick(); // Called again expect(fetchOptionsMock).toHaveBeenCalledTimes(2); // No search query but page 2 expect(fetchOptionsMock).toHaveBeenLastCalledWith(undefined, 2); }); it('doesnt calls the fetchMoreOptions method if press down if is last item if no have more options to fetch', async () => { const responsePromise = new Promise((resolve) => { resolve({ results: [1, 2], }); }); const fetchOptionsMock = jest.fn().mockReturnValue(responsePromise); const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, fetchOptions: fetchOptionsMock, delay: 0, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowDown', }); // So it calls the fetchOptions method wrapper.vm.beforeShowHandler(); // Should be called with `undefined` search query and `undefined` next page. expect(fetchOptionsMock).toHaveBeenLastCalledWith(undefined, undefined); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); // Wait until options were fetched. await wrapper.vm.$nextTick(); // Wait until options were stored in the state await wrapper.vm.$nextTick(); // Active option is first one expect(wrapper.vm.activeOption!.value).toEqual(1); // Not called again yet await wrapper.vm.$nextTick(); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); // Press down one time dropdownComponent.$el.dispatchEvent(event); // now the active option is last loaded one expect(wrapper.vm.activeOption!.value).toEqual(2); await wrapper.vm.$nextTick(); // But it not calls the fetchOptions method expect(fetchOptionsMock).toHaveBeenCalledTimes(1); }); it('doesnt calls the fetchMoreOptions method if press down if is currently loading more options', async () => { const responsePromise1 = new Promise((resolve) => { resolve({ results: [1, 2], hasMorePages: true, }); }); const responsePromise2 = new Promise(() => { // Never resolves }); const fetchOptionsMock = jest.fn().mockReturnValue(responsePromise1); const wrapper = shallowMount(TRichSelect, { props: { toggleOnClick: true, fetchOptions: fetchOptionsMock, delay: 0, }, }); wrapper.vm.shown = true; const dropdownComponent = wrapper.vm.$refs.dropdownComponent as any; const event = new KeyboardEvent('keydown', { key: 'ArrowDown', }); // So it calls the fetchOptions method wrapper.vm.beforeShowHandler(); // Should be called with `undefined` search query and `undefined` next page. expect(fetchOptionsMock).toHaveBeenLastCalledWith(undefined, undefined); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); // Wait until options were fetched. await wrapper.vm.$nextTick(); // Wait until options were stored in the state await wrapper.vm.$nextTick(); // Active option is first one expect(wrapper.vm.activeOption!.value).toEqual(1); // Not called again yet await wrapper.vm.$nextTick(); expect(fetchOptionsMock).toHaveBeenCalledTimes(1); fetchOptionsMock.mockReturnValue(responsePromise2); // Press down one time dropdownComponent.$el.dispatchEvent(event); // now the active option is last loaded one expect(wrapper.vm.activeOption!.value).toEqual(2); await wrapper.vm.$nextTick(); // Called again expect(fetchOptionsMock).toHaveBeenCalledTimes(2); // No search query but page 2 expect(fetchOptionsMock).toHaveBeenLastCalledWith(undefined, 2); // Wait until options call is resolved (that will never happen in this test // to emulate a busy state) await wrapper.vm.$nextTick(); // Press down one time dropdownComponent.$el.dispatchEvent(event); await wrapper.vm.$nextTick(); // Was not called again (still called twice) since its busy expect(fetchOptionsMock).toHaveBeenCalledTimes(2); }); }); }); describe('fetch options', () => { it('emits a `fetch-options-success` event with the response', async () => { const response = { results: [1, 2], }; const fetchOptions = () => new Promise((resolve) => { resolve(response); }); const wrapper = shallowMount(TRichSelect, { props: { fetchOptions, delay: 0, } as any, }); (wrapper.vm.$ as any).setupState.doFetchOptions(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); expect(wrapper.emitted('fetch-options-success')).toEqual([[response]]); }); it('emits a `fetch-options-error` event when an exception occurs', async () => { const error = new Error('test'); const fetchOptions = () => new Promise((_resolve, reject) => { reject(error); }); const wrapper = shallowMount(TRichSelect, { props: { fetchOptions, delay: 0, } as any, }); (wrapper.vm.$ as any).setupState.doFetchOptions(); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();