diff --git a/cypress/apiRequests/Attribute.js b/cypress/apiRequests/Attribute.js
new file mode 100644
index 000000000..75b13c08d
--- /dev/null
+++ b/cypress/apiRequests/Attribute.js
@@ -0,0 +1,49 @@
+class Attribute {
+ createAttribute(name) {
+ const mutation = `mutation{
+ attributeCreate(input:{
+ name:"${name}"
+ valueRequired:false
+ type:PRODUCT_TYPE
+ }){
+ attribute{
+ id
+ }
+ attributeErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ getAttributes(first, search) {
+ const mutation = `query{
+ attributes(first:${first}, filter:{
+ search:"${search}"
+ }){
+ edges{
+ node{
+ id
+ name
+ }
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ deleteAttribute(attributeId) {
+ const mutation = `mutation{
+ attributeDelete(id:"${attributeId}"){
+ attributeErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+}
+export default Attribute;
diff --git a/cypress/apiRequests/Category.js b/cypress/apiRequests/Category.js
new file mode 100644
index 000000000..a20d10a23
--- /dev/null
+++ b/cypress/apiRequests/Category.js
@@ -0,0 +1,43 @@
+class Category {
+ createCategory(name, slug = name) {
+ const mutation = `mutation{
+ categoryCreate(input:{name:"${name}", slug: "${slug}"}){
+ productErrors{
+ field
+ message
+ }
+ category{
+ id
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+ getCategories(first, search) {
+ const mutation = `query{
+ categories(first:${first}, filter:{
+ search:"${search}"
+ }){
+ edges{
+ node{
+ id
+ name
+ }
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+ deleteCategory(categoryId) {
+ const mutation = `mutation{
+ categoryDelete(id:"${categoryId}"){
+ productErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+}
+export default Category;
diff --git a/cypress/apiRequests/Channels.js b/cypress/apiRequests/Channels.js
index 9018fd675..603d442ed 100644
--- a/cypress/apiRequests/Channels.js
+++ b/cypress/apiRequests/Channels.js
@@ -49,7 +49,19 @@ class Channels {
});
});
}
-
+ getChannels() {
+ const getChannelsInfoQuery = `query{
+ channels{
+ name
+ id
+ isActive
+ slug
+ currencyCode
+ }
+ }
+ `;
+ return cy.sendRequestWithQuery(getChannelsInfoQuery);
+ }
deleteChannel(channelId, targetChennelId) {
const deleteChannelMutation = `mutation{
channelDelete(id: "${channelId}", input:{
diff --git a/cypress/apiRequests/Product.js b/cypress/apiRequests/Product.js
new file mode 100644
index 000000000..72e0df27c
--- /dev/null
+++ b/cypress/apiRequests/Product.js
@@ -0,0 +1,193 @@
+class Product {
+ getFirstProducts(first, search) {
+ let filter = "";
+ if (search) {
+ filter = `, filter:{
+ search:"${search}"
+ }`;
+ }
+ const query = `query{
+ products(first:${first}${filter}){
+ edges{
+ node{
+ id
+ name
+ variants{
+ id
+ }
+ }
+ }
+ }
+ }
+ `;
+ return cy.sendRequestWithQuery(query);
+ }
+
+ updateChannelInProduct(
+ productId,
+ channelId,
+ isPublished = true,
+ isAvailableForPurchase = true,
+ visibleInListings = true
+ ) {
+ const mutation = `mutation{
+ productChannelListingUpdate(id:"${productId}",
+ input:{
+ addChannels:{
+ channelId:"${channelId}"
+ isPublished:${isPublished}
+ isAvailableForPurchase:${isAvailableForPurchase}
+ visibleInListings:${visibleInListings}
+ }
+ }){
+ product{
+ id
+ name
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ updateChannelPriceInVariant(variantId, channelId) {
+ const mutation = `mutation{
+ productVariantChannelListingUpdate(id: "${variantId}", input:{
+ channelId: "${channelId}"
+ price: 10
+ costPrice: 10
+ }){
+ productChannelListingErrors{
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+ createProduct(attributeId, name, productType, category) {
+ const mutation = `mutation{
+ productCreate(input:{
+ attributes:[{
+ id:"${attributeId}"
+ }]
+ name:"${name}"
+ productType:"${productType}"
+ category:"${category}"
+ }){
+ product{
+ id
+ }
+ productErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ createVariant(
+ productId,
+ sku,
+ warehouseId,
+ quantity,
+ channelId,
+ price = 1,
+ costPrice = 1
+ ) {
+ let channelListings = "";
+ let stocks = "";
+ if (channelId) {
+ channelListings = `channelListings:{
+ channelId:"${channelId}"
+ price:"${price}"
+ costPrice:"${costPrice}"
+ }`;
+ }
+ if (warehouseId) {
+ stocks = `stocks:{
+ warehouse:"${warehouseId}"
+ quantity:${quantity}
+ }`;
+ }
+ const mutation = `mutation{
+ productVariantBulkCreate(product:"${productId}", variants:{
+ attributes:[]
+ sku:"${sku}"
+ ${channelListings}
+ ${stocks}
+ }){
+ productVariants{
+ id
+ name
+ }
+ bulkProductErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ createTypeProduct(name, attributeId, slug = name) {
+ const mutation = `mutation{
+ productTypeCreate(input:{
+ name:"${name}"
+ slug: "${slug}"
+ isShippingRequired:true
+ productAttributes:"${attributeId}"
+ }){
+ productErrors{
+ field
+ message
+ }
+ productType{
+ id
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ deleteProduct(productId) {
+ const mutation = `mutation{
+ productDelete(id:"${productId}"){
+ productErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+
+ getProductTypes(first, search) {
+ const query = `query{
+ productTypes(first:${first}, filter:{
+ search:"${search}"
+ }){
+ edges{
+ node{
+ id
+ name
+ }
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(query);
+ }
+
+ deleteProductType(productTypeId) {
+ const mutation = `mutation{
+ productTypeDelete(id:"${productTypeId}"){
+ productErrors{
+ field
+ message
+ }
+ }
+ }`;
+ return cy.sendRequestWithQuery(mutation);
+ }
+}
+
+export default Product;
diff --git a/cypress/apiRequests/frontShop/Collections.js b/cypress/apiRequests/frontShop/Collections.js
new file mode 100644
index 000000000..43719b771
--- /dev/null
+++ b/cypress/apiRequests/frontShop/Collections.js
@@ -0,0 +1,18 @@
+class Collections {
+ getCollection(collectionId, channelSlug) {
+ const operationName = "Collection";
+ const variables = {
+ attributes: {},
+ priceGte: null,
+ priceLte: null,
+ sortBy: null,
+ channel: channelSlug,
+ id: collectionId,
+ pageSize: 6
+ };
+ const query =
+ "query Collection($id: ID!, $channel: String!) {\n collection(id: $id, channel: $channel) {\n id\n slug\n name\n seoDescription\n seoTitle\n backgroundImage {\n url\n __typename\n }\n __typename\n }\n attributes(filter: {channel: $channel, inCollection: $id, filterableInStorefront: true}, first: 100) {\n edges {\n node {\n id\n name\n slug\n values {\n id\n name\n slug\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n";
+ return cy.sendFrontShopRequest(operationName, query, variables);
+ }
+}
+export default Collections;
diff --git a/cypress/elements/catalog/collection-selectors.js b/cypress/elements/catalog/collection-selectors.js
new file mode 100644
index 000000000..8d013cf68
--- /dev/null
+++ b/cypress/elements/catalog/collection-selectors.js
@@ -0,0 +1,7 @@
+/* eslint-disable sort-keys */
+export const COLLECTION_SELECTORS = {
+ createCollectionButton: "[data-test-id = 'create-collection']",
+ nameInput: "[name='name']",
+ saveButton: "[data-test='button-bar-confirm']",
+ addProductButton: "[data-test-id='add-product']"
+};
diff --git a/cypress/elements/catalog/products/assign-products-selectors.js b/cypress/elements/catalog/products/assign-products-selectors.js
new file mode 100644
index 000000000..381a55f5a
--- /dev/null
+++ b/cypress/elements/catalog/products/assign-products-selectors.js
@@ -0,0 +1,7 @@
+/* eslint-disable sort-keys */
+export const ASSIGN_PRODUCTS_SELECTORS = {
+ searchInput: "[name='query']",
+ tableRow: "[class*='MuiTableRow']",
+ checkbox: "[class*='checkboxCell']",
+ submitButton: "[type='submit']"
+};
diff --git a/cypress/elements/catalog/product-selectors.js b/cypress/elements/catalog/products/product-selectors.js
similarity index 100%
rename from cypress/elements/catalog/product-selectors.js
rename to cypress/elements/catalog/products/product-selectors.js
diff --git a/cypress/elements/channels/menage-channel-availability-form.js b/cypress/elements/channels/menage-channel-availability-form.js
new file mode 100644
index 000000000..873a635f9
--- /dev/null
+++ b/cypress/elements/channels/menage-channel-availability-form.js
@@ -0,0 +1,10 @@
+export const MENAGE_CHANNEL_AVAILABILITY_FORM = {
+ channelsMenageButton: "[data-test-id='channels-availiability-manage-button']",
+ allChannelsCheckbox: "[name='allChannels']",
+ channelRow: "[class*='ChannelsAvailabilityContent-option']",
+ channelCheckbox: "[class*='MuiCheckbox']",
+ channelsAvailabilityItem: "[data-test='channel-availability-item']",
+ publishedCheckbox: "[name='isPublished']",
+ radioButtonsValueTrue: "[value='true']",
+ radioButtonsValueFalse: "[value='false']"
+};
diff --git a/cypress/integration/collections.js b/cypress/integration/collections.js
new file mode 100644
index 000000000..a3ea0d8aa
--- /dev/null
+++ b/cypress/integration/collections.js
@@ -0,0 +1,114 @@
+//
+import faker from "faker";
+
+import { COLLECTION_SELECTORS } from "../elements/catalog/collection-selectors";
+import { ASSIGN_PRODUCTS_SELECTORS } from "../elements/catalog/products/assign-products-selectors";
+import { MENAGE_CHANNEL_AVAILABILITY_FORM } from "../elements/channels/menage-channel-availability-form";
+import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
+import { urlList } from "../url/urlList";
+import ChannelsUtils from "../utils/channelsUtils";
+import CollectionsUtils from "../utils/collectionsUtils";
+import ProductsUtils from "../utils/productsUtils";
+
+describe("Collections", () => {
+ const channelsUtils = new ChannelsUtils();
+ const productsUtils = new ProductsUtils();
+ const collectionsFrontUtils = new CollectionsUtils();
+
+ const startsWith = "Cy-";
+ const name = `${startsWith}${faker.random.number()}`;
+
+ let defaultChannel;
+
+ before(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ channelsUtils
+ .getDefaultChannel()
+ .then(channel => {
+ defaultChannel = channel;
+ productsUtils.createTypeAttributeAndCategoryForProduct(name);
+ })
+ .then(() => {
+ const attribute = productsUtils.getAttribute();
+ const productType = productsUtils.getProductType();
+ const category = productsUtils.getCategory();
+ productsUtils.createProductInChannel(
+ name,
+ defaultChannel.id,
+ null,
+ null,
+ productType.id,
+ attribute.id,
+ category.id,
+ 1
+ );
+ });
+ });
+
+ beforeEach(() => {
+ cy.clearSessionData().loginUserViaRequest();
+ cy.visit(urlList.collections);
+ });
+
+ it("should not display hidden collections", () => {
+ const collectionName = `${startsWith}${faker.random.number()}`;
+
+ cy.get(COLLECTION_SELECTORS.createCollectionButton)
+ .click()
+ .get(COLLECTION_SELECTORS.nameInput)
+ .type(collectionName)
+ .get(MENAGE_CHANNEL_AVAILABILITY_FORM.channelsMenageButton)
+ .click()
+ .get(MENAGE_CHANNEL_AVAILABILITY_FORM.allChannelsCheckbox)
+ .click();
+ cy.contains(
+ MENAGE_CHANNEL_AVAILABILITY_FORM.channelRow,
+ defaultChannel.name
+ )
+ .find(MENAGE_CHANNEL_AVAILABILITY_FORM.channelCheckbox)
+ .click()
+ .get(BUTTON_SELECTORS.submit)
+ .click()
+ .get(MENAGE_CHANNEL_AVAILABILITY_FORM.channelsAvailabilityItem)
+ .click()
+ .get(
+ `${MENAGE_CHANNEL_AVAILABILITY_FORM.publishedCheckbox}${MENAGE_CHANNEL_AVAILABILITY_FORM.radioButtonsValueFalse}`
+ )
+ .click()
+ .waitForGraph("CreateCollection")
+ .get(COLLECTION_SELECTORS.saveButton)
+ .click();
+ collectionsFrontUtils.getCreatedCollection().then(collection => {
+ cy.get(COLLECTION_SELECTORS.addProductButton)
+ .click()
+ .get(ASSIGN_PRODUCTS_SELECTORS.searchInput)
+ .type(name);
+ cy.contains(ASSIGN_PRODUCTS_SELECTORS.tableRow, name)
+ .find(ASSIGN_PRODUCTS_SELECTORS.checkbox)
+ .click()
+ .get(ASSIGN_PRODUCTS_SELECTORS.submitButton)
+ .click()
+ .loginInShop();
+ collectionsFrontUtils.isCollectionVisible(
+ collection.id,
+ defaultChannel.slug
+ );
+ });
+ });
+ // xit("should display collections", () => {
+ // createVisibleCollection
+ // addProductToCollection
+ // checkIfCollectionIsNotDisplayed
+ // });
+ // xit("should not display unavailable collections", () => {
+ // createunavailableCollection
+ // addProductToCollection
+ // checkIfCollectionIsNotDisplayed
+ // });
+ // xit("should display products hidden in listing only in collection", () => {
+ // createHiddenInListingsProduct
+ // createVisibleCollection
+ // addProductToCollection
+ // checkIfCollectionIsNotDisplayed
+ // });
+});
diff --git a/cypress/support/index.js b/cypress/support/index.js
index f952dd391..ddfb59da7 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -35,7 +35,6 @@ Cypress.Commands.add("waitForGraph", operationName => {
}
}
});
- cy.wait(`@${operationName}`);
});
Cypress.Commands.add("sendRequestWithQuery", query =>
@@ -43,7 +42,7 @@ Cypress.Commands.add("sendRequestWithQuery", query =>
body: {
method: "POST",
query,
- url: urlList.apiUri,
+ url: urlList.apiUri
},
headers: {
Authorization: `JWT ${window.sessionStorage.getItem("auth")}`
@@ -52,3 +51,21 @@ Cypress.Commands.add("sendRequestWithQuery", query =>
url: urlList.apiUri
})
);
+Cypress.Commands.add(
+ "sendFrontShopRequest",
+ (operationName, query, variables) =>
+ cy.request({
+ method: "POST",
+ url: urlList.apiUri,
+ headers: {
+ authorization: `JWT ${window.localStorage.getItem("token")}`
+ },
+ body: [
+ {
+ operationName,
+ variables,
+ query
+ }
+ ]
+ })
+);
diff --git a/cypress/support/user/index.js b/cypress/support/user/index.js
index 1656ad3c1..b0f7f66b0 100644
--- a/cypress/support/user/index.js
+++ b/cypress/support/user/index.js
@@ -37,12 +37,31 @@ Cypress.Commands.add("loginUserViaRequest", () => {
variables: {
email: Cypress.env("USER_NAME"),
password: Cypress.env("USER_PASSWORD")
- },
+ }
},
method: "POST",
- url: urlList.apiUri,
+ url: urlList.apiUri
})
.then(resp => {
window.sessionStorage.setItem("auth", resp.body.data.tokenCreate.token);
});
});
+Cypress.Commands.add("loginInShop", () => {
+ cy.request({
+ method: "POST",
+ url: Cypress.env("API_URI"),
+ body: [
+ {
+ operationName: "TokenAuth",
+ variables: {
+ email: Cypress.env("USER_NAME"),
+ password: Cypress.env("USER_PASSWORD")
+ },
+ query:
+ "mutation TokenAuth($email: String!, $password: String!) {\n tokenCreate(email: $email, password: $password) {\n token\n errors: accountErrors {\n code\n field\n message\n __typename\n }\n user {\n id\n __typename\n }\n __typename\n }\n}\n"
+ }
+ ]
+ }).then(resp => {
+ window.localStorage.setItem("token", resp.body[0].data.tokenCreate.token);
+ });
+});
diff --git a/cypress/url/urlList.js b/cypress/url/urlList.js
index 2ceb592c5..dec3bea5e 100644
--- a/cypress/url/urlList.js
+++ b/cypress/url/urlList.js
@@ -5,5 +5,6 @@ export const urlList = {
homePage: "/",
orders: "orders/",
products: "products/",
- warehouses: "warehouses/"
+ warehouses: "warehouses/",
+ collections: "collections/"
};
diff --git a/cypress/utils/channelsUtils.js b/cypress/utils/channelsUtils.js
new file mode 100644
index 000000000..7d93ae3eb
--- /dev/null
+++ b/cypress/utils/channelsUtils.js
@@ -0,0 +1,16 @@
+import Channels from "../apiRequests/Channels";
+
+class ChannelsUtils {
+ channels = new Channels();
+ getDefaultChannel() {
+ return this.channels.getChannels().then(resp => {
+ const channelsArray = resp.body.data.channels;
+ return (this.defaultChannel = channelsArray.find(function(
+ channelElement
+ ) {
+ return channelElement.slug === "default-channel";
+ }));
+ });
+ }
+}
+export default ChannelsUtils;
diff --git a/cypress/utils/collectionsUtils.js b/cypress/utils/collectionsUtils.js
new file mode 100644
index 000000000..51deb3579
--- /dev/null
+++ b/cypress/utils/collectionsUtils.js
@@ -0,0 +1,19 @@
+import Collections from "../apiRequests/frontShop/Collections";
+
+class CollectionsUtils {
+ collectionsRequest = new Collections();
+ isCollectionVisible(collectionId, channelSlug) {
+ return this.collectionsRequest
+ .getCollection(collectionId, channelSlug)
+ .then(resp => {
+ const collection = resp.body[0].data.collection;
+ return collection && collection.id === collectionId;
+ });
+ }
+ getCreatedCollection() {
+ return cy
+ .wait(`@CreateCollection`)
+ .its("response.body.data.collectionCreate.collection");
+ }
+}
+export default CollectionsUtils;
diff --git a/cypress/utils/productsUtils.js b/cypress/utils/productsUtils.js
new file mode 100644
index 000000000..43fba4be3
--- /dev/null
+++ b/cypress/utils/productsUtils.js
@@ -0,0 +1,132 @@
+import Attribute from "../apiRequests/Attribute";
+import Category from "../apiRequests/Category";
+import Product from "../apiRequests/Product";
+
+class ProductsUtils {
+ productRequest = new Product();
+ attributeRequest = new Attribute();
+ categoryRequest = new Category();
+
+ product;
+ variants;
+ productType;
+ attribute;
+ category;
+
+ createProductInChannel(
+ name,
+ channelId,
+ warehouseId,
+ quantityInWarehouse,
+ productTypeId,
+ attributeId,
+ categoryId,
+ price
+ ) {
+ return this.createProduct(attributeId, name, productTypeId, categoryId)
+ .then(() =>
+ this.productRequest.updateChannelInProduct(this.product.id, channelId)
+ )
+ .then(() => {
+ this.createVariant(
+ this.product.id,
+ name,
+ warehouseId,
+ quantityInWarehouse,
+ channelId,
+ price
+ );
+ });
+ }
+
+ createTypeAttributeAndCategoryForProduct(name) {
+ return this.createAttribute(name)
+ .then(() => this.createTypeProduct(name, this.attribute.id))
+ .then(() => this.createCategory(name));
+ }
+ createAttribute(name) {
+ return this.attributeRequest
+ .createAttribute(name)
+ .then(
+ resp => (this.attribute = resp.body.data.attributeCreate.attribute)
+ );
+ }
+ createTypeProduct(name, attributeId) {
+ return this.productRequest
+ .createTypeProduct(name, attributeId)
+ .then(
+ resp =>
+ (this.productType = resp.body.data.productTypeCreate.productType)
+ );
+ }
+ createCategory(name) {
+ return this.categoryRequest
+ .createCategory(name)
+ .then(resp => (this.category = resp.body.data.categoryCreate.category));
+ }
+ createProduct(attributeId, name, productTypeId, categoryId) {
+ return this.productRequest
+ .createProduct(attributeId, name, productTypeId, categoryId)
+ .then(resp => (this.product = resp.body.data.productCreate.product));
+ }
+ createVariant(
+ productId,
+ name,
+ warehouseId,
+ quantityInWarehouse,
+ channelId,
+ price
+ ) {
+ return this.productRequest
+ .createVariant(
+ productId,
+ name,
+ warehouseId,
+ quantityInWarehouse,
+ channelId,
+ price
+ )
+ .then(
+ resp =>
+ (this.variants =
+ resp.body.data.productVariantBulkCreate.productVariants)
+ );
+ }
+
+ getCreatedVariants() {
+ return this.variants;
+ }
+ getProductType() {
+ return this.productType;
+ }
+ getAttribute() {
+ return this.attribute;
+ }
+ getCategory() {
+ return this.category;
+ }
+ deleteProperProducts(startsWith) {
+ const product = new Product();
+ const attribute = new Attribute();
+ const category = new Category();
+ cy.deleteProperElements(
+ product.deleteProductType,
+ product.getProductTypes,
+ startsWith,
+ "productType"
+ );
+ cy.deleteProperElements(
+ attribute.deleteAttribute,
+ attribute.getAttributes,
+ startsWith,
+ "attributes"
+ );
+ cy.deleteProperElements(
+ category.deleteCategory,
+ category.getCategories,
+ startsWith,
+ "categories"
+ );
+ }
+}
+export default ProductsUtils;
diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx
index 31c89ad86..ddb5d1e78 100644
--- a/src/collections/components/CollectionListPage/CollectionListPage.tsx
+++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx
@@ -55,6 +55,7 @@ const CollectionListPage: React.FC = ({
disabled={disabled}
variant="contained"
onClick={onAdd}
+ data-test-id="create-collection"
>
= props => {
}
toolbar={