diff --git a/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue b/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue
index e30dba0c8..0c0429689 100644
--- a/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue
+++ b/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue
@@ -183,3 +183,180 @@
}
});
+
+
+## Events
+
+This component emits no events.
+
+## See it in action
+
+
+
+:::warning Backend microservice required To use this component, the QuerySignals microservice
+must be implemented. :::
+
+
+
+Usually, this component is going to be used together with the `ResultsList` one. Related prompts
+groups will be inserted between the results, guiding users to discover new searches directly from
+the results list.
+
+```vue live
+
+
+
+
+
+
+
+
+
+
+```
+
+### Play with the index that related prompts groups are inserted at
+
+The component allows to customise where are the related prompts groups inserted. In the following
+example, the first group of related prompts will be inserted at the index `48` (`offset`), and then
+a second group will be inserted at index `120` because of the `frequency` prop configured to `72`.
+Finally, a third group will be inserted at index `192`. Because `maxGroups` is configured to `3`, no
+more groups will be inserted. Each one of this groups will have up to `6` related prompts
+(`maxRelatedPromptsPerGroup`).
+
+```vue live
+
+
+
+
+
+
+
+
+
+
+```
+
+### Showing/hiding first related prompts group when no more items
+
+By default, the first related prompts group will be inserted when the total number of results is
+smaller than the offset, but this behavior can be deactivated by setting the `showOnlyAfterOffset`
+to `true`.
+
+```vue live
+
+
+
+
+
+
+
+
+
+
+```
+
+### Customise the layout of the component
+
+This component will render by default the `id` of each search item, both the injected, and for the
+groups of related prompts generated, but the common case is to integrate it with another layout
+component, for example the `BaseGrid`. To do so, you can use the `default` slot
+
+```vue
+
+
+
+
+
+
+
+
+ RelatedPromptsGroup:
+ {{ prompt }}
+
+
+
+ Result: {{ item.name }}
+
+
+ Default: {{ item }}
+
+
+
+
+
+
+
+
+```
+
diff --git a/packages/x-components/src/x-modules/related-prompts/store/__tests__/actions.spec.ts b/packages/x-components/src/x-modules/related-prompts/store/__tests__/actions.spec.ts
new file mode 100644
index 000000000..79efe65be
--- /dev/null
+++ b/packages/x-components/src/x-modules/related-prompts/store/__tests__/actions.spec.ts
@@ -0,0 +1,118 @@
+import { mount } from '@vue/test-utils';
+import { Store } from 'vuex';
+import { getRelatedPromptsStub } from '../../../../__stubs__';
+import { getMockedAdapter, installNewXPlugin } from '../../../../__tests__/utils';
+import { SafeStore } from '../../../../store/__tests__/utils';
+import {
+ RelatedPromptsActions,
+ RelatedPromptsGetters,
+ RelatedPromptsMutations,
+ RelatedPromptsState
+} from '../types';
+import { relatedPromptsXStoreModule } from '../module';
+import { resetRelatedPromptsStateWith } from './utils';
+
+describe('testing related prompts module actions', () => {
+ const mockedRelatedPrompts = getRelatedPromptsStub();
+
+ const adapter = getMockedAdapter({
+ relatedPrompts: { relatedPrompts: mockedRelatedPrompts }
+ });
+
+ const store: SafeStore<
+ RelatedPromptsState,
+ RelatedPromptsGetters,
+ RelatedPromptsMutations,
+ RelatedPromptsActions
+ > = new Store(relatedPromptsXStoreModule as any);
+ mount(
+ {},
+ {
+ global: {
+ plugins: [installNewXPlugin({ adapter, store })]
+ }
+ }
+ );
+
+ beforeEach(() => {
+ resetRelatedPromptsStateWith(store);
+ });
+
+ describe('fetchRelatedPrompts', () => {
+ it('should return related prompts', async () => {
+ resetRelatedPromptsStateWith(store, {
+ query: 'honeyboo'
+ });
+
+ const relatedPrompts = await store.dispatch('fetchRelatedPrompts', store.getters.request);
+ expect(relatedPrompts).toEqual(mockedRelatedPrompts);
+ });
+
+ it('should return `null` if there is not request', async () => {
+ const relatedPrompts = await store.dispatch('fetchRelatedPrompts', store.getters.request);
+ expect(relatedPrompts).toBeNull();
+ });
+ });
+
+ describe('fetchAndSaveRelatedPrompts', () => {
+ it('should request and store related prompts in the state', async () => {
+ resetRelatedPromptsStateWith(store, {
+ query: 'honeyboo'
+ });
+
+ const actionPromise = store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request);
+ expect(store.state.status).toEqual('loading');
+ await actionPromise;
+ expect(store.state.relatedPrompts).toEqual(mockedRelatedPrompts);
+ expect(store.state.status).toEqual('success');
+ });
+
+ it('should not clear related prompts in the state if the query is empty', async () => {
+ resetRelatedPromptsStateWith(store, { relatedPrompts: mockedRelatedPrompts });
+
+ await store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request);
+ expect(store.state.relatedPrompts).toEqual(mockedRelatedPrompts);
+ });
+
+ it('should cancel the previous request if it is not yet resolved', async () => {
+ resetRelatedPromptsStateWith(store, { query: 'steak' });
+ const initialRelatedPrompts = store.state.relatedPrompts;
+ adapter.relatedPrompts.mockResolvedValueOnce({
+ relatedPrompts: mockedRelatedPrompts.slice(0, 1)
+ });
+
+ const firstRequest = store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request);
+ const secondRequest = store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request);
+
+ await firstRequest;
+ expect(store.state.status).toEqual('loading');
+ expect(store.state.relatedPrompts).toBe(initialRelatedPrompts);
+ await secondRequest;
+ expect(store.state.status).toEqual('success');
+ expect(store.state.relatedPrompts).toEqual(mockedRelatedPrompts);
+ });
+
+ it('should set the status to error when it fails', async () => {
+ resetRelatedPromptsStateWith(store, { query: 'milk' });
+ adapter.relatedPrompts.mockRejectedValueOnce('Generic error');
+ const relatedPrompts = store.state.relatedPrompts;
+ await store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request);
+
+ expect(store.state.relatedPrompts).toBe(relatedPrompts);
+ expect(store.state.status).toEqual('error');
+ });
+ });
+
+ describe('cancelFetchAndSaveRelatedPrompts', () => {
+ it('should cancel the request and do not modify the stored related prompts', async () => {
+ resetRelatedPromptsStateWith(store, { query: 'honeyboo' });
+ const previousRelatedPrompts = store.state.relatedPrompts;
+ await Promise.all([
+ store.dispatch('fetchAndSaveRelatedPrompts', store.getters.request),
+ store.dispatch('cancelFetchAndSaveRelatedPrompts')
+ ]);
+ expect(store.state.relatedPrompts).toEqual(previousRelatedPrompts);
+ expect(store.state.status).toEqual('success');
+ });
+ });
+});
diff --git a/packages/x-components/src/x-modules/related-prompts/store/__tests__/getters.spec.ts b/packages/x-components/src/x-modules/related-prompts/store/__tests__/getters.spec.ts
new file mode 100644
index 000000000..304ea29c0
--- /dev/null
+++ b/packages/x-components/src/x-modules/related-prompts/store/__tests__/getters.spec.ts
@@ -0,0 +1,37 @@
+import { RelatedPromptsRequest } from '@empathyco/x-types';
+import { map } from '@empathyco/x-utils';
+import { Store } from 'vuex';
+import { relatedPromptsXStoreModule } from '../module';
+import { RelatedPromptsState } from '../types';
+import { resetRelatedPromptsStateWith } from './utils';
+
+describe('testing related prompts module getters', () => {
+ const gettersKeys = map(relatedPromptsXStoreModule.getters, getter => getter);
+ const store: Store = new Store(relatedPromptsXStoreModule as any);
+
+ beforeEach(() => {
+ resetRelatedPromptsStateWith(store);
+ });
+
+ describe(`${gettersKeys.request} getter`, () => {
+ it('should return a request object if there is a query', () => {
+ resetRelatedPromptsStateWith(store, {
+ query: 'queso',
+ params: {
+ catalog: 'es'
+ }
+ });
+
+ expect(store.getters[gettersKeys.request]).toEqual({
+ query: 'queso',
+ extraParams: {
+ catalog: 'es'
+ }
+ });
+ });
+
+ it('should return null when there is not query', () => {
+ expect(store.getters[gettersKeys.request]).toBeNull();
+ });
+ });
+});
diff --git a/packages/x-components/src/x-modules/related-prompts/store/__tests__/utils.ts b/packages/x-components/src/x-modules/related-prompts/store/__tests__/utils.ts
new file mode 100644
index 000000000..cf4e0254f
--- /dev/null
+++ b/packages/x-components/src/x-modules/related-prompts/store/__tests__/utils.ts
@@ -0,0 +1,21 @@
+import { DeepPartial } from '@empathyco/x-utils';
+import { Store } from 'vuex';
+import { resetStoreModuleState } from '../../../../__tests__/utils';
+import { relatedPromptsXStoreModule } from '../module';
+import { RelatedPromptsState } from '../types';
+
+/**
+ * Reset related prompt module state with its original state and the partial state passes as
+ * parameter.
+ *
+ * @param store - Related prompt store state.
+ * @param state - Partial related prompt store state to be replaced.
+ *
+ * @internal
+ */
+export function resetRelatedPromptsStateWith(
+ store: Store,
+ state?: DeepPartial
+): void {
+ resetStoreModuleState(store, relatedPromptsXStoreModule.state(), state);
+}