diff --git a/cypress/elements/catalog/products/variants-selectors.js b/cypress/elements/catalog/products/variants-selectors.js
index 744127469..494a44742 100644
--- a/cypress/elements/catalog/products/variants-selectors.js
+++ b/cypress/elements/catalog/products/variants-selectors.js
@@ -3,7 +3,7 @@ export const VARIANTS_SELECTORS = {
valueContainer: "[data-test-id='value-container']",
nextButton: "[class*='MuiButton-containedPrimary']",
warehouseCheckboxes: "[name*='warehouse:']",
- skuInput: "input[class*='MuiInputBase'][type='text']",
+ skuInput: "[ data-test-id='sku-input']",
attributeSelector: "[data-test='attribute-value']",
attributeOption: "[data-test-type='option']",
addWarehouseButton: "button[class*='MuiIconButton-colorPrimary']",
diff --git a/cypress/fixtures/urlList.js b/cypress/fixtures/urlList.js
index 5a2fbf661..e76e97aea 100644
--- a/cypress/fixtures/urlList.js
+++ b/cypress/fixtures/urlList.js
@@ -26,6 +26,7 @@ export const urlList = {
staffMembers: "staff/",
stripeApiPaymentMethods: "https://api.stripe.com/v1/payment_methods",
translations: "translations/",
+ variants: "variant/",
vouchers: "discounts/vouchers/",
warehouses: "warehouses/",
weightRete: "weight/"
@@ -42,6 +43,9 @@ export const categoryDetailsUrl = categoryId =>
export const customerDetailsUrl = customerId =>
`${urlList.customers}${customerId}`;
+export const giftCardDetailsUrl = giftCardId =>
+ `${urlList.giftCards}${giftCardId}`;
+
export const menuDetailsUrl = menuId => `${urlList.navigation}${menuId}`;
export const pageTypeDetailsUrl = pageTypeId =>
@@ -52,6 +56,12 @@ export const permissionGroupDetails = permissionGroupId =>
export const productDetailsUrl = productId => `${urlList.products}${productId}`;
+export const productVariantDetailUrl = (productId, variantId) =>
+ `${urlList.products}${productId}/${urlList.variants}${variantId}`;
+
+export const productTypeDetailsUrl = productTypeId =>
+ `${urlList.productTypes}${productTypeId}`;
+
export const staffMemberDetailsUrl = staffMemberId =>
`${urlList.staffMembers}${staffMemberId}`;
diff --git a/cypress/integration/products/productsWithoutSku/updatingProductsWithoutSku.js b/cypress/integration/products/productsWithoutSku/updatingProductsWithoutSku.js
new file mode 100644
index 000000000..303e56dab
--- /dev/null
+++ b/cypress/integration/products/productsWithoutSku/updatingProductsWithoutSku.js
@@ -0,0 +1,277 @@
+///
+///
+
+import faker from "faker";
+
+import { PRODUCT_DETAILS } from "../../../elements/catalog/products/product-details";
+import { VARIANTS_SELECTORS } from "../../../elements/catalog/products/variants-selectors";
+import { BUTTON_SELECTORS } from "../../../elements/shared/button-selectors";
+import { SHARED_ELEMENTS } from "../../../elements/shared/sharedElements";
+import {
+ productDetailsUrl,
+ productVariantDetailUrl
+} from "../../../fixtures/urlList";
+import {
+ createVariant,
+ getVariant
+} from "../../../support/api/requests/Product";
+import {
+ createTypeProduct,
+ productAttributeAssignmentUpdate
+} from "../../../support/api/requests/ProductType";
+import { getDefaultChannel } from "../../../support/api/utils/channelsUtils";
+import { createWaitingForCaptureOrder } from "../../../support/api/utils/ordersUtils";
+import {
+ createProductInChannelWithoutVariants,
+ createTypeAttributeAndCategoryForProduct,
+ deleteProductsStartsWith
+} from "../../../support/api/utils/products/productsUtils";
+import {
+ createShipping,
+ deleteShippingStartsWith
+} from "../../../support/api/utils/shippingUtils";
+import filterTests from "../../../support/filterTests";
+
+filterTests({ definedTags: ["all"], version: "3.1.0" }, () => {
+ describe("Updating products without sku", () => {
+ const startsWith = "UpdateProductsSku";
+
+ let defaultChannel;
+ let address;
+ let warehouse;
+ let shippingMethod;
+ let attribute;
+ let category;
+ let productTypeWithVariants;
+ let productTypeWithoutVariants;
+ let product;
+
+ const name = `${startsWith}${faker.datatype.number()}`;
+ const productTypeWithoutVariantsName = `${startsWith}${faker.datatype.number()}`;
+ const email = "example@example.com";
+ const attributeValues = ["value1", "value2"];
+
+ before(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ deleteProductsStartsWith(startsWith);
+ deleteShippingStartsWith(startsWith);
+ getDefaultChannel()
+ .then(channel => {
+ defaultChannel = channel;
+ cy.fixture("addresses");
+ })
+ .then(fixtureAddresses => {
+ address = fixtureAddresses.plAddress;
+ createShipping({
+ channelId: defaultChannel.id,
+ name,
+ address
+ });
+ })
+ .then(
+ ({
+ warehouse: warehouseResp,
+ shippingMethod: shippingMethodResp
+ }) => {
+ warehouse = warehouseResp;
+ shippingMethod = shippingMethodResp;
+ createTypeAttributeAndCategoryForProduct({ name, attributeValues });
+ }
+ )
+ .then(
+ ({
+ attribute: attributeResp,
+ productType: productTypeResp,
+ category: categoryResp
+ }) => {
+ attribute = attributeResp;
+ productTypeWithVariants = productTypeResp;
+ category = categoryResp;
+ productAttributeAssignmentUpdate({
+ productTypeId: productTypeWithVariants.id,
+ attributeId: attribute.id
+ });
+ createTypeProduct({
+ name: productTypeWithoutVariantsName,
+ attributeId: attribute.id,
+ hasVariants: false
+ });
+ }
+ )
+ .then(productTypeResp => {
+ productTypeWithoutVariants = productTypeResp;
+ productAttributeAssignmentUpdate({
+ productTypeId: productTypeWithoutVariants.id,
+ attributeId: attribute.id
+ });
+ createProductInChannelWithoutVariants({
+ name,
+ channelId: defaultChannel.id,
+ attributeId: attribute.id,
+ productTypeId: productTypeWithVariants.id,
+ categoryId: category.id
+ });
+ })
+ .then(productResp => (product = productResp));
+ });
+
+ beforeEach(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ });
+
+ it("should add sku to simple product", () => {
+ const sku = "NewSkuSimpleProd";
+ const simpleProductName = `${startsWith}${faker.datatype.number()}`;
+ let simpleProduct;
+ createProductInChannelWithoutVariants({
+ name: simpleProductName,
+ attributeId: attribute.id,
+ categoryId: category.id,
+ channelId: defaultChannel.id,
+ productTypeId: productTypeWithoutVariants.id
+ })
+ .then(productResp => {
+ simpleProduct = productResp;
+ createVariant({
+ productId: simpleProduct.id,
+ channelId: defaultChannel.id,
+ warehouseId: warehouse.id,
+ quantityInWarehouse: 10
+ });
+ })
+ .then(variantsList => {
+ cy.visitAndWaitForProgressBarToDisappear(
+ productDetailsUrl(simpleProduct.id)
+ )
+ .get(SHARED_ELEMENTS.skeleton)
+ .should("not.exist")
+ .get(PRODUCT_DETAILS.skuInput)
+ .type(sku)
+ .addAliasToGraphRequest("SimpleProductUpdate")
+ .get(BUTTON_SELECTORS.confirm)
+ .click()
+ .waitForRequestAndCheckIfNoErrors("@SimpleProductUpdate");
+ getVariant(variantsList[0].id, defaultChannel.slug);
+ })
+ .then(variantResp => {
+ expect(variantResp.sku).to.eq(sku);
+ });
+ });
+
+ it("should add sku to variant", () => {
+ const sku = "NewSku";
+ let variant;
+ createVariant({
+ attributeId: attribute.id,
+ channelId: defaultChannel.id,
+ productId: product.id,
+ quantityInWarehouse: 10,
+ warehouseId: warehouse.id,
+ attributeName: attributeValues[0]
+ })
+ .then(variantResp => {
+ variant = variantResp[0];
+ cy.visitAndWaitForProgressBarToDisappear(
+ productVariantDetailUrl(product.id, variant.id)
+ )
+ .get(SHARED_ELEMENTS.skeleton)
+ .should("not.exist")
+ .get(VARIANTS_SELECTORS.skuInput)
+ .type(sku)
+ .addAliasToGraphRequest("VariantUpdate")
+ .get(BUTTON_SELECTORS.confirm)
+ .click()
+ .waitForRequestAndCheckIfNoErrors("@VariantUpdate");
+ getVariant(variant.id, defaultChannel.slug);
+ })
+ .then(variantResp => {
+ expect(variantResp.sku).to.equal(sku);
+ });
+ });
+
+ it("should remove sku from variant", () => {
+ let variant;
+ createVariant({
+ attributeId: attribute.id,
+ channelId: defaultChannel.id,
+ productId: product.id,
+ quantityInWarehouse: 10,
+ warehouseId: warehouse.id,
+ sku: name,
+ attributeName: attributeValues[1]
+ })
+ .then(variantResp => {
+ variant = variantResp[0];
+ cy.visitAndWaitForProgressBarToDisappear(
+ productVariantDetailUrl(product.id, variant.id)
+ )
+ .get(SHARED_ELEMENTS.skeleton)
+ .should("not.exist")
+ .get(VARIANTS_SELECTORS.skuInput)
+ .find("input")
+ .clear()
+ .addAliasToGraphRequest("VariantUpdate")
+ .get(BUTTON_SELECTORS.confirm)
+ .click()
+ .waitForRequestAndCheckIfNoErrors("@VariantUpdate");
+ getVariant(variant.id, defaultChannel.slug);
+ })
+ .then(variantResp => {
+ expect(variantResp.sku).to.be.null;
+ checkIfCheckoutForVariantCanBeCompleted(variantResp);
+ });
+ });
+
+ it("should remove sku from simple product", () => {
+ const simpleProductName = `${startsWith}${faker.datatype.number()}`;
+ let simpleProduct;
+ createProductInChannelWithoutVariants({
+ name: simpleProductName,
+ attributeId: attribute.id,
+ categoryId: category.id,
+ channelId: defaultChannel.id,
+ productTypeId: productTypeWithoutVariants.id
+ })
+ .then(productResp => {
+ simpleProduct = productResp;
+ createVariant({
+ productId: simpleProduct.id,
+ channelId: defaultChannel.id,
+ sku: simpleProductName,
+ quantityInWarehouse: 10,
+ warehouseId: warehouse.id
+ });
+ })
+ .then(variantsList => {
+ cy.visitAndWaitForProgressBarToDisappear(
+ productDetailsUrl(simpleProduct.id)
+ )
+ .get(SHARED_ELEMENTS.skeleton)
+ .should("not.exist")
+ .get(PRODUCT_DETAILS.skuInput)
+ .clear()
+ .addAliasToGraphRequest("SimpleProductUpdate")
+ .get(BUTTON_SELECTORS.confirm)
+ .click()
+ .waitForRequestAndCheckIfNoErrors("@SimpleProductUpdate");
+ getVariant(variantsList[0].id, defaultChannel.slug);
+ })
+ .then(variantResp => {
+ expect(variantResp.sku).to.be.null;
+ checkIfCheckoutForVariantCanBeCompleted(variantResp);
+ });
+ });
+
+ function checkIfCheckoutForVariantCanBeCompleted(variant) {
+ createWaitingForCaptureOrder({
+ address,
+ channelSlug: defaultChannel.slug,
+ email,
+ shippingMethodId: shippingMethod.id,
+ variantsList: [variant]
+ }).then(({ order }) => {
+ expect(order.id).to.be.ok;
+ });
+ }
+ });
+});
diff --git a/cypress/support/api/requests/Product.js b/cypress/support/api/requests/Product.js
index 9f3ef3264..3467caa16 100644
--- a/cypress/support/api/requests/Product.js
+++ b/cypress/support/api/requests/Product.js
@@ -153,8 +153,20 @@ export function createVariant({
costPrice = 1,
trackInventory = true,
weight = 1,
+ attributeName = "value",
attributeValues = ["value"]
}) {
+ const skuLines = getValueWithDefault(sku, `sku: "${sku}"`);
+
+ const attributeLines = getValueWithDefault(
+ attributeId,
+ `attributes: [{
+ id:"${attributeId}"
+ values: ["${attributeName}"]
+ }]`,
+ "attributes:[]"
+ );
+
const channelListings = getValueWithDefault(
channelId,
`channelListings:{
@@ -174,12 +186,9 @@ export function createVariant({
const mutation = `mutation{
productVariantBulkCreate(product: "${productId}", variants: {
- attributes: [{
- id:"${attributeId}"
- values: ${getValuesInArray(attributeValues)}
- }]
+ ${attributeLines}
weight: ${weight}
- sku: "${sku}"
+ ${skuLines}
${channelListings}
trackInventory:${trackInventory}
${stocks}
@@ -236,6 +245,7 @@ export function getVariant(id, channel, auth = "auth") {
productVariant(id:"${id}" channel:"${channel}"){
id
name
+ sku
pricing{
onSale
discount{
diff --git a/cypress/support/api/utils/products/productsUtils.js b/cypress/support/api/utils/products/productsUtils.js
index bf314fe4e..5cf160a1a 100644
--- a/cypress/support/api/utils/products/productsUtils.js
+++ b/cypress/support/api/utils/products/productsUtils.js
@@ -29,33 +29,28 @@ export function createProductInChannel({
collectionId = null,
description = null,
trackInventory = true,
- weight = 1
+ weight = 1,
+ sku = name
}) {
let product;
let variantsList;
- return productRequest
- .createProduct({
- attributeId,
- name,
- productTypeId,
- categoryId,
- collectionId,
- description
- })
+ return createProductInChannelWithoutVariants({
+ name,
+ channelId,
+ productTypeId,
+ attributeId,
+ categoryId,
+ isPublished,
+ isAvailableForPurchase,
+ visibleInListings,
+ collectionId,
+ description
+ })
.then(productResp => {
product = productResp;
- productRequest.updateChannelInProduct({
- productId: product.id,
- channelId,
- isPublished,
- isAvailableForPurchase,
- visibleInListings
- });
- })
- .then(() => {
productRequest.createVariant({
productId: product.id,
- sku: name,
+ sku,
attributeId,
warehouseId,
quantityInWarehouse,
@@ -207,3 +202,38 @@ export function createProductWithShipping({
address
}));
}
+
+export function createProductInChannelWithoutVariants({
+ name,
+ channelId,
+ productTypeId,
+ attributeId,
+ categoryId,
+ isPublished = true,
+ isAvailableForPurchase = true,
+ visibleInListings = true,
+ collectionId = null,
+ description = null
+}) {
+ let product;
+ return productRequest
+ .createProduct({
+ attributeId,
+ name,
+ productTypeId,
+ categoryId,
+ collectionId,
+ description
+ })
+ .then(productResp => {
+ product = productResp;
+ productRequest.updateChannelInProduct({
+ productId: product.id,
+ channelId,
+ isPublished,
+ isAvailableForPurchase,
+ visibleInListings
+ });
+ })
+ .then(() => product);
+}
diff --git a/cypress/support/customCommands/sharedElementsOperations/progressBar.js b/cypress/support/customCommands/sharedElementsOperations/progressBar.js
index f0ebc329e..e7674b55d 100644
--- a/cypress/support/customCommands/sharedElementsOperations/progressBar.js
+++ b/cypress/support/customCommands/sharedElementsOperations/progressBar.js
@@ -1,18 +1,5 @@
import { SHARED_ELEMENTS } from "../../../elements/shared/sharedElements";
-// export function visitAndWaitForProgressBarToDisappear(url) {
-// cy.visit(url);
-// return waitForProgressBarToNotBeVisible();
-// }
-
-// export function waitForProgressBarToNotBeVisible() {
-// return cy.get(SHARED_ELEMENTS.progressBar).should("be.not.visible");
-// }
-
-// export function waitForProgressBarToNotExist() {
-// return cy.get(SHARED_ELEMENTS.progressBar).should("not.exist");
-// }
-
Cypress.Commands.add("visitAndWaitForProgressBarToDisappear", url => {
cy.visit(url).waitForProgressBarToNotBeVisible();
});
diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx
index 92669916e..c70ea5835 100644
--- a/src/products/components/ProductStocks/ProductStocks.tsx
+++ b/src/products/components/ProductStocks/ProductStocks.tsx
@@ -225,6 +225,7 @@ const ProductStocks: React.FC = ({