Add variant matrix generation

This commit is contained in:
dominik-zeglen 2019-09-23 17:19:41 +02:00
parent 742523bac7
commit 2879a80d0d
14 changed files with 1076 additions and 148 deletions

View file

@ -4,55 +4,56 @@ import { storiesOf } from "@storybook/react";
import React from "react";
import { attributes } from "@saleor/attributes/fixtures";
import { isSelected } from "@saleor/utils/lists";
import Decorator from "../../../storybook/Decorator";
import { createVariants } from "./createVariants";
import { AllOrAttribute } from "./form";
import ProductVariantCreateContent, {
ProductVariantCreateContentProps
} from "./ProductVariantCreateContent";
import ProductVariantCreateDialog from "./ProductVariantCreateDialog";
const selectedAttributes = [1, 2, 4].map(index => attributes[index].id);
const selectedValues = attributes
.filter(attribute =>
isSelected(attribute.id, selectedAttributes, (a, b) => a === b)
)
.map(attribute => attribute.values.map(value => value.id))
.reduce((acc, curr) => [...acc, ...curr], [])
.filter((_, valueIndex) => valueIndex % 2);
const selectedAttributes = [1, 4, 5].map(index => attributes[index]);
const price: AllOrAttribute = {
all: false,
attribute: selectedAttributes[1].id,
value: "2.79",
values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({
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 = {
attributes,
currencySymbol: "USD",
data: {
attributes: selectedAttributes,
price: {
all: false,
attribute: selectedAttributes[1],
value: "2.79",
values: selectedAttributes.map((_, attributeIndex) =>
(attributeIndex + 4).toFixed(2)
)
},
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"
}
]
attributes: dataAttributes,
price,
stock,
variants: createVariants({
attributes: dataAttributes,
price,
stock,
variants: []
})
},
dispatchFormDataAction: () => undefined,
step: "attributes"
@ -64,7 +65,7 @@ storiesOf("Views / Products / Create multiple variants", module)
style={{
margin: "auto",
overflow: "visible",
width: 600
width: 800
}}
>
<CardContent>{storyFn()}</CardContent>

View file

@ -43,7 +43,7 @@ const ProductVariantCreateAttributes: React.FC<
return null;
}
const isChecked = !!data.attributes.find(
selectedAttribute => selectedAttribute === attribute.id
selectedAttribute => selectedAttribute.id === attribute.id
);
return (

View file

@ -40,7 +40,11 @@ const ProductVariantCreateContent: React.FC<
const classes = useStyles(props);
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 (
@ -51,9 +55,9 @@ const ProductVariantCreateContent: React.FC<
<ProductVariantCreateAttributes
attributes={attributes}
data={data}
onAttributeClick={id =>
onAttributeClick={attributeId =>
dispatchFormDataAction({
id,
attributeId,
type: "selectAttribute"
})
}
@ -63,10 +67,11 @@ const ProductVariantCreateContent: React.FC<
<ProductVariantCreateValues
attributes={selectedAttributes}
data={data}
onValueClick={id =>
onValueClick={(attributeId, valueId) =>
dispatchFormDataAction({
id,
type: "selectValue"
attributeId,
type: "selectValue",
valueId
})
}
/>
@ -90,19 +95,23 @@ const ProductVariantCreateContent: React.FC<
value
})
}
onAttributeSelect={(id, type) =>
onAttributeSelect={(attributeId, type) =>
dispatchFormDataAction({
id,
attributeId,
type:
type === "price"
? "changeApplyPriceToAttributeId"
: "changeApplyStockToAttributeId"
})
}
onValueClick={id =>
onAttributeValueChange={(valueId, value, type) =>
dispatchFormDataAction({
id,
type: "selectValue"
type:
type === "price"
? "changeAttributeValuePrice"
: "changeAttributeValueStock",
value,
valueId
})
}
/>

View file

@ -21,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) => ({
content: {
overflowX: "visible",
overflowY: "hidden",
width: 600
width: 800
}
}));

View file

@ -13,7 +13,6 @@ import Grid from "@saleor/components/Grid";
import Hr from "@saleor/components/Hr";
import SingleSelectField from "@saleor/components/SingleSelectField";
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
import { isSelected } from "@saleor/utils/lists";
import { ProductVariantCreateFormData } from "./form";
const useStyles = makeStyles((theme: Theme) => ({
@ -33,10 +32,14 @@ export type PriceOrStock = "price" | "stock";
export interface ProductVariantCreatePricesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
data: ProductVariantCreateFormData;
onValueClick: (id: string) => void;
onAttributeSelect: (id: string, type: PriceOrStock) => void;
onApplyPriceOrStockChange: (applyToAll: boolean, 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<
@ -47,31 +50,25 @@ const ProductVariantCreatePrices: React.FC<
data,
onApplyPriceOrStockChange,
onApplyToAllChange,
onAttributeSelect
onAttributeSelect,
onAttributeValueChange
} = props;
const classes = useStyles(props);
const intl = useIntl();
const selectedAttributes = attributes.filter(attribute =>
isSelected(attribute.id, data.attributes, (a, b) => a === b)
);
const attributeChoices = selectedAttributes.map(attribute => ({
const attributeChoices = attributes.map(attribute => ({
label: attribute.name,
value: attribute.id
}));
const priceAttributeValues = data.price.all
? null
: data.price.attribute
? selectedAttributes.find(
attribute => attribute.id === data.price.attribute
).values
? attributes.find(attribute => attribute.id === data.price.attribute).values
: [];
const stockAttributeValues = data.stock.all
? null
: data.stock.attribute
? selectedAttributes.find(
attribute => attribute.id === data.stock.attribute
).values
? attributes.find(attribute => attribute.id === data.stock.attribute).values
: [];
return (
@ -142,12 +139,13 @@ const ProductVariantCreatePrices: React.FC<
</div>
</Grid>
{priceAttributeValues &&
priceAttributeValues.map((attribute, attributeIndex) => (
priceAttributeValues.map(
(attributeValue, attributeValueIndex) => (
<>
<FormSpacer />
<Grid variant="inverted">
<div className={classes.label}>
<Typography>{attribute.name}</Typography>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
@ -157,12 +155,20 @@ const ProductVariantCreatePrices: React.FC<
id: "productVariantCreatePricesSetPricePlaceholder"
})}
fullWidth
value={data.price.values[attributeIndex]}
value={data.price.values[attributeValueIndex].value}
onChange={event =>
onAttributeValueChange(
attributeValue.id,
event.target.value,
"price"
)
}
/>
</div>
</Grid>
</>
))}
)
)}
</>
)}
</RadioGroup>
@ -233,12 +239,13 @@ const ProductVariantCreatePrices: React.FC<
</div>
</Grid>
{stockAttributeValues &&
stockAttributeValues.map((attribute, attributeIndex) => (
stockAttributeValues.map(
(attributeValue, attributeValueIndex) => (
<>
<FormSpacer />
<Grid variant="inverted">
<div className={classes.label}>
<Typography>{attribute.name}</Typography>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
@ -248,12 +255,20 @@ const ProductVariantCreatePrices: React.FC<
id: "productVariantCreatePricesSetStockPlaceholder"
})}
fullWidth
value={data.stock.values[attributeIndex]}
value={data.stock.values[attributeValueIndex].value}
onChange={event =>
onAttributeValueChange(
attributeValue.id,
event.target.value,
"stock"
)
}
/>
</div>
</Grid>
</>
))}
)
)}
</>
)}
</RadioGroup>

