feat(React): Search page UI improvements, 'all' entity search. (#2140)

Co-authored-by: John Joyce <john@acryl.io>
This commit is contained in:
John Joyce 2021-02-25 15:27:58 -08:00 committed by GitHub
parent 8fe9520ddc
commit 606a5af0c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 403 additions and 234 deletions

View File

@ -43,9 +43,9 @@ const App: React.VFC = () => {
const entityRegistry = useMemo(() => {
const register = new EntityRegistry();
register.register(new DatasetEntity());
register.register(new UserEntity());
register.register(new DashboardEntity());
register.register(new ChartEntity());
register.register(new UserEntity());
return register;
}, []);
return (

View File

@ -0,0 +1,94 @@
import { ArrowRightOutlined } from '@ant-design/icons';
import { Button, Card, Divider, List, Space, Typography } from 'antd';
import * as React from 'react';
import { useHistory } from 'react-router-dom';
import { SearchCfg } from '../../conf';
import { useGetSearchResultsQuery } from '../../graphql/search.generated';
import { EntityType } from '../../types.generated';
import { IconStyleType } from '../entity/Entity';
import { useEntityRegistry } from '../useEntityRegistry';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
const styles = {
header: { marginBottom: 20 },
resultHeaderCardBody: { padding: '16px 24px' },
resultHeaderCard: { right: '52px', top: '-40px', position: 'absolute' },
resultList: { width: '100%', borderColor: '#f0f0f0', marginTop: '12px', padding: '16px 32px' },
seeAllButton: { fontSize: 18 },
resultsContainer: { width: '100%', padding: '40px 132px' },
};
interface Props {
type: EntityType;
query: string;
}
export const EntityGroupSearchResults = ({ type, query }: Props) => {
const history = useHistory();
const entityRegistry = useEntityRegistry();
const { data } = useGetSearchResultsQuery({
variables: {
input: {
type,
query,
start: 0,
count: SearchCfg.RESULTS_PER_PAGE,
filters: null,
},
},
});
if (!data?.search?.entities.length) {
return null;
}
const results = data?.search?.entities || [];
return (
<Space direction="vertical" style={styles.resultsContainer}>
<List
header={
<span style={styles.header}>
<Typography.Title level={3}>{entityRegistry.getCollectionName(type)}</Typography.Title>
<Card bodyStyle={styles.resultHeaderCardBody} style={styles.resultHeaderCard as any}>
{entityRegistry.getIcon(type, 36, IconStyleType.ACCENT)}
</Card>
</span>
}
footer={
data?.search &&
data?.search?.total > 0 && (
<Button
type="text"
style={styles.seeAllButton}
onClick={() =>
navigateToSearchUrl({
type,
query,
page: 0,
history,
entityRegistry,
})
}
>
<Typography.Text>
See all <b>{entityRegistry.getCollectionName(type)}</b> results
</Typography.Text>
<ArrowRightOutlined />
</Button>
)
}
style={styles.resultList}
dataSource={results}
split={false}
renderItem={(item, index) => (
<>
<List.Item>{entityRegistry.renderSearchResult(type, item)}</List.Item>
{index < results.length - 1 && <Divider />}
</>
)}
bordered
/>
</Space>
);
};

View File

@ -0,0 +1,145 @@
import React, { useState, useEffect } from 'react';
import { FilterOutlined } from '@ant-design/icons';
import { Alert, Button, Card, Divider, List, Modal, Pagination, Row, Typography } from 'antd';
import { SearchCfg } from '../../conf';
import { useGetSearchResultsQuery } from '../../graphql/search.generated';
import { EntityType, FacetFilterInput } from '../../types.generated';
import { IconStyleType } from '../entity/Entity';
import { Message } from '../shared/Message';
import { useEntityRegistry } from '../useEntityRegistry';
import { SearchFilters } from './SearchFilters';
const styles = {
loading: { marginTop: '10%' },
addFilters: { backgroundColor: '#F5F5F5' },
resultSummary: { color: 'gray', marginTop: '36px' },
resultHeaderCardBody: { padding: '16px 24px' },
resultHeaderCard: { right: '52px', top: '-40px', position: 'absolute' },
resultList: { width: '100%', borderColor: '#f0f0f0', marginTop: '12px', padding: '16px 32px' },
paginationRow: { padding: 40 },
resultsContainer: { width: '100%', padding: '20px 132px' },
};
interface Props {
type: EntityType;
query: string;
page: number;
filters: Array<FacetFilterInput>;
onChangeFilters: (filters: Array<FacetFilterInput>) => void;
onChangePage: (page: number) => void;
}
export const EntitySearchResults = ({ type, query, page, filters, onChangeFilters, onChangePage }: Props) => {
const [isEditingFilters, setIsEditingFilters] = useState(false);
const [selectedFilters, setSelectedFilters] = useState(filters);
useEffect(() => {
setSelectedFilters(filters);
}, [filters]);
const entityRegistry = useEntityRegistry();
const { loading, error, data } = useGetSearchResultsQuery({
variables: {
input: {
type,
query,
start: (page - 1) * SearchCfg.RESULTS_PER_PAGE,
count: SearchCfg.RESULTS_PER_PAGE,
filters,
},
},
});
const results = data?.search?.entities || [];
const pageStart = data?.search?.start || 0;
const pageSize = data?.search?.count || 0;
const totalResults = data?.search?.total || 0;
const lastResultIndex =
pageStart * pageSize + pageSize > totalResults ? totalResults : pageStart * pageSize + pageSize;
const onFilterSelect = (selected: boolean, field: string, value: string) => {
const newFilters = selected
? [...selectedFilters, { field, value }]
: selectedFilters.filter((filter) => filter.field !== field || filter.value !== value);
setSelectedFilters(newFilters);
};
const onEditFilters = () => {
setIsEditingFilters(true);
};
const onApplyFilters = () => {
onChangeFilters(selectedFilters);
setIsEditingFilters(false);
};
const onCloseEditFilters = () => {
setIsEditingFilters(false);
setSelectedFilters(filters);
};
if (error || (!loading && !error && !data)) {
return <Alert type="error" message={error?.message || 'Entity failed to load'} />;
}
return (
<div style={styles.resultsContainer}>
{loading && <Message type="loading" content="Loading..." style={styles.loading} />}
<Button style={styles.addFilters} onClick={onEditFilters} data-testid="filters-button">
<FilterOutlined />
Filters{' '}
{filters.length > 0 && (
<>
{' '}
(<b>{filters.length}</b>)
</>
)}
</Button>
<Modal
title="Filters"
footer={<Button onClick={onApplyFilters}>Apply</Button>}
visible={isEditingFilters}
destroyOnClose
onCancel={onCloseEditFilters}
>
<SearchFilters
facets={data?.search?.facets || []}
selectedFilters={selectedFilters}
onFilterSelect={onFilterSelect}
/>
</Modal>
<Typography.Paragraph style={styles.resultSummary}>
Showing{' '}
<b>
{(page - 1) * pageSize} - {lastResultIndex}
</b>{' '}
of <b>{totalResults}</b> results
</Typography.Paragraph>
<List
header={
<Card bodyStyle={styles.resultHeaderCardBody} style={styles.resultHeaderCard as any}>
{entityRegistry.getIcon(type, 36, IconStyleType.ACCENT)}
</Card>
}
style={styles.resultList}
dataSource={results}
split={false}
renderItem={(item, index) => (
<>
<List.Item>{entityRegistry.renderSearchResult(type, item)}</List.Item>
{index < results.length - 1 && <Divider />}
</>
)}
bordered
/>
<Row justify="center" style={styles.paginationRow}>
<Pagination
current={page}
pageSize={pageSize}
total={totalResults}
showLessItems
onChange={onChangePage}
/>
</Row>
</div>
);
};

View File

@ -1,17 +1,18 @@
import React, { useEffect, useState } from 'react';
import { Input, AutoComplete, Select } from 'antd';
import React from 'react';
import { Input, AutoComplete } from 'antd';
const { Search } = Input;
const { Option } = Select;
const styles = {
autoComplete: { width: 650 },
};
interface Props {
types: Array<string>;
selectedType: string;
initialQuery: string;
placeholderText: string;
suggestions: Array<string>;
onSearch: (type: string, query: string) => void;
onQueryChange: (type: string, query: string) => void;
onSearch: (query: string) => void;
onQueryChange: (query: string) => void;
style?: React.CSSProperties;
}
@ -22,53 +23,17 @@ const defaultProps = {
/**
* Represents the search bar appearing in the default header view.
*/
export const SearchBar = ({
types,
selectedType,
initialQuery,
placeholderText,
suggestions,
onSearch,
onQueryChange,
style,
}: Props) => {
const [activeType, setActiveType] = useState(selectedType);
useEffect(() => {
setActiveType(selectedType);
}, [selectedType]);
const onTypeChange = (value: string) => {
setActiveType(value);
};
export const SearchBar = ({ initialQuery, placeholderText, suggestions, onSearch, onQueryChange, style }: Props) => {
return (
<div
style={{
height: '64px',
width: '900px',
padding: '0px 40px',
margin: '0px auto',
display: 'flex',
alignItems: 'center',
...style,
}}
>
<Select value={activeType} style={{ marginRight: '12px', width: 250 }} onChange={onTypeChange}>
{types.map((t) => (
<Option key={t} value={t}>
{t}
</Option>
))}
</Select>
<div style={style}>
<AutoComplete
style={{ width: 500 }}
style={styles.autoComplete}
options={suggestions.map((result: string) => ({ value: result }))}
onSelect={(value: string) => onSearch(activeType, value)}
onSearch={(value: string) => onQueryChange(activeType, value)}
onSelect={(value: string) => onSearch(value)}
onSearch={(value: string) => onQueryChange(value)}
defaultValue={initialQuery}
>
<Search placeholder={placeholderText} onSearch={(value: string) => onSearch(activeType, value)} />
<Search placeholder={placeholderText} onSearch={(value: string) => onSearch(value)} />
</AutoComplete>
</div>
);

View File

@ -1,5 +1,4 @@
// import { Card } from 'antd';
import { Card, Checkbox } from 'antd';
import { Checkbox } from 'antd';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import * as React from 'react';
@ -22,11 +21,7 @@ interface Props {
export const SearchFilters = ({ facets, selectedFilters, onFilterSelect }: Props) => {
return (
<Card
style={{ border: '1px solid #d2d2d2' }}
title={<h3 style={{ marginBottom: '0px' }}>Filters</h3>}
bodyStyle={{ padding: '24px 0px' }}
>
<>
{facets.map((facet) => (
<div key={facet.field} style={{ padding: '0px 25px 15px 25px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '10px' }}>
@ -53,6 +48,6 @@ export const SearchFilters = ({ facets, selectedFilters, onFilterSelect }: Props
))}
</div>
))}
</Card>
</>
);
};

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import 'antd/dist/antd.css';
import { Image, Layout } from 'antd';
import { Image, Layout, Space, Typography } from 'antd';
import { Link } from 'react-router-dom';
import { SearchBar } from './SearchBar';
import { ManageAccount } from '../shared/ManageAccount';
@ -8,16 +8,32 @@ import { GlobalCfg } from '../../conf';
const { Header } = Layout;
const styles = {
header: {
position: 'fixed',
zIndex: 1,
width: '100%',
backgroundColor: 'rgb(51 62 76)',
height: '80px',
lineHeight: '20px',
color: '#fff',
padding: '0px 40px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
logoImage: { width: '36px', height: '32px' },
title: { color: 'white', paddingLeft: '12px', margin: '0' },
};
type Props = {
types: Array<string>;
selectedType: string;
initialQuery: string;
placeholderText: string;
suggestions: Array<string>;
onSearch: (type: string, query: string) => void;
onQueryChange: (type: string, query: string) => void;
onSearch: (query: string) => void;
onQueryChange: (query: string) => void;
authenticatedUserUrn: string;
authenticatedUserPictureLink?: string;
authenticatedUserPictureLink?: string | null;
};
const defaultProps = {
@ -28,8 +44,6 @@ const defaultProps = {
* A header containing a Logo, Search Bar view, & an account management dropdown.
*/
export const SearchHeader = ({
types,
selectedType,
initialQuery,
placeholderText,
suggestions,
@ -39,43 +53,23 @@ export const SearchHeader = ({
authenticatedUserPictureLink,
}: Props) => {
return (
<Header
style={{
position: 'fixed',
zIndex: 1,
width: '100%',
backgroundColor: 'rgb(51 62 76)',
fontSize: '18px',
height: '64px',
lineHeight: '20px',
color: '#fff',
padding: '0px 80px',
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Link
style={{
height: '64px',
padding: '15px 30px',
display: 'flex',
alignItems: 'center',
}}
to="/"
>
<Image style={{ width: '34px', height: '30px' }} src={GlobalCfg.LOGO_IMAGE} preview={false} />
<div style={{ color: 'white', fontWeight: 'bold', padding: '15px' }}>DataHub</div>
</Link>
<SearchBar
types={types}
initialQuery={initialQuery}
selectedType={selectedType}
placeholderText={placeholderText}
suggestions={suggestions}
onSearch={onSearch}
onQueryChange={onQueryChange}
/>
<ManageAccount urn={authenticatedUserUrn} pictureLink={authenticatedUserPictureLink} />
</div>
<Header style={styles.header as any}>
<Link to="/">
<Space size={4}>
<Image style={styles.logoImage} src={GlobalCfg.LOGO_IMAGE} preview={false} />
<Typography.Title level={4} style={styles.title}>
DataHub
</Typography.Title>
</Space>
</Link>
<SearchBar
initialQuery={initialQuery}
placeholderText={placeholderText}
suggestions={suggestions}
onSearch={onSearch}
onQueryChange={onQueryChange}
/>
<ManageAccount urn={authenticatedUserUrn} pictureLink={authenticatedUserPictureLink || ''} />
</Header>
);
};

View File

@ -1,18 +1,27 @@
import React from 'react';
import * as QueryString from 'query-string';
import { useHistory, useLocation, useParams } from 'react-router';
import { Affix, Col, Row, Tabs, Layout, List, Alert } from 'antd';
import { Affix, Tabs } from 'antd';
import { SearchablePage } from './SearchablePage';
import { useGetSearchResultsQuery } from '../../graphql/search.generated';
import { SearchResults } from './SearchResults';
import { SearchFilters } from './SearchFilters';
import { SearchCfg } from '../../conf';
import { useEntityRegistry } from '../useEntityRegistry';
import { FacetFilterInput } from '../../types.generated';
import useFilters from './utils/useFilters';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
import { Message } from '../shared/Message';
import { EntitySearchResults } from './EntitySearchResults';
import { IconStyleType } from '../entity/Entity';
import { EntityGroupSearchResults } from './EntityGroupSearchResults';
const ALL_ENTITIES_TAB_NAME = 'All';
const styles = {
tabs: {
backgroundColor: '#FFFFFF',
paddingLeft: '165px',
paddingTop: '12px',
color: 'rgba(0, 0, 0, 0.45)',
},
tab: { fontSize: 20 },
};
type SearchPageParams = {
type?: string;
@ -29,101 +38,73 @@ export const SearchPage = () => {
const searchTypes = entityRegistry.getSearchEntityTypes();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const selectedType = entityRegistry.getTypeOrDefaultFromPathName(
useParams<SearchPageParams>().type || '',
undefined,
);
const activeType = selectedType || entityRegistry.getDefaultSearchEntityType();
const activeType = entityRegistry.getTypeOrDefaultFromPathName(useParams<SearchPageParams>().type || '', undefined);
const query: string = params.query ? (params.query as string) : '';
const page: number = params.page && Number(params.page as string) > 0 ? Number(params.page as string) : 1;
const filters: Array<FacetFilterInput> = useFilters(params);
const { loading, error, data } = useGetSearchResultsQuery({
variables: {
input: {
type: activeType,
query,
start: (page - 1) * SearchCfg.RESULTS_PER_PAGE,
count: SearchCfg.RESULTS_PER_PAGE,
filters,
},
},
});
if (error || (!loading && !error && !data)) {
return <Alert type="error" message={error?.message || 'Entity failed to load'} />;
}
const onSearchTypeChange = (newType: string) => {
const entityType = entityRegistry.getTypeFromCollectionName(newType);
navigateToSearchUrl({ type: entityType, query, page: 1, history, entityRegistry });
const onSearch = (q: string) => {
navigateToSearchUrl({ type: activeType, query: q, page: 1, history, entityRegistry });
};
const onFilterSelect = (selected: boolean, field: string, value: string) => {
const newFilters = selected
? [...filters, { field, value }]
: filters.filter((filter) => filter.field !== field || filter.value !== value);
const onChangeSearchType = (newType: string) => {
if (newType === ALL_ENTITIES_TAB_NAME) {
navigateToSearchUrl({ query, page: 1, history, entityRegistry });
} else {
const entityType = entityRegistry.getTypeFromCollectionName(newType);
navigateToSearchUrl({ type: entityType, query, page: 1, history, entityRegistry });
}
};
const onChangeFilters = (newFilters: Array<FacetFilterInput>) => {
navigateToSearchUrl({ type: activeType, query, page: 1, filters: newFilters, history, entityRegistry });
};
const onResultsPageChange = (newPage: number) => {
const onChangePage = (newPage: number) => {
navigateToSearchUrl({ type: activeType, query, page: newPage, filters, history, entityRegistry });
};
const toSearchResults = (elements: any) => (
<List
dataSource={elements}
renderItem={(item) => (
<List.Item style={{ padding: 32 }}>{entityRegistry.renderSearchResult(activeType, item)}</List.Item>
)}
bordered
/>
);
const searchResults = toSearchResults(data?.search?.entities || []);
return (
<SearchablePage initialQuery={query} selectedType={selectedType}>
<Layout.Content style={{ backgroundColor: 'white' }}>
<Affix offsetTop={64}>
<Tabs
tabBarStyle={{ backgroundColor: 'white', padding: '0px 165px', marginBottom: '0px' }}
activeKey={entityRegistry.getCollectionName(activeType)}
size="large"
onChange={onSearchTypeChange}
>
{searchTypes.map((t) => (
<Tabs.TabPane
tab={entityRegistry.getCollectionName(t)}
key={entityRegistry.getCollectionName(t)}
/>
))}
</Tabs>
</Affix>
{loading && <Message type="loading" content="Loading..." style={{ marginTop: '10%' }} />}
<Row style={{ width: '80%', margin: 'auto auto', backgroundColor: 'white' }}>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 16px' }} span={6}>
<SearchFilters
facets={data?.search?.facets || []}
selectedFilters={filters}
onFilterSelect={onFilterSelect}
<SearchablePage initialQuery={query} onSearch={onSearch}>
<Affix offsetTop={80}>
<Tabs
tabBarStyle={styles.tabs}
activeKey={activeType ? entityRegistry.getCollectionName(activeType) : ALL_ENTITIES_TAB_NAME}
size="large"
onChange={onChangeSearchType}
>
<Tabs.TabPane
style={styles.tab}
tab={<span style={styles.tab}>All</span>}
key={ALL_ENTITIES_TAB_NAME}
/>
{searchTypes.map((t) => (
<Tabs.TabPane
tab={
<>
{entityRegistry.getIcon(t, 16, IconStyleType.TAB_VIEW)}{' '}
<span style={styles.tab}>{entityRegistry.getCollectionName(t)}</span>
</>
}
key={entityRegistry.getCollectionName(t)}
/>
</Col>
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 16px' }} span={18}>
{data?.search && (
<SearchResults
typeName={entityRegistry.getCollectionName(activeType)}
results={searchResults}
pageStart={data?.search?.start}
pageSize={data.search?.count}
totalResults={data.search?.total}
onChangePage={onResultsPageChange}
/>
)}
</Col>
</Row>
</Layout.Content>
))}
</Tabs>
</Affix>
{activeType ? (
<EntitySearchResults
type={activeType}
page={page}
query={query}
filters={filters}
onChangeFilters={onChangeFilters}
onChangePage={onChangePage}
/>
) : (
entityRegistry
.getSearchEntityTypes()
.map((entityType) => <EntityGroupSearchResults type={entityType} query={query} />)
)}
</SearchablePage>
);
};

View File

@ -7,63 +7,48 @@ import { SearchCfg } from '../../conf';
import { useEntityRegistry } from '../useEntityRegistry';
import { useGetAutoCompleteResultsLazyQuery } from '../../graphql/search.generated';
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
import { EntityType } from '../../types.generated';
import { useGetAuthenticatedUser } from '../useGetAuthenticatedUser';
const ALL_ENTITIES_SEARCH_TYPE_NAME = 'All Entities';
const styles = {
pageContainer: { backgroundColor: '#FFFFFF' },
children: { marginTop: 80 },
};
interface Props extends React.PropsWithChildren<any> {
selectedType?: EntityType;
initialQuery?: string;
onSearch?: (query: string) => void;
onAutoComplete?: (query: string) => void;
}
const defaultProps = {
selectedType: undefined,
initialQuery: '',
onSearch: undefined,
onAutoComplete: undefined,
};
/**
* A page that includes a sticky search header (nav bar)
*/
export const SearchablePage = ({ selectedType, initialQuery, children }: Props) => {
export const SearchablePage = ({ initialQuery, onSearch, onAutoComplete, children }: Props) => {
const history = useHistory();
const entityRegistry = useEntityRegistry();
const searchTypes = entityRegistry.getSearchEntityTypes();
const { data: userData } = useGetAuthenticatedUser();
const searchTypeNames = [
ALL_ENTITIES_SEARCH_TYPE_NAME,
...searchTypes.map((entityType) => entityRegistry.getCollectionName(entityType)),
];
const selectedSearchTypeName =
selectedType && searchTypes.includes(selectedType)
? entityRegistry.getCollectionName(selectedType)
: ALL_ENTITIES_SEARCH_TYPE_NAME;
const [getAutoCompleteResults, { data: suggestionsData }] = useGetAutoCompleteResultsLazyQuery();
const search = (typeName: string, query: string) => {
const search = (query: string) => {
navigateToSearchUrl({
type:
ALL_ENTITIES_SEARCH_TYPE_NAME === typeName
? undefined
: entityRegistry.getTypeFromCollectionName(typeName),
query,
history,
entityRegistry,
});
};
const autoComplete = (type: string, query: string) => {
const entityType =
ALL_ENTITIES_SEARCH_TYPE_NAME === type ? searchTypes[0] : entityRegistry.getTypeFromCollectionName(type);
const autoComplete = (query: string) => {
getAutoCompleteResults({
variables: {
input: {
type: entityType,
type: entityRegistry.getDefaultSearchEntityType(),
query,
},
},
@ -71,21 +56,19 @@ export const SearchablePage = ({ selectedType, initialQuery, children }: Props)
};
return (
<Layout>
<Layout style={styles.pageContainer}>
<SearchHeader
types={searchTypeNames}
selectedType={selectedSearchTypeName}
initialQuery={initialQuery as string}
placeholderText={SearchCfg.SEARCH_BAR_PLACEHOLDER_TEXT}
suggestions={
(suggestionsData && suggestionsData?.autoComplete && suggestionsData.autoComplete.suggestions) || []
}
onSearch={search}
onQueryChange={autoComplete}
onSearch={onSearch || search}
onQueryChange={onAutoComplete || autoComplete}
authenticatedUserUrn={userData?.corpUser?.urn || ''}
authenticatedUserPictureLink={userData?.corpUser?.editableInfo?.pictureLink || ''}
authenticatedUserPictureLink={userData?.corpUser?.editableInfo?.pictureLink}
/>
<div style={{ marginTop: 64 }}>{children}</div>
<div style={styles.children}>{children}</div>
</Layout>
);
};

View File

@ -1,42 +1,48 @@
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { Route } from 'react-router';
import { SearchPage } from '../SearchPage';
import TestPageContainer from '../../../utils/test-utils/TestPageContainer';
import { mocks } from '../../../Mocks';
import { PageRoutes } from '../../../conf/Global';
describe('SearchPage', () => {
it('renders', () => {
render(
<MockedProvider mocks={mocks} addTypename={false}>
<TestPageContainer initialEntries={['/search/dataset?filter_platform=hive,kafka&page=1&query=sample']}>
<SearchPage />
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
</TestPageContainer>
</MockedProvider>,
);
});
it('renders loading', () => {
it('renders loading', async () => {
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<TestPageContainer initialEntries={['/search/dataset?filter_platform=hive,kafka&page=1&query=sample']}>
<SearchPage />
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
</TestPageContainer>
</MockedProvider>,
);
expect(getByText('Loading...')).toBeInTheDocument();
await waitFor(() => expect(getByText('Loading...')).toBeInTheDocument());
});
it('renders the selected filters as checked', async () => {
const { getByTestId, queryByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<TestPageContainer initialEntries={['/search/dataset?filter_platform=kafka&page=1&query=test']}>
<SearchPage />
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
</TestPageContainer>
</MockedProvider>,
);
await waitFor(() => expect(getByTestId('filters-button')).toBeInTheDocument());
const filtersButton = getByTestId('filters-button');
fireEvent.click(filtersButton);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
@ -53,11 +59,15 @@ describe('SearchPage', () => {
const { getByTestId, queryByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<TestPageContainer initialEntries={['/search/dataset?filter_platform=kafka,hdfs&page=1&query=test']}>
<SearchPage />
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
</TestPageContainer>
</MockedProvider>,
);
await waitFor(() => expect(getByTestId('filters-button')).toBeInTheDocument());
const filtersButton = getByTestId('filters-button');
fireEvent.click(filtersButton);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
@ -71,14 +81,18 @@ describe('SearchPage', () => {
});
it('clicking a filter selects a new filter', async () => {
const { getByTestId, queryByTestId, queryByText } = render(
const { getByTestId, queryByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<TestPageContainer initialEntries={['/search/dataset?filter_platform=kafka&page=1&query=test']}>
<SearchPage />
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
</TestPageContainer>
</MockedProvider>,
);
await waitFor(() => expect(getByTestId('filters-button')).toBeInTheDocument());
const filtersButton = getByTestId('filters-button');
fireEvent.click(filtersButton);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
@ -87,9 +101,7 @@ describe('SearchPage', () => {
const hdfsPlatformBox = getByTestId('facet-platform-hdfs');
expect(hdfsPlatformBox).toHaveProperty('checked', false);
expect(queryByText('Loading...')).not.toBeInTheDocument();
fireEvent.click(hdfsPlatformBox);
expect(queryByText('Loading...')).toBeInTheDocument();
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());