diff --git a/packages/x-components/src/components/__tests__/page-loader-button.spec.ts b/packages/x-components/src/components/__tests__/page-loader-button.spec.ts index ff11332b37..5ee2816a60 100644 --- a/packages/x-components/src/components/__tests__/page-loader-button.spec.ts +++ b/packages/x-components/src/components/__tests__/page-loader-button.spec.ts @@ -1,9 +1,10 @@ import { mount, Wrapper } from '@vue/test-utils'; -import Vue, { ComponentOptions, nextTick } from 'vue'; +import Vue, { ComponentOptions } from 'vue'; import { Result } from '@empathyco/x-types'; -import { getDataTestSelector } from '../../__tests__/utils'; +import { getDataTestSelector, installNewXPlugin } from '../../__tests__/utils'; import PageLoaderButton from '../page-loader-button.vue'; import { getResultsStub } from '../../__stubs__/index'; +import { bus } from '../../plugins/index'; function renderPageLoaderButton({ query = 'dress', @@ -11,31 +12,35 @@ function renderPageLoaderButton({ totalResults = 100, scopedSlots }: RenderPageLoaderButtonOptions = {}): RenderPageLoaderButtonAPI { - const emit = jest.fn(); + const [, localVue] = installNewXPlugin(); const wrapper = mount(PageLoaderButton as ComponentOptions, { propsData: { buttonClasses: '', buttonEvents: {} }, - mocks: { - $x: { - emit, + localVue, + scopedSlots, + data() { + return { query, results, totalResults - } - }, - scopedSlots + }; + } }); return { - emit, - wrapper + wrapper, + emitSpy: jest.spyOn(bus, 'emit') }; } describe('testing PageLoaderButton component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + beforeEach(() => { jest.clearAllMocks(); }); @@ -73,35 +78,47 @@ describe('testing PageLoaderButton component', () => { expect(wrapper.find('.x-rounded-full').exists()).toBe(true); }); - it('emits the event UserReachedResultsListEnd when the button is clicked', async () => { - const { wrapper, emit } = renderPageLoaderButton(); + // TODO: Enable test when BaseEventButton component is migrated + // eslint-disable-next-line jest/no-disabled-tests + it.skip('emits the event UserReachedResultsListEnd when the button is clicked', () => { + const { wrapper, emitSpy } = renderPageLoaderButton(); const baseEventButton = wrapper.find(getDataTestSelector('load-content')); baseEventButton.trigger('click'); - await nextTick(); - expect(emit).toHaveBeenCalledTimes(1); - expect(emit).toHaveBeenCalledWith('UserReachedResultsListEnd', undefined, { - target: baseEventButton.element + expect(emitSpy).toHaveBeenCalledTimes(1); + expect(emitSpy).toHaveBeenCalledWith('UserReachedResultsListEnd', undefined, { + target: baseEventButton.element, + location: 'none', + moduleName: null, + replaceable: true }); }); - it('emits an event passed via prop', async () => { - const { wrapper, emit } = renderPageLoaderButton(); + // TODO: Enable test when BaseEventButton component is migrated + // eslint-disable-next-line jest/no-disabled-tests + it.skip('emits an event passed via prop', async () => { + const { wrapper, emitSpy } = renderPageLoaderButton(); const baseEventButton = wrapper.find(getDataTestSelector('load-content')); wrapper.setProps({ buttonEvents: { UserClickedCloseX: undefined } }); await wrapper.vm.$nextTick(); baseEventButton.trigger('click'); - await nextTick(); - - expect(emit).toHaveBeenCalledTimes(2); - expect(emit).toHaveBeenCalledWith('UserReachedResultsListEnd', undefined, { - target: baseEventButton.element + jest.runAllTimers(); + + expect(emitSpy).toHaveBeenCalledTimes(2); + expect(emitSpy).toHaveBeenCalledWith('UserReachedResultsListEnd', undefined, { + target: baseEventButton.element, + location: 'none', + moduleName: null, + replaceable: true }); - expect(emit).toHaveBeenCalledWith('UserClickedCloseX', undefined, { - target: baseEventButton.element + expect(emitSpy).toHaveBeenCalledWith('UserClickedCloseX', undefined, { + target: baseEventButton.element, + location: 'none', + moduleName: null, + replaceable: true }); }); }); @@ -124,8 +141,8 @@ interface RenderPageLoaderButtonOptions { * Options to configure how the page loader button component should be rendered. */ interface RenderPageLoaderButtonAPI { - /** Mock of the {@link XBus.emit} function. */ - emit: jest.Mock; /** The wrapper for the page loader button component. */ wrapper: Wrapper; + /* A jest spy of the X emit method. */ + emitSpy: ReturnType; } diff --git a/packages/x-components/src/components/modals/__tests__/base-id-modal-close.spec.ts b/packages/x-components/src/components/modals/__tests__/base-id-modal-close.spec.ts index fa5f94a2ae..bf6435c758 100644 --- a/packages/x-components/src/components/modals/__tests__/base-id-modal-close.spec.ts +++ b/packages/x-components/src/components/modals/__tests__/base-id-modal-close.spec.ts @@ -2,6 +2,8 @@ import { mount, Wrapper } from '@vue/test-utils'; import Vue from 'vue'; import { getDataTestSelector, installNewXPlugin } from '../../../__tests__/utils'; import BaseIdModalClose from '../base-id-modal-close.vue'; +import { bus } from '../../../plugins/index'; +import { dummyCreateEmitter } from '../../../__tests__/bus.dummy'; /** * Renders the {@link BaseIdModalClose} with the provided options. @@ -13,6 +15,9 @@ function renderBaseIdModalClose({ id = 'random', template = `` }: RenderBaseIdModalCloseOptions = {}): RenderBaseIdModalCloseAPI { + // Making bus not repeat subjects + jest.spyOn(bus, 'createEmitter' as any).mockImplementation(dummyCreateEmitter.bind(bus) as any); + const [, localVue] = installNewXPlugin(); const containerWrapper = mount( { @@ -32,15 +37,20 @@ function renderBaseIdModalClose({ modalId, async click() { await wrapper.trigger('click'); + jest.runAllTimers(); } }; } describe('testing Close Button component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + it("emits UserClickedCloseModal with the component's id as payload", async () => { - const { wrapper, modalId, click } = renderBaseIdModalClose(); + const { modalId, click } = renderBaseIdModalClose(); const listener = jest.fn(); - wrapper.vm.$x.on('UserClickedCloseModal').subscribe(listener); + bus.on('UserClickedCloseModal').subscribe(listener); await click(); @@ -69,20 +79,22 @@ describe('testing Close Button component', () => { }); const listener = jest.fn(); - wrapper.vm.$x.on('UserClickedCloseModal').subscribe(listener); + bus.on('UserClickedCloseModal').subscribe(listener); await click(); expect(listener).toHaveBeenCalledTimes(0); wrapper.find(getDataTestSelector('custom-close-modal')).trigger('click'); + jest.runAllTimers(); + expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(modalId); }); }); interface RenderBaseIdModalCloseOptions { - /** Id of the modal to close. */ + /** The id of the modal to close. */ id?: string; /** The template to render. */ template?: string; diff --git a/packages/x-components/src/components/modals/__tests__/base-id-modal-open.spec.ts b/packages/x-components/src/components/modals/__tests__/base-id-modal-open.spec.ts index 248a3a4c26..e7532f2bef 100644 --- a/packages/x-components/src/components/modals/__tests__/base-id-modal-open.spec.ts +++ b/packages/x-components/src/components/modals/__tests__/base-id-modal-open.spec.ts @@ -2,6 +2,8 @@ import { mount, Wrapper } from '@vue/test-utils'; import Vue from 'vue'; import { getDataTestSelector, installNewXPlugin } from '../../../__tests__/utils'; import BaseIdModalOpen from '../base-id-modal-open.vue'; +import { bus } from '../../../plugins/x-bus'; +import { dummyCreateEmitter } from '../../../__tests__/bus.dummy'; /** * Renders the {@link BaseIdModalOpen} with the provided options. @@ -13,6 +15,9 @@ function renderBaseIdModalOpen({ id = 'myModal', template = `` }: RenderBaseIdModalOpenOptions = {}): RenderBaseIdModalOpenAPI { + // Making bus not repeat subjects + jest.spyOn(bus, 'createEmitter' as any).mockImplementation(dummyCreateEmitter.bind(bus) as any); + const [, localVue] = installNewXPlugin(); const containerWrapper = mount( { @@ -21,7 +26,10 @@ function renderBaseIdModalOpen({ }, template }, - { propsData: { modalId: id }, localVue } + { + propsData: { modalId: id }, + localVue + } ); const wrapper = containerWrapper.findComponent(BaseIdModalOpen); @@ -32,15 +40,20 @@ function renderBaseIdModalOpen({ modalId, async click() { await wrapper.trigger('click'); + jest.runAllTimers(); } }; } describe('testing Open Button component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + it("emits UserClickedOpenModal with the component's id as payload", async () => { - const { wrapper, modalId, click } = renderBaseIdModalOpen(); + const { modalId, click } = renderBaseIdModalOpen(); const listener = jest.fn(); - wrapper.vm.$x.on('UserClickedOpenModal').subscribe(listener); + bus.on('UserClickedOpenModal').subscribe(listener); await click(); @@ -69,20 +82,22 @@ describe('testing Open Button component', () => { }); const listener = jest.fn(); - wrapper.vm.$x.on('UserClickedOpenModal').subscribe(listener); + bus.on('UserClickedOpenModal').subscribe(listener); await click(); expect(listener).toHaveBeenCalledTimes(0); wrapper.find(getDataTestSelector('custom-open-modal')).trigger('click'); + jest.runAllTimers(); + expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(modalId); }); }); interface RenderBaseIdModalOpenOptions { - /** Id of the modal to open. */ + /** The id of the modal to open. */ id?: string; /** The template to render. */ template?: string; diff --git a/packages/x-components/src/components/result/__tests__/base-result-link.spec.ts b/packages/x-components/src/components/result/__tests__/base-result-link.spec.ts index e148a8d6da..c1b93950c0 100644 --- a/packages/x-components/src/components/result/__tests__/base-result-link.spec.ts +++ b/packages/x-components/src/components/result/__tests__/base-result-link.spec.ts @@ -8,6 +8,8 @@ import { XEvent, XEventsTypes } from '../../../wiring/events.types'; import BaseResultLink from '../base-result-link.vue'; import { WireMetadata } from '../../../wiring/index'; import { PropsWithType } from '../../../utils/index'; +import { bus } from '../../../plugins/index'; +import { dummyCreateEmitter } from '../../../__tests__/bus.dummy'; describe('testing BaseResultLink component', () => { const result = createResultStub('Product 001', { @@ -16,6 +18,12 @@ describe('testing BaseResultLink component', () => { let localVue: typeof Vue; let resultLinkWrapper: Wrapper; const template = ''; + // Making bus not repeat subjects + jest.spyOn(bus, 'createEmitter' as any).mockImplementation(dummyCreateEmitter.bind(bus) as any); + + beforeAll(() => { + jest.useFakeTimers(); + }); beforeEach(() => { [, localVue] = installNewXPlugin(); @@ -33,24 +41,26 @@ describe('testing BaseResultLink component', () => { }); // eslint-disable-next-line max-len - it('emits UserClickedAResult when the user clicks in the left, middle or right button on the component', () => { + it('emits UserClickedAResult when the user clicks in the left, middle or right button on the component', async () => { const listener = jest.fn(); - resultLinkWrapper.vm.$x.on('UserClickedAResult').subscribe(listener); + bus.on('UserClickedAResult').subscribe(listener); - resultLinkWrapper.trigger('click'); + await resultLinkWrapper.trigger('click'); + jest.runAllTimers(); expect(listener).toHaveBeenNthCalledWith(1, result); - resultLinkWrapper.trigger('click', { button: 1 }); + await resultLinkWrapper.trigger('click', { button: 1 }); + jest.runAllTimers(); expect(listener).toHaveBeenNthCalledWith(2, result); - resultLinkWrapper.trigger('click', { button: 2 }); + await resultLinkWrapper.trigger('click', { button: 2 }); + jest.runAllTimers(); expect(listener).toHaveBeenNthCalledWith(3, result); expect(listener).toHaveBeenCalledTimes(3); }); - it('emits events provided from parent element with provided location in metadata', () => { - const listener = jest.fn(); + it('emits events provided from parent element with provided location in metadata', async () => { const resultLinkWrapper = mount( { components: { BaseResultLink }, @@ -66,8 +76,13 @@ describe('testing BaseResultLink component', () => { propsData: { result } } ); - resultLinkWrapper.vm.$x.on('UserClickedResultAddToCart', true).subscribe(listener); - resultLinkWrapper.trigger('click'); + + const listener = jest.fn(); + bus.on('UserClickedResultAddToCart', true).subscribe(listener); + + await resultLinkWrapper.trigger('click'); + jest.runAllTimers(); + expect(listener).toHaveBeenCalledWith({ eventPayload: result, metadata: expect.objectContaining({ @@ -76,7 +91,7 @@ describe('testing BaseResultLink component', () => { }); }); - it('emits events with the extra metadata provided from parent element', () => { + it('emits events with the extra metadata provided from parent element', async () => { const injectedResultLinkMetadataPerEvent: Partial< Record< PropsWithType, @@ -90,8 +105,6 @@ describe('testing BaseResultLink component', () => { replaceable: false } }; - const resultClickListener = jest.fn(); - const addToCartClickListener = jest.fn(); const resultLinkWrapper = mount( { @@ -108,11 +121,15 @@ describe('testing BaseResultLink component', () => { propsData: { result } } ); - resultLinkWrapper.vm.$x.on('UserClickedAResult', true).subscribe(resultClickListener); - resultLinkWrapper.vm.$x - .on('UserClickedResultAddToCart', true) - .subscribe(addToCartClickListener); - resultLinkWrapper.trigger('click'); + + const resultClickListener = jest.fn(); + bus.on('UserClickedAResult', true).subscribe(resultClickListener); + + const addToCartClickListener = jest.fn(); + bus.on('UserClickedResultAddToCart', true).subscribe(addToCartClickListener); + + await resultLinkWrapper.trigger('click'); + jest.runAllTimers(); expect(resultClickListener).toHaveBeenCalledTimes(1); expect(resultClickListener).toHaveBeenCalledWith({ diff --git a/packages/x-components/src/components/result/__tests__/base-result-rating.spec.ts b/packages/x-components/src/components/result/__tests__/base-result-rating.spec.ts index 03d24d4f06..5a762f02b3 100644 --- a/packages/x-components/src/components/result/__tests__/base-result-rating.spec.ts +++ b/packages/x-components/src/components/result/__tests__/base-result-rating.spec.ts @@ -2,8 +2,8 @@ import { Result } from '@empathyco/x-types'; import { mount, WrapperArray } from '@vue/test-utils'; import { createResultStub } from '../../../__stubs__/results-stubs.factory'; import { getDataTestSelector, installNewXPlugin } from '../../../__tests__/utils'; -import { XComponentBusAPI } from '../../../plugins/x-plugin.types'; import BaseResultRating from '../base-result-rating.vue'; +import { bus } from '../../../plugins/index'; const result = createResultStub('Product Test', { rating: { @@ -37,13 +37,17 @@ function renderBaseResultRating({ getEmptyIcons: (): WrapperArray => wrapper.find(getDataTestSelector('rating-empty')).findAll(':scope > *'), clickRating: async () => { - wrapper.findComponent(BaseResultRating).trigger('click'); - return await wrapper.vm.$nextTick(); - }, - on: wrapper.vm.$x.on + await wrapper.findComponent(BaseResultRating).trigger('click'); + jest.runAllTimers(); + } }; } + describe('testing BaserResultRating component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + it('renders the default icons a number of times based on the max prop', () => { const { getFilledIcons, getEmptyIcons } = renderBaseResultRating({ template: ``, @@ -76,12 +80,12 @@ describe('testing BaserResultRating component', () => { }); it('emits event when clicked with the result as payload', async () => { - const { on, clickRating } = renderBaseResultRating({ + const { clickRating } = renderBaseResultRating({ template: ``, result }); const eventListener = jest.fn(); - on('UserClickedAResultRating').subscribe(eventListener); + bus.on('UserClickedAResultRating').subscribe(eventListener); await clickRating(); expect(eventListener).toHaveBeenCalledWith(result); @@ -89,14 +93,19 @@ describe('testing BaserResultRating component', () => { }); interface RenderBaseResultRatingOptions { + /** The template to render. */ template: string; + /** The result with the rating to be interacted with. */ result: Result; } interface RenderBaseResultRatingApi { + /** Retrieves the wrapper with the main html content. */ getHtml: () => string; + /** Retrieves the wrapper that matches the rating filled icons. */ getFilledIcons: () => WrapperArray; + /** Retrieves the wrapper that matches the rating empty icons. */ getEmptyIcons: () => WrapperArray; + /** Clicks the rating icons and waits for the view to update. */ clickRating: () => Promise; - on: XComponentBusAPI['on']; } diff --git a/packages/x-components/src/components/suggestions/__tests__/base-suggestion.spec.ts b/packages/x-components/src/components/suggestions/__tests__/base-suggestion.spec.ts index a841d37370..c02ff1cf9f 100644 --- a/packages/x-components/src/components/suggestions/__tests__/base-suggestion.spec.ts +++ b/packages/x-components/src/components/suggestions/__tests__/base-suggestion.spec.ts @@ -2,7 +2,6 @@ import { Suggestion } from '@empathyco/x-types'; import { mount, Wrapper } from '@vue/test-utils'; import Vue from 'vue'; import { createQuerySuggestion } from '../../../__stubs__/index'; -import { XPlugin } from '../../../plugins/x-plugin'; import { normalizeString } from '../../../utils/normalize'; import { XEventsTypes } from '../../../wiring/events.types'; import { WireMetadata } from '../../../wiring/wiring.types'; @@ -10,6 +9,7 @@ import { getDataTestSelector, installNewXPlugin } from '../../../__tests__/utils import BaseSuggestion from '../base-suggestion.vue'; import { createSimpleFacetStub } from '../../../__stubs__/facets-stubs.factory'; import { createPopularSearch } from '../../../__stubs__/popular-searches-stubs.factory'; +import { bus } from '../../../plugins/index'; function renderBaseSuggestion({ query = 'bebe', @@ -17,7 +17,7 @@ function renderBaseSuggestion({ suggestionSelectedEvents = {} }: BaseSuggestionOptions = {}): BaseSuggestionAPI { const [, localVue] = installNewXPlugin(); - const emit = jest.spyOn(XPlugin.bus, 'emit'); + const emit = jest.spyOn(bus, 'emit'); const wrapper = mount( { components: { BaseSuggestion }, @@ -56,6 +56,14 @@ function renderBaseSuggestion({ } describe('testing Base Suggestion component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('renders a basic suggestion', () => { const { wrapper } = renderBaseSuggestion({ suggestion: createPopularSearch('milk') diff --git a/packages/x-components/src/composables/use-$x.ts b/packages/x-components/src/composables/use-$x.ts index 075e4d01fb..ff12925ebb 100644 --- a/packages/x-components/src/composables/use-$x.ts +++ b/packages/x-components/src/composables/use-$x.ts @@ -1,5 +1,5 @@ -import { getCurrentInstance } from 'vue'; -import { XComponentAPI } from '../plugins'; +import { UseAliasAPI, useAliasApi } from './use-alias-api'; +import { useXBus, UseXBusAPI } from './use-x-bus'; /** * Function which returns the `$x` object from the current component instance. @@ -8,6 +8,16 @@ import { XComponentAPI } from '../plugins'; * * @public */ -export function use$x(): XComponentAPI { - return (getCurrentInstance()?.proxy as unknown as { $x: XComponentAPI }).$x; +export function use$x(): UseXComponentAPI { + const xAliasAPI = useAliasApi(); + const xBusAPI = useXBus(); + return Object.assign(xAliasAPI, xBusAPI); } + +/** + * The XComponentAPI exposes access to the {@link @empathyco/x-bus#XBus}, and store aliases to the + * components. + * + * @public + */ +export interface UseXComponentAPI extends UseXBusAPI, UseAliasAPI {} diff --git a/packages/x-components/src/composables/use-alias-api.ts b/packages/x-components/src/composables/use-alias-api.ts index 9c188f1c29..a506efc879 100644 --- a/packages/x-components/src/composables/use-alias-api.ts +++ b/packages/x-components/src/composables/use-alias-api.ts @@ -23,7 +23,7 @@ import { useStore } from './use-store'; * * @internal */ -export function useAliasApi(this: any): UseAliasAPI { +export function useAliasApi(): UseAliasAPI { const queryModules = [ 'facets', 'searchBox', @@ -158,7 +158,7 @@ export interface UseAliasAPI { /** The {@link DeviceXModule} detected device. */ readonly device: string | null; /** The {@link FacetsXModule} facets. */ - readonly facets: ReadonlyArray; + readonly facets: Record; /** The {@link HistoryQueriesXModule} history queries matching the query. */ readonly historyQueries: ReadonlyArray; /** The {@link HistoryQueriesXModule} history queries with 1 or more results. */ diff --git a/packages/x-components/src/composables/use-x-bus.ts b/packages/x-components/src/composables/use-x-bus.ts index 827c8db52d..4f7ac79b01 100644 --- a/packages/x-components/src/composables/use-x-bus.ts +++ b/packages/x-components/src/composables/use-x-bus.ts @@ -79,7 +79,7 @@ interface PrivateExtendedVueComponent extends Vue { xComponent?: Vue | undefined; } -interface UseXBusAPI { +export interface UseXBusAPI { /* eslint-disable jsdoc/require-description-complete-sentence */ /** {@inheritDoc XBus.(on:1)} */ on: XBus['on']; diff --git a/packages/x-components/src/x-modules/history-queries/components/__tests__/history-query.spec.ts b/packages/x-components/src/x-modules/history-queries/components/__tests__/history-query.spec.ts index 7ec6583172..34ee149567 100644 --- a/packages/x-components/src/x-modules/history-queries/components/__tests__/history-query.spec.ts +++ b/packages/x-components/src/x-modules/history-queries/components/__tests__/history-query.spec.ts @@ -6,11 +6,11 @@ import { ComponentOptions } from 'vue'; import { createHistoryQuery } from '../../../../__stubs__/history-queries-stubs.factory'; import { getDataTestSelector, installNewXPlugin } from '../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../components/x-component.utils'; -import { XPlugin } from '../../../../plugins/x-plugin'; import { RootXStoreState } from '../../../../store/store.types'; import { WireMetadata } from '../../../../wiring/wiring.types'; import { historyQueriesXModule } from '../../x-module'; import HistoryQuery from '../history-query.vue'; +import { bus } from '../../../../plugins/index'; import { resetXHistoryQueriesStateWith } from './utils'; function renderHistoryQuery({ @@ -46,7 +46,7 @@ function renderHistoryQuery({ return { wrapper: wrapper.findComponent(HistoryQuery), suggestion, - emitSpy: jest.spyOn(XPlugin.bus, 'emit'), + emitSpy: jest.spyOn(bus, 'emit'), getSuggestionWrapper() { return wrapper.get(getDataTestSelector('history-query')); }, @@ -60,6 +60,14 @@ function renderHistoryQuery({ } describe('testing history-query component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('is an XComponent that belongs to the history queries', () => { const { wrapper } = renderHistoryQuery(); expect(isXComponent(wrapper.vm)).toEqual(true); @@ -115,10 +123,16 @@ describe('testing history-query component', () => { expect(getSuggestionWrapper().text()).toEqual('🔍 baileys'); }); - it('emits `UserPressedRemoveHistoryQuery` when `RemoveHistoryQuery` button is clicked', () => { - const { getRemoveWrapper, emitSpy, suggestion } = renderHistoryQuery(); + // TODO: Enable test when BaseEventButton component is migrated + // eslint-disable-next-line jest/no-disabled-tests,max-len + it.skip('emits `UserPressedRemoveHistoryQuery` when `RemoveHistoryQuery` button is clicked', () => { + const { emitSpy, suggestion, getRemoveWrapper } = renderHistoryQuery({ + suggestion: createHistoryQuery({ query: 'milk' }) + }); getRemoveWrapper().trigger('click'); + + expect(emitSpy).toHaveBeenCalledTimes(1); expect(emitSpy).toHaveBeenCalledWith( 'UserPressedRemoveHistoryQuery', suggestion, diff --git a/packages/x-components/src/x-modules/next-queries/components/__tests__/next-query.spec.ts b/packages/x-components/src/x-modules/next-queries/components/__tests__/next-query.spec.ts index d3d806d614..04bd6beff8 100644 --- a/packages/x-components/src/x-modules/next-queries/components/__tests__/next-query.spec.ts +++ b/packages/x-components/src/x-modules/next-queries/components/__tests__/next-query.spec.ts @@ -5,45 +5,51 @@ import { getDataTestSelector, installNewXPlugin } from '../../../../__tests__/ut import { getXComponentXModuleName, isXComponent } from '../../../../components/x-component.utils'; import { WireMetadata } from '../../../../wiring/wiring.types'; import { default as NextQueryComponent } from '../next-query.vue'; -import { XComponentAPI } from '../../../../plugins/x-plugin.types'; +import { bus } from '../../../../plugins/index'; +import { dummyCreateEmitter } from '../../../../__tests__/bus.dummy'; -describe('testing next query item component', () => { - function renderNextQuery({ - suggestion = createNextQueryStub('milk'), - template = '' - }: RenderNextQueryOptions = {}): RenderNextQueryAPI { - const [, localVue] = installNewXPlugin(); - const wrapperTemplate = mount( - { - props: ['suggestion', 'highlightCurated'], - components: { - NextQuery: NextQueryComponent - }, - template - }, - { - localVue, - propsData: { suggestion } - } - ); - const wrapper = wrapperTemplate.findComponent(NextQueryComponent); - const $x = wrapperTemplate.vm.$x; - - return { - wrapper, - $x, - suggestion, - async clickNextQuery() { - wrapper.trigger('click'); - await localVue.nextTick(); +function renderNextQuery({ + suggestion = createNextQueryStub('milk'), + template = '' +}: RenderNextQueryOptions = {}): RenderNextQueryAPI { + // Making bus not repeat subjects + jest.spyOn(bus, 'createEmitter' as any).mockImplementation(dummyCreateEmitter.bind(bus) as any); + + const [, localVue] = installNewXPlugin(); + const wrapperTemplate = mount( + { + props: ['suggestion', 'highlightCurated'], + components: { + NextQuery: NextQueryComponent }, - hasIsCuratedClass() { - return wrapper - .find(getDataTestSelector('next-query')) - .element.classList.contains('x-next-query--is-curated'); - } - }; - } + template + }, + { + localVue, + propsData: { suggestion } + } + ); + const wrapper = wrapperTemplate.findComponent(NextQueryComponent); + + return { + wrapper, + suggestion, + async clickNextQuery() { + wrapper.trigger('click'); + await localVue.nextTick(); + }, + hasIsCuratedClass() { + return wrapper + .find(getDataTestSelector('next-query')) + .element.classList.contains('x-next-query--is-curated'); + } + }; +} + +describe('testing next query item component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); it('is an XComponent and has an XModule', () => { const { wrapper } = renderNextQuery(); @@ -52,10 +58,12 @@ describe('testing next query item component', () => { }); it('emits UserSelectedANextQuery when a next query is selected', async () => { + const { clickNextQuery, suggestion, wrapper } = renderNextQuery(); const listener = jest.fn(); - const { $x, clickNextQuery, suggestion, wrapper } = renderNextQuery(); - $x.on('UserSelectedANextQuery', true).subscribe(listener); + bus.on('UserSelectedANextQuery', true).subscribe(listener); + await clickNextQuery(); + jest.runAllTimers(); expect(listener).toHaveBeenCalled(); expect(listener).toHaveBeenCalledWith({ @@ -111,8 +119,10 @@ describe('testing next query item component', () => { interface RenderNextQueryOptions { /** The next query data to render. */ suggestion?: NextQuery; - /** The template to render. Receives the `nextQuery` via prop, and has registered the - * {@link NextQueryComponent} as `NextQuery`. */ + /** + * The template to render. Receives the `nextQuery` via prop, and has registered the + * {@link NextQueryComponent} as `NextQuery`. + */ template?: string; } @@ -120,8 +130,6 @@ interface RenderNextQueryAPI { /** The Vue testing utils wrapper for the {@link NextQueryComponent}. */ wrapper: Wrapper; /** The {@link XComponentAPI} used by the rendered {@link NextQueryComponent}. */ - $x: XComponentAPI; - /** The rendered next query data. */ suggestion: NextQuery; /** Clicks the next query and waits for the view to update. */ clickNextQuery: () => Promise; diff --git a/packages/x-components/src/x-modules/popular-searches/components/__tests__/popular-search.spec.ts b/packages/x-components/src/x-modules/popular-searches/components/__tests__/popular-search.spec.ts index 1f8efb77ea..4891fe5484 100644 --- a/packages/x-components/src/x-modules/popular-searches/components/__tests__/popular-search.spec.ts +++ b/packages/x-components/src/x-modules/popular-searches/components/__tests__/popular-search.spec.ts @@ -5,11 +5,11 @@ import Vuex, { Store } from 'vuex'; import { createPopularSearch } from '../../../../__stubs__/popular-searches-stubs.factory'; import { installNewXPlugin } from '../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../components/x-component.utils'; -import { XPlugin } from '../../../../plugins/x-plugin'; import { RootXStoreState } from '../../../../store/store.types'; import { WireMetadata } from '../../../../wiring/wiring.types'; import { popularSearchesXModule } from '../../x-module'; import PopularSearch from '../popular-search.vue'; +import { bus } from '../../../../plugins/index'; function renderPopularSearch({ suggestion = createPopularSearch('baileys'), @@ -38,11 +38,15 @@ function renderPopularSearch({ return { wrapper: wrapper.findComponent(PopularSearch), suggestion, - emitSpy: jest.spyOn(XPlugin.bus, 'emit') + emitSpy: jest.spyOn(bus, 'emit') }; } describe('testing popular-search component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + it('is an XComponent that belongs to the popular searches', () => { const { wrapper } = renderPopularSearch(); expect(isXComponent(wrapper.vm)).toEqual(true); diff --git a/packages/x-components/src/x-modules/query-suggestions/components/__tests__/query-suggestion.spec.ts b/packages/x-components/src/x-modules/query-suggestions/components/__tests__/query-suggestion.spec.ts index 9c72ef59f3..f80b72d387 100644 --- a/packages/x-components/src/x-modules/query-suggestions/components/__tests__/query-suggestion.spec.ts +++ b/packages/x-components/src/x-modules/query-suggestions/components/__tests__/query-suggestion.spec.ts @@ -5,11 +5,11 @@ import Vuex, { Store } from 'vuex'; import { createQuerySuggestion } from '../../../../__stubs__/query-suggestions-stubs.factory'; import { getDataTestSelector, installNewXPlugin } from '../../../../__tests__/utils'; import { getXComponentXModuleName, isXComponent } from '../../../../components/x-component.utils'; -import { XPlugin } from '../../../../plugins/x-plugin'; import { RootXStoreState } from '../../../../store/store.types'; import { WireMetadata } from '../../../../wiring/wiring.types'; import { querySuggestionsXModule } from '../../x-module'; import QuerySuggestion from '../query-suggestion.vue'; +import { bus } from '../../../../plugins/index'; import { resetXQuerySuggestionsStateWith } from './utils'; function renderQuerySuggestion({ @@ -41,7 +41,7 @@ function renderQuerySuggestion({ return { wrapper: wrapper.findComponent(QuerySuggestion), suggestion, - emitSpy: jest.spyOn(XPlugin.bus, 'emit'), + emitSpy: jest.spyOn(bus, 'emit'), getMatchingPart() { return wrapper.get(getDataTestSelector('matching-part')); } @@ -49,6 +49,10 @@ function renderQuerySuggestion({ } describe('testing query-suggestion component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + it('is an XComponent that belongs to the query suggestions', () => { const { wrapper } = renderQuerySuggestion(); expect(isXComponent(wrapper.vm)).toEqual(true); diff --git a/packages/x-components/src/x-modules/semantic-queries/components/__tests__/semantic-query.spec.ts b/packages/x-components/src/x-modules/semantic-queries/components/__tests__/semantic-query.spec.ts index 2b44f219a3..a654e78ae3 100644 --- a/packages/x-components/src/x-modules/semantic-queries/components/__tests__/semantic-query.spec.ts +++ b/packages/x-components/src/x-modules/semantic-queries/components/__tests__/semantic-query.spec.ts @@ -7,12 +7,16 @@ import SemanticQuery from '../semantic-query.vue'; import { getXComponentXModuleName, isXComponent } from '../../../../components/index'; import { createSemanticQuery } from '../../../../__stubs__/index'; import { getDataTestSelector, installNewXPlugin } from '../../../../__tests__/utils'; -import { XPlugin } from '../../../../plugins/index'; +import { bus } from '../../../../plugins/index'; import { RootXStoreState } from '../../../../store/store.types'; import { semanticQueriesXModule } from '../../x-module'; import { resetSemanticQueriesStateWith } from './utils'; describe('semantic queries component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + function renderSemanticQuery({ template = '', suggestion = createSemanticQuery({ query: 'jeans' }), @@ -44,7 +48,7 @@ describe('semantic queries component', () => { return { wrapper: wrapper.findComponent(SemanticQuery), - emitSpy: jest.spyOn(XPlugin.bus, 'emit'), + emitSpy: jest.spyOn(bus, 'emit'), suggestion }; }