diff --git a/packages/_vue3-migration-test/src/router.ts b/packages/_vue3-migration-test/src/router.ts index b7a8230b51..6888e1a186 100644 --- a/packages/_vue3-migration-test/src/router.ts +++ b/packages/_vue3-migration-test/src/router.ts @@ -10,6 +10,7 @@ import { TestCrossFade, TestElementsList, TestFacets, + TestFilters, TestFade, TestFadeAndSlide, TestScroll, @@ -111,6 +112,11 @@ const routes = [ name: 'Facets', component: TestFacets }, + { + path: '/filters', + name: 'Filters', + component: TestFilters + }, { path: '/scroll', name: 'Scroll', diff --git a/packages/_vue3-migration-test/src/x-modules/facets/components/index.ts b/packages/_vue3-migration-test/src/x-modules/facets/components/index.ts index 82e5a09c6f..f996c15827 100644 --- a/packages/_vue3-migration-test/src/x-modules/facets/components/index.ts +++ b/packages/_vue3-migration-test/src/x-modules/facets/components/index.ts @@ -1 +1,2 @@ export { default as TestFacets } from './test-facets.vue'; +export { default as TestFilters } from './test-filters.vue'; diff --git a/packages/_vue3-migration-test/src/x-modules/facets/components/test-filters.vue b/packages/_vue3-migration-test/src/x-modules/facets/components/test-filters.vue new file mode 100644 index 0000000000..162e93bd81 --- /dev/null +++ b/packages/_vue3-migration-test/src/x-modules/facets/components/test-filters.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/packages/x-components/src/x-modules/facets/components/filters/__tests__/hierarchical-filter.spec.ts b/packages/x-components/src/x-modules/facets/components/filters/__tests__/hierarchical-filter.spec.ts index f1fb2c4d45..aff85c72e7 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/__tests__/hierarchical-filter.spec.ts +++ b/packages/x-components/src/x-modules/facets/components/filters/__tests__/hierarchical-filter.spec.ts @@ -1,20 +1,21 @@ -import { - HierarchicalFacet, - HierarchicalFilter as HierarchicalFilterModel -} from '@empathyco/x-types'; +import { HierarchicalFilter as HierarchicalFilterModel } from '@empathyco/x-types'; import { mount, Wrapper, WrapperArray } from '@vue/test-utils'; -import Vue from 'vue'; import { createHierarchicalFacetStub } from '../../../../../__stubs__/facets-stubs.factory'; import { getDataTestSelector, installNewXPlugin } from '../../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../../components'; import { XPlugin } from '../../../../../plugins/x-plugin'; -import { XEventsTypes } from '../../../../../wiring/events.types'; import { flatHierarchicalFilters } from '../../../utils'; import { facetsXModule } from '../../../x-module'; import { resetXFacetsStateWith } from '../../__tests__/utils'; import HierarchicalFilter from '../hierarchical-filter.vue'; -function renderHierarchicalFilter({ +const metadata = { + moduleName: 'facets', + location: 'none', + replaceable: true +}; + +function render({ template = ` { - it('is an x-component', () => { - const { hierarchicalFilterWrapper } = renderHierarchicalFilter(); - - expect(isXComponent(hierarchicalFilterWrapper.vm)).toEqual(true); - }); - - it('belongs to the `facets` x-module', () => { - const { hierarchicalFilterWrapper } = renderHierarchicalFilter(); + it('is an XComponent that belongs to the facets', () => { + const { hierarchicalFilterWrapper } = render(); + expect(isXComponent(hierarchicalFilterWrapper.vm)).toBeTruthy(); expect(getXComponentXModuleName(hierarchicalFilterWrapper.vm)).toEqual('facets'); }); it('renders the provided filter by default', () => { - const { getFilterWrapper, getRootFilter } = renderHierarchicalFilter(); + const { getFilterWrapper, getRootFilter } = render(); expect(getFilterWrapper().text()).toEqual(getRootFilter().label); }); it('emits `UserClickedAFilter` and `UserClickedAHierarchicalFilter` event when clicked', () => { - const { getFilterWrapper, clickFilter, emit, getRootFilter } = renderHierarchicalFilter(); + const { clickFilter, emitSpy, getRootFilter } = render(); const filter = getRootFilter(); clickFilter(); - expect(emit).toHaveBeenCalledTimes(2); - expect(emit).toHaveBeenCalledWith('UserClickedAFilter', filter, { - target: getFilterWrapper().element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - expect(emit).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, { - target: getFilterWrapper().element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); + expect(emitSpy).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAFilter', filter, metadata); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, metadata); }); it('emits configured events when clicked', () => { - const { getFilterWrapper, clickFilter, emit, getRootFilter } = renderHierarchicalFilter({ + const { clickFilter, emitSpy, getRootFilter } = render({ clickEvents: { UserAcceptedAQuery: 'potato', UserBlurredSearchBox: undefined @@ -167,31 +152,16 @@ describe('testing `HierarchicalFilter` component', () => { const filter = getRootFilter(); clickFilter(); - expect(emit).toHaveBeenCalledTimes(4); + expect(emitSpy).toHaveBeenCalledTimes(4); ['UserClickedAFilter', 'UserClickedAHierarchicalFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { - target: getFilterWrapper().element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - }); - expect(emit).toHaveBeenCalledWith('UserAcceptedAQuery', 'potato', { - target: getFilterWrapper().element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - expect(emit).toHaveBeenCalledWith('UserBlurredSearchBox', undefined, { - target: getFilterWrapper().element, - moduleName: 'facets', - location: undefined, - replaceable: true + expect(emitSpy).toHaveBeenCalledWith(event, filter, metadata); }); + expect(emitSpy).toHaveBeenCalledWith('UserAcceptedAQuery', 'potato', metadata); + expect(emitSpy).toHaveBeenCalledWith('UserBlurredSearchBox', undefined, metadata); }); it('allows replacing the root element of the component', () => { - const { hierarchicalFilterWrapper, getRootFilter, emit } = renderHierarchicalFilter({ + const { hierarchicalFilterWrapper, getRootFilter, emitSpy } = render({ template: ` { > {{ filter.label }} - - ` + ` }); const filter = getRootFilter(); @@ -217,26 +186,20 @@ describe('testing `HierarchicalFilter` component', () => { customInputWrapper.trigger('change'); - expect(emit).toHaveBeenCalledTimes(2); - const expectedMetadata = { - target: customLabelWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true - }; - expect(emit).toHaveBeenCalledWith('UserClickedAFilter', filter, expectedMetadata); - expect(emit).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, expectedMetadata); + expect(emitSpy).toHaveBeenCalledTimes(2); + + expect(emitSpy).toHaveBeenCalledWith('UserClickedAFilter', filter, metadata); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, metadata); }); it('allows customizing the rendered label content with an slot', () => { - const { getFilterWrapper, getRootFilter } = renderHierarchicalFilter({ + const { getFilterWrapper, getRootFilter } = render({ template: ` - - ` + ` }); const defaultButton = getFilterWrapper().find(getDataTestSelector('filter')); @@ -246,7 +209,7 @@ describe('testing `HierarchicalFilter` component', () => { }); it('exposes proper css classes and attributes in the default slot', async () => { - const { getFilterWrapper, mutateFilter, getRootFilter } = renderHierarchicalFilter(); + const { getFilterWrapper, mutateFilter, getRootFilter } = render(); expect(getFilterWrapper().attributes()).not.toHaveProperty('disabled'); expect(getFilterWrapper().classes()).toHaveLength(4); @@ -289,7 +252,7 @@ describe('testing `HierarchicalFilter` component', () => { }); it('adds selected classes to the rendered element when the filter is selected', async () => { - const { getFilterWrapper, mutateFilter, getRootFilter } = renderHierarchicalFilter(); + const { getFilterWrapper, mutateFilter, getRootFilter } = render(); expect(getFilterWrapper().classes()).not.toEqual( expect.arrayContaining(['x-selected', 'x-hierarchical-filter--is-selected']) @@ -305,7 +268,7 @@ describe('testing `HierarchicalFilter` component', () => { describe('children testing', () => { it('allows customizing the slot for all the children', () => { - const { getFiltersWrappers, getFilters } = renderHierarchicalFilter({ + const { getFiltersWrappers, getFilters } = render({ template: ` Custom - {{ filter.label }} @@ -322,7 +285,7 @@ describe('testing `HierarchicalFilter` component', () => { it('renders children filter only when available', async () => { const { hierarchicalFilterWrapper, getFiltersWrappers, mutateFilter, getRootFilter } = - renderHierarchicalFilter(); + render(); const filter = getRootFilter(); await mutateFilter(filter, { children: [] }); const childrenFiltersWrapper = hierarchicalFilterWrapper.find( @@ -336,33 +299,22 @@ describe('testing `HierarchicalFilter` component', () => { ); }); - // eslint-disable-next-line max-len it('emits `UserClickedAFilter` and `UserClickedAHierarchicalFilter` events when a child is clicked', () => { - const { getFiltersWrappers, emit } = renderHierarchicalFilter(); + const { getFiltersWrappers, emitSpy } = render(); expect(getFiltersWrappers().wrappers.length).toBeGreaterThan(1); getFiltersWrappers().wrappers.forEach(filterWrapper => { - emit.mockClear(); + emitSpy.mockClear(); filterWrapper.trigger('click'); const filter = (filterWrapper.vm as any).filter; - expect(emit).toHaveBeenCalledTimes(2); - expect(emit).toHaveBeenCalledWith('UserClickedAFilter', filter, { - target: filterWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - expect(emit).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, { - target: filterWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); + expect(emitSpy).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAFilter', filter, metadata); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAHierarchicalFilter', filter, metadata); }); }); it('emits configured events when a child is clicked', () => { - const { getFiltersWrappers, emit } = renderHierarchicalFilter({ + const { getFiltersWrappers, emitSpy } = render({ clickEvents: { UserAcceptedAQuery: 'potato', UserBlurredSearchBox: undefined @@ -370,36 +322,21 @@ describe('testing `HierarchicalFilter` component', () => { }); expect(getFiltersWrappers().wrappers.length).toBeGreaterThan(1); getFiltersWrappers().wrappers.forEach(filterWrapper => { - emit.mockClear(); + emitSpy.mockClear(); filterWrapper.trigger('click'); const filter = (filterWrapper.vm as any).filter; - expect(emit).toHaveBeenCalledTimes(4); + expect(emitSpy).toHaveBeenCalledTimes(4); ['UserClickedAFilter', 'UserClickedAHierarchicalFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { - target: filterWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - }); - expect(emit).toHaveBeenCalledWith('UserAcceptedAQuery', 'potato', { - target: filterWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true - }); - expect(emit).toHaveBeenCalledWith('UserBlurredSearchBox', undefined, { - target: filterWrapper.element, - moduleName: 'facets', - location: undefined, - replaceable: true + expect(emitSpy).toHaveBeenCalledWith(event, filter, metadata); }); + expect(emitSpy).toHaveBeenCalledWith('UserAcceptedAQuery', 'potato', metadata); + expect(emitSpy).toHaveBeenCalledWith('UserBlurredSearchBox', undefined, metadata); }); }); it('adds a CSS class when the filter is partially selected', () => { - const { getFiltersWrappers, getPartiallySelectedFilters } = renderHierarchicalFilter(); + const { getFiltersWrappers, getPartiallySelectedFilters } = render(); const partiallySelectedIds = getPartiallySelectedFilters().map(filter => filter.id); expect(getFiltersWrappers().length).toBeGreaterThan(0); @@ -417,7 +354,7 @@ describe('testing `HierarchicalFilter` component', () => { }); it('exposes proper css classes and attributes in the default slot to children', async () => { - const { mutateFilter, getFilterWrapperByText, getFilters } = renderHierarchicalFilter({ + const { mutateFilter, getFilterWrapperByText, getFilters } = render({ template: ` { > {{ filter.label }} - - ` + ` }); const grandChild0Wrapper = getFilterWrapperByText('grand-child-0')!; @@ -493,7 +429,7 @@ describe('testing `HierarchicalFilter` component', () => { }); it('allows adding classes to the inner filters lists', () => { - const { hierarchicalFilterWrapper } = renderHierarchicalFilter({ + const { hierarchicalFilterWrapper } = render({ childrenFiltersClass: 'custom-class', facet: createHierarchicalFacetStub('category', createFilter => [ createFilter('root', false, createFilter => [ @@ -512,7 +448,7 @@ describe('testing `HierarchicalFilter` component', () => { }); it('allows adding classes to the filter item', () => { - const { hierarchicalFilterWrapper } = renderHierarchicalFilter({ + const { hierarchicalFilterWrapper } = render({ filterItemClass: 'custom-class', facet: createHierarchicalFacetStub('category', createFilter => [ createFilter('root', false, createFilter => [ @@ -531,71 +467,3 @@ describe('testing `HierarchicalFilter` component', () => { }); }); }); - -interface HierarchicalFilterOptions { - clickEvents?: Partial; - facet?: HierarchicalFacet; - childrenFiltersClass?: string; - filterItemClass?: string; - template?: string; -} - -interface HierarchicalFilterAPI { - /** - * Clicks the root filter. - * - * @returns A promise that resolves after Vue updates the view. - */ - clickFilter: () => Promise; - /** Mock for the `$x.emit` function. Can be used to check the emitted events. */ - emit: jest.SpyInstance; - /** - * Get the filter wrapper. This is the clickable element that represents the filter. - * - * @returns The filter Wrapper. - */ - getFilterWrapper: () => Wrapper; - /** - * Gets a filter wrapper by the rendered button text. - * - * @param text - The rendered filter text. - * @returns The filter Wrapper. - */ - getFilterWrapperByText: (text: string) => Wrapper | undefined; - /** - * Get all the filters including children. - * - * @returns The filters WrapperArray. - */ - getFiltersWrappers: () => WrapperArray; - /** The hierarchical filter wrapper.*/ - hierarchicalFilterWrapper: Wrapper; - /** - * Returns the root filter. - * - * @returns The root filter. - */ - getRootFilter: () => HierarchicalFilterModel; - /** - * Saves a new filter in the store. - * - * @param filter - The filter to save in the store. - * @returns A promise that resolves after re-rendering the component. - */ - mutateFilter: ( - filter: HierarchicalFilterModel, - newFilterState: Partial - ) => Promise; - /** - * Returns all the filters of the hierarchical facet. - * - * @returns All the filters of the hierarchical facet. - */ - getFilters: () => HierarchicalFilterModel[]; - /** - * Returns all the partially selected filters of the hierarchical facet. - * - * @returns All the partially selected filters of the hierarchical facet. - */ - getPartiallySelectedFilters: () => HierarchicalFilterModel[]; -} diff --git a/packages/x-components/src/x-modules/facets/components/filters/__tests__/number-range-filter.spec.ts b/packages/x-components/src/x-modules/facets/components/filters/__tests__/number-range-filter.spec.ts index 0b78d82a77..afef730476 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/__tests__/number-range-filter.spec.ts +++ b/packages/x-components/src/x-modules/facets/components/filters/__tests__/number-range-filter.spec.ts @@ -1,19 +1,24 @@ -import { NumberRangeFilter as NumberRangeFilterModel } from '@empathyco/x-types'; -import { mount, Wrapper } from '@vue/test-utils'; -import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import { nextTick, ref } from 'vue'; import { createNumberRangeFilter } from '../../../../../__stubs__/filters-stubs.factory'; -import { getDataTestSelector } from '../../../../../__tests__/utils'; +import { getDataTestSelector, installNewXPlugin } from '../../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../../components'; -import { XEventsTypes } from '../../../../../wiring/index'; import NumberRangeFilter from '../number-range-filter.vue'; +import { XPlugin } from '../../../../../plugins/index'; -function renderNumberRangeFilter({ +const metadata = { + moduleName: 'facets', + location: 'none', + replaceable: true +}; + +function render({ template = '', - filter = createNumberRangeFilter('price', { min: 0, max: 20 }), - clickEvents -}: NumberRangeFilterWrapperData = {}): NumberRangeFilterAPI { - Vue.observable(filter); - const emit = jest.fn(); + filter = ref(createNumberRangeFilter('price', { min: 0, max: 20 })), + clickEvents = {} +} = {}) { + installNewXPlugin(); + const wrapper = mount( { components: { NumberRangeFilter }, @@ -24,11 +29,6 @@ function renderNumberRangeFilter({ propsData: { filter, clickEvents - }, - mocks: { - $x: { - emit - } } } ); @@ -36,81 +36,70 @@ function renderNumberRangeFilter({ const filterWrapper = wrapper.findComponent(NumberRangeFilter); return { - wrapper, - filterWrapper, - emit, + wrapper: filterWrapper, + emitSpy: jest.spyOn(XPlugin.bus, 'emit'), filter, - clickFilter() { - wrapper.trigger('click'); - }, - selectFilter() { - filter.selected = true; - return Vue.nextTick(); + clickFilter: () => wrapper.trigger('click'), + selectFilter: () => { + filter.value.selected = true; + return nextTick(); } }; } describe('testing NumberRangeFilter component', () => { - it('is an x-component', () => { - const { filterWrapper } = renderNumberRangeFilter(); - - expect(isXComponent(filterWrapper.vm)).toEqual(true); - }); - - it('belongs to the `facets` x-module', () => { - const { filterWrapper } = renderNumberRangeFilter(); + it('is an XComponent that belongs to the facets', () => { + const { wrapper } = render(); - expect(getXComponentXModuleName(filterWrapper.vm)).toEqual('facets'); + expect(isXComponent(wrapper.vm)).toBeTruthy(); + expect(getXComponentXModuleName(wrapper.vm)).toEqual('facets'); }); it('renders the provided filter by default', () => { - const { wrapper, filter } = renderNumberRangeFilter(); + const { wrapper, filter } = render(); - expect(wrapper.text()).toEqual(filter.label); + expect(wrapper.text()).toEqual(filter.value.label); }); - it('emits `UserClickedAFilter` & `UserClickedANumberRangeFilter` events when clicked', () => { - const { wrapper, clickFilter, emit, filter } = renderNumberRangeFilter(); + it('emits `UserClickedAFilter` & `UserClickedANumberRangeFilter` events when clicked', async () => { + const { clickFilter, emitSpy, filter } = render(); - clickFilter(); + await clickFilter(); - expect(emit).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledTimes(2); ['UserClickedAFilter', 'UserClickedANumberRangeFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { target: wrapper.element }); + expect(emitSpy).toHaveBeenCalledWith(event, filter.value, metadata); }); }); - it('emits configured events when clicked', () => { - const { wrapper, clickFilter, emit, filter } = renderNumberRangeFilter({ + it('emits configured events when clicked', async () => { + const { clickFilter, emitSpy, filter } = render({ clickEvents: { UserAcceptedAQuery: 'potato' } }); - clickFilter(); + await clickFilter(); - expect(emit).toHaveBeenCalledTimes(3); + expect(emitSpy).toHaveBeenCalledTimes(3); ['UserClickedAFilter', 'UserClickedANumberRangeFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { target: wrapper.element }); - }); - expect(emit).toHaveBeenNthCalledWith(3, 'UserAcceptedAQuery', 'potato', { - target: wrapper.element + expect(emitSpy).toHaveBeenCalledWith(event, filter.value, metadata); }); + expect(emitSpy).toHaveBeenNthCalledWith(3, 'UserAcceptedAQuery', 'potato', metadata); }); it('allows customizing the rendered content with an slot', () => { - const { wrapper, filter } = renderNumberRangeFilter({ + const { wrapper, filter } = render({ template: ` - - {{ filter.label }} - - ` + + {{ filter.label }} + ` }); const customLabel = wrapper.find(getDataTestSelector('custom-label')); - expect(customLabel.text()).toEqual(filter.label); + expect(customLabel.text()).toEqual(filter.value.label); }); it('adds selected classes to the rendered element when the filter is selected', async () => { - const { wrapper, selectFilter } = renderNumberRangeFilter(); + const { wrapper, selectFilter } = render(); expect(wrapper.classes()).not.toContain('x-selected'); expect(wrapper.classes()).not.toContain('x-number-range-filter--is-selected'); @@ -121,18 +110,3 @@ describe('testing NumberRangeFilter component', () => { expect(wrapper.classes()).toContain('x-number-range-filter--is-selected'); }); }); - -interface NumberRangeFilterWrapperData { - clickEvents?: Partial; - filter?: NumberRangeFilterModel; - template?: string; -} - -interface NumberRangeFilterAPI { - clickFilter: () => void; - emit: jest.Mock; - filter: NumberRangeFilterModel; - filterWrapper: Wrapper; - selectFilter: () => Promise; - wrapper: Wrapper; -} diff --git a/packages/x-components/src/x-modules/facets/components/filters/__tests__/renderless-filter.spec.ts b/packages/x-components/src/x-modules/facets/components/filters/__tests__/renderless-filter.spec.ts index 8689b6316d..1827d83a42 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/__tests__/renderless-filter.spec.ts +++ b/packages/x-components/src/x-modules/facets/components/filters/__tests__/renderless-filter.spec.ts @@ -1,36 +1,34 @@ -import { BooleanFilter } from '@empathyco/x-types'; -import { mount, Wrapper } from '@vue/test-utils'; -import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import { nextTick, ref } from 'vue'; import { getXComponentXModuleName, isXComponent } from '../../../../../components'; -import { XEventsTypes } from '../../../../../wiring/events.types'; import { createSimpleFilter, getSimpleFilterStub } from '../../../../../__stubs__/filters-stubs.factory'; -import { getDataTestSelector } from '../../../../../__tests__/utils'; +import { getDataTestSelector, installNewXPlugin } from '../../../../../__tests__/utils'; import RenderlessFilter from '../renderless-filter.vue'; +import { XPlugin } from '../../../../../plugins/x-plugin'; -function renderComponent({ - filter = createSimpleFilter('category', 'food'), - clickEvents, +function render({ + filter = ref(createSimpleFilter('category', 'food')), + clickEvents = {}, template = ` - - - - ` -}: RenderOptions = {}): RenderAPI { - Vue.observable(filter); - const emit = jest.fn(); + + + ` +} = {}) { + installNewXPlugin(); + const wrapper = mount( { components: { RenderlessFilter }, @@ -38,15 +36,7 @@ function renderComponent({ template }, { - propsData: { - filter, - clickEvents - }, - mocks: { - $x: { - emit - } - } + propsData: { filter, clickEvents } } ); @@ -54,60 +44,52 @@ function renderComponent({ return { wrapper: renderlessFilterWrapper, - emit, + emitSpy: jest.spyOn(XPlugin.bus, 'emit'), filter, - clickFilter() { - renderlessFilterWrapper.trigger('click'); - }, - async selectFilter() { - filter.selected = true; - await Vue.nextTick(); + clickFilter: () => renderlessFilterWrapper.trigger('click'), + selectFilter: async () => { + filter.value.selected = true; + await nextTick(); } }; } describe('testing Renderless Filter component', () => { - it('is an x-component', () => { - const { wrapper } = renderComponent(); - - expect(isXComponent(wrapper.vm)).toEqual(true); - }); - - it('belongs to the `facets` x-module', () => { - const { wrapper } = renderComponent(); + it('is an XComponent that belongs to the facets', () => { + const { wrapper } = render(); + expect(isXComponent(wrapper.vm)).toBeTruthy(); expect(getXComponentXModuleName(wrapper.vm)).toEqual('facets'); }); - it('emits UserClickedAFilter and other custom events when clicked', () => { - const filter = getSimpleFilterStub(); - const { wrapper, clickFilter, emit } = renderComponent({ + it('emits UserClickedAFilter and other custom events when clicked', async () => { + const filter = ref(getSimpleFilterStub()); + const { clickFilter, emitSpy } = render({ filter, - clickEvents: { - UserClickedASimpleFilter: filter - } + clickEvents: { UserClickedASimpleFilter: filter.value } }); + const metadata = { + moduleName: 'facets', + location: 'none', + replaceable: true + }; - clickFilter(); + await clickFilter(); - expect(emit).toHaveBeenCalledTimes(2); - expect(emit).toHaveBeenCalledWith('UserClickedAFilter', filter, { - target: wrapper.element - }); - expect(emit).toHaveBeenCalledWith('UserClickedASimpleFilter', filter, { - target: wrapper.element - }); + expect(emitSpy).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledWith('UserClickedAFilter', filter.value, metadata); + expect(emitSpy).toHaveBeenCalledWith('UserClickedASimpleFilter', filter.value, metadata); }); it('allows customizing the rendered content with an slot', () => { - const { wrapper, filter } = renderComponent(); + const { wrapper, filter } = render(); const customLabel = wrapper.find(getDataTestSelector('custom-label')); - expect(customLabel.text()).toEqual(filter.label); + expect(customLabel.text()).toEqual(filter.value.label); }); it('adds selected classes to the rendered element when the filter is selected', async () => { - const { wrapper, selectFilter } = renderComponent(); + const { wrapper, selectFilter } = render(); expect(wrapper.classes()).not.toContain('x-selected'); @@ -117,39 +99,14 @@ describe('testing Renderless Filter component', () => { }); it('disables the filter when it has no results', async () => { - const filter = createSimpleFilter('category', 'men', false); - const { wrapper } = renderComponent({ filter }); + const filter = ref(createSimpleFilter('category', 'men', false)); + const { wrapper } = render({ filter }); expect(wrapper.attributes('disabled')).toBeUndefined(); - filter.totalResults = 0; - await wrapper.vm.$nextTick(); + filter.value.totalResults = 0; + await nextTick(); - expect(wrapper.attributes('disabled')).toBe('disabled'); + expect(wrapper.attributes('disabled')).toEqual('disabled'); }); }); - -interface RenderOptions { - /** The template containing the {@link RenderlessFilter} component to render. */ - template?: string; - /** The filter data. Passed as prop to the {@link RenderlessFilter} component. */ - filter?: BooleanFilter; - /** - * Additional events to emit when the filter is clicked. - * Passed as prop to the {@link RenderlessFilter} component. - */ - clickEvents?: Partial; -} - -interface RenderAPI { - /** Wrapper of the {@link RenderlessFilter} component. */ - wrapper: Wrapper; - /** Mock of the {@link XBus.emit} function. */ - emit: jest.Mock; - /** The rendered filter data. */ - filter: BooleanFilter; - /** Fakes a click on the filter component. */ - clickFilter: () => void; - /** Sets the {@link RenderAPI.filter} `selected` property to `true`. */ - selectFilter: () => Promise; -} diff --git a/packages/x-components/src/x-modules/facets/components/filters/__tests__/simple-filter.spec.ts b/packages/x-components/src/x-modules/facets/components/filters/__tests__/simple-filter.spec.ts index 8c1ff466d2..c220c6b397 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/__tests__/simple-filter.spec.ts +++ b/packages/x-components/src/x-modules/facets/components/filters/__tests__/simple-filter.spec.ts @@ -1,19 +1,24 @@ -import { SimpleFilter as SimpleFilterModel } from '@empathyco/x-types'; -import { mount, Wrapper } from '@vue/test-utils'; -import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import { nextTick, ref } from 'vue'; import { createSimpleFilter } from '../../../../../__stubs__/filters-stubs.factory'; -import { getDataTestSelector } from '../../../../../__tests__/utils'; +import { getDataTestSelector, installNewXPlugin } from '../../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../../components'; -import { XEventsTypes } from '../../../../../wiring/events.types'; import SimpleFilter from '../simple-filter.vue'; +import { XPlugin } from '../../../../../plugins/index'; -function renderSimpleFilter({ +const metadata = { + moduleName: 'facets', + location: 'none', + replaceable: true +}; + +function render({ template = '', - filter = createSimpleFilter('category', 'women'), - clickEvents -}: RenderSimpleFilterOptions = {}): RenderSimpleFilterAPI { - Vue.observable(filter); - const emit = jest.fn(); + filter = ref(createSimpleFilter('category', 'women')), + clickEvents = {} +} = {}) { + installNewXPlugin(); + const wrapper = mount( { components: { SimpleFilter }, @@ -21,15 +26,7 @@ function renderSimpleFilter({ template }, { - propsData: { - filter, - clickEvents - }, - mocks: { - $x: { - emit - } - } + propsData: { filter, clickEvents } } ); @@ -37,85 +34,71 @@ function renderSimpleFilter({ return { wrapper: filterWrapper, - emit, + emitSpy: jest.spyOn(XPlugin.bus, 'emit'), filter, - clickFilter() { - wrapper.trigger('click'); - }, - selectFilter() { - filter.selected = true; - return Vue.nextTick(); - }, - updateFilter(newFields) { - Object.assign(filter, newFields); - return Vue.nextTick(); + clickFilter: () => wrapper.trigger('click'), + selectFilter: () => { + filter.value.selected = true; + return nextTick(); } }; } describe('testing SimpleFilter component', () => { - it('is an x-component', () => { - const { wrapper } = renderSimpleFilter(); - - expect(isXComponent(wrapper.vm)).toEqual(true); - }); - - it('belongs to the `facets` x-module', () => { - const { wrapper } = renderSimpleFilter(); + it('is an XComponent that belongs to the facets', () => { + const { wrapper } = render(); + expect(isXComponent(wrapper.vm)).toBeTruthy(); expect(getXComponentXModuleName(wrapper.vm)).toEqual('facets'); }); it('renders the provided filter by default', () => { - const { wrapper, filter } = renderSimpleFilter(); + const { wrapper, filter } = render(); - expect(wrapper.text()).toEqual(filter.label); + expect(wrapper.text()).toEqual(filter.value.label); }); - it('emits `UserClickedAFilter` & `UserClickedASimpleFilter` events when clicked', () => { - const { wrapper, clickFilter, emit, filter } = renderSimpleFilter(); + it('emits `UserClickedAFilter` & `UserClickedASimpleFilter` events when clicked', async () => { + const { clickFilter, emitSpy, filter } = render(); - clickFilter(); + await clickFilter(); - expect(emit).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledTimes(2); ['UserClickedAFilter', 'UserClickedASimpleFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { target: wrapper.element }); + expect(emitSpy).toHaveBeenCalledWith(event, filter.value, metadata); }); }); - it('emits configured events when clicked', () => { - const { wrapper, clickFilter, emit, filter } = renderSimpleFilter({ + it('emits configured events when clicked', async () => { + const { clickFilter, emitSpy, filter } = render({ clickEvents: { UserAcceptedAQuery: 'potato' } }); - clickFilter(); + await clickFilter(); - expect(emit).toHaveBeenCalledTimes(3); + expect(emitSpy).toHaveBeenCalledTimes(3); ['UserClickedAFilter', 'UserClickedASimpleFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { target: wrapper.element }); - }); - expect(emit).toHaveBeenNthCalledWith(3, 'UserAcceptedAQuery', 'potato', { - target: wrapper.element + expect(emitSpy).toHaveBeenCalledWith(event, filter.value, metadata); }); + expect(emitSpy).toHaveBeenNthCalledWith(3, 'UserAcceptedAQuery', 'potato', metadata); }); it('allows customizing the default button content', () => { - const { wrapper, filter } = renderSimpleFilter({ + const { wrapper, filter } = render({ template: ` - - ` + ` }); const customLabel = wrapper.find(getDataTestSelector('custom-label')); - expect(customLabel.text()).toEqual(filter.label); + expect(customLabel.text()).toEqual(filter.value.label); }); it('allows replacing the root element of the component', () => { - const { wrapper, emit, filter } = renderSimpleFilter({ + const { wrapper, emitSpy, filter } = render({ template: ` - - ` + ` }); const labelWrapper = wrapper.get(getDataTestSelector('label')); const inputWrapper = wrapper.get(getDataTestSelector('input')); - expect(labelWrapper.text()).toBe(filter.label); + expect(labelWrapper.text()).toEqual(filter.value.label); inputWrapper.trigger('change'); - expect(emit).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledTimes(2); ['UserClickedAFilter', 'UserClickedASimpleFilter'].forEach(event => { - expect(emit).toHaveBeenCalledWith(event, filter, { target: wrapper.element }); + expect(emitSpy).toHaveBeenCalledWith(event, filter.value, metadata); }); }); it('exposes proper css classes and attributes in the default slot', async () => { - const { wrapper, selectFilter, updateFilter } = renderSimpleFilter({ + const { wrapper, selectFilter, filter } = render({ template: ` { :aria-checked="filter.selected.toString()"> {{ filter.label }} - - ` + ` }); const buttonWrapper = wrapper.get(getDataTestSelector('button')); @@ -166,7 +147,7 @@ describe('testing SimpleFilter component', () => { expect(buttonWrapper.attributes()).toHaveProperty('aria-checked', 'false'); expect(buttonWrapper.element).toHaveProperty('disabled', false); - await selectFilter(); // Faking filter selection because XBus is mocked. + await selectFilter(); expect(buttonWrapper.attributes('aria-checked')).toBe('true'); expect(buttonWrapper.classes()).toHaveLength(4); expect(buttonWrapper.classes()).toEqual( @@ -178,15 +159,17 @@ describe('testing SimpleFilter component', () => { ]) ); - await updateFilter({ totalResults: 0 }); + filter.value.totalResults = 0; + await nextTick(); expect(buttonWrapper.element).toHaveProperty('disabled', true); - await updateFilter({ totalResults: undefined }); + filter.value.totalResults = undefined; + await nextTick(); expect(buttonWrapper.element).toHaveProperty('disabled', false); }); it('adds selected classes to the rendered element when the filter is selected', async () => { - const { wrapper, selectFilter } = renderSimpleFilter(); + const { wrapper, selectFilter } = render(); expect(wrapper.classes()).not.toContain('x-selected'); expect(wrapper.classes()).not.toContain('x-simple-filter--is-selected'); @@ -197,36 +180,3 @@ describe('testing SimpleFilter component', () => { expect(wrapper.classes()).toContain('x-simple-filter--is-selected'); }); }); - -interface RenderSimpleFilterOptions { - /** The events to emit when the filter is clicked. */ - clickEvents?: Partial; - /** The filter data to render. */ - filter?: SimpleFilterModel; - /** Template including the {@link SimpleFilter} to render. */ - template?: string; -} - -interface RenderSimpleFilterAPI { - /** Fakes a click in the {@link SimpleFilter} component. */ - clickFilter: () => void; - /** Mock for the {@link XBus.emit} function. */ - emit: jest.Mock; - /** The data rendered. */ - filter: SimpleFilterModel; - /** - * Selects the filter. - * - * @returns A promise that resolves after Vue updates the view. - */ - selectFilter: () => Promise; - /** - * Updates the rendered filter data. - * - * @param newFilter - The new fields to set to the rendered filter. - * @returns A promise that resolves after Vue updates the view. - */ - updateFilter: (newFilter: Partial) => Promise; - /** Test wrapper of the {@link SimpleFilter} component. */ - wrapper: Wrapper; -} diff --git a/packages/x-components/src/x-modules/facets/components/filters/renderless-filter.vue b/packages/x-components/src/x-modules/facets/components/filters/renderless-filter.vue index f3096c30d0..7ca88d17bd 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/renderless-filter.vue +++ b/packages/x-components/src/x-modules/facets/components/filters/renderless-filter.vue @@ -1,91 +1,74 @@ diff --git a/packages/x-components/src/x-modules/facets/components/filters/simple-filter.vue b/packages/x-components/src/x-modules/facets/components/filters/simple-filter.vue index 83646d0b6c..6eab6bbbb9 100644 --- a/packages/x-components/src/x-modules/facets/components/filters/simple-filter.vue +++ b/packages/x-components/src/x-modules/facets/components/filters/simple-filter.vue @@ -1,26 +1,25 @@