Experimental filters: add unit tests and bump UI library (#4045)
This commit is contained in:
parent
a8794d41a8
commit
ae813a406e
11 changed files with 264 additions and 91 deletions
5
.changeset/silly-hotels-confess.md
Normal file
5
.changeset/silly-hotels-confess.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-dashboard": patch
|
||||
---
|
||||
|
||||
Experimental filters: add unit tests and bump UI library
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -27,7 +27,7 @@
|
|||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@reach/auto-id": "^0.16.0",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.112",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.113",
|
||||
"@saleor/sdk": "0.6.0",
|
||||
"@sentry/react": "^6.0.0",
|
||||
"@types/faker": "^5.1.6",
|
||||
|
@ -8495,9 +8495,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@saleor/macaw-ui": {
|
||||
"version": "0.8.0-pre.112",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.112.tgz",
|
||||
"integrity": "sha512-USVepQkr3t4/6WWTyHOJ+vj6bxoPHUI0m7e13ZF012YJT1c+ORRtJtYnnKdxazCDl7GjEH1shDJmpexQmtqPJQ==",
|
||||
"version": "0.8.0-pre.113",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.113.tgz",
|
||||
"integrity": "sha512-DJOOUIT9oaH4F/i+f37kdzoVcFggqx2JaXmUWYtx8CYMJyJpJPQuLPe734FDA9On3HnRlNs54NHDW3m7VLJM/A==",
|
||||
"dependencies": {
|
||||
"@dessert-box/react": "^0.4.0",
|
||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||
|
@ -40717,9 +40717,9 @@
|
|||
}
|
||||
},
|
||||
"@saleor/macaw-ui": {
|
||||
"version": "0.8.0-pre.112",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.112.tgz",
|
||||
"integrity": "sha512-USVepQkr3t4/6WWTyHOJ+vj6bxoPHUI0m7e13ZF012YJT1c+ORRtJtYnnKdxazCDl7GjEH1shDJmpexQmtqPJQ==",
|
||||
"version": "0.8.0-pre.113",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.113.tgz",
|
||||
"integrity": "sha512-DJOOUIT9oaH4F/i+f37kdzoVcFggqx2JaXmUWYtx8CYMJyJpJPQuLPe734FDA9On3HnRlNs54NHDW3m7VLJM/A==",
|
||||
"requires": {
|
||||
"@dessert-box/react": "^0.4.0",
|
||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@reach/auto-id": "^0.16.0",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.112",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.113",
|
||||
"@saleor/sdk": "0.6.0",
|
||||
"@sentry/react": "^6.0.0",
|
||||
"@types/faker": "^5.1.6",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { InitialStateResponse } from "./InitialStateResponse";
|
|||
|
||||
describe("ConditionalFilter / API / InitialStateResponse", () => {
|
||||
it("should filter by dynamic attribute token", () => {
|
||||
// Arrange
|
||||
const initialState = InitialStateResponse.empty();
|
||||
initialState.attribute = {
|
||||
"attribute-1": {
|
||||
|
@ -22,11 +23,14 @@ describe("ConditionalFilter / API / InitialStateResponse", () => {
|
|||
const expectedOutput = [
|
||||
{ label: "Choice 1", slug: "choice-1", value: "value-1" },
|
||||
];
|
||||
// Act
|
||||
const result = initialState.filterByUrlToken(token);
|
||||
// Assert
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should filter by static token type", () => {
|
||||
// Arrange
|
||||
const initialState = InitialStateResponse.empty();
|
||||
initialState.category = [
|
||||
{ label: "Category 1", value: "1", slug: "category-1" },
|
||||
|
@ -35,11 +39,14 @@ describe("ConditionalFilter / API / InitialStateResponse", () => {
|
|||
new UrlEntry("s0.category-1", "category-1"),
|
||||
);
|
||||
const expectedOutput = ["category-1"];
|
||||
// Act
|
||||
const result = initialState.filterByUrlToken(token);
|
||||
// Assert
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should filter by boolean attribute token", () => {
|
||||
// Arrange
|
||||
const initialState = InitialStateResponse.empty();
|
||||
initialState.attribute = {
|
||||
"attribute-2": {
|
||||
|
@ -60,11 +67,14 @@ describe("ConditionalFilter / API / InitialStateResponse", () => {
|
|||
type: "BOOLEAN",
|
||||
value: "true",
|
||||
};
|
||||
// Act
|
||||
const result = initialState.filterByUrlToken(token);
|
||||
// Assert
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should filter by static attribute token", () => {
|
||||
// Arrange
|
||||
const initialState = InitialStateResponse.empty();
|
||||
initialState.attribute = {
|
||||
size: {
|
||||
|
@ -75,9 +85,10 @@ describe("ConditionalFilter / API / InitialStateResponse", () => {
|
|||
choices: [],
|
||||
},
|
||||
};
|
||||
|
||||
const token = UrlToken.fromUrlEntry(new UrlEntry("n0.size", "123"));
|
||||
// Act
|
||||
const result = initialState.filterByUrlToken(token);
|
||||
// Assert
|
||||
expect(result).toEqual("123");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,92 +10,96 @@ import {
|
|||
import { createInitialStateFromData } from "./helpers";
|
||||
|
||||
describe("ConditionalFilter / API / createInitialStateFromData", () => {
|
||||
const channelQuery = {
|
||||
data: {
|
||||
channels: [
|
||||
{ id: "1", name: "Channel 1", slug: "channel-1" },
|
||||
{ id: "2", name: "Channel 2", slug: "channel-2" },
|
||||
],
|
||||
},
|
||||
} as ApolloQueryResult<_GetChannelOperandsQuery>;
|
||||
|
||||
const collectionQuery = {
|
||||
data: {
|
||||
collections: {
|
||||
edges: [
|
||||
{ node: { id: "1", name: "Collection 1", slug: "collection-1" } },
|
||||
{ node: { id: "2", name: "Collection 2", slug: "collection-2" } },
|
||||
it("should create initial state from queries", () => {
|
||||
// Arrange
|
||||
const channelQuery = {
|
||||
data: {
|
||||
channels: [
|
||||
{ id: "1", name: "Channel 1", slug: "channel-1" },
|
||||
{ id: "2", name: "Channel 2", slug: "channel-2" },
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ApolloQueryResult<_SearchCollectionsOperandsQuery>;
|
||||
} as ApolloQueryResult<_GetChannelOperandsQuery>;
|
||||
|
||||
const categoryQuery = {
|
||||
data: {
|
||||
categories: {
|
||||
edges: [
|
||||
{ node: { id: "1", name: "Category 1", slug: "category-1" } },
|
||||
{ node: { id: "2", name: "Category 2", slug: "category-2" } },
|
||||
],
|
||||
const collectionQuery = {
|
||||
data: {
|
||||
collections: {
|
||||
edges: [
|
||||
{ node: { id: "1", name: "Collection 1", slug: "collection-1" } },
|
||||
{ node: { id: "2", name: "Collection 2", slug: "collection-2" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ApolloQueryResult<_SearchCategoriesOperandsQuery>;
|
||||
} as ApolloQueryResult<_SearchCollectionsOperandsQuery>;
|
||||
|
||||
const productTypeQuery = {
|
||||
data: {
|
||||
productTypes: {
|
||||
edges: [
|
||||
{ node: { id: "1", name: "Product Type 1", slug: "product-type-1" } },
|
||||
{ node: { id: "2", name: "Product Type 2", slug: "product-type-2" } },
|
||||
],
|
||||
const categoryQuery = {
|
||||
data: {
|
||||
categories: {
|
||||
edges: [
|
||||
{ node: { id: "1", name: "Category 1", slug: "category-1" } },
|
||||
{ node: { id: "2", name: "Category 2", slug: "category-2" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ApolloQueryResult<_SearchProductTypesOperandsQuery>;
|
||||
} as ApolloQueryResult<_SearchCategoriesOperandsQuery>;
|
||||
|
||||
const attributeQuery = {
|
||||
data: {
|
||||
attributes: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "1",
|
||||
name: "Attribute 1",
|
||||
slug: "attribute-1",
|
||||
inputType: "MULTISELECT",
|
||||
choices: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "1",
|
||||
name: "Choice 1",
|
||||
slug: "choice-1",
|
||||
const productTypeQuery = {
|
||||
data: {
|
||||
productTypes: {
|
||||
edges: [
|
||||
{
|
||||
node: { id: "1", name: "Product Type 1", slug: "product-type-1" },
|
||||
},
|
||||
{
|
||||
node: { id: "2", name: "Product Type 2", slug: "product-type-2" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ApolloQueryResult<_SearchProductTypesOperandsQuery>;
|
||||
|
||||
const attributeQuery = {
|
||||
data: {
|
||||
attributes: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "1",
|
||||
name: "Attribute 1",
|
||||
slug: "attribute-1",
|
||||
inputType: "MULTISELECT",
|
||||
choices: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "1",
|
||||
name: "Choice 1",
|
||||
slug: "choice-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "2",
|
||||
name: "Choice 2",
|
||||
slug: "choice-2",
|
||||
{
|
||||
node: {
|
||||
id: "2",
|
||||
name: "Choice 2",
|
||||
slug: "choice-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "2",
|
||||
name: "Attribute 2",
|
||||
slug: "attribute-2",
|
||||
inputType: "BOOLEAN",
|
||||
{
|
||||
node: {
|
||||
id: "2",
|
||||
name: "Attribute 2",
|
||||
slug: "attribute-2",
|
||||
inputType: "BOOLEAN",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ApolloQueryResult<_SearchAttributeOperandsQuery>;
|
||||
|
||||
it("should create initial state from queries", () => {
|
||||
} as ApolloQueryResult<_SearchAttributeOperandsQuery>;
|
||||
const data = [
|
||||
channelQuery,
|
||||
collectionQuery,
|
||||
|
@ -104,8 +108,9 @@ describe("ConditionalFilter / API / createInitialStateFromData", () => {
|
|||
attributeQuery,
|
||||
];
|
||||
const channel = ["channel-1"];
|
||||
|
||||
// Act
|
||||
const result = createInitialStateFromData(data, channel);
|
||||
// Assert
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ConditionSelected } from "./ConditionSelected";
|
|||
import { ItemOption } from "./ConditionValue";
|
||||
|
||||
export class Condition {
|
||||
private constructor(
|
||||
public constructor(
|
||||
public options: ConditionOptions,
|
||||
public selected: ConditionSelected,
|
||||
public loading: boolean,
|
||||
|
@ -79,7 +79,7 @@ export class Condition {
|
|||
|
||||
if (token.isAttribute()) {
|
||||
const attribute = response.attributeByName(token.name);
|
||||
const options = ConditionOptions.fromAtributeType(attribute.inputType);
|
||||
const options = ConditionOptions.fromAttributeType(attribute.inputType);
|
||||
const option = options.find(item => item.label === token.conditionKind)!;
|
||||
const value = response.filterByUrlToken(token);
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ export class ConditionOptions extends Array<ConditionItem> {
|
|||
return name in ATTRIBUTE_INPUT_TYPE_CONDITIONS;
|
||||
}
|
||||
|
||||
public static fromAtributeType(inputType: AttributeInputType) {
|
||||
public static fromAttributeType(inputType: AttributeInputType) {
|
||||
const options = ATTRIBUTE_INPUT_TYPE_CONDITIONS[inputType];
|
||||
|
||||
if (!options) {
|
||||
|
@ -75,7 +75,7 @@ export class ConditionOptions extends Array<ConditionItem> {
|
|||
}
|
||||
|
||||
public isEmpty() {
|
||||
return this.length === 0
|
||||
return this.length === 0;
|
||||
}
|
||||
|
||||
public findByLabel(label: string) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ConditionItem } from "./ConditionOptions";
|
|||
import { ConditionValue, isItemOptionArray, isTuple } from "./ConditionValue";
|
||||
|
||||
export class ConditionSelected {
|
||||
private constructor(
|
||||
public constructor(
|
||||
public value: ConditionValue,
|
||||
public conditionValue: ConditionItem | null,
|
||||
public options: ConditionValue[],
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ConditionSelected } from "./ConditionSelected";
|
|||
import { ConditionValue, ItemOption } from "./ConditionValue";
|
||||
import { Constraint } from "./Constraint";
|
||||
|
||||
class ExpressionValue {
|
||||
export class ExpressionValue {
|
||||
constructor(
|
||||
public value: string,
|
||||
public label: string,
|
||||
|
@ -68,7 +68,7 @@ class ExpressionValue {
|
|||
}
|
||||
|
||||
export class FilterElement {
|
||||
private constructor(
|
||||
public constructor(
|
||||
public value: ExpressionValue,
|
||||
public condition: Condition,
|
||||
public loading: boolean,
|
||||
|
@ -165,7 +165,7 @@ export class FilterElement {
|
|||
}
|
||||
|
||||
public equals(element: FilterElement) {
|
||||
return this.value.value === element.value.value
|
||||
return this.value.value === element.value.value;
|
||||
}
|
||||
|
||||
public static isCompatible(element: unknown): element is FilterElement {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { TokenArray } from ".";
|
||||
|
||||
describe("ConditionalFilter / ValueProvider / TokenArray", () => {
|
||||
it("should parse empty params", () => {
|
||||
// Arrange
|
||||
const url = new TokenArray("");
|
||||
// Act
|
||||
const fetchingParams = url.getFetchingParams();
|
||||
// Assert
|
||||
expect(fetchingParams).toEqual({
|
||||
category: [],
|
||||
collection: [],
|
||||
channel: [],
|
||||
productType: [],
|
||||
attribute: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse params with values", () => {
|
||||
// Arrange
|
||||
const params = new URLSearchParams({
|
||||
"0[s0.price]": "123",
|
||||
"1": "AND",
|
||||
"2[s0.channel]": "channel-pln",
|
||||
"3": "OR",
|
||||
"4[s2.collection][0]": "featured-products",
|
||||
"5": "AND",
|
||||
"6[s0.productType]": "beer",
|
||||
"7": "AND",
|
||||
"8[s2.category][0]": "accessories",
|
||||
"9[s2.category][1]": "groceries",
|
||||
"10": "AND",
|
||||
"11[o2.bottle-size][0]": "attribute-id",
|
||||
});
|
||||
// Act
|
||||
const url = new TokenArray(params.toString());
|
||||
const fetchingParams = url.getFetchingParams();
|
||||
// Assert
|
||||
expect(fetchingParams).toEqual({
|
||||
attribute: {
|
||||
"bottle-size": ["attribute-id"],
|
||||
},
|
||||
category: ["accessories", "groceries"],
|
||||
channel: ["channel-pln"],
|
||||
collection: ["featured-products"],
|
||||
productType: ["beer"],
|
||||
});
|
||||
});
|
||||
});
|
103
src/components/ConditionalFilter/queryVariables.test.ts
Normal file
103
src/components/ConditionalFilter/queryVariables.test.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
import { Condition, FilterContainer, FilterElement } from "./FilterElement";
|
||||
import { ConditionOptions } from "./FilterElement/ConditionOptions";
|
||||
import { ConditionSelected } from "./FilterElement/ConditionSelected";
|
||||
import { ExpressionValue } from "./FilterElement/FilterElement";
|
||||
import { createProductQueryVariables } from "./queryVariables";
|
||||
|
||||
const createConditionValue = (
|
||||
label: string,
|
||||
slug: string,
|
||||
value: string,
|
||||
originalSlug?: string,
|
||||
) => ({
|
||||
label,
|
||||
slug,
|
||||
value,
|
||||
originalSlug,
|
||||
});
|
||||
const createConditionItem = (type: string, value: string, label: string) => ({
|
||||
type,
|
||||
value,
|
||||
label,
|
||||
});
|
||||
|
||||
const createConditionOptions = (
|
||||
label: string,
|
||||
slug: string,
|
||||
value: string,
|
||||
originalSlug: string,
|
||||
) => [
|
||||
{
|
||||
label,
|
||||
slug,
|
||||
value,
|
||||
originalSlug,
|
||||
},
|
||||
];
|
||||
|
||||
describe("ConditionalFilter / queryVariables / createProductQueryVariables", () => {
|
||||
it("should return empty variables for empty filters", () => {
|
||||
// Arrange
|
||||
const filters: FilterContainer = [];
|
||||
const expectedOutput = {
|
||||
attributes: [],
|
||||
};
|
||||
// Act
|
||||
const result = createProductQueryVariables(filters);
|
||||
// Assert
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should create variables with selected filters", () => {
|
||||
// Arrange
|
||||
const filters: FilterContainer = [
|
||||
new FilterElement(
|
||||
new ExpressionValue("price", "Price", "price"),
|
||||
new Condition(
|
||||
ConditionOptions.fromStaticElementName("price"),
|
||||
new ConditionSelected(
|
||||
createConditionValue("price", "price", "123"),
|
||||
createConditionItem("price", "123", "Price"),
|
||||
[],
|
||||
false,
|
||||
),
|
||||
false,
|
||||
),
|
||||
false,
|
||||
),
|
||||
"AND",
|
||||
new FilterElement(
|
||||
new ExpressionValue("bottle-size", "Bottle size", "DROPDOWN"),
|
||||
new Condition(
|
||||
ConditionOptions.fromAttributeType("DROPDOWN"),
|
||||
new ConditionSelected(
|
||||
createConditionValue(
|
||||
"bottle-size",
|
||||
"bottle-id",
|
||||
"bottle-id",
|
||||
"0-5l",
|
||||
),
|
||||
createConditionItem("DROPDOWN", "bottle-id", "Bottle size"),
|
||||
createConditionOptions(
|
||||
"bottle-size",
|
||||
"bottle-id",
|
||||
"bottle-id",
|
||||
"0-5l",
|
||||
),
|
||||
false,
|
||||
),
|
||||
false,
|
||||
),
|
||||
false,
|
||||
),
|
||||
];
|
||||
const expectedOutput = {
|
||||
attributes: [{ slug: "bottle-size", values: ["0-5l"] }],
|
||||
price: { eq: "123" },
|
||||
};
|
||||
// Act
|
||||
const result = createProductQueryVariables(filters);
|
||||
// Assert
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue