From 3917e13613c96fc4c4f83c7c431e75c2a704800f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 6 Sep 2019 14:58:44 +0200 Subject: [PATCH] Refacto filters to handle multiple values --- src/components/Filter/Filter.tsx | 4 +- src/components/Filter/FilterContent.tsx | 14 +- src/components/Filter/FilterElement.tsx | 8 +- src/components/Filter/types.ts | 4 +- src/components/FilterBar/FilterBar.tsx | 6 +- src/components/TableFilter/FilterChips.tsx | 6 +- src/fixtures.ts | 2 +- .../OrderListFilter/OrderListFilter.tsx | 33 ++-- .../OrderListPage/OrderListPage.tsx | 3 +- .../__snapshots__/filters.test.ts.snap | 109 ++++++++++++ src/orders/views/OrderList/filters.test.ts | 155 ++++++++++++++++++ src/orders/views/OrderList/filters.ts | 12 +- .../ProductListFilter/ProductListFilter.tsx | 28 ++-- .../ProductListPage/ProductListPage.tsx | 4 +- .../__snapshots__/filters.test.ts.snap | 66 ++++++++ .../views/ProductList/filters.test.ts | 77 +++++++++ src/products/views/ProductList/filters.ts | 10 +- src/types.ts | 7 +- .../menu/__snapshots__/menu.test.ts.snap | 28 ++-- src/utils/menu/menu.test.ts | 23 ++- src/utils/menu/menu.ts | 126 ++++++++------ 21 files changed, 581 insertions(+), 144 deletions(-) create mode 100644 src/orders/views/OrderList/__snapshots__/filters.test.ts.snap create mode 100644 src/orders/views/OrderList/filters.test.ts create mode 100644 src/products/views/ProductList/__snapshots__/filters.test.ts.snap create mode 100644 src/products/views/ProductList/filters.test.ts diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index a68e682df..dbe641496 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -19,9 +19,9 @@ import { FilterContent } from "."; import { FilterContentSubmitData } from "./FilterContent"; import { IFilter } from "./types"; -export interface FilterProps { +export interface FilterProps { currencySymbol: string; - menu: IFilter; + menu: IFilter; filterLabel: string; onFilterAdd: (filter: FilterContentSubmitData) => void; } diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index bf6b4b1d1..1ad5d77ee 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -10,13 +10,13 @@ import SingleSelectField from "../SingleSelectField"; import FilterElement from "./FilterElement"; import { IFilter } from "./types"; -export interface FilterContentSubmitData { - name: string; +export interface FilterContentSubmitData { + name: TKeys; value: string | string[]; } export interface FilterContentProps { currencySymbol: string; - filters: IFilter; + filters: IFilter; onSubmit: (data: FilterContentSubmitData) => void; } @@ -27,10 +27,10 @@ function checkFilterValue(value: string | string[]): boolean { return value.some(v => !!v); } -function getFilterChoices(items: IFilter) { +function getFilterChoices(items: IFilter) { return items.map(filterItem => ({ label: filterItem.label, - value: filterItem.value + value: filterItem.value.toString() })); } @@ -46,7 +46,7 @@ const FilterContent: React.FC = ({ onSubmit }) => { const intl = useIntl(); - const [menuValue, setMenuValue] = React.useState(""); + const [menuValue, setMenuValue] = React.useState(null); const [filterValue, setFilterValue] = React.useState(""); const classes = useStyles({}); @@ -95,7 +95,7 @@ const FilterContent: React.FC = ({ }} value={ filterItemIndex === menus.length - 1 - ? menuValue + ? menuValue.toString() : menus[filterItemIndex - 1].label.toString() } placeholder={intl.formatMessage({ diff --git a/src/components/Filter/FilterElement.tsx b/src/components/Filter/FilterElement.tsx index da4e1d8f6..00dc915e4 100644 --- a/src/components/Filter/FilterElement.tsx +++ b/src/components/Filter/FilterElement.tsx @@ -10,9 +10,9 @@ import PriceField from "../PriceField"; import SingleSelectField from "../SingleSelectField"; import { FieldType, IFilterItem } from "./types"; -export interface FilterElementProps { +export interface FilterElementProps { className?: string; - filter: IFilterItem; + filter: IFilterItem; value: string | string[]; onChange: (value: string | string[]) => void; } @@ -26,10 +26,10 @@ const useStyles = makeStyles({ } }); -export interface FilterElementProps { +export interface FilterElementProps { className?: string; currencySymbol: string; - filter: IFilterItem; + filter: IFilterItem; value: string | string[]; onChange: (value: string | string[]) => void; } diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index 53bf9302a..c991801b8 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -25,6 +25,6 @@ export interface FilterData { value?: string; } -export type IFilterItem = IMenuItem; +export type IFilterItem = IMenuItem; -export type IFilter = IMenu; +export type IFilter = IMenu; diff --git a/src/components/FilterBar/FilterBar.tsx b/src/components/FilterBar/FilterBar.tsx index 7f81046c5..e55ca25da 100644 --- a/src/components/FilterBar/FilterBar.tsx +++ b/src/components/FilterBar/FilterBar.tsx @@ -6,9 +6,9 @@ import Debounce from "../Debounce"; import { IFilter } from "../Filter/types"; import FilterTabs, { FilterChips, FilterTab } from "../TableFilter"; -export interface FilterBarProps - extends FilterProps { - filterMenu: IFilter; +export interface FilterBarProps + extends FilterProps { + filterMenu: IFilter; } const FilterBar: React.FC = ({ diff --git a/src/components/TableFilter/FilterChips.tsx b/src/components/TableFilter/FilterChips.tsx index 176c53b44..180c26aa4 100644 --- a/src/components/TableFilter/FilterChips.tsx +++ b/src/components/TableFilter/FilterChips.tsx @@ -99,16 +99,16 @@ const useStyles = makeStyles( } ); -interface FilterChipProps { +interface FilterChipProps { currencySymbol: string; - menu: IFilter; + menu: IFilter; filtersList: Filter[]; filterLabel: string; placeholder: string; search: string; isCustomSearch: boolean; onSearchChange: (event: React.ChangeEvent) => void; - onFilterAdd: (filter: FilterContentSubmitData) => void; + onFilterAdd: (filter: FilterContentSubmitData) => void; onFilterDelete: () => void; onFilterSave: () => void; } diff --git a/src/fixtures.ts b/src/fixtures.ts index b55c9d863..2b290708b 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -46,7 +46,7 @@ export const countries = [ { code: "AS", label: "American Samoa" } ]; -export const filterPageProps: FilterPageProps<{}> = { +export const filterPageProps: FilterPageProps<{}, unknown> = { currencySymbol: "USD", currentTab: 0, filterTabs: [ diff --git a/src/orders/components/OrderListFilter/OrderListFilter.tsx b/src/orders/components/OrderListFilter/OrderListFilter.tsx index 676a3548c..06d9fa0d5 100644 --- a/src/orders/components/OrderListFilter/OrderListFilter.tsx +++ b/src/orders/components/OrderListFilter/OrderListFilter.tsx @@ -10,17 +10,16 @@ import { FilterProps } from "../../../types"; import { OrderStatusFilter } from "../../../types/globalTypes"; import { OrderListUrlFilters } from "../../urls"; -type OrderListFilterProps = FilterProps; +type OrderListFilterProps = FilterProps; export enum OrderFilterKeys { - date, - dateEqual, - dateRange, - dateLastWeek, - dateLastMonth, - dateLastYear, - email, - fulfillment + date = "date", + dateEqual = "dateEqual", + dateRange = "dateRange", + dateLastWeek = "dateLastWeek", + dateLastMonth = "dateLastMonth", + dateLastYear = "dateLastYear", + fulfillment = "fulfillment" } const OrderListFilter: React.FC = props => { @@ -28,7 +27,7 @@ const OrderListFilter: React.FC = props => { const tz = React.useContext(TimezoneContext); const intl = useIntl(); - const filterMenu: IFilter = [ + const filterMenu: IFilter = [ { children: [ { @@ -44,7 +43,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Last 7 Days" }), - value: OrderFilterKeys.dateLastWeek.toString() + value: OrderFilterKeys.dateLastWeek }, { children: [], @@ -59,7 +58,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Last 30 Days" }), - value: OrderFilterKeys.dateLastMonth.toString() + value: OrderFilterKeys.dateLastMonth }, { children: [], @@ -74,7 +73,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Last Year" }), - value: OrderFilterKeys.dateLastYear.toString() + value: OrderFilterKeys.dateLastYear }, { children: [], @@ -88,7 +87,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Specific Date" }), - value: OrderFilterKeys.dateEqual.toString() + value: OrderFilterKeys.dateEqual }, { children: [], @@ -101,7 +100,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Range" }), - value: OrderFilterKeys.dateRange.toString() + value: OrderFilterKeys.dateRange } ], data: { @@ -113,7 +112,7 @@ const OrderListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Date" }), - value: OrderFilterKeys.date.toString() + value: OrderFilterKeys.date }, { children: [], @@ -155,7 +154,7 @@ const OrderListFilter: React.FC = props => { defaultMessage: "Fulfillment Status", description: "order" }), - value: OrderFilterKeys.fulfillment.toString() + value: OrderFilterKeys.fulfillment } ]; diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 3ccd68653..a4cc2db64 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -7,6 +7,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; +import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter"; import { FilterPageProps, ListActions, PageListProps } from "@saleor/types"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import { OrderListUrlFilters } from "../../urls"; @@ -16,7 +17,7 @@ import OrderListFilter from "../OrderListFilter"; export interface OrderListPageProps extends PageListProps, ListActions, - FilterPageProps { + FilterPageProps { orders: OrderList_orders_edges_node[]; } diff --git a/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..8c70c08ce --- /dev/null +++ b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Crate filter chips 1`] = ` +Array [ + Object { + "label": "Date from 2019-09-01", + "onClick": [Function], + }, + Object { + "label": "Date to 2019-09-10", + "onClick": [Function], + }, + Object { + "label": "Fulfilled", + "onClick": [Function], + }, + Object { + "label": "Partially Fulfilled", + "onClick": [Function], + }, +] +`; + +exports[`Create filter object with date 1`] = ` +Object { + "dateFrom": "2019-09-01", + "dateTo": "2019-09-01", +} +`; + +exports[`Create filter object with date last month 1`] = ` +Object { + "dateFrom": "2019-09-01", + "dateTo": undefined, +} +`; + +exports[`Create filter object with date last week 1`] = ` +Object { + "dateFrom": "2019-09-01", + "dateTo": undefined, +} +`; + +exports[`Create filter object with date last year 1`] = ` +Object { + "dateFrom": "2019-09-01", + "dateTo": undefined, +} +`; + +exports[`Create filter object with date range 1`] = ` +Object { + "dateFrom": "2019-09-01", + "dateTo": "2019-09-10", +} +`; + +exports[`Create filter object with fulfillment status 1`] = ` +Object { + "status": Array [ + "PARTIALLY_FULFILLED", + ], +} +`; + +exports[`Create filter object with multiple deduped values 1`] = ` +Object { + "status": Array [ + "FULFILLED", + ], +} +`; + +exports[`Create filter object with multiple values 1`] = ` +Object { + "status": Array [ + "FULFILLED", + "PARTIALLY_FULFILLED", + ], +} +`; + +exports[`Get filter variables from multiple status value 1`] = ` +Object { + "created": Object { + "gte": "2019-09-01", + "lte": "2019-09-10", + }, + "customer": "email@example.com", + "status": Array [ + "FULFILLED", + "PARTIALLY_FULFILLED", + ], +} +`; + +exports[`Get filter variables from single status value 1`] = ` +Object { + "created": Object { + "gte": "2019-09-01", + "lte": "2019-09-10", + }, + "customer": "email@example.com", + "status": Array [ + "FULFILLED", + ], +} +`; diff --git a/src/orders/views/OrderList/filters.test.ts b/src/orders/views/OrderList/filters.test.ts new file mode 100644 index 000000000..6217678b0 --- /dev/null +++ b/src/orders/views/OrderList/filters.test.ts @@ -0,0 +1,155 @@ +import { createIntl } from "react-intl"; + +import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter"; +import { OrderStatus, OrderStatusFilter } from "@saleor/types/globalTypes"; +import { createFilter, createFilterChips, getFilterVariables } from "./filters"; + +const mockIntl = createIntl({ + locale: "en" +}); + +describe("Create filter object", () => { + it("with date", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.dateEqual, + value: "2019-09-01" + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with date range", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.dateRange, + value: ["2019-09-01", "2019-09-10"] + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with date last week", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.dateLastWeek, + value: "2019-09-01" + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with date last month", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.dateLastMonth, + value: "2019-09-01" + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with date last year", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.dateLastYear, + value: "2019-09-01" + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with fulfillment status", () => { + const filter = createFilter( + {}, + { + name: OrderFilterKeys.fulfillment, + value: OrderStatusFilter.PARTIALLY_FULFILLED + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with multiple values", () => { + const filter = createFilter( + { + status: [OrderStatusFilter.FULFILLED] + }, + { + name: OrderFilterKeys.fulfillment, + value: OrderStatusFilter.PARTIALLY_FULFILLED + } + ); + + expect(filter).toMatchSnapshot(); + }); + + it("with multiple deduped values", () => { + const filter = createFilter( + { + status: [OrderStatusFilter.FULFILLED] + }, + { + name: OrderFilterKeys.fulfillment, + value: OrderStatusFilter.FULFILLED + } + ); + + expect(filter).toMatchSnapshot(); + }); +}); + +test("Crate filter chips", () => { + const chips = createFilterChips( + { + dateFrom: "2019-09-01", + dateTo: "2019-09-10", + status: [OrderStatus.FULFILLED, OrderStatus.PARTIALLY_FULFILLED] + }, + { + formatDate: date => date + }, + jest.fn(), + mockIntl as any + ); + + expect(chips).toMatchSnapshot(); +}); + +describe("Get filter variables", () => { + it("from single status value", () => { + const filter = getFilterVariables({ + dateFrom: "2019-09-01", + dateTo: "2019-09-10", + email: "email@example.com", + status: OrderStatus.FULFILLED.toString() + }); + + expect(filter).toMatchSnapshot(); + }); + + it("from multiple status value", () => { + const filter = getFilterVariables({ + dateFrom: "2019-09-01", + dateTo: "2019-09-10", + email: "email@example.com", + status: [ + OrderStatus.FULFILLED.toString(), + OrderStatus.PARTIALLY_FULFILLED.toString() + ] + }); + + expect(filter).toMatchSnapshot(); + }); +}); diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 017196a2d..46ad23977 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -86,15 +86,15 @@ export function getFilterVariables( export function createFilter( filter: OrderListUrlFilters, - data: FilterContentSubmitData + data: FilterContentSubmitData ): OrderListUrlFilters { const { name: filterName, value } = data; - if (filterName === OrderFilterKeys.dateEqual.toString()) { + if (filterName === OrderFilterKeys.dateEqual) { return { dateFrom: valueOrFirst(value), dateTo: valueOrFirst(value) }; - } else if (filterName === OrderFilterKeys.dateRange.toString()) { + } else if (filterName === OrderFilterKeys.dateRange) { return { dateFrom: value[0], dateTo: value[1] @@ -104,15 +104,13 @@ export function createFilter( OrderFilterKeys.dateLastWeek, OrderFilterKeys.dateLastMonth, OrderFilterKeys.dateLastYear - ] - .map(value => value.toString()) - .includes(filterName) + ].includes(filterName) ) { return { dateFrom: valueOrFirst(value), dateTo: undefined }; - } else if (filterName === OrderFilterKeys.fulfillment.toString()) { + } else if (filterName === OrderFilterKeys.fulfillment) { return { status: dedupeFilter( filter.status diff --git a/src/products/components/ProductListFilter/ProductListFilter.tsx b/src/products/components/ProductListFilter/ProductListFilter.tsx index deeaca78b..6bd04e778 100644 --- a/src/products/components/ProductListFilter/ProductListFilter.tsx +++ b/src/products/components/ProductListFilter/ProductListFilter.tsx @@ -7,21 +7,23 @@ import { FilterProps } from "@saleor/types"; import { StockAvailability } from "@saleor/types/globalTypes"; import { ProductListUrlFilters } from "../../urls"; -type ProductListFilterProps = FilterProps; +type ProductListFilterProps = FilterProps< + ProductListUrlFilters, + ProductFilterKeys +>; export enum ProductFilterKeys { - published, - price, - priceEqual, - priceRange, - stock, - query + published = "published", + price = "price", + priceEqual = "priceEqual", + priceRange = "priceRange", + stock = "stock" } const ProductListFilter: React.FC = props => { const intl = useIntl(); - const filterMenu: IFilter = [ + const filterMenu: IFilter = [ { children: [], data: { @@ -55,7 +57,7 @@ const ProductListFilter: React.FC = props => { defaultMessage: "Visibility", description: "product visibility" }), - value: ProductFilterKeys.published.toString() + value: ProductFilterKeys.published }, { children: [], @@ -85,7 +87,7 @@ const ProductListFilter: React.FC = props => { defaultMessage: "Stock", description: "product stock" }), - value: ProductFilterKeys.stock.toString() + value: ProductFilterKeys.stock }, { children: [ @@ -102,7 +104,7 @@ const ProductListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Specific Price" }), - value: ProductFilterKeys.priceEqual.toString() + value: ProductFilterKeys.priceEqual }, { children: [], @@ -115,7 +117,7 @@ const ProductListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Range" }), - value: ProductFilterKeys.priceRange.toString() + value: ProductFilterKeys.priceRange } ], data: { @@ -127,7 +129,7 @@ const ProductListFilter: React.FC = props => { label: intl.formatMessage({ defaultMessage: "Price" }), - value: ProductFilterKeys.price.toString() + value: ProductFilterKeys.price } ]; diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index ec18734b2..fb083d483 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -26,12 +26,12 @@ import { } from "@saleor/types"; import { ProductListUrlFilters } from "../../urls"; import ProductList from "../ProductList"; -import ProductListFilter from "../ProductListFilter"; +import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter"; export interface ProductListPageProps extends PageListProps, ListActions, - FilterPageProps, + FilterPageProps, FetchMoreProps { availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[]; currencySymbol: string; diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..20fe2f275 --- /dev/null +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Crate filter chips 1`] = ` +Array [ + Object { + "label": "Price from $10.00", + "onClick": [Function], + }, + Object { + "label": "Price to $20.00", + "onClick": [Function], + }, + Object { + "label": "Available", + "onClick": [Function], + }, + Object { + "label": "Published", + "onClick": [Function], + }, +] +`; + +exports[`Create filter object with price 1`] = ` +Object { + "priceFrom": "10", + "priceTo": "10", +} +`; + +exports[`Create filter object with price range 1`] = ` +Object { + "priceFrom": Array [ + "10", + "20", + ], + "priceTo": Array [ + "10", + "20", + ], +} +`; + +exports[`Create filter object with publication status 1`] = ` +Object { + "isPublished": "false", +} +`; + +exports[`Create filter object with stock status 1`] = ` +Object { + "status": "OUT_OF_STOCK", +} +`; + +exports[`Get filter variables 1`] = ` +Object { + "isPublished": true, + "price": Object { + "gte": 10, + "lte": 20, + }, + "search": undefined, + "stockAvailability": "IN_STOCK", +} +`; diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts new file mode 100644 index 000000000..cdb06af94 --- /dev/null +++ b/src/products/views/ProductList/filters.test.ts @@ -0,0 +1,77 @@ +import { createIntl } from "react-intl"; + +import { ProductFilterKeys } from "@saleor/products/components/ProductListFilter"; +import { StockAvailability } from "@saleor/types/globalTypes"; +import { createFilter, createFilterChips, getFilterVariables } from "./filters"; + +const mockIntl = createIntl({ + locale: "en" +}); + +describe("Create filter object", () => { + it("with price", () => { + const filter = createFilter({ + name: ProductFilterKeys.priceEqual, + value: "10" + }); + + expect(filter).toMatchSnapshot(); + }); + + it("with price range", () => { + const filter = createFilter({ + name: ProductFilterKeys.priceEqual, + value: ["10", "20"] + }); + + expect(filter).toMatchSnapshot(); + }); + + it("with publication status", () => { + const filter = createFilter({ + name: ProductFilterKeys.published, + value: "false" + }); + + expect(filter).toMatchSnapshot(); + }); + + it("with stock status", () => { + const filter = createFilter({ + name: ProductFilterKeys.stock, + value: StockAvailability.OUT_OF_STOCK + }); + + expect(filter).toMatchSnapshot(); + }); +}); + +test("Crate filter chips", () => { + const chips = createFilterChips( + { + isPublished: "true", + priceFrom: "10", + priceTo: "20", + status: StockAvailability.IN_STOCK + }, + { + currencySymbol: "USD", + locale: "en" + }, + jest.fn(), + mockIntl as any + ); + + expect(chips).toMatchSnapshot(); +}); + +test("Get filter variables", () => { + const filter = getFilterVariables({ + isPublished: "true", + priceFrom: "10", + priceTo: "20", + status: StockAvailability.IN_STOCK + }); + + expect(filter).toMatchSnapshot(); +}); diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 3368e19b3..5a47fc9aa 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -34,26 +34,26 @@ export function getFilterVariables( } export function createFilter( - filter: FilterContentSubmitData + filter: FilterContentSubmitData ): ProductListUrlFilters { const filterName = filter.name; - if (filterName === ProductFilterKeys.priceEqual.toString()) { + if (filterName === ProductFilterKeys.priceEqual) { const value = filter.value as string; return { priceFrom: value, priceTo: value }; - } else if (filterName === ProductFilterKeys.priceRange.toString()) { + } else if (filterName === ProductFilterKeys.priceRange) { const { value } = filter; return { priceFrom: value[0], priceTo: value[1] }; - } else if (filterName === ProductFilterKeys.published.toString()) { + } else if (filterName === ProductFilterKeys.published) { return { isPublished: filter.value as string }; - } else if (filterName === ProductFilterKeys.stock.toString()) { + } else if (filterName === ProductFilterKeys.stock) { const value = filter.value as string; return { status: StockAvailability[value] diff --git a/src/types.ts b/src/types.ts index 27deb132c..68de233fb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,7 +65,7 @@ export interface PageListProps defaultSettings?: ListSettings; onAdd: () => void; } -export interface FilterPageProps { +export interface FilterPageProps { currencySymbol: string; currentTab: number; filterTabs: GetFilterTabsOutput; @@ -73,12 +73,13 @@ export interface FilterPageProps { initialSearch: string; onAll: () => void; onSearchChange: (value: string) => void; - onFilterAdd: (filter: FilterContentSubmitData) => void; + onFilterAdd: (filter: FilterContentSubmitData) => void; onFilterDelete: () => void; onFilterSave: () => void; onTabChange: (tab: number) => void; } -export interface FilterProps extends FilterPageProps { +export interface FilterProps + extends FilterPageProps { allTabLabel: string; filterLabel: string; searchPlaceholder: string; diff --git a/src/utils/menu/__snapshots__/menu.test.ts.snap b/src/utils/menu/__snapshots__/menu.test.ts.snap index 045953f35..c21c5d0bb 100644 --- a/src/utils/menu/__snapshots__/menu.test.ts.snap +++ b/src/utils/menu/__snapshots__/menu.test.ts.snap @@ -8,7 +8,7 @@ Array [ "label": "1", "parent": null, "sort": 0, - "value": "1", + "value": 0, }, Object { "data": null, @@ -16,7 +16,7 @@ Array [ "label": "2", "parent": null, "sort": 1, - "value": "2", + "value": 1, }, Object { "data": null, @@ -24,7 +24,7 @@ Array [ "label": "3", "parent": null, "sort": 2, - "value": "3", + "value": 2, }, Object { "data": null, @@ -32,7 +32,7 @@ Array [ "label": "4", "parent": null, "sort": 3, - "value": "4", + "value": 3, }, Object { "data": null, @@ -40,7 +40,7 @@ Array [ "label": "4.1", "parent": "3", "sort": 0, - "value": "4.1", + "value": 4, }, Object { "data": null, @@ -48,7 +48,7 @@ Array [ "label": "4.2", "parent": "3", "sort": 1, - "value": "4.2", + "value": 5, }, ] `; @@ -61,24 +61,24 @@ Array [ "children": Array [], "data": null, "label": "4.1", - "value": "4.1", + "value": 4, }, Object { "children": Array [], "data": null, "label": "4.2", - "value": "4.2", + "value": 5, }, ], "data": null, "label": "4", - "value": "4", + "value": 3, }, Object { "children": Array [], "data": null, "label": "4.1", - "value": "4.1", + "value": 4, }, ] `; @@ -89,7 +89,7 @@ Array [ "children": Array [], "data": null, "label": "4.1", - "value": "4.1", + "value": 4, }, Object { "children": Array [ @@ -97,18 +97,18 @@ Array [ "children": Array [], "data": null, "label": "4.1", - "value": "4.1", + "value": 4, }, Object { "children": Array [], "data": null, "label": "4.2", - "value": "4.2", + "value": 5, }, ], "data": null, "label": "4", - "value": "4", + "value": 3, }, ] `; diff --git a/src/utils/menu/menu.test.ts b/src/utils/menu/menu.test.ts index 9b6d6b65b..2e42c134e 100644 --- a/src/utils/menu/menu.test.ts +++ b/src/utils/menu/menu.test.ts @@ -9,24 +9,33 @@ import { walkToRoot } from "./menu"; -const validMenu: IMenu = [ +enum MenuKey { + one, + two, + three, + four, + fourOne, + foutTwo +} + +const validMenu: IMenu = [ { children: [], data: null, label: "1", - value: "1" + value: MenuKey.one }, { children: [], data: null, label: "2", - value: "2" + value: MenuKey.two }, { children: [], data: null, label: "3", - value: "3" + value: MenuKey.three }, { children: [ @@ -34,18 +43,18 @@ const validMenu: IMenu = [ children: [], data: null, label: "4.1", - value: "4.1" + value: MenuKey.fourOne }, { children: [], data: null, label: "4.2", - value: "4.2" + value: MenuKey.foutTwo } ], data: null, label: "4", - value: "4" + value: MenuKey.four } ]; diff --git a/src/utils/menu/menu.ts b/src/utils/menu/menu.ts index 4abae4363..55fba25ba 100644 --- a/src/utils/menu/menu.ts +++ b/src/utils/menu/menu.ts @@ -1,62 +1,76 @@ -interface IBaseMenuItem { +interface IBaseMenuItem { label: React.ReactNode; - value?: string; + value?: TValue; data: TMenuData | null; } -export type IFlatMenuItem = IBaseMenuItem & { +export type IFlatMenuItem = IBaseMenuItem< + TMenuData, + TValue +> & { id: string; parent: string | null; sort: number; }; -export type IMenuItem = IBaseMenuItem & { - children: Array>; +export type IMenuItem = IBaseMenuItem< + TMenuData, + TValue +> & { + children: Array>; }; -export type IMenu = Array>; -export type IFlatMenu = Array>; +export type IMenu = Array< + IMenuItem +>; +export type IFlatMenu = Array< + IFlatMenuItem +>; -export function validateMenuOptions( - menu: IMenu +export function validateMenuOptions( + menu: IMenu ): boolean { - const values: string[] = toFlat(menu) + const values: TValue[] = toFlat(menu) .map(menuItem => menuItem.value) .filter(value => value !== undefined); const uniqueValues = Array.from(new Set(values)); return uniqueValues.length === values.length; } -function _getMenuItemByPath( - menuItem: IMenuItem, +function _getMenuItemByPath( + menuItem: IMenuItem, path: number[] -): IMenuItem { +): IMenuItem { if (path.length === 0) { return menuItem; } return _getMenuItemByPath(menuItem.children[path[0]], path.slice(1)); } -export function getMenuItemByPath( - menu: IMenu, +export function getMenuItemByPath( + menu: IMenu, path: number[] -): IMenuItem { +): IMenuItem { return _getMenuItemByPath(menu[path[0]], path.slice(1)); } -export function getMenuItemByValue( - menu: IMenu, - value: string -): IMenuItem { +export function getMenuItemByValue( + menu: IMenu, + value: TValue +): IMenuItem { const flatMenu = toFlat(menu); - const flatMenuItem: IFlatMenuItem = flatMenu.find( + const flatMenuItem: IFlatMenuItem = flatMenu.find( menuItem => menuItem.value === value ); + if (flatMenuItem === undefined) { + throw new Error(`Value ${value} does not exist in menu`); + } + return _fromFlat(flatMenu, flatMenuItem); } -function _walkToMenuItem( - menuItem: IMenuItem, +function _walkToMenuItem( + menuItem: IMenuItem, path: number[] -): IMenu { +): IMenu { const node = menuItem.children[path[0]]; if (path.length === 1) { @@ -66,18 +80,18 @@ function _walkToMenuItem( return [node, ..._walkToMenuItem(node, path.slice(1))]; } -export function walkToMenuItem( - menu: IMenu, +export function walkToMenuItem( + menu: IMenu, path: number[] -): IMenu { +): IMenu { const walkByNode = menu[path[0]]; return [walkByNode, ..._walkToMenuItem(walkByNode, path.slice(1))]; } -function _walkToRoot( - flatMenu: IFlatMenu, +function _walkToRoot( + flatMenu: IFlatMenu, parent: string -): IFlatMenu { +): IFlatMenu { const menuItem = flatMenu.find(menuItem => menuItem.id === parent); if (menuItem.parent === null) { @@ -86,10 +100,10 @@ function _walkToRoot( return [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)]; } -export function walkToRoot( - menu: IMenu, - value: string -): IMenu { +export function walkToRoot( + menu: IMenu, + value: TValue +): IMenu { const flatMenu = toFlat(menu); const menuItem = flatMenu.find(menuItem => menuItem.value === value); @@ -99,13 +113,13 @@ export function walkToRoot( ).map(flatMenuItem => _fromFlat(flatMenu, flatMenuItem)); } -function _toFlat( - menuItem: IMenuItem, +function _toFlat( + menuItem: IMenuItem, sort: number, parent: string -): IFlatMenu { +): IFlatMenu { const id = parent ? [parent, sort].join(":") : sort.toString(); - const flatMenuItem: IFlatMenuItem = { + const flatMenuItem: IFlatMenuItem = { data: menuItem.data, id, label: menuItem.label, @@ -117,22 +131,28 @@ function _toFlat( flatMenuItem, ...menuItem.children .map((child, childIndex) => _toFlat(child, childIndex, id)) - .reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu) + .reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu< + TMenuData, + TValue + >) ]; } -export function toFlat( - menu: IMenu -): IFlatMenu { +export function toFlat( + menu: IMenu +): IFlatMenu { return menu .map((menuItem, menuItemIndex) => _toFlat(menuItem, menuItemIndex, null)) - .reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu); + .reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu< + TMenuData, + TValue + >); } -function _fromFlat( - menu: IFlatMenu, - flatMenuItem: IFlatMenuItem -): IMenuItem { - const children: Array> = menu +function _fromFlat( + menu: IFlatMenu, + flatMenuItem: IFlatMenuItem +): IMenuItem { + const children: Array> = menu .filter(menuItem => menuItem.parent === flatMenuItem.id) .map(menuItem => _fromFlat(menu, menuItem)); @@ -143,16 +163,16 @@ function _fromFlat( value: flatMenuItem.value }; } -export function fromFlat( - menu: IFlatMenu -): IMenu { +export function fromFlat( + menu: IFlatMenu +): IMenu { return menu .filter(menuItem => menuItem.parent === null) .map(menuItem => _fromFlat(menu, menuItem)); } -export function isLeaf( - menuItem: IMenuItem +export function isLeaf( + menuItem: IMenuItem ): boolean { return menuItem.children.length === 0; }