View file

@ -32,13 +32,13 @@ const useStyles = makeStyles((theme: Theme) => ({
width: "auto"
},
colPrice: {
width: 110
width: 160
},
colSku: {
width: 110
width: 210
},
colStock: {
width: 110
width: 160
},
hr: {
marginBottom: theme.spacing.unit,

View file

@ -14,7 +14,7 @@ import { ProductVariantCreateFormData } from "./form";
export interface ProductVariantCreateValuesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
data: ProductVariantCreateFormData;
onValueClick: (id: string) => void;
onValueClick: (attributeId: string, valueId: string) => void;
}
const useStyles = makeStyles((theme: Theme) => ({
@ -47,10 +47,16 @@ const ProductVariantCreateValues: React.FC<
<div className={classes.valueContainer}>
{attribute.values.map(value => (
<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}`}
label={value.name}
onChange={() => onValueClick(value.id)}
onChange={() => onValueClick(attribute.id, value.id)}
/>
))}
</div>

View file

@ -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 [],
}
`;

View file

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

View file

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

View file

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

View file

@ -1,16 +1,23 @@
import { ProductVariantCreateInput } from "../../../types/globalTypes";
export interface AttributeValue {
id: string;
value: string;
}
export interface AllOrAttribute {
all: boolean;
attribute: string;
value: string;
values: AttributeValue[];
}
export interface Attribute {
id: string;
values: string[];
}
export interface ProductVariantCreateFormData {
attributes: string[];
attributes: Attribute[];
price: AllOrAttribute;
stock: AllOrAttribute;
values: string[];
variants: ProductVariantCreateInput[];
}
@ -28,6 +35,5 @@ export const initialForm: ProductVariantCreateFormData = {
value: "",
values: []
},
values: [],
variants: []
};

View file

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

View file

@ -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";
export type ProductVariantCreateReducerActionType =
@ -10,22 +11,30 @@ export type ProductVariantCreateReducerActionType =
| "changeApplyPriceToAttributeId"
| "changeApplyStockToAllValue"
| "changeApplyStockToAttributeId"
| "changeAttributePrice"
| "changeAttributeStock"
| "changeAttributeValuePrice"
| "changeAttributeValueStock"
| "selectAttribute"
| "selectValue";
export interface ProductVariantCreateReducerAction {
all?: boolean;
id?: string;
attributeId?: string;
type: ProductVariantCreateReducerActionType;
value?: string;
valueId?: string;
}
function selectAttribute(
state: ProductVariantCreateFormData,
attribute: string
attributeId: string
): 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 {
...initialForm,
@ -35,14 +44,24 @@ function selectAttribute(
function selectValue(
state: ProductVariantCreateFormData,
value: string
attributeId: string,
valueId: string
): 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 {
...initialForm,
attributes: state.attributes,
values
attributes: updatedAttributes
};
}
@ -72,13 +91,27 @@ function applyStockToAll(
};
}
function changeAttributePrice(
function changeAttributeValuePrice(
state: ProductVariantCreateFormData,
attribute: string,
attributeValueId: string,
price: string
): ProductVariantCreateFormData {
const index = state.price.values.indexOf(attribute);
const values = updateAtIndex(price, state.price.values, index);
const index = state.price.values.findIndex(
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 {
...state,
@ -89,13 +122,27 @@ function changeAttributePrice(
};
}
function changeAttributeStock(
function changeAttributeValueStock(
state: ProductVariantCreateFormData,
attribute: string,
attributeValueId: string,
stock: string
): ProductVariantCreateFormData {
const index = state.stock.values.indexOf(attribute);
const values = updateAtIndex(stock, state.stock.values, index);
const index = state.stock.values.findIndex(
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 {
...state,
@ -108,13 +155,22 @@ function changeAttributeStock(
function changeApplyPriceToAttributeId(
state: ProductVariantCreateFormData,
attribute: string
attributeId: string
): ProductVariantCreateFormData {
const attribute = state.attributes.find(
attribute => attribute.id === attributeId
);
const values = attribute.values.map(id => ({
id,
value: ""
}));
return {
...state,
price: {
...state.price,
attribute
attribute: attributeId,
values
}
};
}
@ -127,7 +183,13 @@ function changeApplyStockToAttributeId(
...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,
value: string
): ProductVariantCreateFormData {
return {
const data = {
...state,
stock: {
...state.stock,
value
}
};
return {
...data,
variants: createVariants(data)
};
}
function reduceProductVariantCreateFormData(
@ -164,29 +231,30 @@ function reduceProductVariantCreateFormData(
) {
switch (action.type) {
case "selectAttribute":
return selectAttribute(prevState, action.id);
return selectAttribute(prevState, action.attributeId);
case "selectValue":
return selectValue(prevState, action.id);
return selectValue(prevState, action.attributeId, action.valueId);
case "applyPriceToAll":
return applyPriceToAll(prevState, action.all);
case "applyStockToAll":
return applyStockToAll(prevState, action.all);
case "changeAttributePrice":
return changeAttributePrice(prevState, action.id, action.value);
case "changeAttributeStock":
return changeAttributeStock(prevState, action.id, action.value);
case "changeAttributeValuePrice":
return changeAttributeValuePrice(prevState, action.valueId, action.value);
case "changeAttributeValueStock":
return changeAttributeValueStock(prevState, action.valueId, action.value);
case "changeApplyPriceToAttributeId":
return changeApplyPriceToAttributeId(prevState, action.id);
return changeApplyPriceToAttributeId(prevState, action.attributeId);
case "changeApplyStockToAttributeId":
return changeApplyStockToAttributeId(prevState, action.id);
return changeApplyStockToAttributeId(prevState, action.attributeId);
case "changeApplyPriceToAllValue":
return changeApplyPriceToAllValue(prevState, action.value);
case "changeApplyStockToAllValue":
return changeApplyStockToAllValue(prevState, action.value);
}
default:
return prevState;
}
}
export default reduceProductVariantCreateFormData;