feat(search-react): custom result list empty state

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2022-10-02 10:30:44 +02:00
parent 92da032e0e
commit c27186ff01
3 changed files with 99 additions and 23 deletions
@@ -119,7 +119,7 @@ export const WithError = () => {
);
};
export const WithNoResults = () => {
export const WithDefaultNoResultsComponent = () => {
const [query] = useState<Partial<SearchQuery>>({
types: ['techdocs'],
});
@@ -131,6 +131,21 @@ export const WithNoResults = () => {
);
};
export const WithCustomNoResultsComponent = () => {
const [query] = useState<Partial<SearchQuery>>({
types: ['techdocs'],
});
return (
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
<SearchResultList
query={query}
noResultsComponent={<ListItemText primary="No results were found" />}
/>
</TestApiProvider>
);
};
const CustomResultListItem = (props: any) => {
const { icon, result } = props;
@@ -121,6 +121,42 @@ describe('SearchResultList', () => {
});
});
it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => {
query.mockResolvedValueOnce({ results: [] });
await renderWithEffects(
wrapInTestApp(
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
<SearchResultList
query={{ types: ['techdocs'] }}
disableRenderingWithNoResults
/>
</TestApiProvider>,
),
);
await waitFor(() => {
expect(screen.queryByText('Documentation')).not.toBeInTheDocument();
});
});
it('Should render custom component when no results returned', async () => {
query.mockResolvedValueOnce({ results: [] });
await renderWithEffects(
wrapInTestApp(
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
<SearchResultList
query={{ types: ['techdocs'] }}
noResultsComponent="No results were found"
/>
</TestApiProvider>,
),
);
await waitFor(() => {
expect(screen.getByText('No results were found')).toBeInTheDocument();
});
});
it('Shows an error panel when results rendering fails', async () => {
query.mockRejectedValueOnce(new Error());
await renderWithEffects(
@@ -14,9 +14,9 @@
* limitations under the License.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import { List, ListProps } from '@material-ui/core';
import { List, ListItem, ListProps } from '@material-ui/core';
import {
EmptyState,
@@ -41,7 +41,11 @@ export type SearchResultListLayoutProps = ListProps & {
/**
* Function to customize how result items are rendered.
*/
renderResultItem?: (resultItem: SearchResult) => JSX.Element;
renderResultItem?: (
value: SearchResult,
index: number,
array: SearchResult[],
) => JSX.Element | null;
/**
* If defined, will render a default error panel.
*/
@@ -50,6 +54,10 @@ export type SearchResultListLayoutProps = ListProps & {
* If defined, will render a default loading progress.
*/
loading?: boolean;
/**
* Optional component to render when no results. Default to <EmptyState /> component.
*/
noResultsComponent?: ReactNode;
};
/**
@@ -58,7 +66,21 @@ export type SearchResultListLayoutProps = ListProps & {
* @public
*/
export const SearchResultListLayout = (props: SearchResultListLayoutProps) => {
const { loading, error, resultItems, renderResultItem, ...rest } = props;
const {
loading,
error,
resultItems,
renderResultItem = resultItem => (
<DefaultResultListItem
key={resultItem.document.location}
result={resultItem.document}
/>
),
noResultsComponent = (
<EmptyState missing="data" title="Sorry, no results were found" />
),
...rest
} = props;
return (
<List {...rest}>
@@ -70,10 +92,10 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => {
/>
) : null}
{!loading && !error && resultItems?.length
? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null)
? resultItems.map(renderResultItem)
: null}
{!loading && !error && !resultItems?.length ? (
<EmptyState missing="data" title="Sorry, no results were found" />
<ListItem>{noResultsComponent}</ListItem>
) : null}
</List>
);
@@ -91,6 +113,10 @@ export type SearchResultListProps = Omit<
* A search query used for requesting the results to be listed.
*/
query: Partial<SearchQuery>;
/**
* Optional property to provide if component should not render the group when no results are found.
*/
disableRenderingWithNoResults?: boolean;
};
/**
@@ -99,13 +125,7 @@ export type SearchResultListProps = Omit<
* @public
*/
export const SearchResultList = (props: SearchResultListProps) => {
const {
query,
renderResultItem = ({ document }) => (
<DefaultResultListItem key={document.location} result={document} />
),
...rest
} = props;
const { query, disableRenderingWithNoResults, ...rest } = props;
return (
<AnalyticsContext
@@ -115,15 +135,20 @@ export const SearchResultList = (props: SearchResultListProps) => {
}}
>
<SearchResultState query={query}>
{({ loading, error, value }) => (
<SearchResultListLayout
{...rest}
loading={loading}
error={error}
resultItems={value?.results}
renderResultItem={renderResultItem}
/>
)}
{({ loading, error, value }) => {
if (!value?.results?.length && disableRenderingWithNoResults) {
return null;
}
return (
<SearchResultListLayout
{...rest}
loading={loading}
error={error}
resultItems={value?.results}
/>
);
}}
</SearchResultState>
</AnalyticsContext>
);