Experimental filters: add search params to query and unit tests (#4057)

This commit is contained in:
Krzysztof Żuraw 2023-08-07 16:08:51 +02:00 committed by GitHub
parent a63af3ab73
commit df1459949d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 192 additions and 23 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---
Experimental filters: add search params to query and unit tests

View file

@ -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();
});
});

View file

@ -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);
});
});

View file

@ -1,41 +1,41 @@
import { FilterElement } from "../FilterElement" import { FilterElement } from "../FilterElement";
const isTooLong = (value: string, row: number) => { const isTooLong = (value: string, row: number) => {
if (value.length > 25) { if (value.length > 25) {
return { return {
row, row,
rightText: "The value is too long." rightText: "The value is too long.",
} };
} }
return false return false;
} };
export const numeric = (element: FilterElement, row: number) => { export const numeric = (element: FilterElement, row: number) => {
const { value } = element.condition.selected const { value } = element.condition.selected;
if (Array.isArray(value) && value.length === 2) { if (Array.isArray(value) && value.length === 2) {
const [sLte, sGte] = value as [string, string] const [sLte, sGte] = value as [string, string];
const errorsLte = isTooLong(sLte, row) const errorsLte = isTooLong(sLte, row);
const errorsGte = isTooLong(sLte, row) const errorsGte = isTooLong(sLte, row);
if (errorsLte) return errorsGte if (errorsLte) return errorsGte;
if (errorsGte) return errorsGte if (errorsGte) return errorsGte;
const lte = parseFloat(sLte) const lte = parseFloat(sLte);
const gte = parseFloat(sGte) const gte = parseFloat(sGte);
if (lte > gte) { if (lte > gte) {
return { return {
row, row,
rightText: "The value must be higher" rightText: "The value must be higher",
} };
} }
} }
if (typeof value === "string") { if (typeof value === "string") {
return isTooLong(value, row) return isTooLong(value, row);
} }
return false return false;
} };

View file

@ -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",
});
});
});

View file

@ -33,9 +33,11 @@ export const useUrlValueProvider = (
const [value, setValue] = useState<FilterContainer>([]); const [value, setValue] = useState<FilterContainer>([]);
const activeTab = params.get("activeTab"); const activeTab = params.get("activeTab");
const query = params.get("query");
params.delete("asc"); params.delete("asc");
params.delete("sort"); params.delete("sort");
params.delete("activeTab"); params.delete("activeTab");
params.delete("query");
const tokenizedUrl = new TokenArray(params.toString()); const tokenizedUrl = new TokenArray(params.toString());
const fetchingParams = tokenizedUrl.getFetchingParams(); const fetchingParams = tokenizedUrl.getFetchingParams();
@ -56,6 +58,7 @@ export const useUrlValueProvider = (
search: stringify({ search: stringify({
...prepareStructure(filterValue), ...prepareStructure(filterValue),
...{ activeTab: activeTab || undefined }, ...{ activeTab: activeTab || undefined },
...{ query: query || undefined },
}), }),
}); });
setValue(filterValue); setValue(filterValue);

View file

@ -7,7 +7,7 @@ import { ExpressionValue } from "./FilterElement/FilterElement";
import { FilterValueProvider } from "./FilterValueProvider"; import { FilterValueProvider } from "./FilterValueProvider";
import { useContainerState } from "./useContainerState"; import { useContainerState } from "./useContainerState";
describe("useContainerState", () => { describe("ConditionalFilter / useContainerState", () => {
const valueProvider: FilterValueProvider = { const valueProvider: FilterValueProvider = {
loading: false, loading: false,
value: [], value: [],

View file

@ -4,7 +4,7 @@ import { act,renderHook } from "@testing-library/react-hooks";
import { STATIC_OPTIONS } from "./constants"; import { STATIC_OPTIONS } from "./constants";
import { useFilterLeftOperandsProvider } from "./useFilterLeftOperands"; import { useFilterLeftOperandsProvider } from "./useFilterLeftOperands";
describe("useFilterLeftOperandsProvider", () => { describe("ConditionalFilter / useFilterLeftOperandsProvider", () => {
it("should set unique operands", () => { it("should set unique operands", () => {
// Arrange // Arrange
const { result } = renderHook(() => useFilterLeftOperandsProvider()); const { result } = renderHook(() => useFilterLeftOperandsProvider());

View file

@ -13867,13 +13867,14 @@ export type InitialProductFilterProductTypesQueryHookResult = ReturnType<typeof
export type InitialProductFilterProductTypesLazyQueryHookResult = ReturnType<typeof useInitialProductFilterProductTypesLazyQuery>; export type InitialProductFilterProductTypesLazyQueryHookResult = ReturnType<typeof useInitialProductFilterProductTypesLazyQuery>;
export type InitialProductFilterProductTypesQueryResult = Apollo.QueryResult<Types.InitialProductFilterProductTypesQuery, Types.InitialProductFilterProductTypesQueryVariables>; export type InitialProductFilterProductTypesQueryResult = Apollo.QueryResult<Types.InitialProductFilterProductTypesQuery, Types.InitialProductFilterProductTypesQueryVariables>;
export const ProductListDocument = gql` 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( products(
before: $before before: $before
after: $after after: $after
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
search: $search
where: $where where: $where
sortBy: $sort sortBy: $sort
channel: $channel channel: $channel
@ -13917,6 +13918,7 @@ ${ProductListAttributeFragmentDoc}`;
* last: // value for 'last' * last: // value for 'last'
* before: // value for 'before' * before: // value for 'before'
* filter: // value for 'filter' * filter: // value for 'filter'
* search: // value for 'search'
* where: // value for 'where' * where: // value for 'where'
* channel: // value for 'channel' * channel: // value for 'channel'
* sort: // value for 'sort' * sort: // value for 'sort'

View file

@ -10677,6 +10677,7 @@ export type ProductListQueryVariables = Exact<{
last?: InputMaybe<Scalars['Int']>; last?: InputMaybe<Scalars['Int']>;
before?: InputMaybe<Scalars['String']>; before?: InputMaybe<Scalars['String']>;
filter?: InputMaybe<ProductFilterInput>; filter?: InputMaybe<ProductFilterInput>;
search?: InputMaybe<Scalars['String']>;
where?: InputMaybe<ProductWhereInput>; where?: InputMaybe<ProductWhereInput>;
channel?: InputMaybe<Scalars['String']>; channel?: InputMaybe<Scalars['String']>;
sort?: InputMaybe<ProductOrder>; sort?: InputMaybe<ProductOrder>;

View file

@ -61,6 +61,7 @@ export const productListQuery = gql`
$last: Int $last: Int
$before: String $before: String
$filter: ProductFilterInput $filter: ProductFilterInput
$search: String
$where: ProductWhereInput $where: ProductWhereInput
$channel: String $channel: String
$sort: ProductOrder $sort: ProductOrder
@ -72,6 +73,7 @@ export const productListQuery = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
search: $search
where: $where where: $where
sortBy: $sort sortBy: $sort
channel: $channel channel: $channel

View file

@ -66,7 +66,6 @@ export function getSortQueryVariables(
} }
const field = getSortQueryField(params.sort); const field = getSortQueryField(params.sort);
// TODO: apply fix after https://github.com/saleor/saleor/issues/13557 is done
return { return {
direction, direction,
field, field,