Skip to content

Commit

Permalink
feat: add the capability to reload the current search
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-cid committed Jun 11, 2024
1 parent 18dc522 commit 1604e0b
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 28 deletions.
12 changes: 8 additions & 4 deletions packages/x-components/src/x-modules/search/events.types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
Banner,
Facet,
Promoted,
Redirection,
Result,
Sort,
Redirection,
TaggingRequest,
Promoted,
Banner
TaggingRequest
} from '@empathyco/x-types';
import { InternalSearchRequest, InternalSearchResponse } from './types';

Expand All @@ -26,6 +26,10 @@ export interface SearchXEvents {
* Payload: The new page number.
*/
PageChanged: number;
/**
* Reload the current search has been requested.
*/
ReloadSearchRequested: undefined;
/**
* Results have been changed.
* Payload: The new {@link @empathyco/x-types#Result | results}.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { createLocalVue } from '@vue/test-utils';
import Vuex, { Store, StoreOptions } from 'vuex';
import { getBannersStub } from '../../../../__stubs__/banners-stubs.factory';
//eslint-disable-next-line max-len
import { getEmptySearchResponseStub } from '../../../../__stubs__/empty-search-response-stubs.factory';
import { getFacetsStub } from '../../../../__stubs__/facets-stubs.factory';
import { getPartialResultsStub } from '../../../../__stubs__/partials-results-stubs.factory';
import { getPromotedsStub } from '../../../../__stubs__/promoteds-stubs.factory';
import { getRedirectionsStub } from '../../../../__stubs__/redirections-stubs.factory';
import { getResultsStub } from '../../../../__stubs__/results-stubs.factory';
import { getSearchResponseStub } from '../../../../__stubs__/search-response-stubs.factory';
import {
getBannersStub,
getEmptySearchResponseStub,
getFacetsStub,
getPartialResultsStub,
getPromotedsStub,
getRedirectionsStub,
getResultsStub,
getSearchResponseStub
} from '../../../../__stubs__';
import { getMockedAdapter, installNewXPlugin } from '../../../../__tests__/utils';
import { SafeStore } from '../../../../store/__tests__/utils';
import { UrlParams } from '../../../../types/url-params';
import { UrlParams } from '../../../../types';
import { searchXStoreModule } from '../module';
import { SearchActions, SearchGetters, SearchMutations, SearchState } from '../types';
import { resetSearchStateWith } from './utils';
Expand Down Expand Up @@ -682,4 +683,110 @@ describe('testing search module actions', () => {
expect(store.state.origin).toBeNull();
});
});

describe('reloadSearch', () => {
it('should include the origin, start and rows properties in the request', async () => {
resetSearchStateWith(store, { query: 'lego', origin: 'search_box:external' });
const { page, ...restRequest } = store.getters.request!;
await store.dispatch('reloadSearch');

expect(adapter.search).toHaveBeenCalledTimes(1);
expect(adapter.search).toHaveBeenCalledWith({
...restRequest,
origin: 'search_box:external',
start: 0,
rows: 24
});
});

it('should calculate correctly the start and rows properties', async () => {
resetSearchStateWith(store, {
config: { pageSize: 48 },
page: 2,
query: 'lego',
results: getResultsStub(48)
});
const { page, ...restRequest } = store.getters.request!;
await store.dispatch('reloadSearch');

expect(adapter.search).toHaveBeenCalledTimes(1);
expect(adapter.search).toHaveBeenCalledWith({
...restRequest,
start: 0,
rows: 48
});
});

it('should request and store total results in the state', async () => {
resetSearchStateWith(store, {
query: 'lego'
});

adapter.search.mockResolvedValueOnce({
...emptySearchResponseStub,
totalResults: 116
});

const actionPromise = store.dispatch('reloadSearch');
await actionPromise;
expect(store.state.totalResults).toBe(116);
});

it('should clear the total results in the state', async () => {
resetSearchStateWith(store, {
query: ''
});
const actionPromise = store.dispatch('reloadSearch');
await actionPromise;
expect(store.state.totalResults).toBe(0);
});

it('should cancel the previous request if it is not yet resolved', async () => {
resetSearchStateWith(store, { query: 'beer' });
const {
results: initialResults,
facets: initialFacets,
banners: initialBanners,
promoteds: initialPromoteds,
redirections: initialRedirections
} = store.state;
adapter.search.mockResolvedValueOnce({
...emptySearchResponseStub,
results: resultsStub.slice(0, 1),
facets: facetsStub.slice(0, 1)
});

const firstRequest = store.dispatch('reloadSearch');
const secondRequest = store.dispatch('reloadSearch');

await firstRequest;
expect(store.state.status).toEqual('loading');
expect(store.state.results).toBe(initialResults);
expect(store.state.facets).toBe(initialFacets);
expect(store.state.banners).toEqual(initialBanners);
expect(store.state.promoteds).toEqual(initialPromoteds);
expect(store.state.promoteds).toEqual(initialPromoteds);
expect(store.state.redirections).toEqual(initialRedirections);
await secondRequest;
expect(store.state.status).toEqual('success');
expect(store.state.results).toEqual(resultsStub);
expect(store.state.facets).toEqual(facetsStub);
expect(store.state.banners).toEqual(bannersStub);
expect(store.state.promoteds).toEqual(promotedsStub);
expect(store.state.redirections).toEqual(redirectionsStub);
});

it('should set the status to error when it fails', async () => {
resetSearchStateWith(store, { query: 'lego' });
adapter.search.mockRejectedValueOnce('Generic error');
const { results, facets, banners, promoteds } = store.state;
await store.dispatch('reloadSearch');

expect(store.state.results).toBe(results);
expect(store.state.facets).toBe(facets);
expect(store.state.banners).toBe(banners);
expect(store.state.promoteds).toBe(promoteds);
expect(store.state.status).toEqual('error');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './fetch-and-save-search-response.action';
export * from './fetch-search-response.action';
export * from './increase-page-apending-results.action';
export * from './reload-search.action';
export * from './reset-request-on-refinement.action';
export { saveOrigin as saveSearchOrigin } from './save-origin.action';
export * from './save-search-response.action';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { SearchRequest, SearchResponse } from '@empathyco/x-types';
import { createFetchAndSaveActions } from '../../../../store';
import { InternalSearchRequest } from '../../types';
import { SearchActionContext, SearchState } from '../types';

const { fetchAndSave, cancelPrevious } = createFetchAndSaveActions<
SearchActionContext,
InternalSearchRequest | null,
SearchResponse | null
>({
fetch({ dispatch, state }, request) {
return request
? dispatch('fetchSearchResponse', enrichRequest(request, state))
: Promise.resolve(null);
},
onSuccess({ dispatch }, response) {
if (response !== null) {
dispatch('saveSearchResponse', response);
}
}
});

/**
* Enriches the {@link SearchRequest} object with the origin and page properties taken from the
* {@link SearchState | search state}.
*
* @param request - The {@link InternalSearchRequest}.
* @param state - {@link SearchState}.
*
* @returns The search request.
* @internal
*/
function enrichRequest(request: InternalSearchRequest, state: SearchState): SearchRequest {
const { page, ...restRequest } = request;
const {
config: { pageSize },
origin
} = state;

return {
...restRequest,
...(origin && { origin }),
start: 0,
rows: pageSize
};
}

/**
* Default implementation for {@link SearchActions.fetchAndSaveSearchResponse} action.
*
* @param context - The {@link https://vuex.vuejs.org/guide/actions.html | context} of the
* actions, provided by Vuex.
* @returns A promise that resolves after saving the response.
* @public
*/
export const reloadSearch = (context: SearchActionContext) =>
fetchAndSave(context, context.getters.request);

/**
* Default implementation for {@link SearchActions.cancelFetchAndSaveSearchResponse} action.
*
* @public
*/
export const cancelReloadSearch = cancelPrevious;
18 changes: 11 additions & 7 deletions packages/x-components/src/x-modules/search/store/module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { isFacetFilter } from '@empathyco/x-types';
import { setQuery } from '../../../store/utils/query.utils';
import { setStatus } from '../../../store/utils/status-store.utils';
import { setStatus } from '../../../store';
import { groupItemsBy } from '../../../utils/array';
import { mergeConfig, setConfig } from '../../../store/utils/config-store.utils';
import { UNKNOWN_FACET_KEY } from '../../facets/store/constants';
import {
cancelFetchAndSaveSearchResponse,
fetchAndSaveSearchResponse
} from './actions/fetch-and-save-search-response.action';
import { fetchSearchResponse } from './actions/fetch-search-response.action';
import { increasePageAppendingResults } from './actions/increase-page-apending-results.action';
import { resetRequestOnRefinement } from './actions/reset-request-on-refinement.action';
cancelReloadSearch,
fetchAndSaveSearchResponse,
fetchSearchResponse,
increasePageAppendingResults,
reloadSearch,
resetRequestOnRefinement,
saveSearchResponse
} from './actions';
import { saveOrigin } from './actions/save-origin.action';
import { saveSearchResponse } from './actions/save-search-response.action';
import { setUrlParams } from './actions/set-url-params.action';
import { query } from './getters/query.getter';
import { request } from './getters/request.getter';
Expand Down Expand Up @@ -118,9 +120,11 @@ export const searchXStoreModule: SearchXStoreModule = {
},
actions: {
cancelFetchAndSaveSearchResponse,
cancelReloadSearch,
fetchSearchResponse,
fetchAndSaveSearchResponse,
increasePageAppendingResults,
reloadSearch,
resetRequestOnRefinement,
saveSearchResponse,
setUrlParams,
Expand Down
20 changes: 13 additions & 7 deletions packages/x-components/src/x-modules/search/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ import {
Redirection,
RelatedTag,
Result,
Sort,
TaggingRequest,
SearchRequest,
SearchResponse
SearchResponse,
Sort,
TaggingRequest
} from '@empathyco/x-types';
import { Dictionary } from '@empathyco/x-utils';
import { XActionContext, XStoreModule } from '../../../store';
import { StatusMutations, StatusState, XActionContext, XStoreModule } from '../../../store';
import { QueryMutations, QueryState } from '../../../store/utils/query.utils';
import { StatusMutations, StatusState } from '../../../store/utils/status-store.utils';
import { QueryOrigin, QueryOriginInit } from '../../../types/origin';
import { UrlParams } from '../../../types/url-params';
import { QueryOrigin, QueryOriginInit, UrlParams } from '../../../types';
import { SearchConfig } from '../config.types';
import { InternalSearchRequest, WatchedInternalSearchRequest } from '../types';
import { ConfigMutations } from '../../../store/utils/config-store.utils';
Expand Down Expand Up @@ -246,6 +244,10 @@ export interface SearchActions {
* Cancels / interrupt {@link SearchActions.fetchAndSaveSearchResponse} synchronous promise.
*/
cancelFetchAndSaveSearchResponse(): void;
/**
* Cancels / interrupt {@link SearchActions.reloadSearch} synchronous promise.
*/
cancelReloadSearch(): void;
/**
* Fetches a new search response and stores them in the module state.
*/
Expand All @@ -264,6 +266,10 @@ export interface SearchActions {
* for other purposes, please use the {@link SearchMutations.setPage} mutation.
*/
increasePageAppendingResults(): void;
/**
* Fetches again the current search and stores its response in the module state.
*/
reloadSearch(): void;
/**
* Checks if the url has params on it and then updates the state with these values.
*
Expand Down
18 changes: 18 additions & 0 deletions packages/x-components/src/x-modules/search/wiring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export const cancelFetchAndSaveSearchResponseWire = wireDispatchWithoutPayload(
'cancelFetchAndSaveSearchResponse'
);

/**
* Cancels the {@link SearchActions.reloadSearch} request promise.
*
* @public
*/
export const cancelReloadSearchWire = wireDispatchWithoutPayload('cancelReloadSearch');

/**
* Sets the search state `origin`.
*
Expand Down Expand Up @@ -130,6 +137,13 @@ export const setSearchPage = wireCommit('setPage');
*/
export const setSearchExtraParams = wireCommit('setParams');

/**
* Requests and stores the search current search response.
*
* @public
*/
export const reloadSearchWire = wireDispatch('reloadSearch');

/**
* Resets the search state `isNoResults`.
*
Expand Down Expand Up @@ -244,6 +258,7 @@ export const searchWiring = createWiring({
UserClearedQuery: {
setSearchQuery,
cancelFetchAndSaveSearchResponseWire,
cancelReloadSearchWire,
resetFromNoResultsWithFilters,
resetIsNoResults
},
Expand Down Expand Up @@ -272,6 +287,9 @@ export const searchWiring = createWiring({
ResultsChanged: {
resetAppending
},
ReloadSearchRequested: {
reloadSearchWire
},
SelectedSortProvided: {
setSort
},
Expand Down

0 comments on commit 1604e0b

Please sign in to comment.