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 ? (