diff --git a/cypress/apiRequests/Category.js b/cypress/apiRequests/Category.js index cb4be5384..9f21a37f6 100644 --- a/cypress/apiRequests/Category.js +++ b/cypress/apiRequests/Category.js @@ -15,6 +15,31 @@ export function createCategory(name, slug = name) { .sendRequestWithQuery(mutation) .its("body.data.categoryCreate.category"); } + +export function getCategory(categoryId) { + const mutation = `query{ + category(id:"${categoryId}"){ + name + description + products{ + edges{ + node{ + name + } + } + } + children(first:100){ + edges{ + node{ + name + } + } + } + } + }`; + return cy.sendRequestWithQuery(mutation).its("body.data.category"); +} + export function getCategories(first, search) { const mutation = `query{ categories(first:${first}, filter:{ @@ -32,6 +57,7 @@ export function getCategories(first, search) { .sendRequestWithQuery(mutation) .then(resp => resp.body.data.categories.edges); } + export function deleteCategory(categoryId) { const mutation = `mutation{ categoryDelete(id:"${categoryId}"){ diff --git a/cypress/apiRequests/Product.js b/cypress/apiRequests/Product.js index 5a4712901..76387cdd6 100644 --- a/cypress/apiRequests/Product.js +++ b/cypress/apiRequests/Product.js @@ -97,15 +97,11 @@ export function createProduct({ collectionId, `collections:["${collectionId}"]` ); + const category = getValueWithDefault(categoryId, `category:"${categoryId}"`); const descriptionLine = getValueWithDefault( description, `description:"{\\"blocks\\":[{\\"type\\":\\"paragraph\\",\\"data\\":{\\"text\\":\\"${description}\\"}}]}"` ); - const categoryLine = getValueWithDefault( - categoryId, - `category:"${categoryId}"` - ); - const mutation = `mutation{ productCreate(input:{ attributes:[{ @@ -115,7 +111,7 @@ export function createProduct({ slug:"${name}" seo:{title:"${name}" description:""} productType:"${productTypeId}" - ${categoryLine} + ${category} ${collection} ${descriptionLine} }){ diff --git a/cypress/elements/catalog/categories/categories-list.js b/cypress/elements/catalog/categories/categories-list.js new file mode 100644 index 000000000..4e9627da0 --- /dev/null +++ b/cypress/elements/catalog/categories/categories-list.js @@ -0,0 +1,3 @@ +export const CATEGORIES_LIST = { + addCategoryButton: '[data-test-id="createCategory"]' +}; diff --git a/cypress/elements/catalog/categories/category-details.js b/cypress/elements/catalog/categories/category-details.js new file mode 100644 index 000000000..035ff5f04 --- /dev/null +++ b/cypress/elements/catalog/categories/category-details.js @@ -0,0 +1,9 @@ +export const CATEGORY_DETAILS = { + nameInput: '[name="name"]', + descriptionInput: '[data-test-id="description"]', + createSubcategoryButton: '[data-test-id="createSubcategory"]', + categoryChildrenRow: '[data-test="id"]', + productsTab: '[data-test-id="productsTab"]', + addProducts: '[data-test-id="addProducts"]', + productRow: '[data-test-id="productRow"]' +}; diff --git a/cypress/elements/shared/sharedElements.js b/cypress/elements/shared/sharedElements.js index d43ee7245..a935ae025 100644 --- a/cypress/elements/shared/sharedElements.js +++ b/cypress/elements/shared/sharedElements.js @@ -4,9 +4,10 @@ export const SHARED_ELEMENTS = { circularProgress: '[class*="CircularProgress-circle"]', skeleton: '[data-test-id="skeleton"]', table: 'table[class*="Table"]', - selectOption: '[data-test="selectFieldOption"]', + tableRow: '[data-test="id"]', confirmationMsg: "[data-test='notification-success']", - notificationSuccess: '[data-test="notification-success"]', + searchInput: '[data-test-id="searchInput"]', + selectOption: '[data-test="selectFieldOption"]', richTextEditor: { empty: '[class*="codex-editor--empty"]' } diff --git a/cypress/integration/allEnv/categories.js b/cypress/integration/allEnv/categories.js new file mode 100644 index 000000000..7b1aea558 --- /dev/null +++ b/cypress/integration/allEnv/categories.js @@ -0,0 +1,136 @@ +// +import faker from "faker"; + +import { getCategory } from "../../apiRequests/Category"; +import { CATEGORIES_LIST } from "../../elements/catalog/categories/categories-list"; +import { CATEGORY_DETAILS } from "../../elements/catalog/categories/category-details"; +import { BUTTON_SELECTORS } from "../../elements/shared/button-selectors"; +import { SHARED_ELEMENTS } from "../../elements/shared/sharedElements"; +import { createCategory } from "../../steps/categoriesSteps"; +import { confirmationMessageShouldDisappear } from "../../steps/shared/confirmationMessages"; +import { categoryDetails, urlList } from "../../url/urlList"; +import { deleteCategoriesStartsWith } from "../../utils/categoryUtils"; +import * as channelsUtils from "../../utils/channelsUtils"; +import * as productsUtils from "../../utils/products/productsUtils"; +import { deleteShippingStartsWith } from "../../utils/shippingUtils"; + +describe("Categories", () => { + const startsWith = "CyCollections"; + const name = `${startsWith}${faker.datatype.number()}`; + + let attribute; + let category; + let productType; + let product; + + let defaultChannel; + + before(() => { + cy.clearSessionData().loginUserViaRequest(); + productsUtils.deleteProductsStartsWith(startsWith); + deleteCategoriesStartsWith(startsWith); + deleteShippingStartsWith(startsWith); + channelsUtils.deleteChannelsStartsWith(startsWith); + + channelsUtils + .getDefaultChannel() + .then(channel => { + defaultChannel = channel; + productsUtils.createTypeAttributeAndCategoryForProduct(name); + }) + .then( + ({ + category: categoryResp, + attribute: attributeResp, + productType: productTypeResp + }) => { + category = categoryResp; + attribute = attributeResp; + productType = productTypeResp; + productsUtils.createProductInChannel({ + name, + channelId: defaultChannel.id, + productTypeId: productType.id, + attributeId: attribute.id, + categoryId: category.id + }); + } + ) + .then(({ product: productResp }) => (product = productResp)); + }); + + beforeEach(() => { + cy.clearSessionData().loginUserViaRequest(); + }); + + it("should create category", () => { + const categoryName = `${startsWith}${faker.datatype.number()}`; + + cy.visit(urlList.categories) + .get(CATEGORIES_LIST.addCategoryButton) + .click(); + createCategory({ name: categoryName, description: categoryName }) + .its("response.body.data.categoryCreate.category") + .then(newCategory => { + getCategory(newCategory.id); + }) + .then(newCategory => { + expect(newCategory.name).to.eq(categoryName); + // Uncomment this expect after fixing bug SALEOR-3728 + // expect(newCategory.description).to.eq(categoryName); + }); + }); + + it("should add subcategory", () => { + const categoryName = `${startsWith}${faker.datatype.number()}`; + cy.visit(categoryDetails(category.id)) + .get(CATEGORY_DETAILS.createSubcategoryButton) + .click(); + createCategory({ name: categoryName, description: categoryName }) + .visit(categoryDetails(category.id)) + .contains(CATEGORY_DETAILS.categoryChildrenRow, categoryName) + .should("be.visible"); + getCategory(category.id).then(categoryResp => { + expect(categoryResp.children.edges[0].node.name).to.eq(categoryName); + }); + }); + + it("should add product to category", () => { + cy.visit(categoryDetails(category.id)) + .get(CATEGORY_DETAILS.productsTab) + .click() + .get(CATEGORY_DETAILS.addProducts) + .click() + .url() + .should("include", urlList.addProduct); + }); + + it("should remove product from category", () => { + cy.visit(categoryDetails(category.id)) + .get(CATEGORY_DETAILS.productsTab) + .click(); + cy.contains(CATEGORY_DETAILS.productRow, product.name) + .find(BUTTON_SELECTORS.checkbox) + .click() + .get(BUTTON_SELECTORS.deleteIcon) + .click() + .addAliasToGraphRequest("productBulkDelete") + .get(BUTTON_SELECTORS.submit) + .click(); + confirmationMessageShouldDisappear(); + cy.contains(CATEGORY_DETAILS.productRow, product.name) + .should("not.exist") + .wait("@productBulkDelete"); + getCategory(category.id).then(categoryResp => { + expect(categoryResp.products).to.be.null; + }); + }); + + it("should enter category details page", () => { + cy.visit(urlList.categories) + .get(SHARED_ELEMENTS.searchInput) + .type(category.name); + cy.contains(SHARED_ELEMENTS.tableRow, category.name).click(); + cy.contains(SHARED_ELEMENTS.header, category.name).should("be.visible"); + }); +}); diff --git a/cypress/integration/allEnv/collections.js b/cypress/integration/allEnv/collections.js index 43f286b5e..a3a3d7695 100644 --- a/cypress/integration/allEnv/collections.js +++ b/cypress/integration/allEnv/collections.js @@ -106,6 +106,7 @@ describe("Collections", () => { expect(isVisible).to.equal(true); }); }); + it("should not display collection not set as available in channel", () => { const collectionName = `${startsWith}${faker.datatype.number()}`; let collection; @@ -131,6 +132,7 @@ describe("Collections", () => { expect(isVisible).to.equal(false); }); }); + it("should display products hidden in listing", () => { // Products "hidden in listings" are not displayed in Category listings or search results, // but are listed on Collections diff --git a/cypress/steps/categoriesSteps.js b/cypress/steps/categoriesSteps.js new file mode 100644 index 000000000..adeb27f0c --- /dev/null +++ b/cypress/steps/categoriesSteps.js @@ -0,0 +1,15 @@ +import { CATEGORY_DETAILS } from "../elements/catalog/categories/category-details"; +import { BUTTON_SELECTORS } from "../elements/shared/button-selectors"; +import { confirmationMessageShouldDisappear } from "./shared/confirmationMessages"; + +export function createCategory({ name, description }) { + cy.get(CATEGORY_DETAILS.nameInput) + .type(name) + .get(CATEGORY_DETAILS.descriptionInput) + .type(description) + .addAliasToGraphRequest("CategoryCreate") + .get(BUTTON_SELECTORS.confirm) + .click(); + confirmationMessageShouldDisappear(); + return cy.wait("@CategoryCreate"); +} diff --git a/cypress/steps/collectionsSteps.js b/cypress/steps/collectionsSteps.js index 14c39a335..32101f79c 100644 --- a/cypress/steps/collectionsSteps.js +++ b/cypress/steps/collectionsSteps.js @@ -1,5 +1,4 @@ import { COLLECTION_SELECTORS } from "../elements/catalog/collection-selectors"; -import { PRODUCT_DETAILS } from "../elements/catalog/products/product-details"; import { AVAILABLE_CHANNELS_FORM } from "../elements/channels/available-channels-form"; import { SELECT_CHANNELS_TO_ASSIGN } from "../elements/channels/select-channels-to-assign"; import { ASSIGN_ELEMENTS_SELECTORS } from "../elements/shared/assign-elements-selectors"; diff --git a/cypress/steps/shared/confirmationMessages.js b/cypress/steps/shared/confirmationMessages.js new file mode 100644 index 000000000..516cd2939 --- /dev/null +++ b/cypress/steps/shared/confirmationMessages.js @@ -0,0 +1,8 @@ +import { SHARED_ELEMENTS } from "../../elements/shared/sharedElements"; + +export function confirmationMessageShouldDisappear() { + cy.get(SHARED_ELEMENTS.confirmationMsg) + .should("be.visible") + .get(SHARED_ELEMENTS.confirmationMsg) + .should("not.exist"); +} diff --git a/cypress/steps/shippingMethodSteps.js b/cypress/steps/shippingMethodSteps.js index 260a6f2af..04c0490b2 100644 --- a/cypress/steps/shippingMethodSteps.js +++ b/cypress/steps/shippingMethodSteps.js @@ -56,13 +56,9 @@ export function createShippingZone( export function changeWeightUnit(weightUnit) { fillBaseSelect(SHIPPING_ZONES_LIST.unitSelect, weightUnit); cy.addAliasToGraphRequest("UpdateDefaultWeightUnit"); - cy.get(SHIPPING_ZONES_LIST.saveUnit) - .click() - .get(SHARED_ELEMENTS.notificationSuccess) - .should("be.visible") - .wait("@UpdateDefaultWeightUnit") - .get(SHARED_ELEMENTS.notificationSuccess) - .should("not.exist"); + cy.get(SHIPPING_ZONES_LIST.saveUnit).click(); + confirmationMessageShouldDisappear(); + cy.wait("@UpdateDefaultWeightUnit"); } export function createShippingRate({ @@ -132,13 +128,9 @@ export function saveRate() { cy.addAliasToGraphRequest("ShippingMethodChannelListingUpdate") .addAliasToGraphRequest("ShippingZone") .get(BUTTON_SELECTORS.confirm) - .click() - .get(SHARED_ELEMENTS.notificationSuccess) - .should("be.visible") - .wait(`@ShippingMethodChannelListingUpdate`) - .wait(`@ShippingZone`) - .get(SHARED_ELEMENTS.notificationSuccess) - .should("not.exist"); + .click(); + confirmationMessageShouldDisappear(); + cy.wait(`@ShippingMethodChannelListingUpdate`).wait(`@ShippingZone`); } export function fillUpWeightLimits({ max, min }) { diff --git a/cypress/url/urlList.js b/cypress/url/urlList.js index f74c3eb23..50c10029f 100644 --- a/cypress/url/urlList.js +++ b/cypress/url/urlList.js @@ -6,6 +6,7 @@ export const urlList = { homePage: "/", orders: "orders/", products: "products/", + addProduct: "products/add", warehouses: "warehouses/", shippingMethods: "shipping/", sales: "discounts/sales/", @@ -14,6 +15,7 @@ export const urlList = { staffMembers: "staff/", newPassword: "new-password/", permissionsGroups: "permission-groups/", + categories: "categories/", weightRete: "weight/", attributes: "attributes/", productTypes: "product-types/", @@ -31,6 +33,9 @@ export const staffMemberDetailsUrl = staffMemberId => export const permissionGroupDetails = permissionGroupId => `${urlList.permissionsGroups}${permissionGroupId}`; +export const categoryDetails = categoryId => + `${urlList.categories}${categoryId}`; + export const shippingZoneDetailsUrl = shippingZoneId => `${urlList.shippingMethods}${shippingZoneId}`; diff --git a/cypress/utils/categoryUtils.js b/cypress/utils/categoryUtils.js new file mode 100644 index 000000000..7080d16e3 --- /dev/null +++ b/cypress/utils/categoryUtils.js @@ -0,0 +1,5 @@ +import { deleteCategory, getCategories } from "../apiRequests/Category"; + +export function deleteCategoriesStartsWith(startsWith) { + cy.deleteElementsStartsWith(deleteCategory, getCategories, startsWith); +} diff --git a/src/categories/components/CategoryListPage/CategoryListPage.tsx b/src/categories/components/CategoryListPage/CategoryListPage.tsx index c06474fce..e5c1bcecd 100644 --- a/src/categories/components/CategoryListPage/CategoryListPage.tsx +++ b/src/categories/components/CategoryListPage/CategoryListPage.tsx @@ -56,7 +56,12 @@ export const CategoryListPage: React.FC = ({ return ( -