Skip to content

Commit

Permalink
test: test for RP module and doc for related-prompts-list
Browse files Browse the repository at this point in the history
  • Loading branch information
victorcg88 committed Oct 28, 2024
1 parent f9d5b10 commit 976f943
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,180 @@
}
});
</script>

<docs lang="mdx">
## Events

This component emits no events.

## See it in action

<!-- prettier-ignore-start -->

:::warning Backend microservice required To use this component, the <b>QuerySignals</b> microservice
must be implemented. :::

<!-- prettier-ignore-end -->

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
<template>
<div>
<SearchInput />
<ResultsList>
<RelatedPromptsList />
</ResultsList>
</div>
</template>

<script>
import { RelatedPromptsList } from '@empathyco/x-components/related-prompts';
import { ResultsList } from '@empathyco/x-components/search';
import { SearchInput } from '@empathyco/x-components/search-box';

export default {
name: 'RelatedPromptsListDemo',
components: {
RelatedPromptsList,
ResultsList,
SearchInput
}
};
</script>
```

### 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
<template>
<div>
<SearchInput />
<ResultsList>
<RelatedPromptsList
:offset="48"
:frequency="72"
:maxRelatedPromptsPerGroup="6"
:maxGroups="3"
/>
</ResultsList>
</div>
</template>

<script>
import { RelatedPromptsList } from '@empathyco/x-components/related-prompts';
import { ResultsList } from '@empathyco/x-components/search';
import { SearchInput } from '@empathyco/x-components/search-box';

export default {
name: 'RelatedPromptsListDemo',
components: {
RelatedPromptsList,
ResultsList,
SearchInput
}
};
</script>
```

### 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
<template>
<div>
<SearchInput />
<ResultsList>
<RelatedPromptsList
:offset="48"
:frequency="72"
:maxRelatedPromptsPerGroup="1"
:showOnlyAfterOffset="true"
/>
</ResultsList>
</div>
</template>

<script>
import { RelatedPromptsList } from '@empathyco/x-components/related-prompts';
import { ResultsList } from '@empathyco/x-components/search';
import { SearchInput } from '@empathyco/x-components/search-box';

export default {
name: 'RelatedPromptsListDemo',
components: {
RelatedPromptsList,
ResultsList,
SearchInput
}
};
</script>
```

### 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
<template>
<div>
<SearchInput />
<ResultsList>
<RelatedPromptsList
:offset="48"
:frequency="72"
:maxRelatedPromptsPerGroup="6"
:maxGroups="3"
#default="{ items }"
>
<BaseGrid :items="items" :animation="animation">
<template #related-prompts-group="{ item }">
<span v-for="const prompt of items.relatedPrompts">
RelatedPromptsGroup:
<pre>{{ prompt }}</pre>
</span>
</template>
<template #result="{ item }">
<span>Result: {{ item.name }}</span>
</template>
<template #default="{ item }">
<span>Default: {{ item }}</span>
</template>
</BaseGrid>
</RelatedPromptsList>
</ResultsList>
</div>
</template>

<script>
import { RelatedPromptsList } from '@empathyco/x-components/related-prompts';
import { ResultsList } from '@empathyco/x-components/search';
import { SearchInput } from '@empathyco/x-components/search-box';
import { BaseGrid } from '@empathyco/x-components';

export default {
name: 'RelatedPromptsListDemo',
components: {
RelatedPromptsLis,
ResultsList,
BaseGrid,
SearchInput
}
};
</script>
```
</docs>
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
Original file line number Diff line number Diff line change
@@ -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<RelatedPromptsState> = 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<RelatedPromptsRequest>({
query: 'queso',
extraParams: {
catalog: 'es'
}
});
});

it('should return null when there is not query', () => {
expect(store.getters[gettersKeys.request]).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
@@ -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<RelatedPromptsState>,
state?: DeepPartial<RelatedPromptsState>
): void {
resetStoreModuleState<RelatedPromptsState>(store, relatedPromptsXStoreModule.state(), state);
}

0 comments on commit 976f943

Please sign in to comment.