diff --git a/cypress/apiRequests/Attribute.js b/cypress/apiRequests/Attribute.js
index b0f23be23..5cd8ef9d2 100644
--- a/cypress/apiRequests/Attribute.js
+++ b/cypress/apiRequests/Attribute.js
@@ -1,13 +1,17 @@
class Attribute {
- createAttribute(name) {
+ createAttribute(name, attributeValues = ["value"]) {
+ attributeValues = attributeValues.map(element => `{name:"${element}"}`);
const mutation = `mutation{
attributeCreate(input:{
name:"${name}"
valueRequired:false
type:PRODUCT_TYPE
+ values: [${attributeValues}]
}){
attribute{
id
+ name
+ values{name}
}
attributeErrors{
field
diff --git a/cypress/apiRequests/Product.js b/cypress/apiRequests/Product.js
index 8aa6b0ce8..77f665755 100644
--- a/cypress/apiRequests/Product.js
+++ b/cypress/apiRequests/Product.js
@@ -1,7 +1,6 @@
-import Utils from "./utils/Utils";
+import { getValueWithDefault } from "./utils/Utils";
class Product {
- utils = new Utils();
getFirstProducts(first, search) {
const filter = search
? `, filter:{
@@ -49,7 +48,7 @@ class Product {
}
}
}`;
- cy.sendRequestWithQuery(mutation);
+ return cy.sendRequestWithQuery(mutation);
}
updateChannelPriceInVariant(variantId, channelId) {
@@ -88,7 +87,7 @@ class Product {
return cy.sendRequestWithQuery(mutation);
}
- createVariant(
+ createVariant({
productId,
sku,
warehouseId,
@@ -96,8 +95,8 @@ class Product {
channelId,
price = 1,
costPrice = 1
- ) {
- const channelListings = this.utils.getValueWithDefault(
+ }) {
+ const channelListings = getValueWithDefault(
channelId,
`channelListings:{
channelId:"${channelId}"
@@ -106,7 +105,7 @@ class Product {
}`
);
- const stocks = this.utils.getValueWithDefault(
+ const stocks = getValueWithDefault(
warehouseId,
`stocks:{
warehouse:"${warehouseId}"
@@ -141,6 +140,7 @@ class Product {
slug: "${slug}"
isShippingRequired: true
productAttributes: "${attributeId}"
+ variantAttributes: "${attributeId}"
}){
productErrors{
field
diff --git a/cypress/apiRequests/Warehouse.js b/cypress/apiRequests/Warehouse.js
index 8237a9333..2bbf18c15 100644
--- a/cypress/apiRequests/Warehouse.js
+++ b/cypress/apiRequests/Warehouse.js
@@ -20,6 +20,7 @@ class Warehouse {
}
warehouse{
id
+ name
}
}
}`;
diff --git a/cypress/apiRequests/storeFront/ProductDetails.js b/cypress/apiRequests/storeFront/ProductDetails.js
index e3bb9d7a1..dc1c3fa30 100644
--- a/cypress/apiRequests/storeFront/ProductDetails.js
+++ b/cypress/apiRequests/storeFront/ProductDetails.js
@@ -4,9 +4,31 @@ class ProductDetails {
id
name
}
+
+ fragment Price on TaxedMoney {
+ gross {
+ amount
+ currency
+ }
+ }
+
+ fragment ProductVariantFields on ProductVariant {
+ id
+ sku
+ name
+ pricing {
+ price {
+ ...Price
+ }
+ }
+ }
+
query ProductDetails{
product(id: "${productId}", channel: "${channelId}") {
...BasicProductFields
+ variants {
+ ...ProductVariantFields
+ }
isAvailable
isAvailableForPurchase
availableForPurchase
diff --git a/cypress/apiRequests/utils/Utils.js b/cypress/apiRequests/utils/Utils.js
index cbb37b751..33373f8d1 100644
--- a/cypress/apiRequests/utils/Utils.js
+++ b/cypress/apiRequests/utils/Utils.js
@@ -1,6 +1,2 @@
-class Utils {
- getValueWithDefault(condition, value, defaultValue = "") {
- return condition ? value : defaultValue;
- }
-}
-export default Utils;
+export const getValueWithDefault = (condition, value, defaultValue = "") =>
+ condition ? value : defaultValue;
diff --git a/cypress/elements/catalog/products/product-selectors.js b/cypress/elements/catalog/products/product-selectors.js
index a75877582..d78adce75 100644
--- a/cypress/elements/catalog/products/product-selectors.js
+++ b/cypress/elements/catalog/products/product-selectors.js
@@ -22,6 +22,8 @@ export const PRODUCTS_SELECTORS = {
channelAvailabilityList: "ul[role='menu']",
goBackButton: "[data-test-id='app-header-back-button']",
assignedChannels: "[data-test='channel-availability-item']",
+ publishedRadioButton: "[role=radiogroup]",
+ addVariantsButton: "[data-test*='button-add-variant']",
publishedRadioButtons: "[name*='isPublished']",
availableForPurchaseRadioButtons: "[name*='isAvailableForPurchase']",
radioButtonsValueTrue: "[value='true']",
diff --git a/cypress/elements/catalog/variants-selectors.js b/cypress/elements/catalog/variants-selectors.js
new file mode 100644
index 000000000..f53e7295e
--- /dev/null
+++ b/cypress/elements/catalog/variants-selectors.js
@@ -0,0 +1,15 @@
+export const VARIANTS_SELECTORS = {
+ attributeCheckbox: "[name*='value:']",
+ valueContainer: "[data-test-id='value-container']",
+ nextButton: "[class*='MuiButton-containedPrimary']",
+ priceInput: "[name*='channel-price']",
+ costPriceInput: "[name*='channel-costPrice']",
+ warehouseCheckboxes: "[name*='warehouse:']",
+ skuInput: "input[class*='MuiInputBase'][type='text']",
+ attributeSelector: "[data-test='attribute-value']",
+ attributeOption: "[data-test-type='option']",
+ addWarehouseButton: "button[class*='MuiIconButton-colorPrimary']",
+ warehouseOption: "[role='menuitem']",
+ saveButton: "[data-test='button-bar-confirm']",
+ skuInputInAddVariant: "[name='sku']"
+};
diff --git a/cypress/integration/products/menageProducts/availableForPurchaseProducts.js b/cypress/integration/products/menageProducts/availableForPurchaseProducts.js
index 39413c295..aa33c504e 100644
--- a/cypress/integration/products/menageProducts/availableForPurchaseProducts.js
+++ b/cypress/integration/products/menageProducts/availableForPurchaseProducts.js
@@ -1,6 +1,6 @@
import faker from "faker";
-import ProductSteps from "../../../steps/productSteps";
+import ProductSteps from "../../../steps/products/productSteps";
import { productDetailsUrl } from "../../../url/urlList";
import ChannelsUtils from "../../../utils/channelsUtils";
import ProductsUtils from "../../../utils/productsUtils";
diff --git a/cypress/integration/products/menageProducts/publishedProducts.js b/cypress/integration/products/menageProducts/publishedProducts.js
index 974f5756d..e5661b7bf 100644
--- a/cypress/integration/products/menageProducts/publishedProducts.js
+++ b/cypress/integration/products/menageProducts/publishedProducts.js
@@ -1,6 +1,6 @@
import faker from "faker";
-import ProductSteps from "../../../steps/productSteps";
+import ProductSteps from "../../../steps/products/productSteps";
import { productDetailsUrl } from "../../../url/urlList";
import ChannelsUtils from "../../../utils/channelsUtils";
import ProductsUtils from "../../../utils/productsUtils";
diff --git a/cypress/integration/products/menageProducts/visibleInListingsProducts.js b/cypress/integration/products/menageProducts/visibleInListingsProducts.js
index b41dc2df9..e34e1c413 100644
--- a/cypress/integration/products/menageProducts/visibleInListingsProducts.js
+++ b/cypress/integration/products/menageProducts/visibleInListingsProducts.js
@@ -1,6 +1,6 @@
import faker from "faker";
-import ProductSteps from "../../../steps/productSteps";
+import ProductSteps from "../../../steps/products/productSteps";
import { productDetailsUrl } from "../../../url/urlList";
import ChannelsUtils from "../../../utils/channelsUtils";
import ProductsUtils from "../../../utils/productsUtils";
diff --git a/cypress/integration/products/productsVariants.js b/cypress/integration/products/productsVariants.js
new file mode 100644
index 000000000..7c28eb999
--- /dev/null
+++ b/cypress/integration/products/productsVariants.js
@@ -0,0 +1,175 @@
+import faker from "faker";
+
+import Channels from "../../apiRequests/Channels";
+import Product from "../../apiRequests/Product";
+import VariantsSteps from "../../steps/products/VariantsSteps";
+import { urlList } from "../../url/urlList";
+import ChannelsUtils from "../../utils/channelsUtils";
+import ProductsUtils from "../../utils/productsUtils";
+import ShippingUtils from "../../utils/shippingUtils";
+import { getProductVariants } from "../../utils/storeFront/storeFrontProductUtils";
+
+//
+describe("creating variants", () => {
+ const startsWith = "Cy-";
+ const attributeValues = ["value1", "value2"];
+
+ const productUtils = new ProductsUtils();
+ const channelsUtils = new ChannelsUtils();
+ const shippingUtils = new ShippingUtils();
+ const product = new Product();
+ const channels = new Channels();
+
+ const variantsSteps = new VariantsSteps();
+
+ let defaultChannel;
+ let warehouse;
+ let attribute;
+ let productType;
+ let category;
+
+ before(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ shippingUtils.deleteShipping(startsWith);
+ productUtils.deleteProperProducts(startsWith);
+ channelsUtils.deleteChannels(startsWith);
+
+ const name = `${startsWith}${faker.random.number()}`;
+ channelsUtils
+ .getDefaultChannel()
+ .then(channel => {
+ defaultChannel = channel;
+ cy.fixture("addresses");
+ })
+ .then(fixtureAddresses =>
+ shippingUtils.createShipping({
+ channelId: defaultChannel.id,
+ name,
+ address: fixtureAddresses.plAddress
+ })
+ )
+ .then(() => (warehouse = shippingUtils.getWarehouse()));
+
+ productUtils
+ .createTypeAttributeAndCategoryForProduct(name, attributeValues)
+ .then(() => {
+ attribute = productUtils.getAttribute();
+ productType = productUtils.getProductType();
+ category = productUtils.getCategory();
+ });
+ });
+
+ beforeEach(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ });
+
+ it("should create variant visible on frontend", () => {
+ const name = `${startsWith}${faker.random.number()}`;
+ const price = 10;
+ let createdProduct;
+
+ product
+ .createProduct(attribute.id, name, productType.id, category.id)
+ .then(resp => {
+ createdProduct = resp.body.data.productCreate.product;
+ product.updateChannelInProduct({
+ productId: createdProduct.id,
+ channelId: defaultChannel.id
+ });
+ cy.visit(`${urlList.products}${createdProduct.id}`);
+ variantsSteps.createFirstVariant({
+ sku: name,
+ warehouseId: warehouse.id,
+ price,
+ attribute: attributeValues[0]
+ });
+ getProductVariants(createdProduct.id, defaultChannel.slug);
+ })
+ .then(([variant]) => {
+ expect(variant).to.have.property("name", attributeValues[0]);
+ expect(variant).to.have.property("price", price);
+ });
+ });
+ it("should create several variants", () => {
+ const name = `${startsWith}${faker.random.number()}`;
+ const secondVariantSku = `${startsWith}${faker.random.number()}`;
+ const variants = [{ price: 7 }, { name: attributeValues[1], price: 16 }];
+ let createdProduct;
+
+ productUtils
+ .createProductInChannel({
+ name,
+ attributeId: attribute.id,
+ channelId: defaultChannel.id,
+ warehouseId: warehouse.id,
+ productTypeId: productType.id,
+ categoryId: category.id,
+ price: variants[0].price
+ })
+ .then(() => {
+ createdProduct = productUtils.getCreatedProduct();
+ cy.visit(`${urlList.products}${createdProduct.id}`);
+ variantsSteps.createVariant({
+ sku: secondVariantSku,
+ warehouseName: warehouse.name,
+ attributeName: variants[1].name,
+ price: variants[1].price
+ });
+ })
+ .then(() => getProductVariants(createdProduct.id, defaultChannel.slug))
+ .then(([firstVariant, secondVariant]) => {
+ expect(firstVariant).to.have.property("price", variants[0].price);
+ expect(secondVariant).to.have.property("name", variants[1].name);
+ expect(secondVariant).to.have.property("price", variants[1].price);
+ });
+ });
+ it("should create variant for many channels", () => {
+ const name = `${startsWith}${faker.random.number()}`;
+ const variantsPrice = 10;
+ let newChannel;
+ let createdProduct;
+ channels
+ .createChannel(true, name, name, "PLN")
+ .then(resp => {
+ newChannel = resp.body.data.channelCreate.channel;
+ productUtils.createProduct(
+ attribute.id,
+ name,
+ productType.id,
+ category.id
+ );
+ })
+ .then(() => {
+ createdProduct = productUtils.getCreatedProduct();
+ product.updateChannelInProduct({
+ productId: createdProduct.id,
+ channelId: defaultChannel.id
+ });
+ })
+ .then(() => {
+ product.updateChannelInProduct({
+ productId: createdProduct.id,
+ channelId: newChannel.id
+ });
+ })
+ .then(() => {
+ cy.visit(`${urlList.products}${createdProduct.id}`);
+ variantsSteps.createFirstVariant({
+ sku: name,
+ warehouseId: warehouse.id,
+ price: variantsPrice,
+ attribute: attributeValues[0]
+ });
+ getProductVariants(createdProduct.id, defaultChannel.slug);
+ })
+ .then(([variant]) => {
+ expect(variant).to.have.property("name", attributeValues[0]);
+ expect(variant).to.have.property("price", variantsPrice);
+ getProductVariants(createdProduct.id, newChannel.slug);
+ })
+ .then(([variant]) => {
+ expect(variant).to.have.property("name", attributeValues[0]);
+ expect(variant).to.have.property("price", variantsPrice);
+ });
+ });
+});
diff --git a/cypress/steps/products/VariantsSteps.js b/cypress/steps/products/VariantsSteps.js
new file mode 100644
index 000000000..0234d5faf
--- /dev/null
+++ b/cypress/steps/products/VariantsSteps.js
@@ -0,0 +1,55 @@
+import { PRODUCTS_SELECTORS } from "../../elements/catalog/products/product-selectors";
+import { VARIANTS_SELECTORS } from "../../elements/catalog/variants-selectors";
+
+class VariantsSteps {
+ createFirstVariant({ sku, warehouseId, price, attribute }) {
+ cy.get(PRODUCTS_SELECTORS.addVariantsButton).click();
+ cy.get(VARIANTS_SELECTORS.valueContainer)
+ .contains(attribute)
+ .find(VARIANTS_SELECTORS.attributeCheckbox)
+ .click()
+ .get(VARIANTS_SELECTORS.nextButton)
+ .click()
+ .get(VARIANTS_SELECTORS.priceInput)
+ .each($priceInput => {
+ cy.wrap($priceInput).type(price);
+ });
+ cy.get(`[name*='${warehouseId}']`)
+ .click()
+ .get(VARIANTS_SELECTORS.nextButton)
+ .click()
+ .get(VARIANTS_SELECTORS.skuInput)
+ .type(sku);
+ cy.addAliasToGraphRequest("ProductVariantBulkCreate");
+ cy.get(VARIANTS_SELECTORS.nextButton).click();
+ cy.wait("@ProductVariantBulkCreate");
+ }
+ createVariant({
+ sku,
+ warehouseName,
+ attributeName,
+ price,
+ costPrice = price
+ }) {
+ cy.get(PRODUCTS_SELECTORS.addVariantsButton)
+ .click()
+ .get(VARIANTS_SELECTORS.attributeSelector)
+ .click()
+ .get(VARIANTS_SELECTORS.attributeOption)
+ .contains(attributeName)
+ .click()
+ .get(VARIANTS_SELECTORS.priceInput)
+ .type(price)
+ .get(VARIANTS_SELECTORS.costPriceInput)
+ .type(costPrice)
+ .get(VARIANTS_SELECTORS.skuInputInAddVariant)
+ .type(sku)
+ .get(VARIANTS_SELECTORS.addWarehouseButton)
+ .click();
+ cy.contains(VARIANTS_SELECTORS.warehouseOption, warehouseName).click();
+ cy.addAliasToGraphRequest("ProductVariantDetails");
+ cy.get(VARIANTS_SELECTORS.saveButton).click();
+ cy.wait("@ProductVariantDetails");
+ }
+}
+export default VariantsSteps;
diff --git a/cypress/steps/productSteps.js b/cypress/steps/products/productSteps.js
similarity index 94%
rename from cypress/steps/productSteps.js
rename to cypress/steps/products/productSteps.js
index 355c96d64..78d5870d1 100644
--- a/cypress/steps/productSteps.js
+++ b/cypress/steps/products/productSteps.js
@@ -1,4 +1,4 @@
-import { PRODUCTS_SELECTORS } from "../elements/catalog/products/product-selectors";
+import { PRODUCTS_SELECTORS } from "../../elements/catalog/products/product-selectors";
class ProductSteps {
valueTrue = PRODUCTS_SELECTORS.radioButtonsValueTrue;
diff --git a/cypress/utils/productsUtils.js b/cypress/utils/productsUtils.js
index 2ac9f0ce7..d14b5b715 100644
--- a/cypress/utils/productsUtils.js
+++ b/cypress/utils/productsUtils.js
@@ -19,7 +19,7 @@ class ProductsUtils {
name,
productTypeId,
categoryId
- ).then(() => this.createVariant(this.product.id, name));
+ ).then(() => this.createVariant(this.product.id, name, attributeId));
}
createProductInChannel({
@@ -46,25 +46,25 @@ class ProductsUtils {
})
)
.then(() => {
- this.createVariant(
- this.product.id,
- name,
+ this.createVariant({
+ productId: this.product.id,
+ sku: name,
warehouseId,
quantityInWarehouse,
channelId,
price
- );
+ });
});
}
- createTypeAttributeAndCategoryForProduct(name) {
- return this.createAttribute(name)
+ createTypeAttributeAndCategoryForProduct(name, attributeValues) {
+ return this.createAttribute(name, attributeValues)
.then(() => this.createTypeProduct(name, this.attribute.id))
.then(() => this.createCategory(name));
}
- createAttribute(name) {
+ createAttribute(name, attributeValues) {
return this.attributeRequest
- .createAttribute(name)
+ .createAttribute(name, attributeValues)
.then(
resp => (this.attribute = resp.body.data.attributeCreate.attribute)
);
@@ -87,23 +87,23 @@ class ProductsUtils {
.createProduct(attributeId, name, productTypeId, categoryId)
.then(resp => (this.product = resp.body.data.productCreate.product));
}
- createVariant(
+ createVariant({
productId,
- name,
+ sku,
warehouseId,
quantityInWarehouse,
channelId,
price
- ) {
+ }) {
return this.productRequest
- .createVariant(
+ .createVariant({
productId,
- name,
+ sku,
warehouseId,
- quantityInWarehouse,
+ quantity: quantityInWarehouse,
channelId,
price
- )
+ })
.then(
resp =>
(this.variants =
diff --git a/cypress/utils/storeFront/storeFrontProductUtils.js b/cypress/utils/storeFront/storeFrontProductUtils.js
index e020465e7..e6872c738 100644
--- a/cypress/utils/storeFront/storeFrontProductUtils.js
+++ b/cypress/utils/storeFront/storeFrontProductUtils.js
@@ -26,3 +26,12 @@ export const isProductVisibleInSearchResult = (productName, channelSlug) =>
responseValueKey: ["edges", 0, "node", "name"],
value: productName
});
+
+export const getProductVariants = (productId, channelSlug) =>
+ productDetails.getProductDetails(productId, channelSlug).then(resp => {
+ const variantsList = resp.body.data.product.variants;
+ return variantsList.map(element => ({
+ name: element.name,
+ price: element.pricing.price.gross.amount
+ }));
+ });
diff --git a/package-lock.json b/package-lock.json
index 32b6981df..5109def20 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52734,4 +52734,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/pages/components/PageDetailsPage/form.tsx b/src/pages/components/PageDetailsPage/form.tsx
index cfbe1c0fa..e1c21856f 100644
--- a/src/pages/components/PageDetailsPage/form.tsx
+++ b/src/pages/components/PageDetailsPage/form.tsx
@@ -122,7 +122,7 @@ function usePageForm(
title: page?.title || ""
});
const [content, changeContent] = useRichText({
- initial: page?.content,
+ initial: pageExists ? page?.content : null,
triggerChange
});
diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx
index 84a72acbd..210c51816 100644
--- a/src/products/components/ProductStocks/ProductStocks.tsx
+++ b/src/products/components/ProductStocks/ProductStocks.tsx
@@ -310,6 +310,7 @@ const ProductStocks: React.FC = ({
>
setExpansionState(!isExpanded)}
>
diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx
index 7d98048cc..97235b282 100644
--- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx
+++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx
@@ -200,6 +200,7 @@ const ProductVariantCreatePage: React.FC = props
)}
{step !== ProductVariantCreatorStep.summary ? (