Refactor Filter type to accept Is, Is Not, Contains, Does not Contain (#128)
* Refactor Filter type to accept Is, Is Not, Contains, Does not Contain * Remove any and add tests
This commit is contained in:
@ -2,10 +2,8 @@ import { ChangeEvent, ComponentType, useState } from 'react';
|
||||
import EditableCellWrapper from './EditableCellWrapper';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSearch } from '../../services/api/search/search';
|
||||
import {
|
||||
SearchConfigType,
|
||||
SearchableType,
|
||||
} from '../../interfaces/search/interface';
|
||||
import { SearchConfigType } from '../../interfaces/search/interface';
|
||||
import { AnyEntity } from '../../interfaces/entities/generic.interface';
|
||||
|
||||
const StyledEditModeContainer = styled.div`
|
||||
width: 200px;
|
||||
@ -51,7 +49,7 @@ const StyledEditModeResultItem = styled.div`
|
||||
`;
|
||||
|
||||
export type EditableRelationProps<
|
||||
RelationType extends SearchableType,
|
||||
RelationType extends AnyEntity,
|
||||
ChipComponentPropsType,
|
||||
> = {
|
||||
relation?: RelationType | null;
|
||||
@ -66,7 +64,7 @@ export type EditableRelationProps<
|
||||
};
|
||||
|
||||
function EditableRelation<
|
||||
RelationType extends SearchableType,
|
||||
RelationType extends AnyEntity,
|
||||
ChipComponentPropsType,
|
||||
>({
|
||||
relation,
|
||||
|
||||
@ -31,7 +31,7 @@ type OwnProps<
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
availableFilters?: FilterConfigType<TData>[];
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onFiltersUpdate?: (sorts: Array<SelectedFilterType<TData>>) => void;
|
||||
onFiltersUpdate?: (filters: Array<SelectedFilterType<TData>>) => void;
|
||||
onRowSelectionChange?: (rowSelection: string[]) => void;
|
||||
};
|
||||
|
||||
@ -138,7 +138,7 @@ const Table = <
|
||||
viewName={viewName}
|
||||
viewIcon={viewIcon}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters as FilterConfigType<any>[]}
|
||||
availableFilters={availableFilters}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
onFiltersUpdate={onFiltersUpdate}
|
||||
/>
|
||||
|
||||
@ -10,18 +10,19 @@ import {
|
||||
SearchResultsType,
|
||||
useSearch,
|
||||
} from '../../../services/api/search/search';
|
||||
import { SearchableType } from '../../../interfaces/search/interface';
|
||||
|
||||
type OwnProps<TData extends FilterableFieldsType> = {
|
||||
isFilterSelected: boolean;
|
||||
availableFilters: FilterConfigType<TData>[];
|
||||
onFilterSelect: (filter: SelectedFilterType<TData>) => void;
|
||||
onFilterRemove: (filterId: SelectedFilterType<TData>['key']) => void;
|
||||
};
|
||||
|
||||
export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
availableFilters,
|
||||
onFilterSelect,
|
||||
isFilterSelected,
|
||||
onFilterRemove,
|
||||
}: OwnProps<TData>) => {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
@ -59,7 +60,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
);
|
||||
|
||||
const renderSearchResults = (
|
||||
filterSearchResults: SearchResultsType<SearchableType>,
|
||||
filterSearchResults: SearchResultsType,
|
||||
selectedFilter: FilterConfigType<TData>,
|
||||
selectedFilterOperand: FilterOperandType<TData>,
|
||||
) => {
|
||||
@ -98,7 +99,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
onClick={() => {
|
||||
setSelectedFilter(filter);
|
||||
setSelectedFilterOperand(filter.operands[0]);
|
||||
setFilterSearch(filter.searchConfig);
|
||||
filter.searchConfig && setFilterSearch(filter.searchConfig);
|
||||
setSearchInput('');
|
||||
}}
|
||||
>
|
||||
@ -126,8 +127,23 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
type="text"
|
||||
placeholder={selectedFilter.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterSearch(selectedFilter.searchConfig);
|
||||
setSearchInput(event.target.value);
|
||||
if (selectedFilter.searchConfig) {
|
||||
setFilterSearch(selectedFilter.searchConfig);
|
||||
setSearchInput(event.target.value);
|
||||
} else {
|
||||
if (event.target.value === '') {
|
||||
onFilterRemove(selectedFilter.key);
|
||||
} else {
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: event.target.value,
|
||||
displayValue: event.target.value,
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</DropdownButton.StyledSearchField>
|
||||
|
||||
@ -123,6 +123,7 @@ function TableHeader<SortField, TData extends FilterableFieldsType>({
|
||||
isFilterSelected={filters.length > 0}
|
||||
availableFilters={availableFilters || []}
|
||||
onFilterSelect={filterSelect}
|
||||
onFilterRemove={filterUnselect}
|
||||
/>
|
||||
<SortDropdownButton<SortField>
|
||||
isSortSelected={sorts.length > 0}
|
||||
|
||||
@ -3,15 +3,15 @@ import { lightTheme } from '../../../../layout/styles/themes';
|
||||
import { FilterDropdownButton } from '../FilterDropdownButton';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { SEARCH_PEOPLE_QUERY } from '../../../../services/api/search/search';
|
||||
import { SEARCH_COMPANY_QUERY } from '../../../../services/api/search/search';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data';
|
||||
import { availableFilters } from '../../../../pages/people/people-filters';
|
||||
import { Person } from '../../../../interfaces/entities/person.interface';
|
||||
import {
|
||||
FilterableFieldsType,
|
||||
SelectedFilterType,
|
||||
} from '../../../../interfaces/filters/interface';
|
||||
import { mockCompaniesData } from '../../../../pages/companies/__tests__/__data__/mock-data';
|
||||
|
||||
const component = {
|
||||
title: 'FilterDropdownButton',
|
||||
@ -27,52 +27,25 @@ type OwnProps<FilterProperties extends FilterableFieldsType> = {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
||||
variables: {
|
||||
where: undefined,
|
||||
},
|
||||
query: SEARCH_COMPANY_QUERY,
|
||||
variables: { where: { name: { _ilike: '%%' } }, limit: 5 },
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
searchResults: mockData,
|
||||
searchResults: mockCompaniesData,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
||||
variables: {
|
||||
where: {
|
||||
_or: [
|
||||
{ firstname: { _ilike: '%%' } },
|
||||
{ lastname: { _ilike: '%%' } },
|
||||
],
|
||||
},
|
||||
limit: 5,
|
||||
},
|
||||
query: SEARCH_COMPANY_QUERY,
|
||||
variables: { where: { name: { _ilike: '%Airc%' } }, limit: 5 },
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
searchResults: mockData,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
||||
variables: {
|
||||
where: {
|
||||
_or: [
|
||||
{ firstname: { _ilike: '%Jane%' } },
|
||||
{ lastname: { _ilike: '%Jane%' } },
|
||||
],
|
||||
},
|
||||
limit: 5,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
searchResults: [mockData.find((p) => p.firstname === 'Jane')],
|
||||
searchResults: mockCompaniesData.filter(
|
||||
(company) => company.name === 'Aircall',
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -101,6 +74,9 @@ const InnerRegularFilterDropdownButton = ({
|
||||
availableFilters={availableFilters}
|
||||
isFilterSelected={true}
|
||||
onFilterSelect={outerSetFilters}
|
||||
onFilterRemove={(filterId) => {
|
||||
console.log(filterId);
|
||||
}}
|
||||
/>
|
||||
</StyleDiv>
|
||||
);
|
||||
|
||||
@ -24,8 +24,8 @@ export const RegularSortAndFilterBar = ({
|
||||
const personFilter = {
|
||||
label: 'People',
|
||||
operand: {
|
||||
label: 'Include',
|
||||
id: 'include',
|
||||
label: 'Is',
|
||||
id: 'is',
|
||||
whereTemplate: (person: Person) => {
|
||||
return { email: { _eq: person.email } };
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import { RegularFilterDropdownButton } from '../__stories__/FilterDropdownButton.stories';
|
||||
|
||||
it('Checks the default top option is Include', async () => {
|
||||
it('Checks the default top option is Is', async () => {
|
||||
const setFilters = jest.fn();
|
||||
const { getByText } = render(
|
||||
<RegularFilterDropdownButton setFilter={setFilters} />,
|
||||
@ -10,27 +10,27 @@ it('Checks the default top option is Include', async () => {
|
||||
const sortDropdownButton = getByText('Filter');
|
||||
fireEvent.click(sortDropdownButton);
|
||||
|
||||
const filterByPeople = getByText('People');
|
||||
fireEvent.click(filterByPeople);
|
||||
const filterByCompany = getByText('Company');
|
||||
fireEvent.click(filterByCompany);
|
||||
|
||||
await waitFor(() => {
|
||||
const firstSearchResult = getByText('Alexandre Prot');
|
||||
const firstSearchResult = getByText('Airbnb');
|
||||
expect(firstSearchResult).toBeDefined();
|
||||
});
|
||||
|
||||
const filterByJohn = getByText('Alexandre Prot');
|
||||
fireEvent.click(filterByJohn);
|
||||
const filterByAirbnb = getByText('Airbnb');
|
||||
fireEvent.click(filterByAirbnb);
|
||||
|
||||
expect(setFilters).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
displayValue: 'Alexandre Prot',
|
||||
key: 'fullname',
|
||||
label: 'People',
|
||||
displayValue: 'Airbnb',
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Checks the selection of top option for Not Equal', async () => {
|
||||
it('Checks the selection of top option for Is Not', async () => {
|
||||
const setFilters = jest.fn();
|
||||
const { getByText } = render(
|
||||
<RegularFilterDropdownButton setFilter={setFilters} />,
|
||||
@ -39,28 +39,28 @@ it('Checks the selection of top option for Not Equal', async () => {
|
||||
const sortDropdownButton = getByText('Filter');
|
||||
fireEvent.click(sortDropdownButton);
|
||||
|
||||
const filterByPeople = getByText('People');
|
||||
fireEvent.click(filterByPeople);
|
||||
const filterByCompany = getByText('Company');
|
||||
fireEvent.click(filterByCompany);
|
||||
|
||||
const openOperandOptions = getByText('Equal');
|
||||
const openOperandOptions = getByText('Is');
|
||||
fireEvent.click(openOperandOptions);
|
||||
|
||||
const selectOperand = getByText('Not equal');
|
||||
const selectOperand = getByText('Is not');
|
||||
fireEvent.click(selectOperand);
|
||||
|
||||
await waitFor(() => {
|
||||
const firstSearchResult = getByText('Alexandre Prot');
|
||||
const firstSearchResult = getByText('Airbnb');
|
||||
expect(firstSearchResult).toBeDefined();
|
||||
});
|
||||
|
||||
const filterByJohn = getByText('Alexandre Prot');
|
||||
fireEvent.click(filterByJohn);
|
||||
const filterByAirbnb = getByText('Airbnb');
|
||||
fireEvent.click(filterByAirbnb);
|
||||
|
||||
expect(setFilters).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: 'fullname',
|
||||
displayValue: 'Alexandre Prot',
|
||||
label: 'People',
|
||||
displayValue: 'Airbnb',
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
}),
|
||||
);
|
||||
const blueSortDropdownButton = getByText('Filter');
|
||||
@ -78,13 +78,13 @@ it('Calls the filters when typing a new name', async () => {
|
||||
const sortDropdownButton = getByText('Filter');
|
||||
fireEvent.click(sortDropdownButton);
|
||||
|
||||
const filterByPeople = getByText('People');
|
||||
fireEvent.click(filterByPeople);
|
||||
const filterByCompany = getByText('Company');
|
||||
fireEvent.click(filterByCompany);
|
||||
|
||||
const filterSearch = getByPlaceholderText('People');
|
||||
const filterSearch = getByPlaceholderText('Company');
|
||||
fireEvent.click(filterSearch);
|
||||
|
||||
fireEvent.change(filterSearch, { target: { value: 'Jane' } });
|
||||
fireEvent.change(filterSearch, { target: { value: 'Airc' } });
|
||||
|
||||
await waitFor(() => {
|
||||
const loadingDiv = getByTestId('loading-search-results');
|
||||
@ -92,22 +92,22 @@ it('Calls the filters when typing a new name', async () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const firstSearchResult = getByText('Jane Doe');
|
||||
const firstSearchResult = getByText('Aircall');
|
||||
expect(firstSearchResult).toBeDefined();
|
||||
|
||||
const alexandreSearchResult = queryByText('Alexandre Prot');
|
||||
expect(alexandreSearchResult).not.toBeInTheDocument();
|
||||
const airbnbResult = queryByText('Airbnb');
|
||||
expect(airbnbResult).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
const filterByJane = getByText('Jane Doe');
|
||||
const filterByAircall = getByText('Aircall');
|
||||
|
||||
fireEvent.click(filterByJane);
|
||||
fireEvent.click(filterByAircall);
|
||||
|
||||
expect(setFilters).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: 'fullname',
|
||||
displayValue: 'Jane Doe',
|
||||
label: 'People',
|
||||
key: 'company_name',
|
||||
displayValue: 'Aircall',
|
||||
label: 'Company',
|
||||
}),
|
||||
);
|
||||
const blueSortDropdownButton = getByText('Filter');
|
||||
|
||||
Reference in New Issue
Block a user