From df1459949dd4f649142fffd48bb83b9d919b713f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=BBuraw?= <9116238+krzysztofzuraw@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:08:51 +0200 Subject: [PATCH] Experimental filters: add search params to query and unit tests (#4057) --- .changeset/funny-poems-beam.md | 5 ++ .../FilterElement/Constraint.test.ts | 35 ++++++++ .../Validation/numeric.test.ts | 41 ++++++++++ .../ConditionalFilter/Validation/numeric.ts | 36 ++++----- .../ValueProvider/UrlToken.test.ts | 81 +++++++++++++++++++ .../ValueProvider/useUrlValueProvider.ts | 3 + .../useContainerState.test.ts | 2 +- .../useFilterLeftOperands.test.ts | 4 +- src/graphql/hooks.generated.ts | 4 +- src/graphql/types.generated.ts | 1 + src/products/queries.ts | 2 + src/products/views/ProductList/sort.ts | 1 - 12 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 .changeset/funny-poems-beam.md create mode 100644 src/components/ConditionalFilter/FilterElement/Constraint.test.ts create mode 100644 src/components/ConditionalFilter/Validation/numeric.test.ts create mode 100644 src/components/ConditionalFilter/ValueProvider/UrlToken.test.ts diff --git a/.changeset/funny-poems-beam.md b/.changeset/funny-poems-beam.md new file mode 100644 index 000000000..45876b25b --- /dev/null +++ b/.changeset/funny-poems-beam.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Experimental filters: add search params to query and unit tests diff --git a/src/components/ConditionalFilter/FilterElement/Constraint.test.ts b/src/components/ConditionalFilter/FilterElement/Constraint.test.ts new file mode 100644 index 000000000..375a35a9b --- /dev/null +++ b/src/components/ConditionalFilter/FilterElement/Constraint.test.ts @@ -0,0 +1,35 @@ +import { Constraint } from "./Constraint"; + +describe("ConditionalFilter / FilterElement / Constraint", () => { + it("should get dependency for a valid slug", () => { + // Arrange & Act + const dependency = Constraint.getDependency("price"); + // Assert + expect(dependency).toBe("channel"); + }); + + it("should return null for an invalid slug", () => { + // Arrange & Act + const dependency = Constraint.getDependency("invalidSlug"); + // Assert + expect(dependency).toBeNull(); + }); + + it("should create an instance from a valid slug", () => { + // Arrange & Act + const constraint = Constraint.fromSlug("channel"); + // Assert + expect(constraint).toEqual({ + dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished"], + disabled: ["left", "condition"], + removable: false, + }); + }); + + it("should return null for an invalid slug", () => { + // Arrange & Act + const constraint = Constraint.fromSlug("invalidSlug"); + // Assert + expect(constraint).toBeNull(); + }); +}); diff --git a/src/components/ConditionalFilter/Validation/numeric.test.ts b/src/components/ConditionalFilter/Validation/numeric.test.ts new file mode 100644 index 000000000..657e472f1 --- /dev/null +++ b/src/components/ConditionalFilter/Validation/numeric.test.ts @@ -0,0 +1,41 @@ +import { Condition, FilterElement } from "../FilterElement"; +import { ConditionOptions } from "../FilterElement/ConditionOptions"; +import { ConditionSelected } from "../FilterElement/ConditionSelected"; +import { ExpressionValue } from "../FilterElement/FilterElement"; +import { numeric } from "./numeric"; + +describe("ConditionalFilter / validation / numeric", () => { + it.each([ + [ + "12345678901234567890123456", + { rightText: "The value is too long.", row: 0 }, + ], + ["123", false], + [["123", "321"], false], + [["100", "1"], { rightText: "The value must be higher", row: 0 }], + ])("should validate %p", (value, expected) => { + // Arrange + const element = new FilterElement( + new ExpressionValue("price", "Price", "price"), + new Condition( + ConditionOptions.fromStaticElementName("price"), + new ConditionSelected( + value, + { + type: "price", + value: "price", + label: "Price", + }, + [], + false, + ), + false, + ), + false, + ); + // Act + const result = numeric(element, 0); + // Assert + expect(result).toEqual(expected); + }); +}); diff --git a/src/components/ConditionalFilter/Validation/numeric.ts b/src/components/ConditionalFilter/Validation/numeric.ts index 4defa2c5d..07a892459 100644 --- a/src/components/ConditionalFilter/Validation/numeric.ts +++ b/src/components/ConditionalFilter/Validation/numeric.ts @@ -1,41 +1,41 @@ -import { FilterElement } from "../FilterElement" +import { FilterElement } from "../FilterElement"; const isTooLong = (value: string, row: number) => { if (value.length > 25) { return { row, - rightText: "The value is too long." - } + rightText: "The value is too long.", + }; } - return false -} + return false; +}; export const numeric = (element: FilterElement, row: number) => { - const { value } = element.condition.selected + const { value } = element.condition.selected; if (Array.isArray(value) && value.length === 2) { - const [sLte, sGte] = value as [string, string] - const errorsLte = isTooLong(sLte, row) - const errorsGte = isTooLong(sLte, row) + const [sLte, sGte] = value as [string, string]; + const errorsLte = isTooLong(sLte, row); + const errorsGte = isTooLong(sLte, row); - if (errorsLte) return errorsGte - if (errorsGte) return errorsGte + if (errorsLte) return errorsGte; + if (errorsGte) return errorsGte; - const lte = parseFloat(sLte) - const gte = parseFloat(sGte) + const lte = parseFloat(sLte); + const gte = parseFloat(sGte); if (lte > gte) { return { row, - rightText: "The value must be higher" - } + rightText: "The value must be higher", + }; } } if (typeof value === "string") { - return isTooLong(value, row) + return isTooLong(value, row); } - return false -} \ No newline at end of file + return false; +}; diff --git a/src/components/ConditionalFilter/ValueProvider/UrlToken.test.ts b/src/components/ConditionalFilter/ValueProvider/UrlToken.test.ts new file mode 100644 index 000000000..71ab27307 --- /dev/null +++ b/src/components/ConditionalFilter/ValueProvider/UrlToken.test.ts @@ -0,0 +1,81 @@ +import { ConditionSelected } from "../FilterElement/ConditionSelected"; +import { UrlEntry } from "./UrlToken"; + +describe("UrlEntry", () => { + it("should create an instance with a single value", () => { + // Arrange & Act + const entry = new UrlEntry("key", "value"); + // Assert + expect(entry).toEqual({ key: "value" }); + }); + + it("should create an instance with an array value", () => { + // Arrange & Act + const entry = new UrlEntry("key", ["value1", "value2"]); + // Assert + expect(entry).toEqual({ key: ["value1", "value2"] }); + }); + + it("should create an instance from a ParsedQs object", () => { + // Arrange & Act + const entry = UrlEntry.fromQs({ key: "value" }); + // Assert + expect(entry).toEqual({ key: "value" }); + }); + + it("should create an instance for an attribute condition", () => { + // Arrange + const condition = new ConditionSelected( + { + label: "5l", + slug: "value-id", + value: "value-id", + originalSlug: "5l", + }, + { + type: "DROPDOWN", + value: "bottle-size", + label: "Bottle Size", + }, + [], + false, + ); + // Act + const entry = UrlEntry.forAttribute(condition, "bottle-size"); + // Assert + expect(entry).toEqual({ "s-1.bottle-size": "value-id" }); + }); + + it.skip("should create an instance for a static condition", () => { + // Arrange + const condition = new ConditionSelected( + "value", + { + type: "price", + value: "price", + label: "Price", + }, + [], + false, + ); + // Act + const entry = UrlEntry.forStatic(condition, "price"); + // Assert + expect(entry).toEqual({ static: "value" }); + }); + + it("should return the correct info", () => { + // Arrange + const entry = new UrlEntry("s0.price", "value"); + // Act + const info = entry.getInfo(); + // Assert + expect(info).toEqual({ + key: "s0.price", + value: "value", + entryName: "price", + type: "s", + conditionKid: "is", + }); + }); +}); diff --git a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts index d8f3523d1..28f7a4e18 100644 --- a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts +++ b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts @@ -33,9 +33,11 @@ export const useUrlValueProvider = ( const [value, setValue] = useState([]); const activeTab = params.get("activeTab"); + const query = params.get("query"); params.delete("asc"); params.delete("sort"); params.delete("activeTab"); + params.delete("query"); const tokenizedUrl = new TokenArray(params.toString()); const fetchingParams = tokenizedUrl.getFetchingParams(); @@ -56,6 +58,7 @@ export const useUrlValueProvider = ( search: stringify({ ...prepareStructure(filterValue), ...{ activeTab: activeTab || undefined }, + ...{ query: query || undefined }, }), }); setValue(filterValue); diff --git a/src/components/ConditionalFilter/useContainerState.test.ts b/src/components/ConditionalFilter/useContainerState.test.ts index 806c4a98c..6591a8d65 100644 --- a/src/components/ConditionalFilter/useContainerState.test.ts +++ b/src/components/ConditionalFilter/useContainerState.test.ts @@ -7,7 +7,7 @@ import { ExpressionValue } from "./FilterElement/FilterElement"; import { FilterValueProvider } from "./FilterValueProvider"; import { useContainerState } from "./useContainerState"; -describe("useContainerState", () => { +describe("ConditionalFilter / useContainerState", () => { const valueProvider: FilterValueProvider = { loading: false, value: [], diff --git a/src/components/ConditionalFilter/useFilterLeftOperands.test.ts b/src/components/ConditionalFilter/useFilterLeftOperands.test.ts index e169c903f..b882fcd60 100644 --- a/src/components/ConditionalFilter/useFilterLeftOperands.test.ts +++ b/src/components/ConditionalFilter/useFilterLeftOperands.test.ts @@ -1,10 +1,10 @@ import { AttributeInputTypeEnum } from "@dashboard/graphql"; -import { act,renderHook } from "@testing-library/react-hooks"; +import { act, renderHook } from "@testing-library/react-hooks"; import { STATIC_OPTIONS } from "./constants"; import { useFilterLeftOperandsProvider } from "./useFilterLeftOperands"; -describe("useFilterLeftOperandsProvider", () => { +describe("ConditionalFilter / useFilterLeftOperandsProvider", () => { it("should set unique operands", () => { // Arrange const { result } = renderHook(() => useFilterLeftOperandsProvider()); diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index 1a89e4f66..d4be210cd 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -13867,13 +13867,14 @@ export type InitialProductFilterProductTypesQueryHookResult = ReturnType; export type InitialProductFilterProductTypesQueryResult = Apollo.QueryResult; export const ProductListDocument = gql` - query ProductList($first: Int, $after: String, $last: Int, $before: String, $filter: ProductFilterInput, $where: ProductWhereInput, $channel: String, $sort: ProductOrder, $hasChannel: Boolean!) { + query ProductList($first: Int, $after: String, $last: Int, $before: String, $filter: ProductFilterInput, $search: String, $where: ProductWhereInput, $channel: String, $sort: ProductOrder, $hasChannel: Boolean!) { products( before: $before after: $after first: $first last: $last filter: $filter + search: $search where: $where sortBy: $sort channel: $channel @@ -13917,6 +13918,7 @@ ${ProductListAttributeFragmentDoc}`; * last: // value for 'last' * before: // value for 'before' * filter: // value for 'filter' + * search: // value for 'search' * where: // value for 'where' * channel: // value for 'channel' * sort: // value for 'sort' diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index 5f1e511c0..34064cce1 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -10677,6 +10677,7 @@ export type ProductListQueryVariables = Exact<{ last?: InputMaybe; before?: InputMaybe; filter?: InputMaybe; + search?: InputMaybe; where?: InputMaybe; channel?: InputMaybe; sort?: InputMaybe; diff --git a/src/products/queries.ts b/src/products/queries.ts index be4016b2c..7e48911ba 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -61,6 +61,7 @@ export const productListQuery = gql` $last: Int $before: String $filter: ProductFilterInput + $search: String $where: ProductWhereInput $channel: String $sort: ProductOrder @@ -72,6 +73,7 @@ export const productListQuery = gql` first: $first last: $last filter: $filter + search: $search where: $where sortBy: $sort channel: $channel diff --git a/src/products/views/ProductList/sort.ts b/src/products/views/ProductList/sort.ts index bb30b9d13..63bf7313e 100644 --- a/src/products/views/ProductList/sort.ts +++ b/src/products/views/ProductList/sort.ts @@ -66,7 +66,6 @@ export function getSortQueryVariables( } const field = getSortQueryField(params.sort); - // TODO: apply fix after https://github.com/saleor/saleor/issues/13557 is done return { direction, field,