diff --git a/packages/x-components/src/composables/__tests__/use-alias-api.spec.ts b/packages/x-components/src/composables/__tests__/use-alias-api.spec.ts
new file mode 100644
index 0000000000..bf30279753
--- /dev/null
+++ b/packages/x-components/src/composables/__tests__/use-alias-api.spec.ts
@@ -0,0 +1,230 @@
+import Vue, { defineComponent } from 'vue';
+import { createLocalVue, mount, Wrapper } from '@vue/test-utils';
+import Vuex, { Store } from 'vuex';
+import { AnyXStoreModule } from '../../store/index';
+import { UseAliasAPI, useAliasApi, UseAliasQueryAPI, UseAliasStatusAPI } from '../use-alias-api';
+import { searchBoxXStoreModule } from '../../x-modules/search-box/index';
+import { nextQueriesXStoreModule } from '../../x-modules/next-queries/index';
+import { querySuggestionsXStoreModule } from '../../x-modules/query-suggestions/index';
+import { relatedTagsXStoreModule } from '../../x-modules/related-tags/index';
+import { searchXStoreModule } from '../../x-modules/search/index';
+import { facetsXStoreModule } from '../../x-modules/facets/index';
+import { identifierResultsXStoreModule } from '../../x-modules/identifier-results/index';
+import { popularSearchesXStoreModule } from '../../x-modules/popular-searches/index';
+import { recommendationsXStoreModule } from '../../x-modules/recommendations/index';
+import { historyQueriesXStoreModule } from '../../x-modules/history-queries/index';
+
+const renderUseAliasApiTest = (registerXModules = true): renderUseAliasApiTestAPI => {
+ const testComponent = defineComponent({
+ setup() {
+ const xAliasAPI = useAliasApi();
+ const query = xAliasAPI.query;
+ const status = xAliasAPI.status;
+ return {
+ query,
+ status,
+ xAliasAPI
+ };
+ },
+ template: '
'
+ });
+
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const store = new Store({
+ modules: {
+ x: {
+ namespaced: true,
+ modules: registerXModules
+ ? {
+ searchBox: { namespaced: true, ...searchBoxXStoreModule } as AnyXStoreModule,
+ nextQueries: { namespaced: true, ...nextQueriesXStoreModule } as AnyXStoreModule,
+ querySuggestions: {
+ namespaced: true,
+ ...querySuggestionsXStoreModule
+ } as AnyXStoreModule,
+ relatedTags: { namespaced: true, ...relatedTagsXStoreModule } as AnyXStoreModule,
+ search: { namespaced: true, ...searchXStoreModule } as AnyXStoreModule,
+ facets: { namespaced: true, ...facetsXStoreModule } as AnyXStoreModule,
+ historyQueries: {
+ namespaced: true,
+ ...historyQueriesXStoreModule
+ } as AnyXStoreModule,
+ identifierResults: {
+ namespaced: true,
+ ...identifierResultsXStoreModule
+ } as AnyXStoreModule,
+ popularSearches: {
+ namespaced: true,
+ ...popularSearchesXStoreModule
+ } as AnyXStoreModule,
+ recommendations: {
+ namespaced: true,
+ ...recommendationsXStoreModule
+ } as AnyXStoreModule
+ }
+ : {}
+ }
+ }
+ });
+
+ const wrapper = mount(testComponent, {
+ localVue,
+ store
+ });
+
+ return {
+ store,
+ wrapper,
+ query: (wrapper.vm as any).query,
+ status: (wrapper.vm as any).status,
+ xAliasAPI: (wrapper.vm as any).xAliasAPI
+ };
+};
+describe('testing useAliasApi composable', () => {
+ it('returns default values when no module is registered', () => {
+ const { xAliasAPI } = renderUseAliasApiTest(false);
+
+ const defaultValues = {
+ query: {
+ facets: '',
+ searchBox: '',
+ nextQueries: '',
+ querySuggestions: '',
+ relatedTags: '',
+ search: ''
+ },
+ status: {
+ identifierResults: undefined,
+ nextQueries: undefined,
+ popularSearches: undefined,
+ querySuggestions: undefined,
+ recommendations: undefined,
+ relatedTags: undefined,
+ search: undefined
+ },
+ device: null,
+ facets: {},
+ historyQueries: [],
+ historyQueriesWithResults: [],
+ fullHistoryQueries: [],
+ isHistoryQueriesEnabled: false,
+ fromNoResultsWithFilters: false,
+ identifierResults: [],
+ searchBoxStatus: undefined,
+ isEmpathizeOpen: false,
+ nextQueries: [],
+ noResults: false,
+ partialResults: [],
+ popularSearches: [],
+ querySuggestions: [],
+ fullQuerySuggestions: [],
+ recommendations: [],
+ redirections: [],
+ relatedTags: [],
+ results: [],
+ scroll: {},
+ selectedFilters: [],
+ selectedRelatedTags: [],
+ semanticQueries: [],
+ spellcheckedQuery: null,
+ totalResults: 0,
+ selectedSort: ''
+ };
+ expect(xAliasAPI).toMatchObject(defaultValues);
+ });
+ it('updates the query values when the module is registered', () => {
+ const { store, query } = renderUseAliasApiTest();
+
+ expect(query).toEqual({
+ searchBox: '',
+ nextQueries: '',
+ querySuggestions: '',
+ relatedTags: '',
+ search: '',
+ facets: ''
+ });
+
+ store.commit('x/searchBox/setQuery', 'salchichón');
+ store.commit('x/nextQueries/setQuery', 'chorizo');
+ store.commit('x/querySuggestions/setQuery', 'lomo');
+ store.commit('x/relatedTags/setQuery', 'jamón');
+ store.commit('x/search/setQuery', 'cecina');
+ store.commit('x/facets/setQuery', 'mortadela');
+ store.commit('x/historyQueries/setQuery', 'queso');
+
+ expect(query).toEqual({
+ searchBox: 'salchichón',
+ nextQueries: 'chorizo',
+ querySuggestions: 'lomo',
+ relatedTags: 'jamón',
+ search: 'cecina',
+ facets: 'mortadela'
+ });
+ });
+ it('updates the status values when the module is registered', () => {
+ const REQUEST_STATUS_REGEX = /success|loading|error|initial/;
+ const { status } = renderUseAliasApiTest();
+
+ expect(status).toEqual({
+ identifierResults: expect.stringMatching(REQUEST_STATUS_REGEX),
+ popularSearches: expect.stringMatching(REQUEST_STATUS_REGEX),
+ recommendations: expect.stringMatching(REQUEST_STATUS_REGEX),
+ nextQueries: expect.stringMatching(REQUEST_STATUS_REGEX),
+ querySuggestions: expect.stringMatching(REQUEST_STATUS_REGEX),
+ relatedTags: expect.stringMatching(REQUEST_STATUS_REGEX),
+ search: expect.stringMatching(REQUEST_STATUS_REGEX)
+ });
+ });
+ it('reacts dynamically to referenced values changing', () => {
+ const { store, xAliasAPI } = renderUseAliasApiTest();
+ expect(xAliasAPI.historyQueries[0]).toBeUndefined();
+
+ store.dispatch('x/historyQueries/addQueryToHistory', 'chorizo');
+
+ expect(xAliasAPI.historyQueries[0].query).toEqual('chorizo');
+ });
+ it('has every property defined as a getter', () => {
+ const { xAliasAPI } = renderUseAliasApiTest();
+ /**
+ * Checks that every property defined by the object and keys is a getter or an object that
+ * only contains getters.
+ *
+ * @param obj - The object to check.
+ * @param keys - The subset of keys from the object to check.
+ * @returns True when the object properties defined by the keys are getters or object with
+ * getters.
+ */
+ function isJSGetterOrDictionaryOfJSGetters(
+ // object and string[] are the parameters used by getOwnPropertyDescriptor.
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ obj: object,
+ keys: string[]
+ ): boolean {
+ return keys.every(key => {
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key);
+ const value = obj[key as keyof typeof obj];
+ return (
+ (descriptor?.set === undefined &&
+ descriptor?.value === undefined &&
+ descriptor?.get !== undefined) ||
+ (typeof value === 'object' &&
+ isJSGetterOrDictionaryOfJSGetters(value, Object.keys(value)))
+ );
+ });
+ }
+
+ const aliasKeys = Object.keys(xAliasAPI);
+
+ expect(isJSGetterOrDictionaryOfJSGetters(xAliasAPI, aliasKeys)).toEqual(true);
+ });
+});
+
+type renderUseAliasApiTestAPI = {
+ store: Store;
+ wrapper: Wrapper;
+ query: UseAliasQueryAPI;
+ status: UseAliasStatusAPI;
+ xAliasAPI: UseAliasAPI;
+};
diff --git a/packages/x-components/src/composables/index.ts b/packages/x-components/src/composables/index.ts
index f712b88054..141a198abc 100644
--- a/packages/x-components/src/composables/index.ts
+++ b/packages/x-components/src/composables/index.ts
@@ -7,3 +7,4 @@ export * from './use-store';
export * from './use-state';
export * from './use-getter';
export * from './use-hybrid-inject';
+export * from './use-alias-api';
diff --git a/packages/x-components/src/composables/use-alias-api.ts b/packages/x-components/src/composables/use-alias-api.ts
new file mode 100644
index 0000000000..9c188f1c29
--- /dev/null
+++ b/packages/x-components/src/composables/use-alias-api.ts
@@ -0,0 +1,258 @@
+import {
+ Facet,
+ Filter,
+ HistoryQuery,
+ NextQuery,
+ PartialResult,
+ Redirection,
+ RelatedTag,
+ Result,
+ SemanticQuery,
+ Suggestion
+} from '@empathyco/x-types';
+import { ScrollComponentState } from '../x-modules/scroll/store/types';
+import { InputStatus } from '../x-modules/search-box/store/types';
+import { RequestStatus } from '../store/utils/status-store.utils';
+import { getGetterPath } from '../plugins/index';
+import { useStore } from './use-store';
+
+/**
+ * Creates an object containing the alias part of {@link XComponentAPI}.
+ *
+ * @returns An object containing the alias part of the {@link XComponentAPI}.
+ *
+ * @internal
+ */
+export function useAliasApi(this: any): UseAliasAPI {
+ const queryModules = [
+ 'facets',
+ 'searchBox',
+ 'nextQueries',
+ 'querySuggestions',
+ 'relatedTags',
+ 'search'
+ ] as const;
+ const statusModules = [
+ 'identifierResults',
+ 'nextQueries',
+ 'popularSearches',
+ 'querySuggestions',
+ 'recommendations',
+ 'relatedTags',
+ 'search'
+ ] as const;
+
+ const store = useStore();
+
+ const query = queryModules.reduce((acc, moduleName) => {
+ return Object.defineProperty(acc, moduleName, {
+ get(): string {
+ return store.state.x[moduleName]?.query ?? '';
+ },
+ enumerable: true
+ });
+ }, {} as UseAliasQueryAPI);
+
+ const status = statusModules.reduce((acc, moduleName) => {
+ return Object.defineProperty(acc, moduleName, {
+ get(): RequestStatus | undefined {
+ return store.state.x[moduleName]?.status;
+ },
+ enumerable: true
+ });
+ }, {} as UseAliasStatusAPI);
+
+ return {
+ query,
+ status,
+ get device() {
+ return store.state.x.device?.name ?? null;
+ },
+ get facets() {
+ return store.getters[getGetterPath('facets', 'facets')] ?? {};
+ },
+ get historyQueries() {
+ return store.getters[getGetterPath('historyQueries', 'historyQueries')] ?? [];
+ },
+ get historyQueriesWithResults() {
+ return store.getters[getGetterPath('historyQueries', 'historyQueriesWithResults')] ?? [];
+ },
+ get fullHistoryQueries() {
+ return store.state.x.historyQueries?.historyQueries ?? [];
+ },
+ get isHistoryQueriesEnabled() {
+ return store.state.x.historyQueries?.isEnabled ?? false;
+ },
+ get fromNoResultsWithFilters() {
+ return store.state.x.search?.fromNoResultsWithFilters ?? false;
+ },
+ get identifierResults() {
+ return store.state.x.identifierResults?.identifierResults ?? [];
+ },
+ get searchBoxStatus() {
+ return store.state.x.searchBox?.inputStatus ?? undefined;
+ },
+ get isEmpathizeOpen() {
+ return store.state.x.empathize?.isOpen ?? false;
+ },
+ get nextQueries() {
+ return store.getters[getGetterPath('nextQueries', 'nextQueries')] ?? [];
+ },
+ get noResults() {
+ return store.state.x.search?.isNoResults ?? false;
+ },
+ get partialResults() {
+ return store.state.x.search?.partialResults ?? [];
+ },
+ get popularSearches() {
+ return store.state.x.popularSearches?.popularSearches ?? [];
+ },
+ get querySuggestions() {
+ return store.getters[getGetterPath('querySuggestions', 'querySuggestions')] ?? [];
+ },
+ get fullQuerySuggestions() {
+ return store.state.x.querySuggestions?.suggestions ?? [];
+ },
+ get recommendations() {
+ return store.state.x.recommendations?.recommendations ?? [];
+ },
+ get redirections() {
+ return store.state.x.search?.redirections ?? [];
+ },
+ get relatedTags() {
+ return store.getters[getGetterPath('relatedTags', 'relatedTags')] ?? [];
+ },
+ get results() {
+ return store.state.x.search?.results ?? [];
+ },
+ get scroll() {
+ return store.state.x.scroll?.data ?? {};
+ },
+ get selectedFilters() {
+ return store.getters[getGetterPath('facets', 'selectedFilters')] ?? [];
+ },
+ get selectedRelatedTags() {
+ return store.state.x.relatedTags?.selectedRelatedTags ?? [];
+ },
+ get semanticQueries() {
+ return store.state.x.semanticQueries?.semanticQueries ?? [];
+ },
+ get spellcheckedQuery() {
+ return store.state.x.search?.spellcheckedQuery ?? null;
+ },
+ get totalResults() {
+ return store.state.x.search?.totalResults ?? 0;
+ },
+ get selectedSort() {
+ return store.state.x.search?.sort ?? '';
+ }
+ };
+}
+
+/**
+ * Alias to facilitate retrieving values from the store.
+ *
+ * @public
+ */
+export interface UseAliasAPI {
+ /** The {@link DeviceXModule} detected device. */
+ readonly device: string | null;
+ /** The {@link FacetsXModule} facets. */
+ readonly facets: ReadonlyArray;
+ /** The {@link HistoryQueriesXModule} history queries matching the query. */
+ readonly historyQueries: ReadonlyArray;
+ /** The {@link HistoryQueriesXModule} history queries with 1 or more results. */
+ readonly historyQueriesWithResults: ReadonlyArray;
+ /** The {@link HistoryQueriesXModule} history queries. */
+ readonly fullHistoryQueries: ReadonlyArray;
+ /** The {@link HistoryQueriesXModule} history queries enabled flag. */
+ readonly isHistoryQueriesEnabled: Readonly;
+ /** The {@link SearchXModule} no results with filters flag. */
+ readonly fromNoResultsWithFilters: Readonly;
+ /** The {@link IdentifierResultsXModule} results. */
+ readonly identifierResults: ReadonlyArray;
+ /** The {@link SearchBoxXModule } input status. */
+ readonly searchBoxStatus: InputStatus | undefined;
+ /** The {@link Empathize} is open state. */
+ readonly isEmpathizeOpen: boolean;
+ /** The {@link NextQueriesXModule} next queries. */
+ readonly nextQueries: ReadonlyArray;
+ /** The {@link SearchXModule} no results situation. */
+ readonly noResults: boolean;
+ /** The {@link SearchXModule} partial results. */
+ readonly partialResults: ReadonlyArray;
+ /** The {@link PopularSearchesXModule} popular searches. */
+ readonly popularSearches: ReadonlyArray;
+ /** The query value of the different modules. */
+ readonly query: UseAliasQueryAPI;
+ /** The {@link QuerySuggestionsXModule} query suggestions that should be displayed. */
+ readonly querySuggestions: ReadonlyArray;
+ /** The {@link QuerySuggestionsXModule} query suggestions. */
+ readonly fullQuerySuggestions: ReadonlyArray;
+ /** The {@link RecommendationsXModule} recommendations. */
+ readonly recommendations: ReadonlyArray;
+ /** The {@link SearchXModule} redirections. */
+ readonly redirections: ReadonlyArray;
+ /** The {@link RelatedTagsXModule} related tags (Both selected and deselected). */
+ readonly relatedTags: ReadonlyArray;
+ /** The {@link SearchXModule} search results. */
+ readonly results: ReadonlyArray;
+ /** The {@link ScrollXModule} data state. */
+ readonly scroll: Record;
+ /** The {@link FacetsXModule} selected filters. */
+ readonly selectedFilters: Filter[];
+ /** The {@link RelatedTagsXModule} selected related tags. */
+ readonly selectedRelatedTags: ReadonlyArray;
+ /** The {@link SemanticQueriesXModule} queries. */
+ readonly semanticQueries: ReadonlyArray;
+ /** The {@link SearchXModule} spellchecked query. */
+ readonly spellcheckedQuery: string | null;
+ /** The status value of the different modules. */
+ readonly status: UseAliasStatusAPI;
+ /** The {@link SearchXModule} total results. */
+ readonly totalResults: number;
+ /** The {@link SearchXModule} selected sort. */
+ readonly selectedSort: string;
+}
+
+/**
+ * Alias to facilitate retrieving the modules with query.
+ *
+ * @public
+ */
+export interface UseAliasQueryAPI {
+ /** The {@link FacetsXModule} query. */
+ readonly facets: string;
+ /** The {@link SearchBoxXModule} query. */
+ readonly searchBox: string;
+ /** The {@link NextQueriesXModule} query. */
+ readonly nextQueries: string;
+ /** The {@link QuerySuggestionsXModule} query. */
+ readonly querySuggestions: string;
+ /** The {@link RelatedTagsXModule} query. */
+ readonly relatedTags: string;
+ /** The {@link SearchXModule} query. */
+ readonly search: string;
+}
+
+/**
+ * Alias to facilitate retrieving the modules with status.
+ *
+ * @public
+ */
+export interface UseAliasStatusAPI {
+ /** The {@link IdentifierResultsXModule} status. */
+ readonly identifierResults: RequestStatus | undefined;
+ /** The {@link NextQueriesXModule} status. */
+ readonly nextQueries: RequestStatus | undefined;
+ /** The {@link PopularSearchesXModule} status. */
+ readonly popularSearches: RequestStatus | undefined;
+ /** The {@link QuerySuggestionsXModule} status. */
+ readonly querySuggestions: RequestStatus | undefined;
+ /** The {@link RecommendationsXModule} status. */
+ readonly recommendations: RequestStatus | undefined;
+ /** The {@link RelatedTagsXModule} status. */
+ readonly relatedTags: RequestStatus | undefined;
+ /** The {@link SearchXModule} status. */
+ readonly search: RequestStatus | undefined;
+}
diff --git a/packages/x-components/src/composables/use-getter.ts b/packages/x-components/src/composables/use-getter.ts
index 96632f7d64..b7099f80fc 100644
--- a/packages/x-components/src/composables/use-getter.ts
+++ b/packages/x-components/src/composables/use-getter.ts
@@ -16,10 +16,10 @@ import { useStore } from './use-store';
export function useGetter<
Module extends XModuleName,
GetterName extends keyof ExtractGetters & string
->(module: Module, getters: GetterName[]): Dictionary> {
+>(module: Module, getters: GetterName[]): Dictionary {
const store = useStore();
- return getters.reduce>>((getterDictionary, getterName) => {
+ return getters.reduce>((getterDictionary, getterName) => {
const getterPath = getGetterPath(module, getterName);
getterDictionary[getterName] = computed(() => store.getters[getterPath]);
return getterDictionary;
diff --git a/packages/x-components/src/composables/use-state.ts b/packages/x-components/src/composables/use-state.ts
index fce5d2140a..55612eb464 100644
--- a/packages/x-components/src/composables/use-state.ts
+++ b/packages/x-components/src/composables/use-state.ts
@@ -15,11 +15,11 @@ import { useStore } from './use-store';
export function useState<
Module extends XModuleName,
Path extends keyof ExtractState & string
->(module: Module, paths: Path[]): Dictionary> {
+>(module: Module, paths: Path[]): Dictionary {
const store = useStore();
- return paths.reduce>>((stateDictionary, path) => {
- stateDictionary[path] = computed(() => store.state.x[module][path]);
+ return paths.reduce>((stateDictionary, path) => {
+ stateDictionary[path] = computed(() => store.state.x[module]?.[path]);
return stateDictionary;
}, {});
}