From de0a28754d8fefe8c4db6aa7b7cb5d8407016be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Antonio=20Caba=C3=B1eros?= Date: Wed, 24 Apr 2024 10:03:54 +0200 Subject: [PATCH] refactor(sort-mixin): get rid of SortMixin and refactor components which use it (#1448) BREAKING CHANGE: base-dropdown component will no longer emit the `change` event, use `update:modelValue` instead. base-dropdown prop for the selected item was renamed from `value` to `modelValue`. --- packages/_vue3-migration-test/package.json | 4 + .../src/components/index.ts | 1 + .../src/components/test-base-dropdown.vue | 22 + packages/_vue3-migration-test/src/main.ts | 4 +- packages/_vue3-migration-test/src/router.ts | 7 +- packages/_vue3-migration-test/vite.config.ts | 1 + packages/x-components/.eslintrc.js | 9 +- packages/x-components/jest.setup.ts | 3 + .../__tests__/base-dropdown.spec.ts | 234 ++---- .../components/animations/animate-width.vue | 3 +- .../src/components/animations/fade.vue | 3 +- .../src/components/base-dropdown.vue | 666 ++++++++---------- .../base-column-picker-dropdown.vue | 28 +- .../x-components/src/components/no-element.ts | 2 +- packages/x-components/src/views/home/Home.vue | 6 +- .../components/renderless-extra-param.vue | 2 +- .../components/__tests__/my-history.spec.ts | 4 +- .../__tests__/sort-dropdown.spec.ts | 162 ++--- .../components/__tests__/sort-list.spec.ts | 121 +--- .../__tests__/sort-picker-list.spec.ts | 135 +--- .../src/x-modules/search/components/index.ts | 1 - .../search/components/sort-dropdown.vue | 86 ++- .../x-modules/search/components/sort-list.vue | 100 +-- .../search/components/sort-picker-list.vue | 101 +-- .../x-modules/search/components/sort.mixin.ts | 45 -- .../src/x-modules/search/store/module.ts | 1 - pnpm-lock.yaml | 57 ++ 27 files changed, 749 insertions(+), 1059 deletions(-) create mode 100644 packages/_vue3-migration-test/src/components/test-base-dropdown.vue delete mode 100644 packages/x-components/src/x-modules/search/components/sort.mixin.ts diff --git a/packages/_vue3-migration-test/package.json b/packages/_vue3-migration-test/package.json index 289615e91..7d6eac011 100644 --- a/packages/_vue3-migration-test/package.json +++ b/packages/_vue3-migration-test/package.json @@ -9,7 +9,11 @@ "build": "vue-tsc && vite build" }, "dependencies": { + "@empathyco/x-bus": "^1.0.3-alpha.1", + "@empathyco/x-types": "^10.1.0-alpha.2", + "@empathyco/x-utils": "^1.0.3-alpha.1", "@vue/compat": "^3.4.22", + "@vueuse/core": "~10.7.1", "vue": "^3.4.22", "vue-router": "^4.3.0" }, diff --git a/packages/_vue3-migration-test/src/components/index.ts b/packages/_vue3-migration-test/src/components/index.ts index 9616d4a90..de7210842 100644 --- a/packages/_vue3-migration-test/src/components/index.ts +++ b/packages/_vue3-migration-test/src/components/index.ts @@ -1 +1,2 @@ export * from './animations'; +export { default as TestBaseDropdown } from './test-base-dropdown.vue'; diff --git a/packages/_vue3-migration-test/src/components/test-base-dropdown.vue b/packages/_vue3-migration-test/src/components/test-base-dropdown.vue new file mode 100644 index 000000000..f2f7d444e --- /dev/null +++ b/packages/_vue3-migration-test/src/components/test-base-dropdown.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/_vue3-migration-test/src/main.ts b/packages/_vue3-migration-test/src/main.ts index 194db49bf..c8fc07e36 100644 --- a/packages/_vue3-migration-test/src/main.ts +++ b/packages/_vue3-migration-test/src/main.ts @@ -6,7 +6,9 @@ import router from './router'; const VUE_COMPAT_MODE = Number(import.meta.env.VITE_VUE_COMPAT_MODE); if (VUE_COMPAT_MODE === 2) { configureCompat({ - INSTANCE_LISTENERS: 'suppress-warning' + INSTANCE_LISTENERS: 'suppress-warning', + RENDER_FUNCTION: false, + COMPONENT_V_MODEL: false }); } diff --git a/packages/_vue3-migration-test/src/router.ts b/packages/_vue3-migration-test/src/router.ts index 9ec37cfd1..8d6109ff4 100644 --- a/packages/_vue3-migration-test/src/router.ts +++ b/packages/_vue3-migration-test/src/router.ts @@ -1,5 +1,5 @@ import { createRouter, createWebHistory } from 'vue-router'; -import { TestAnimateWidth, TestFade } from './'; +import { TestAnimateWidth, TestBaseDropdown, TestFade } from './'; const routes = [ { @@ -11,6 +11,11 @@ const routes = [ path: '/fade', name: 'Fade', component: TestFade + }, + { + path: '/base-dropdown', + name: 'BaseDropdown', + component: TestBaseDropdown } ]; diff --git a/packages/_vue3-migration-test/vite.config.ts b/packages/_vue3-migration-test/vite.config.ts index 298c907ab..f369e93a2 100644 --- a/packages/_vue3-migration-test/vite.config.ts +++ b/packages/_vue3-migration-test/vite.config.ts @@ -27,6 +27,7 @@ export default defineConfig(({ mode }) => { ], resolve: { alias: { + '@vueuse/core': resolve(__dirname, 'node_modules/@vueuse/core'), vue: resolve(__dirname, 'node_modules/@vue/compat') } }, diff --git a/packages/x-components/.eslintrc.js b/packages/x-components/.eslintrc.js index 068101346..75aafd665 100644 --- a/packages/x-components/.eslintrc.js +++ b/packages/x-components/.eslintrc.js @@ -8,7 +8,8 @@ module.exports = { rules: { 'no-dupe-class-members': 'off', '@typescript-eslint/no-unused-vars-experimental': 'off', - 'vue/require-default-prop': 'off' + 'vue/require-default-prop': 'off', + '@typescript-eslint/explicit-function-return-type': 'off' }, overrides: [ { @@ -29,6 +30,12 @@ module.exports = { rules: { 'max-len': 'off' } + }, + { + files: ['*.spec.ts'], + rules: { + 'max-len': 'off' + } } ] }; diff --git a/packages/x-components/jest.setup.ts b/packages/x-components/jest.setup.ts index 8ffa92215..ba707431c 100644 --- a/packages/x-components/jest.setup.ts +++ b/packages/x-components/jest.setup.ts @@ -5,3 +5,6 @@ window.ResizeObserver = jest.fn().mockImplementation(() => ({ unobserve: jest.fn(), disconnect: jest.fn() })); + +// Disable console warnings to reduce noise overwriting entirely modules in the store +console.warn = () => {}; diff --git a/packages/x-components/src/components/__tests__/base-dropdown.spec.ts b/packages/x-components/src/components/__tests__/base-dropdown.spec.ts index be6da7f06..4c59fd563 100644 --- a/packages/x-components/src/components/__tests__/base-dropdown.spec.ts +++ b/packages/x-components/src/components/__tests__/base-dropdown.spec.ts @@ -1,93 +1,54 @@ -import { Identifiable } from '@empathyco/x-types'; -import { mount, Wrapper, WrapperArray } from '@vue/test-utils'; -import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import BaseDropdown from '../base-dropdown.vue'; import { getDataTestSelector } from '../../__tests__/utils'; +type Key = 'End' | 'Home' | 'ArrowUp' | 'ArrowDown' | 'Enter' | 'Space' | 'Esc'; + function renderDropdown({ template = ` - - + + `, items = ['one', 'two', 'three'], initialValue = items[0] -}: RenderDropdownOptions = {}): RenderDropdownAPI { +}: Partial<{ template?: string; items?: any[]; initialValue?: any }> = {}) { const wrapper = mount( { - props: ['items'], - components: { - BaseDropdown - }, template, - data() { - return { - value: initialValue - }; - } + components: { BaseDropdown }, + props: ['items'], + data: () => ({ value: initialValue }) }, { - propsData: { - items - } + propsData: { items } } ); - const dropdownWrapper = wrapper.findComponent(BaseDropdown); - - function getDropdownToggle(): Wrapper { - return dropdownWrapper.find(getDataTestSelector('dropdown-toggle')); - } - - function getListItems(): WrapperArray { - return dropdownWrapper.findAll(getDataTestSelector('dropdown-item')); - } + const dropdown = wrapper.findComponent(BaseDropdown); - function getHighlightedItem(): Wrapper { - return dropdownWrapper.find('.x-dropdown__item--is-highlighted'); - } + const getDropdownToggle = () => dropdown.find(getDataTestSelector('dropdown-toggle')); + const getListItems = () => dropdown.findAll(getDataTestSelector('dropdown-item')); + const getHighlightedItem = () => dropdown.find('.x-dropdown__item--is-highlighted'); + const getSelectedItem = () => dropdown.find('.x-dropdown__item--is-selected'); - function getSelectedItem(): Wrapper { - return dropdownWrapper.find('.x-dropdown__item--is-selected'); - } - - function clickBody(): Promise { + function clickBody() { document.dispatchEvent(new Event('mousedown')); - return dropdownWrapper.vm.$nextTick(); + return nextTick(); } - function clickToggleButton(): Promise { - getDropdownToggle().trigger('click'); - return dropdownWrapper.vm.$nextTick(); - } + const clickToggleButton = () => getDropdownToggle().trigger('click'); + const clickNthItem = (nth: number) => getListItems().at(nth).trigger('click'); + const pressKeyFromToggle = (key: Key) => getDropdownToggle().trigger(`keydown`, { key }); + const pressKeyFromFocusedItem = (key: Key) => getHighlightedItem().trigger(`keydown`, { key }); - function clickNthItem(nth: number): Promise { - getListItems().at(nth).trigger('click'); - return dropdownWrapper.vm.$nextTick(); - } - - function pressKeyFromToggle(key: Key): Promise { - const keyDownEventInit: KeyboardEventInit = { - key - }; - getDropdownToggle().trigger(`keydown`, keyDownEventInit); - return dropdownWrapper.vm.$nextTick(); - } - - function pressKeyFromFocusedItem(key: Key): Promise { - const keyDownEventInit: KeyboardEventInit = { - key - }; - getHighlightedItem().trigger(`keydown`, keyDownEventInit); - return dropdownWrapper.vm.$nextTick(); - } - - async function search(search: string): Promise { + async function search(search: string) { jest.useFakeTimers(); - await chainTypeChars(search, dropdownWrapper.vm.$nextTick()); + await chainTypeChars(search, nextTick()); jest.runAllTimers(); jest.useRealTimers(); } @@ -100,7 +61,7 @@ function renderDropdown({ pendingChars, previousCharPromise.then(() => /* Vue test utils has a strange issue where the `home` and `end` event handlers - * are always executed every time a `keydown` event is emmitted. That's why the only + * are always executed every time a `keydown` event is emitted. That's why the only * way to make it work is to pass the real key value in the options. * The `keydown.` syntax of the `trigger` method can't be used. */ getHighlightedItem().trigger('keydown', { key: charToType }) @@ -109,8 +70,13 @@ function renderDropdown({ : previousCharPromise; } + function isListVisible() { + const element = dropdown.find(getDataTestSelector('dropdown-list')).element; + return element.style.display !== 'none'; + } + return { - wrapper: dropdownWrapper, + wrapper: dropdown, getDropdownToggle, getListItems, getHighlightedItem, @@ -120,20 +86,21 @@ function renderDropdown({ clickNthItem, pressKeyFromToggle, pressKeyFromFocusedItem, - search - }; + search, + isListVisible + } as const; } describe('testing Dropdown component', () => { it('does not render the list if it is collapsed', async () => { - const { getDropdownToggle, getListItems, clickToggleButton } = renderDropdown(); + const { getDropdownToggle, clickToggleButton, isListVisible } = renderDropdown(); - expect(getDropdownToggle().exists()).toBe(true); - expect(getListItems().isVisible()).toBe(false); + expect(getDropdownToggle().exists()).toBeTruthy(); + expect(isListVisible()).toBeFalsy(); await clickToggleButton(); - expect(getListItems().isVisible()).toBe(true); + expect(isListVisible()).toBeTruthy(); }); it('renders the provided items', async () => { @@ -162,7 +129,7 @@ describe('testing Dropdown component', () => { getHighlightedItem } = renderDropdown({ template: ` - + @@ -183,7 +150,7 @@ describe('testing Dropdown component', () => { it('allows to customize the toggle button', async () => { const { clickToggleButton, getDropdownToggle } = renderDropdown({ template: ` - + @@ -198,10 +165,9 @@ describe('testing Dropdown component', () => { }); describe('opening the dropdown', () => { - const items = ['stonks', 'go', 'brrr']; + const items = ['stones', 'go', 'bum']; const selectedIndex = 1; - // eslint-disable-next-line max-len it('opens and focuses the selected element when the button has focus and the arrow DOWN key is pressed', async () => { const { pressKeyFromToggle, getHighlightedItem, getListItems } = renderDropdown({ items, @@ -214,7 +180,6 @@ describe('testing Dropdown component', () => { expect(getHighlightedItem().text()).toBe(selectedElement.text()); }); - // eslint-disable-next-line max-len it('opens and focuses the selected element when the button has focus and arrow UP key is pressed', async () => { const { pressKeyFromToggle, getHighlightedItem, getListItems } = renderDropdown({ items, @@ -230,29 +195,29 @@ describe('testing Dropdown component', () => { describe('closing the dropdown', () => { it('closes the dropdown when it is open and the toggle button is clicked', async () => { - const { clickToggleButton, getListItems } = renderDropdown(); + const { clickToggleButton, isListVisible } = renderDropdown(); await clickToggleButton(); - expect(getListItems().isVisible()).toBe(true); + expect(isListVisible()).toBeTruthy(); await clickToggleButton(); - expect(getListItems().isVisible()).toBe(false); + expect(isListVisible()).toBeFalsy(); }); it('closes the dropdown when losing focus', async () => { - const { clickToggleButton, getListItems, clickBody } = renderDropdown(); + const { clickToggleButton, getListItems, clickBody, isListVisible } = renderDropdown(); await clickToggleButton(); expect(getListItems()).not.toHaveLength(0); await clickBody(); - expect(getListItems().isVisible()).toBe(false); + expect(isListVisible()).toBeFalsy(); }); it('closes the dropdown when selecting an element', async () => { const items = [0, 1, 2, 3, 4]; const value = items[0]; - const { clickNthItem, clickToggleButton, getDropdownToggle, getListItems } = renderDropdown({ + const { clickNthItem, clickToggleButton, getDropdownToggle, isListVisible } = renderDropdown({ items, initialValue: value }); @@ -261,17 +226,16 @@ describe('testing Dropdown component', () => { await clickNthItem(2); expect(getDropdownToggle().text()).toEqual('2'); - expect(getListItems().isVisible()).toBe(false); + expect(isListVisible()).toBeFalsy(); }); - // eslint-disable-next-line max-len it('closes the dropdown without modifying the selected item when the escape key is pressed', async () => { const { getHighlightedItem, clickToggleButton, pressKeyFromFocusedItem, - getListItems, - getSelectedItem + getSelectedItem, + isListVisible } = renderDropdown({ items: ['spain', 'uk', 'poland'], initialValue: 'spain' @@ -286,7 +250,7 @@ describe('testing Dropdown component', () => { expect(getSelectedItem().text()).toBe('spain'); await pressKeyFromFocusedItem('Esc'); - expect(getListItems().isVisible()).toBe(false); + expect(isListVisible()).toBeFalsy(); await clickToggleButton(); expect(getSelectedItem().text()).toBe('spain'); @@ -319,7 +283,6 @@ describe('testing Dropdown component', () => { expect(getHighlightedItem().text()).toBe(items[2]); }); - // eslint-disable-next-line max-len it('focuses the first element when the home button is pressed and the last when the end key is pressed', async () => { const { pressKeyFromFocusedItem, clickToggleButton, getListItems, getHighlightedItem } = renderDropdown(); @@ -334,7 +297,6 @@ describe('testing Dropdown component', () => { expect(getHighlightedItem().text()).toBe(listItems.at(0).text()); }); - // eslint-disable-next-line max-len it('focuses the first element starting to search from the focused one which its text starts with the typed characters', async () => { const { search, getHighlightedItem, clickToggleButton } = renderDropdown({ items: ['chocolate', 'milk', 'milk chocolate', 'chocolate milk'], @@ -352,7 +314,6 @@ describe('testing Dropdown component', () => { expect(getHighlightedItem().text()).toBe('milk chocolate'); }); - // eslint-disable-next-line max-len it('focuses the first element when typing if no element is found starting with the typed chars', async () => { const { search, getHighlightedItem, clickToggleButton } = renderDropdown({ items: ['beer', 'steak'], @@ -366,88 +327,3 @@ describe('testing Dropdown component', () => { }); }); }); - -type Key = 'End' | 'Home' | 'ArrowUp' | 'ArrowDown' | 'Enter' | 'Space' | 'Esc'; - -interface RenderDropdownOptions { - /** The template to test. */ - template?: string; - /** Items to be rendered by the {@link BaseDropdown}. */ - items?: Array; - /** The initial selected value. */ - initialValue?: null | number | string | Identifiable; -} - -interface RenderDropdownAPI { - /** Dropdown test wrapper. */ - wrapper: Wrapper; - /** - * Retrieves the button wrapper to open the dropdown. - * - * @returns The Wrapper to open the dropdown. - */ - getDropdownToggle: () => Wrapper; - /** - * Retrieves wrappers of the list item buttons. - * - * @returns The wrappers of the list item buttons. - */ - getListItems: () => WrapperArray; - /** - * Retrieves the highlighted item wrapper. - * This can only be used if the dropdown is open. - * - * @returns The highlighted item. - */ - getHighlightedItem: () => Wrapper; - /** - * Retrieves the selected item wrapper. - * This can only be used if the dropdown is open. - * - * @returns The selected item. - */ - getSelectedItem: () => Wrapper; - /** - * Simulates a click event in the body. - * - * @returns A promise that resolves after updating the view. - */ - clickBody: () => Promise; - /** - * Simulates a click event in the button that opens the dropdown. - * - * @returns A promise that resolves after updating the view. - */ - clickToggleButton: () => Promise; - /** - * Simulates a click event in element with the index passed. - * To use this, the dropdown must be opened first. - * - * @returns A promise that resolves after updating the view. - */ - clickNthItem: (item: number) => Promise; - /** - * Simulates a key press that happens when the dropdown open button is focused. - * - * @param key - The key to press. - * @returns A promise that resolves after updating the view. - */ - pressKeyFromToggle: (key: Key) => Promise; - /** - * Simulates a key press that happens when the an element from the list is focused. - * To use this, the dropdown must be opened first. - * - * @param key - The key to press. - * @returns A promise that resolves after updating the view. - */ - pressKeyFromFocusedItem: (key: Key) => Promise; - /** - * Searches inside the items for the characters. It emits an event for each character that the - * passed string has. - * To use this, the dropdown must be opened first. - * - * @param itemText - The text to find inside the items. - * @returns A promise that resolves after updating the view. - */ - search: (itemText: string) => Promise; -} diff --git a/packages/x-components/src/components/animations/animate-width.vue b/packages/x-components/src/components/animations/animate-width.vue index f2f9f6f52..bafc52fc0 100644 --- a/packages/x-components/src/components/animations/animate-width.vue +++ b/packages/x-components/src/components/animations/animate-width.vue @@ -1,5 +1,6 @@