Add variant matrix generation
This commit is contained in:
parent
742523bac7
commit
2879a80d0d
14 changed files with 1076 additions and 148 deletions
|
@ -4,55 +4,56 @@ import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { attributes } from "@saleor/attributes/fixtures";
|
import { attributes } from "@saleor/attributes/fixtures";
|
||||||
import { isSelected } from "@saleor/utils/lists";
|
|
||||||
import Decorator from "../../../storybook/Decorator";
|
import Decorator from "../../../storybook/Decorator";
|
||||||
|
import { createVariants } from "./createVariants";
|
||||||
|
import { AllOrAttribute } from "./form";
|
||||||
import ProductVariantCreateContent, {
|
import ProductVariantCreateContent, {
|
||||||
ProductVariantCreateContentProps
|
ProductVariantCreateContentProps
|
||||||
} from "./ProductVariantCreateContent";
|
} from "./ProductVariantCreateContent";
|
||||||
import ProductVariantCreateDialog from "./ProductVariantCreateDialog";
|
import ProductVariantCreateDialog from "./ProductVariantCreateDialog";
|
||||||
|
|
||||||
const selectedAttributes = [1, 2, 4].map(index => attributes[index].id);
|
const selectedAttributes = [1, 4, 5].map(index => attributes[index]);
|
||||||
const selectedValues = attributes
|
|
||||||
.filter(attribute =>
|
const price: AllOrAttribute = {
|
||||||
isSelected(attribute.id, selectedAttributes, (a, b) => a === b)
|
all: false,
|
||||||
)
|
attribute: selectedAttributes[1].id,
|
||||||
.map(attribute => attribute.values.map(value => value.id))
|
value: "2.79",
|
||||||
.reduce((acc, curr) => [...acc, ...curr], [])
|
values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({
|
||||||
.filter((_, valueIndex) => valueIndex % 2);
|
id: attribute.id,
|
||||||
|
value: (attributeIndex + 4).toFixed(2)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const stock: AllOrAttribute = {
|
||||||
|
all: false,
|
||||||
|
attribute: selectedAttributes[1].id,
|
||||||
|
value: "8",
|
||||||
|
values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({
|
||||||
|
id: attribute.id,
|
||||||
|
value: (selectedAttributes.length * 10 - attributeIndex).toString()
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataAttributes = selectedAttributes.map(attribute => ({
|
||||||
|
id: attribute.id,
|
||||||
|
values: attribute.values
|
||||||
|
.map(value => value.id)
|
||||||
|
.filter((_, valueIndex) => valueIndex % 2 !== 1)
|
||||||
|
}));
|
||||||
|
|
||||||
const props: ProductVariantCreateContentProps = {
|
const props: ProductVariantCreateContentProps = {
|
||||||
attributes,
|
attributes,
|
||||||
currencySymbol: "USD",
|
currencySymbol: "USD",
|
||||||
data: {
|
data: {
|
||||||
attributes: selectedAttributes,
|
attributes: dataAttributes,
|
||||||
price: {
|
price,
|
||||||
all: false,
|
stock,
|
||||||
attribute: selectedAttributes[1],
|
variants: createVariants({
|
||||||
value: "2.79",
|
attributes: dataAttributes,
|
||||||
values: selectedAttributes.map((_, attributeIndex) =>
|
price,
|
||||||
(attributeIndex + 4).toFixed(2)
|
stock,
|
||||||
)
|
variants: []
|
||||||
},
|
})
|
||||||
stock: {
|
|
||||||
all: false,
|
|
||||||
attribute: selectedAttributes[1],
|
|
||||||
value: "8",
|
|
||||||
values: selectedAttributes.map((_, attributeIndex) =>
|
|
||||||
(selectedAttributes.length * 10 - attributeIndex).toString()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
values: selectedValues,
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
attributes: attributes
|
|
||||||
.filter(attribute => selectedAttributes.includes(attribute.id))
|
|
||||||
.map(attribute => ({
|
|
||||||
id: attribute.id,
|
|
||||||
values: [attribute.values[0].id]
|
|
||||||
})),
|
|
||||||
product: "=1uahc98nas"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
dispatchFormDataAction: () => undefined,
|
dispatchFormDataAction: () => undefined,
|
||||||
step: "attributes"
|
step: "attributes"
|
||||||
|
@ -64,7 +65,7 @@ storiesOf("Views / Products / Create multiple variants", module)
|
||||||
style={{
|
style={{
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
width: 600
|
width: 800
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent>{storyFn()}</CardContent>
|
<CardContent>{storyFn()}</CardContent>
|
||||||
|
|
|
@ -43,7 +43,7 @@ const ProductVariantCreateAttributes: React.FC<
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const isChecked = !!data.attributes.find(
|
const isChecked = !!data.attributes.find(
|
||||||
selectedAttribute => selectedAttribute === attribute.id
|
selectedAttribute => selectedAttribute.id === attribute.id
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -40,7 +40,11 @@ const ProductVariantCreateContent: React.FC<
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
const selectedAttributes = attributes.filter(attribute =>
|
const selectedAttributes = attributes.filter(attribute =>
|
||||||
isSelected(attribute.id, data.attributes, (a, b) => a === b)
|
isSelected(
|
||||||
|
attribute.id,
|
||||||
|
data.attributes.map(dataAttribute => dataAttribute.id),
|
||||||
|
(a, b) => a === b
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -51,9 +55,9 @@ const ProductVariantCreateContent: React.FC<
|
||||||
<ProductVariantCreateAttributes
|
<ProductVariantCreateAttributes
|
||||||
attributes={attributes}
|
attributes={attributes}
|
||||||
data={data}
|
data={data}
|
||||||
onAttributeClick={id =>
|
onAttributeClick={attributeId =>
|
||||||
dispatchFormDataAction({
|
dispatchFormDataAction({
|
||||||
id,
|
attributeId,
|
||||||
type: "selectAttribute"
|
type: "selectAttribute"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -63,10 +67,11 @@ const ProductVariantCreateContent: React.FC<
|
||||||
<ProductVariantCreateValues
|
<ProductVariantCreateValues
|
||||||
attributes={selectedAttributes}
|
attributes={selectedAttributes}
|
||||||
data={data}
|
data={data}
|
||||||
onValueClick={id =>
|
onValueClick={(attributeId, valueId) =>
|
||||||
dispatchFormDataAction({
|
dispatchFormDataAction({
|
||||||
id,
|
attributeId,
|
||||||
type: "selectValue"
|
type: "selectValue",
|
||||||
|
valueId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -90,19 +95,23 @@ const ProductVariantCreateContent: React.FC<
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onAttributeSelect={(id, type) =>
|
onAttributeSelect={(attributeId, type) =>
|
||||||
dispatchFormDataAction({
|
dispatchFormDataAction({
|
||||||
id,
|
attributeId,
|
||||||
type:
|
type:
|
||||||
type === "price"
|
type === "price"
|
||||||
? "changeApplyPriceToAttributeId"
|
? "changeApplyPriceToAttributeId"
|
||||||
: "changeApplyStockToAttributeId"
|
: "changeApplyStockToAttributeId"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onValueClick={id =>
|
onAttributeValueChange={(valueId, value, type) =>
|
||||||
dispatchFormDataAction({
|
dispatchFormDataAction({
|
||||||
id,
|
type:
|
||||||
type: "selectValue"
|
type === "price"
|
||||||
|
? "changeAttributeValuePrice"
|
||||||
|
: "changeAttributeValueStock",
|
||||||
|
value,
|
||||||
|
valueId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -21,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
content: {
|
content: {
|
||||||
overflowX: "visible",
|
overflowX: "visible",
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
width: 600
|
width: 800
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import Grid from "@saleor/components/Grid";
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||||
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||||
import { isSelected } from "@saleor/utils/lists";
|
|
||||||
import { ProductVariantCreateFormData } from "./form";
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
@ -33,10 +32,14 @@ export type PriceOrStock = "price" | "stock";
|
||||||
export interface ProductVariantCreatePricesProps {
|
export interface ProductVariantCreatePricesProps {
|
||||||
attributes: ProductDetails_product_productType_variantAttributes[];
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
data: ProductVariantCreateFormData;
|
data: ProductVariantCreateFormData;
|
||||||
onValueClick: (id: string) => void;
|
|
||||||
onAttributeSelect: (id: string, type: PriceOrStock) => void;
|
|
||||||
onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void;
|
onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void;
|
||||||
onApplyToAllChange: (value: string, type: PriceOrStock) => void;
|
onApplyToAllChange: (value: string, type: PriceOrStock) => void;
|
||||||
|
onAttributeSelect: (id: string, type: PriceOrStock) => void;
|
||||||
|
onAttributeValueChange: (
|
||||||
|
id: string,
|
||||||
|
value: string,
|
||||||
|
type: PriceOrStock
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductVariantCreatePrices: React.FC<
|
const ProductVariantCreatePrices: React.FC<
|
||||||
|
@ -47,31 +50,25 @@ const ProductVariantCreatePrices: React.FC<
|
||||||
data,
|
data,
|
||||||
onApplyPriceOrStockChange,
|
onApplyPriceOrStockChange,
|
||||||
onApplyToAllChange,
|
onApplyToAllChange,
|
||||||
onAttributeSelect
|
onAttributeSelect,
|
||||||
|
onAttributeValueChange
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const selectedAttributes = attributes.filter(attribute =>
|
const attributeChoices = attributes.map(attribute => ({
|
||||||
isSelected(attribute.id, data.attributes, (a, b) => a === b)
|
|
||||||
);
|
|
||||||
const attributeChoices = selectedAttributes.map(attribute => ({
|
|
||||||
label: attribute.name,
|
label: attribute.name,
|
||||||
value: attribute.id
|
value: attribute.id
|
||||||
}));
|
}));
|
||||||
const priceAttributeValues = data.price.all
|
const priceAttributeValues = data.price.all
|
||||||
? null
|
? null
|
||||||
: data.price.attribute
|
: data.price.attribute
|
||||||
? selectedAttributes.find(
|
? attributes.find(attribute => attribute.id === data.price.attribute).values
|
||||||
attribute => attribute.id === data.price.attribute
|
|
||||||
).values
|
|
||||||
: [];
|
: [];
|
||||||
const stockAttributeValues = data.stock.all
|
const stockAttributeValues = data.stock.all
|
||||||
? null
|
? null
|
||||||
: data.stock.attribute
|
: data.stock.attribute
|
||||||
? selectedAttributes.find(
|
? attributes.find(attribute => attribute.id === data.stock.attribute).values
|
||||||
attribute => attribute.id === data.stock.attribute
|
|
||||||
).values
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -142,27 +139,36 @@ const ProductVariantCreatePrices: React.FC<
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
{priceAttributeValues &&
|
{priceAttributeValues &&
|
||||||
priceAttributeValues.map((attribute, attributeIndex) => (
|
priceAttributeValues.map(
|
||||||
<>
|
(attributeValue, attributeValueIndex) => (
|
||||||
<FormSpacer />
|
<>
|
||||||
<Grid variant="inverted">
|
<FormSpacer />
|
||||||
<div className={classes.label}>
|
<Grid variant="inverted">
|
||||||
<Typography>{attribute.name}</Typography>
|
<div className={classes.label}>
|
||||||
</div>
|
<Typography>{attributeValue.name}</Typography>
|
||||||
<div>
|
</div>
|
||||||
<TextField
|
<div>
|
||||||
label={intl.formatMessage({
|
<TextField
|
||||||
defaultMessage: "Price",
|
label={intl.formatMessage({
|
||||||
description: "variant price",
|
defaultMessage: "Price",
|
||||||
id: "productVariantCreatePricesSetPricePlaceholder"
|
description: "variant price",
|
||||||
})}
|
id: "productVariantCreatePricesSetPricePlaceholder"
|
||||||
fullWidth
|
})}
|
||||||
value={data.price.values[attributeIndex]}
|
fullWidth
|
||||||
/>
|
value={data.price.values[attributeValueIndex].value}
|
||||||
</div>
|
onChange={event =>
|
||||||
</Grid>
|
onAttributeValueChange(
|
||||||
</>
|
attributeValue.id,
|
||||||
))}
|
event.target.value,
|
||||||
|
"price"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
@ -233,27 +239,36 @@ const ProductVariantCreatePrices: React.FC<
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
{stockAttributeValues &&
|
{stockAttributeValues &&
|
||||||
stockAttributeValues.map((attribute, attributeIndex) => (
|
stockAttributeValues.map(
|
||||||
<>
|
(attributeValue, attributeValueIndex) => (
|
||||||
<FormSpacer />
|
<>
|
||||||
<Grid variant="inverted">
|
<FormSpacer />
|
||||||
<div className={classes.label}>
|
<Grid variant="inverted">
|
||||||
<Typography>{attribute.name}</Typography>
|
<div className={classes.label}>
|
||||||
</div>
|
<Typography>{attributeValue.name}</Typography>
|
||||||
<div>
|
</div>
|
||||||
<TextField
|
<div>
|
||||||
label={intl.formatMessage({
|
<TextField
|
||||||
defaultMessage: "Stock",
|
label={intl.formatMessage({
|
||||||
description: "variant stock",
|
defaultMessage: "Stock",
|
||||||
id: "productVariantCreatePricesSetStockPlaceholder"
|
description: "variant stock",
|
||||||
})}
|
id: "productVariantCreatePricesSetStockPlaceholder"
|
||||||
fullWidth
|
})}
|
||||||
value={data.stock.values[attributeIndex]}
|
fullWidth
|
||||||
/>
|
value={data.stock.values[attributeValueIndex].value}
|
||||||
</div>
|
onChange={event =>
|
||||||
</Grid>
|
onAttributeValueChange(
|
||||||
</>
|
attributeValue.id,
|
||||||
))}
|
event.target.value,
|
||||||
|
"stock"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
@ -32,13 +32,13 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
width: "auto"
|
width: "auto"
|
||||||
},
|
},
|
||||||
colPrice: {
|
colPrice: {
|
||||||
width: 110
|
width: 160
|
||||||
},
|
},
|
||||||
colSku: {
|
colSku: {
|
||||||
width: 110
|
width: 210
|
||||||
},
|
},
|
||||||
colStock: {
|
colStock: {
|
||||||
width: 110
|
width: 160
|
||||||
},
|
},
|
||||||
hr: {
|
hr: {
|
||||||
marginBottom: theme.spacing.unit,
|
marginBottom: theme.spacing.unit,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ProductVariantCreateFormData } from "./form";
|
||||||
export interface ProductVariantCreateValuesProps {
|
export interface ProductVariantCreateValuesProps {
|
||||||
attributes: ProductDetails_product_productType_variantAttributes[];
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
data: ProductVariantCreateFormData;
|
data: ProductVariantCreateFormData;
|
||||||
onValueClick: (id: string) => void;
|
onValueClick: (attributeId: string, valueId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
@ -47,10 +47,16 @@ const ProductVariantCreateValues: React.FC<
|
||||||
<div className={classes.valueContainer}>
|
<div className={classes.valueContainer}>
|
||||||
{attribute.values.map(value => (
|
{attribute.values.map(value => (
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
checked={isSelected(value.id, data.values, (a, b) => a === b)}
|
checked={isSelected(
|
||||||
|
value.id,
|
||||||
|
data.attributes.find(
|
||||||
|
dataAttribute => attribute.id === dataAttribute.id
|
||||||
|
).values,
|
||||||
|
(a, b) => a === b
|
||||||
|
)}
|
||||||
name={`value:${value.id}`}
|
name={`value:${value.id}`}
|
||||||
label={value.name}
|
label={value.name}
|
||||||
onChange={() => onValueClick(value.id)}
|
onChange={() => onValueClick(attribute.id, value.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,457 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Reducer is able to select attribute values 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"variants": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reducer is able to select attributes 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"variants": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reducer is able to select price for all variants 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "45.99",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"variants": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reducer is able to select price to each attribute value 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": false,
|
||||||
|
"attribute": "attr-1",
|
||||||
|
"value": "",
|
||||||
|
"values": Array [
|
||||||
|
Object {
|
||||||
|
"id": "val-1-1",
|
||||||
|
"value": "45.99",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "val-1-7",
|
||||||
|
"value": "51.99",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"variants": Array [],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reducer is able to select stock for all variants 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "45.99",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"variants": Array [
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"priceOverride": "",
|
||||||
|
"product": "",
|
||||||
|
"quantity": 45,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Reducer is able to select stock to each attribute value 1`] = `
|
||||||
|
Object {
|
||||||
|
"attributes": Array [
|
||||||
|
Object {
|
||||||
|
"id": "attr-1",
|
||||||
|
"values": Array [
|
||||||
|
"val-1-1",
|
||||||
|
"val-1-7",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-2",
|
||||||
|
"values": Array [
|
||||||
|
"val-2-2",
|
||||||
|
"val-2-4",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "attr-4",
|
||||||
|
"values": Array [
|
||||||
|
"val-4-1",
|
||||||
|
"val-4-5",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"price": Object {
|
||||||
|
"all": true,
|
||||||
|
"attribute": undefined,
|
||||||
|
"value": "",
|
||||||
|
"values": Array [],
|
||||||
|
},
|
||||||
|
"stock": Object {
|
||||||
|
"all": false,
|
||||||
|
"attribute": "attr-1",
|
||||||
|
"value": "",
|
||||||
|
"values": Array [
|
||||||
|
Object {
|
||||||
|
"id": "val-1-1",
|
||||||
|
"value": "13",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "val-1-7",
|
||||||
|
"value": "19",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"variants": Array [],
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {
|
||||||
|
createVariantFlatMatrixDimension,
|
||||||
|
createVariants
|
||||||
|
} from "./createVariants";
|
||||||
|
import { thirdStep } from "./fixtures";
|
||||||
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
describe("Creates variant matrix", () => {
|
||||||
|
it("with proper size", () => {
|
||||||
|
const attributes = thirdStep.attributes;
|
||||||
|
|
||||||
|
const matrix = createVariantFlatMatrixDimension([[]], attributes);
|
||||||
|
|
||||||
|
expect(matrix).toHaveLength(
|
||||||
|
attributes.reduce((acc, attribute) => acc * attribute.values.length, 1)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with constant price and stock", () => {
|
||||||
|
const price = "49.99";
|
||||||
|
const stock = 80;
|
||||||
|
|
||||||
|
const data: ProductVariantCreateFormData = {
|
||||||
|
...thirdStep,
|
||||||
|
price: {
|
||||||
|
...thirdStep.price,
|
||||||
|
all: true,
|
||||||
|
value: price
|
||||||
|
},
|
||||||
|
stock: {
|
||||||
|
...thirdStep.stock,
|
||||||
|
all: true,
|
||||||
|
value: stock.toString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const variants = createVariants(data);
|
||||||
|
variants.forEach(variant => {
|
||||||
|
expect(variant.priceOverride).toBe(price);
|
||||||
|
expect(variant.quantity).toBe(stock);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { ProductVariantCreateInput } from "@saleor/types/globalTypes";
|
||||||
|
import { Attribute, ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
interface CreateVariantAttributeValueInput {
|
||||||
|
attributeId: string;
|
||||||
|
attributeValueId: string;
|
||||||
|
}
|
||||||
|
type CreateVariantInput = CreateVariantAttributeValueInput[];
|
||||||
|
function createVariant(
|
||||||
|
data: ProductVariantCreateFormData,
|
||||||
|
attributes: CreateVariantInput
|
||||||
|
): ProductVariantCreateInput {
|
||||||
|
const priceOverride = data.price.all
|
||||||
|
? data.price.value
|
||||||
|
: data.price.values.find(
|
||||||
|
value =>
|
||||||
|
attributes.find(
|
||||||
|
attribute => attribute.attributeId === data.price.attribute
|
||||||
|
).attributeValueId === value.id
|
||||||
|
).value;
|
||||||
|
const quantity = parseInt(
|
||||||
|
data.stock.all
|
||||||
|
? data.stock.value
|
||||||
|
: data.stock.values.find(
|
||||||
|
value =>
|
||||||
|
attributes.find(
|
||||||
|
attribute => attribute.attributeId === data.stock.attribute
|
||||||
|
).attributeValueId === value.id
|
||||||
|
).value,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
attributes: attributes.map(attribute => ({
|
||||||
|
id: attribute.attributeId,
|
||||||
|
values: [attribute.attributeValueId]
|
||||||
|
})),
|
||||||
|
priceOverride,
|
||||||
|
product: "",
|
||||||
|
quantity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAttributeToVariant(
|
||||||
|
attribute: Attribute,
|
||||||
|
variant: CreateVariantInput
|
||||||
|
): CreateVariantInput[] {
|
||||||
|
return attribute.values.map(attributeValueId => [
|
||||||
|
...variant,
|
||||||
|
{
|
||||||
|
attributeId: attribute.id,
|
||||||
|
attributeValueId
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
function addVariantAttributeInput(
|
||||||
|
data: CreateVariantInput[],
|
||||||
|
attribute: Attribute
|
||||||
|
): CreateVariantInput[] {
|
||||||
|
const variants = data
|
||||||
|
.map(variant => addAttributeToVariant(attribute, variant))
|
||||||
|
.reduce((acc, variantInput) => [...acc, ...variantInput]);
|
||||||
|
|
||||||
|
return variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVariantFlatMatrixDimension(
|
||||||
|
variants: CreateVariantInput[],
|
||||||
|
attributes: Attribute[]
|
||||||
|
): CreateVariantInput[] {
|
||||||
|
if (attributes.length > 0) {
|
||||||
|
return createVariantFlatMatrixDimension(
|
||||||
|
addVariantAttributeInput(variants, attributes[0]),
|
||||||
|
attributes.slice(1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return variants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVariants(
|
||||||
|
data: ProductVariantCreateFormData
|
||||||
|
): ProductVariantCreateInput[] {
|
||||||
|
const variants = createVariantFlatMatrixDimension([[]], data.attributes).map(
|
||||||
|
variant => createVariant(data, variant)
|
||||||
|
);
|
||||||
|
|
||||||
|
return variants;
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { initialForm, ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
export const attributes = [
|
||||||
|
{
|
||||||
|
id: "attr-1",
|
||||||
|
values: Array(9)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => `val-1-${index + 1}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "attr-2",
|
||||||
|
values: Array(6)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => `val-2-${index + 1}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "attr-3",
|
||||||
|
values: Array(4)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => `val-3-${index + 1}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "attr-4",
|
||||||
|
values: Array(11)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => `val-4-${index + 1}`)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const secondStep: ProductVariantCreateFormData = {
|
||||||
|
...initialForm,
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
id: attributes[0].id,
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: attributes[1].id,
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: attributes[3].id,
|
||||||
|
values: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
export const thirdStep: ProductVariantCreateFormData = {
|
||||||
|
...secondStep,
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
id: attributes[0].id,
|
||||||
|
values: [0, 6].map(index => attributes[0].values[index])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: attributes[1].id,
|
||||||
|
values: [1, 3].map(index => attributes[1].values[index])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: attributes[3].id,
|
||||||
|
values: [0, 4].map(index => attributes[3].values[index])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
|
@ -1,16 +1,23 @@
|
||||||
import { ProductVariantCreateInput } from "../../../types/globalTypes";
|
import { ProductVariantCreateInput } from "../../../types/globalTypes";
|
||||||
|
|
||||||
|
export interface AttributeValue {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
export interface AllOrAttribute {
|
export interface AllOrAttribute {
|
||||||
all: boolean;
|
all: boolean;
|
||||||
attribute: string;
|
attribute: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
values: AttributeValue[];
|
||||||
|
}
|
||||||
|
export interface Attribute {
|
||||||
|
id: string;
|
||||||
values: string[];
|
values: string[];
|
||||||
}
|
}
|
||||||
export interface ProductVariantCreateFormData {
|
export interface ProductVariantCreateFormData {
|
||||||
attributes: string[];
|
attributes: Attribute[];
|
||||||
price: AllOrAttribute;
|
price: AllOrAttribute;
|
||||||
stock: AllOrAttribute;
|
stock: AllOrAttribute;
|
||||||
values: string[];
|
|
||||||
variants: ProductVariantCreateInput[];
|
variants: ProductVariantCreateInput[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +35,5 @@ export const initialForm: ProductVariantCreateFormData = {
|
||||||
value: "",
|
value: "",
|
||||||
values: []
|
values: []
|
||||||
},
|
},
|
||||||
values: [],
|
|
||||||
variants: []
|
variants: []
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
import { attributes, secondStep, thirdStep } from "./fixtures";
|
||||||
|
import { initialForm } from "./form";
|
||||||
|
import reducer from "./reducer";
|
||||||
|
|
||||||
|
function execActions<TState, TAction>(
|
||||||
|
initialState: TState,
|
||||||
|
reducer: (state: TState, action: TAction) => TState,
|
||||||
|
actions: TAction[]
|
||||||
|
): TState {
|
||||||
|
return actions.reduce((acc, action) => reducer(acc, action), initialState);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Reducer is able to", () => {
|
||||||
|
it("select attributes", () => {
|
||||||
|
const state = execActions(initialForm, reducer, [
|
||||||
|
{
|
||||||
|
attributeId: attributes[0].id,
|
||||||
|
type: "selectAttribute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[1].id,
|
||||||
|
type: "selectAttribute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[3].id,
|
||||||
|
type: "selectAttribute"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.attributes).toHaveLength(3);
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select attribute values", () => {
|
||||||
|
const state = execActions(secondStep, reducer, [
|
||||||
|
{
|
||||||
|
attributeId: attributes[0].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[0].values[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[0].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[0].values[6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[1].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[1].values[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[1].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[1].values[3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[3].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[3].values[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[3].id,
|
||||||
|
type: "selectValue",
|
||||||
|
valueId: attributes[3].values[4]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.attributes[0].values).toHaveLength(2);
|
||||||
|
expect(state.attributes[1].values).toHaveLength(2);
|
||||||
|
expect(state.attributes[2].values).toHaveLength(2);
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select price for all variants", () => {
|
||||||
|
const value = "45.99";
|
||||||
|
const state = execActions(thirdStep, reducer, [
|
||||||
|
{
|
||||||
|
all: true,
|
||||||
|
type: "applyPriceToAll"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeApplyPriceToAllValue",
|
||||||
|
value
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.price.all).toBeTruthy();
|
||||||
|
expect(state.price.value).toBe(value);
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select stock for all variants", () => {
|
||||||
|
const value = 45.99;
|
||||||
|
const state = execActions(thirdStep, reducer, [
|
||||||
|
{
|
||||||
|
all: true,
|
||||||
|
type: "applyStockToAll"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeApplyStockToAllValue",
|
||||||
|
value: value.toString()
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.stock.all).toBeTruthy();
|
||||||
|
expect(state.stock.value).toBe(value.toString());
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select price to each attribute value", () => {
|
||||||
|
const value = 45.99;
|
||||||
|
const state = execActions(thirdStep, reducer, [
|
||||||
|
{
|
||||||
|
all: false,
|
||||||
|
type: "applyPriceToAll"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[0].id,
|
||||||
|
type: "changeApplyPriceToAttributeId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeAttributeValuePrice",
|
||||||
|
value: value.toString(),
|
||||||
|
valueId: attributes[0].values[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeAttributeValuePrice",
|
||||||
|
value: (value + 6).toString(),
|
||||||
|
valueId: attributes[0].values[6]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.price.all).toBeFalsy();
|
||||||
|
expect(state.price.values).toHaveLength(
|
||||||
|
state.attributes.find(attribute => state.price.attribute === attribute.id)
|
||||||
|
.values.length
|
||||||
|
);
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select stock to each attribute value", () => {
|
||||||
|
const value = 13;
|
||||||
|
const state = execActions(thirdStep, reducer, [
|
||||||
|
{
|
||||||
|
all: false,
|
||||||
|
type: "applyStockToAll"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributeId: attributes[0].id,
|
||||||
|
type: "changeApplyStockToAttributeId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeAttributeValueStock",
|
||||||
|
value: value.toString(),
|
||||||
|
valueId: attributes[0].values[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "changeAttributeValueStock",
|
||||||
|
value: (value + 6).toString(),
|
||||||
|
valueId: attributes[0].values[6]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(state.stock.all).toBeFalsy();
|
||||||
|
expect(state.stock.values).toHaveLength(
|
||||||
|
state.attributes.find(attribute => state.stock.attribute === attribute.id)
|
||||||
|
.values.length
|
||||||
|
);
|
||||||
|
expect(state).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
import { toggle, updateAtIndex } from "@saleor/utils/lists";
|
import { add, remove, toggle, updateAtIndex } from "@saleor/utils/lists";
|
||||||
|
import { createVariants } from "./createVariants";
|
||||||
import { initialForm, ProductVariantCreateFormData } from "./form";
|
import { initialForm, ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
export type ProductVariantCreateReducerActionType =
|
export type ProductVariantCreateReducerActionType =
|
||||||
|
@ -10,22 +11,30 @@ export type ProductVariantCreateReducerActionType =
|
||||||
| "changeApplyPriceToAttributeId"
|
| "changeApplyPriceToAttributeId"
|
||||||
| "changeApplyStockToAllValue"
|
| "changeApplyStockToAllValue"
|
||||||
| "changeApplyStockToAttributeId"
|
| "changeApplyStockToAttributeId"
|
||||||
| "changeAttributePrice"
|
| "changeAttributeValuePrice"
|
||||||
| "changeAttributeStock"
|
| "changeAttributeValueStock"
|
||||||
| "selectAttribute"
|
| "selectAttribute"
|
||||||
| "selectValue";
|
| "selectValue";
|
||||||
export interface ProductVariantCreateReducerAction {
|
export interface ProductVariantCreateReducerAction {
|
||||||
all?: boolean;
|
all?: boolean;
|
||||||
id?: string;
|
attributeId?: string;
|
||||||
type: ProductVariantCreateReducerActionType;
|
type: ProductVariantCreateReducerActionType;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
valueId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAttribute(
|
function selectAttribute(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
attribute: string
|
attributeId: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
const attributes = toggle(attribute, state.attributes, (a, b) => a === b);
|
const attributes = toggle(
|
||||||
|
{
|
||||||
|
id: attributeId,
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
state.attributes,
|
||||||
|
(a, b) => a.id === b.id
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...initialForm,
|
...initialForm,
|
||||||
|
@ -35,14 +44,24 @@ function selectAttribute(
|
||||||
|
|
||||||
function selectValue(
|
function selectValue(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
value: string
|
attributeId: string,
|
||||||
|
valueId: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
const values = toggle(value, state.values, (a, b) => a === b);
|
const attribute = state.attributes.find(
|
||||||
|
attribute => attribute.id === attributeId
|
||||||
|
);
|
||||||
|
const values = toggle(valueId, attribute.values, (a, b) => a === b);
|
||||||
|
const updatedAttributes = add(
|
||||||
|
{
|
||||||
|
id: attributeId,
|
||||||
|
values
|
||||||
|
},
|
||||||
|
remove(attribute, state.attributes, (a, b) => a.id === b.id)
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...initialForm,
|
...initialForm,
|
||||||
attributes: state.attributes,
|
attributes: updatedAttributes
|
||||||
values
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,13 +91,27 @@ function applyStockToAll(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeAttributePrice(
|
function changeAttributeValuePrice(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
attribute: string,
|
attributeValueId: string,
|
||||||
price: string
|
price: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
const index = state.price.values.indexOf(attribute);
|
const index = state.price.values.findIndex(
|
||||||
const values = updateAtIndex(price, state.price.values, index);
|
value => value.id === attributeValueId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error(`Value with id ${attributeValueId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = updateAtIndex(
|
||||||
|
{
|
||||||
|
id: attributeValueId,
|
||||||
|
value: price
|
||||||
|
},
|
||||||
|
state.price.values,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -89,13 +122,27 @@ function changeAttributePrice(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeAttributeStock(
|
function changeAttributeValueStock(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
attribute: string,
|
attributeValueId: string,
|
||||||
stock: string
|
stock: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
const index = state.stock.values.indexOf(attribute);
|
const index = state.stock.values.findIndex(
|
||||||
const values = updateAtIndex(stock, state.stock.values, index);
|
value => value.id === attributeValueId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error(`Value with id ${attributeValueId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = updateAtIndex(
|
||||||
|
{
|
||||||
|
id: attributeValueId,
|
||||||
|
value: stock
|
||||||
|
},
|
||||||
|
state.stock.values,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -108,13 +155,22 @@ function changeAttributeStock(
|
||||||
|
|
||||||
function changeApplyPriceToAttributeId(
|
function changeApplyPriceToAttributeId(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
attribute: string
|
attributeId: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
|
const attribute = state.attributes.find(
|
||||||
|
attribute => attribute.id === attributeId
|
||||||
|
);
|
||||||
|
const values = attribute.values.map(id => ({
|
||||||
|
id,
|
||||||
|
value: ""
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
price: {
|
price: {
|
||||||
...state.price,
|
...state.price,
|
||||||
attribute
|
attribute: attributeId,
|
||||||
|
values
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -127,7 +183,13 @@ function changeApplyStockToAttributeId(
|
||||||
...state,
|
...state,
|
||||||
stock: {
|
stock: {
|
||||||
...state.stock,
|
...state.stock,
|
||||||
attribute
|
attribute,
|
||||||
|
values: state.attributes
|
||||||
|
.find(stateAttribute => stateAttribute.id === attribute)
|
||||||
|
.values.map(attributeValue => ({
|
||||||
|
id: attributeValue,
|
||||||
|
value: ""
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -149,13 +211,18 @@ function changeApplyStockToAllValue(
|
||||||
state: ProductVariantCreateFormData,
|
state: ProductVariantCreateFormData,
|
||||||
value: string
|
value: string
|
||||||
): ProductVariantCreateFormData {
|
): ProductVariantCreateFormData {
|
||||||
return {
|
const data = {
|
||||||
...state,
|
...state,
|
||||||
stock: {
|
stock: {
|
||||||
...state.stock,
|
...state.stock,
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
variants: createVariants(data)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduceProductVariantCreateFormData(
|
function reduceProductVariantCreateFormData(
|
||||||
|
@ -164,29 +231,30 @@ function reduceProductVariantCreateFormData(
|
||||||
) {
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "selectAttribute":
|
case "selectAttribute":
|
||||||
return selectAttribute(prevState, action.id);
|
return selectAttribute(prevState, action.attributeId);
|
||||||
|
|
||||||
case "selectValue":
|
case "selectValue":
|
||||||
return selectValue(prevState, action.id);
|
return selectValue(prevState, action.attributeId, action.valueId);
|
||||||
|
|
||||||
case "applyPriceToAll":
|
case "applyPriceToAll":
|
||||||
return applyPriceToAll(prevState, action.all);
|
return applyPriceToAll(prevState, action.all);
|
||||||
case "applyStockToAll":
|
case "applyStockToAll":
|
||||||
return applyStockToAll(prevState, action.all);
|
return applyStockToAll(prevState, action.all);
|
||||||
case "changeAttributePrice":
|
case "changeAttributeValuePrice":
|
||||||
return changeAttributePrice(prevState, action.id, action.value);
|
return changeAttributeValuePrice(prevState, action.valueId, action.value);
|
||||||
case "changeAttributeStock":
|
case "changeAttributeValueStock":
|
||||||
return changeAttributeStock(prevState, action.id, action.value);
|
return changeAttributeValueStock(prevState, action.valueId, action.value);
|
||||||
case "changeApplyPriceToAttributeId":
|
case "changeApplyPriceToAttributeId":
|
||||||
return changeApplyPriceToAttributeId(prevState, action.id);
|
return changeApplyPriceToAttributeId(prevState, action.attributeId);
|
||||||
case "changeApplyStockToAttributeId":
|
case "changeApplyStockToAttributeId":
|
||||||
return changeApplyStockToAttributeId(prevState, action.id);
|
return changeApplyStockToAttributeId(prevState, action.attributeId);
|
||||||
case "changeApplyPriceToAllValue":
|
case "changeApplyPriceToAllValue":
|
||||||
return changeApplyPriceToAllValue(prevState, action.value);
|
return changeApplyPriceToAllValue(prevState, action.value);
|
||||||
case "changeApplyStockToAllValue":
|
case "changeApplyStockToAllValue":
|
||||||
return changeApplyStockToAllValue(prevState, action.value);
|
return changeApplyStockToAllValue(prevState, action.value);
|
||||||
|
default:
|
||||||
|
return prevState;
|
||||||
}
|
}
|
||||||
return prevState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default reduceProductVariantCreateFormData;
|
export default reduceProductVariantCreateFormData;
|
||||||
|
|
Loading…
Reference in a new issue