Experimental filters: add search params to query and unit tests (#4057)
This commit is contained in:
parent
a63af3ab73
commit
df1459949d
12 changed files with 192 additions and 23 deletions
5
.changeset/funny-poems-beam.md
Normal file
5
.changeset/funny-poems-beam.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Experimental filters: add search params to query and unit tests
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
41
src/components/ConditionalFilter/Validation/numeric.test.ts
Normal file
41
src/components/ConditionalFilter/Validation/numeric.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
@ -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",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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);
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { AttributeInputTypeEnum } from "@dashboard/graphql";
|
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 { 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());
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue