From e0ce1a0ad8b8b96ebed7f5f234bfc3185beb2622 Mon Sep 17 00:00:00 2001 From: shashwata Halder Date: Sat, 7 Dec 2024 17:31:39 +0600 Subject: [PATCH 1/4] Add Commission tests (#2471) * add new commission tests * Add commission tests --- tests/pw/feature-map/feature-map.yml | 9 +++ tests/pw/pages/basePage.ts | 16 ++-- tests/pw/pages/commissionPage.ts | 110 +++++++++++++++++++++++++- tests/pw/pages/productsPage.ts | 9 +++ tests/pw/pages/selectors.ts | 88 +++++++++++++++++---- tests/pw/pages/storeSupportsPage.ts | 2 +- tests/pw/tests/e2e/commission.spec.ts | 51 +++++++++++- tests/pw/utils/apiUtils.ts | 30 +++++++ tests/pw/utils/payloads.ts | 67 +++++++++++++++- 9 files changed, 354 insertions(+), 28 deletions(-) diff --git a/tests/pw/feature-map/feature-map.yml b/tests/pw/feature-map/feature-map.yml index 22dadc5420..044d729366 100644 --- a/tests/pw/feature-map/feature-map.yml +++ b/tests/pw/feature-map/feature-map.yml @@ -348,6 +348,15 @@ admin can set commission to Dokan subscription product (category based): true admin can set commission to Dokan subscription product (specific category based): true admin can view commission meta-box on order details [lite]: true + admin can view sub orders meta-box on parent order details [lite]: true + admin can view related orders meta-box on child order details [lite]: true + admin can view commission on product list [lite]: true + admin can view commission on order list [lite]: true + vendor can view earning on product list [lite]: true + vendor can view earning on product add page [lite]: true + vendor can view earning on product edit page [lite]: true + vendor can view earning on order list [lite]: true + vendor can view earning on order details [lite]: true - page: 'Withdraw' features: diff --git a/tests/pw/pages/basePage.ts b/tests/pw/pages/basePage.ts index 0b9eb7fbf8..d0d2bb9a72 100644 --- a/tests/pw/pages/basePage.ts +++ b/tests/pw/pages/basePage.ts @@ -6,6 +6,7 @@ import { expect, Page, BrowserContext, Cookie, Request, Response, Locator, Frame, FrameLocator, JSHandle, ElementHandle } from '@playwright/test'; import { data } from '@utils/testData'; import { selector } from '@pages/selectors'; +import { helpers } from '@utils/helpers'; const { BASE_URL } = process.env; @@ -210,8 +211,8 @@ export class BasePage { } // click & wait for load state to complete - async clickAndWaitForLoadState(selector: string): Promise { - await Promise.all([this.waitForLoadState(), this.page.locator(selector).click()]); + async clickAndWaitForLoadState(selector: string, state: 'load' | 'domcontentloaded' | 'networkidle' = 'domcontentloaded', options?: { timeout?: number }): Promise { + await Promise.all([this.waitForLoadState(state, options), this.page.locator(selector).click()]); } // click & wait for navigation to complete @@ -1425,10 +1426,15 @@ export class BasePage { } } - // multiple elements to be visible - async multipleElementVisible(selectors: any) { + async multipleElementVisible(selectors: { [key: string]: any }) { for (const selector in selectors) { - await this.toBeVisible(selectors[selector]); + if (helpers.isPlainObject(selectors[selector])) { + await this.multipleElementVisible(selectors[selector]); + } else if (typeof selectors[selector] === 'function') { + continue; + } else { + await this.toBeVisible(selectors[selector]); + } } } diff --git a/tests/pw/pages/commissionPage.ts b/tests/pw/pages/commissionPage.ts index f21a027591..444ae59a63 100644 --- a/tests/pw/pages/commissionPage.ts +++ b/tests/pw/pages/commissionPage.ts @@ -10,6 +10,9 @@ const settingsAdmin = selector.admin.dokan.settings; const vendors = selector.admin.dokan.vendors; const productsAdmin = selector.admin.products; +const productsVendor = selector.vendor.product; +const ordersVendor = selector.vendor.orders; + export class CommissionPage extends AdminPage { constructor(page: Page) { super(page); @@ -125,7 +128,6 @@ export class CommissionPage extends AdminPage { // set commission for vendor async setCommissionForVendor(sellerId: string, commission: commission) { await this.gotoUntilNetworkidle(data.subUrls.backend.dokan.vendorDetailsEdit(sellerId)); - await this.selectByValue(vendors.editVendor.commissionType, commission.commissionType); // add commission @@ -163,8 +165,6 @@ export class CommissionPage extends AdminPage { // set commission to dokan subscription product async setCommissionToDokanSubscriptionProduct(productId: string, commission: commission) { await this.gotoUntilNetworkidle(data.subUrls.backend.wc.productDetails(productId)); - - // add commission await this.click(productsAdmin.product.subMenus.commission); // add commission @@ -186,4 +186,108 @@ export class CommissionPage extends AdminPage { // metabox elements are visible await this.multipleElementVisible(selector.admin.wooCommerce.orders.commissionMetaBox); } + + // view suborders metabox + async viewSubOrdersMetaBox(orderId: string) { + await this.gotoUntilNetworkidle(data.subUrls.backend.orderDetails(orderId)); + + // metabox elements are visible + await this.multipleElementVisible(selector.admin.wooCommerce.orders.subOrdersMetaBox); + } + + // view related orders metabox + async viewRelatedOrdersMetaBox(orderId: string) { + await this.gotoUntilNetworkidle(data.subUrls.backend.orderDetails(orderId)); + + // metabox elements are visible + await this.multipleElementVisible(selector.admin.wooCommerce.orders.relatedOrdersMetaBox); + } + + // view commission on product list + async viewCommissionOnProductList() { + await this.gotoUntilNetworkidle(data.subUrls.backend.wc.products); + // commission column & value is visible + await this.toBeVisible(selector.admin.products.commissionColumn); + await this.toBeVisible(selector.admin.products.firstRowProductCommission); + } + + // view commission on order list + async viewCommissionOnOrderList() { + await this.gotoUntilNetworkidle(data.subUrls.backend.wc.orders); + // commission column & value is visible + await this.toBeVisible(selector.admin.wooCommerce.orders.commissionColumn); + await this.toBeVisible(selector.admin.wooCommerce.orders.firstRowOrderCommission); + } + + // vendor view earning on product list + async vendorViewEarningOnProductList() { + await this.goto(data.subUrls.frontend.vDashboard.products); + + // earning column & value is visible + await this.toBeVisible(selector.vendor.product.table.earningColumn); + await this.toBeVisible(selector.vendor.product.firstRowProductEarning); + } + + // vendor view earning on add product details + async vendorViewEarningOnAddProductDetails() { + await this.goIfNotThere(data.subUrls.frontend.vDashboard.products); + await this.clickAndWaitForLoadState(selector.vendor.product.addNewProduct); + await this.toBeVisible(selector.vendor.product.earning); + } + + // vendor view earning on edit product details + async vendorViewEarningOnEditProductDetails(productName: string) { + await this.goToProductEdit(productName); + await this.toBeVisible(selector.vendor.product.earning); + } + + // vendor view earning on order list + async vendorViewEarningOnOrderList() { + await this.goto(data.subUrls.frontend.vDashboard.orders); + + // earning column & value is visible + await this.toBeVisible(selector.vendor.orders.table.earningColumn); + await this.toBeVisible(selector.vendor.orders.firstRowOrderEarning); + } + + // vendor view earning on order list + async vendorViewEarningOnOrderDetails(orderNumber: string) { + await this.goToOrderDetails(orderNumber); + // earning column & value is visible + await this.toBeVisible(selector.vendor.orders.generalDetails.earningAmount); + } + + // todo : remove below functions and call from the original class + + async goToOrderDetails(orderNumber: string): Promise { + await this.searchOrder(orderNumber); + await this.clickAndWaitForLoadState(ordersVendor.view(orderNumber)); + await this.toContainText(ordersVendor.orderDetails.orderNumber, orderNumber); + } + + async searchOrder(orderNumber: string): Promise { + await this.goIfNotThere(data.subUrls.frontend.vDashboard.orders); + + await this.clearAndType(ordersVendor.search.searchInput, orderNumber); + await this.clickAndWaitForResponse(data.subUrls.frontend.vDashboard.orders, ordersVendor.search.searchBtn); + await this.toHaveCount(ordersVendor.numberOfRowsFound, 1); + await this.toBeVisible(ordersVendor.orderLink(orderNumber)); + } + + // go to product edit + async goToProductEdit(productName: string): Promise { + await this.searchProduct(productName); + await this.removeAttribute(productsVendor.rowActions(productName), 'class'); // forcing the row actions to be visible, to avoid flakiness + await this.hover(productsVendor.productCell(productName)); + await this.clickAndWaitForResponseAndLoadStateUntilNetworkIdle(data.subUrls.frontend.vDashboard.products, productsVendor.editProduct(productName)); + await this.toHaveValue(productsVendor.title, productName); + } + + // search product vendor dashboard + async searchProduct(productName: string): Promise { + await this.goIfNotThere(data.subUrls.frontend.vDashboard.products); + await this.clearAndType(productsVendor.search.searchInput, productName); + await this.clickAndWaitForResponse(data.subUrls.frontend.vDashboard.products, productsVendor.search.searchBtn); + await this.toBeVisible(productsVendor.productLink(productName)); + } } diff --git a/tests/pw/pages/productsPage.ts b/tests/pw/pages/productsPage.ts index eef071e8ea..714f37ac59 100644 --- a/tests/pw/pages/productsPage.ts +++ b/tests/pw/pages/productsPage.ts @@ -17,6 +17,14 @@ export class ProductsPage extends AdminPage { super(page); } + // admin search product + async adminSearchProduct(productName: string) { + await this.gotoUntilNetworkidle(data.subUrls.backend.wc.products); + await this.clearAndType(selector.admin.products.search.searchInput, productName); + await this.clickAndWaitForLoadState(selector.admin.products.search.searchButton, 'networkidle'); + await this.toBeVisible(selector.admin.products.productRow(productName)); + } + // admin add product category async addCategory(categoryName: string) { await this.goIfNotThere(data.subUrls.backend.wc.addNewCategories); @@ -323,6 +331,7 @@ export class ProductsPage extends AdminPage { // price await this.toBeVisible(productsVendor.price); + await this.toBeVisible(productsVendor.earning); // discount price & Schedule await this.click(productsVendor.discount.schedule); diff --git a/tests/pw/pages/selectors.ts b/tests/pw/pages/selectors.ts index b536bdd386..bb36387e20 100644 --- a/tests/pw/pages/selectors.ts +++ b/tests/pw/pages/selectors.ts @@ -2979,43 +2979,95 @@ export const selector = { // orders orders: { //table + commissionColumn: 'th#admin_commission', numberOfRowsFound: '(//span[@class="displaying-num"])[1]', noRowsFound: '//td[normalize-space(text())="No items found."]', + firstRow: '(//tbody[@id="the-list"]//tr)[1]', + firstRowOrderCommission: '(//tbody[@id="the-list"]//tr[not(@style="display: none;")])[1]//td[@class="admin_commission column-admin_commission"]', + commissionMetaBox: { metaBoxDiv: 'div#dokan_commission_box', commissionsText: '//h2[normalize-space()="Commissions"]', + table: { + itemColumn: '//div[@id="dokan_commission_box"]//th[normalize-space()="Item"]', + typeColumn: '//div[@id="dokan_commission_box"]//th[normalize-space()="Type"]', + rateColumn: '//div[@id="dokan_commission_box"]//th[normalize-space()="Rate"]', + qtyColumn: '//div[@id="dokan_commission_box"]//th[normalize-space()="Qty"]', + commissionColumn: '//div[@id="dokan_commission_box"]//th[normalize-space()="Commission"]', + }, orderItemInfo: 'div#dokan_commission_box table.woocommerce_order_items', orderTotalInfo: 'div#dokan_commission_box div.wc-order-totals-items', }, + + subOrdersMetaBox: { + metaBoxDiv: 'div#dokan_sub_or_related_orders', + subOrdersText: '//h2[normalize-space()="Sub orders"]', + subOrdersItemInfo: 'div#dokan_sub_or_related_orders div#woocommerce-order-items', + table: { + orderColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Order"]', + dateColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Date"]', + statusColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Status"]', + totalColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Total"]', + vendorColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Vendor"]', + }, + subOrderTable: 'div#dokan_sub_or_related_orders table.woocommerce_order_items', + }, + + relatedOrdersMetaBox: { + metaBoxDiv: 'div#dokan_sub_or_related_orders', + relatedOrdersText: '//h2[normalize-space()="Related orders"]', + relatedOrdersItemInfo: 'div#dokan_sub_or_related_orders div#woocommerce-order-items', + table: { + orderColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Order"]', + dateColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Date"]', + statusColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Status"]', + totalColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Total"]', + vendorColumn: '//div[@id="dokan_sub_or_related_orders"]//th[normalize-space()="Vendor"]', + }, + relatedOrderTable: 'div#dokan_sub_or_related_orders table.woocommerce_order_items', + parentOrderRow: '//td[contains(.,"(Parent order)")]/..', + parentOrderVendor: '//td[contains(.,"(Parent order)")]/..//td[contains(.,"(no name)")]/..', + }, }, }, - // Products + // products products: { - // Products Menus - allProductsMenu: '//li[@id="menu-posts-product"]//a[text()="All Products"]', - addNewMenu: '//li[@id="menu-posts-product"]//a[text()="Add New"]', - categoriesMenu: '//li[@id="menu-posts-product"]//a[text()="Categories"]', - tagsMenu: '//li[@id="menu-posts-product"]//a[text()="Tags"]', - addOnsMenu: '//li[@id="menu-posts-product"]//a[text()="Add-ons"]', - attributesMenu: '//li[@id="menu-posts-product"]//a[text()="Attributes"]', + // products menus + menus: { + allProductsMenu: '//li[@id="menu-posts-product"]//a[text()="All Products"]', + addNewMenu: '//li[@id="menu-posts-product"]//a[text()="Add New"]', + categoriesMenu: '//li[@id="menu-posts-product"]//a[text()="Categories"]', + tagsMenu: '//li[@id="menu-posts-product"]//a[text()="Tags"]', + addOnsMenu: '//li[@id="menu-posts-product"]//a[text()="Add-ons"]', + attributesMenu: '//li[@id="menu-posts-product"]//a[text()="Attributes"]', + }, + + // search + search: { + searchInput: 'input#post-search-input', + searchButton: 'input#search-submit', + }, // table + commissionColumn: 'th#admin_commission', numberOfRowsFound: '(//span[@class="displaying-num"])[1]', noRowsFound: '//td[normalize-space(text())="No products found"]', + productRow: (productName: string) => `//a[@class="row-title" and normalize-space(text())='${productName}']/../../..`, + firstRowProductCommission: '(//tbody[@id="the-list"]//tr)[1]//td[@class="admin_commission column-admin_commission"]', + productCommission: (productName: string) => `//a[@class="row-title" and normalize-space(text())='${productName}']/../../..//td[@class="admin_commission column-admin_commission"]//bdi`, - // Product + // add new product product: { - // Add New Product productName: '#title', - // Product Data + // product data productType: '#product-type', virtual: '#\\_virtual', downloadable: '#\\_downloadable', // todo: group below locators - // Add New Product Sub Menus + // add new product sub menus subMenus: { general: '.general_options a', inventory: '.inventory_options a', @@ -3818,6 +3870,7 @@ export const selector = { skuColumn: '//th[normalize-space()="SKU"]', stockColumn: '//th[normalize-space()="Stock"]', priceColumn: '//th[normalize-space()="Price"]', + earningColumn: '//th[normalize-space()="Earning"]', typeColumn: '//th[normalize-space()="Type"]', viewsColumn: '//th[normalize-space()="Views"]', dateColumn: '//th[normalize-space()="Date"]', @@ -3826,6 +3879,8 @@ export const selector = { // product sub options numberOfRowsFound: '#dokan-product-list-table tbody tr', noProductsFound: '//td[normalize-space()="No product found"]', + firstRow: '(//table[@id="dokan-product-list-table"]//tbody//tr[not(@id="bulk-edit")])[1]', + firstRowProductEarning: '(//table[@id="dokan-product-list-table"]//tbody//tr[not(@id="bulk-edit")])[1]//td[@data-title="Earning"]', productCell: (productName: string) => `//strong//a[contains(text(),'${productName}')]/../..`, productLink: (productName: string) => `//strong//a[contains(text(),'${productName}')]`, editProduct: (productName: string) => `//a[contains(text(),'${productName}')]/../..//span[@class="edit"]//a`, @@ -3864,6 +3919,7 @@ export const selector = { downloadable: '#\\_downloadable', virtual: '#\\_virtual', price: '#\\_regular_price', + earning: 'span.vendor-earning span.vendor-price', // discount discount: { @@ -4250,6 +4306,8 @@ export const selector = { }, numberOfRowsFound: '.dokan-table.dokan-table tbody tr', + firstRow: '(//table//tbody//tr)[1]', + firstRowOrderEarning: '(//table//tbody//tr)[1]//td[@class="dokan-order-earning"]', // order details from table orderTotalTable: (orderNumber: string) => `//strong[contains(text(),'Order ${orderNumber}')]/../../..//td[@class='dokan-order-total']//bdi`, orderTotalAfterRefundTable: (orderNumber: string) => `///strong[contains(text(),'Order ${orderNumber}')]/../../..//td[@class='dokan-order-total']//ins//bdi`, @@ -4283,8 +4341,10 @@ export const selector = { // general details generalDetails: { generalDetailsDiv: '//strong[normalize-space()="General Details"]/../..', - orderDetails: '.list-unstyled.order-status', - customerDetails: '.list-unstyled.customer-details', + orderDetails: 'ul.list-unstyled.order-status', + earningFromOrder: 'li.earning-from-order', + earningAmount: 'li.earning-from-order span.amount', + customerDetails: 'ul.list-unstyled.customer-details', }, // status diff --git a/tests/pw/pages/storeSupportsPage.ts b/tests/pw/pages/storeSupportsPage.ts index a85c3bf632..968d13970f 100644 --- a/tests/pw/pages/storeSupportsPage.ts +++ b/tests/pw/pages/storeSupportsPage.ts @@ -227,7 +227,7 @@ export class StoreSupportsPage extends AdminPage { if (ticketIsOpen) { await this.toBeVisible(addReplyText); } else { - await this.multipleElementVisible(closeTicketText); + await this.toBeVisible(closeTicketText); } await this.multipleElementVisible(replyBox); } diff --git a/tests/pw/tests/e2e/commission.spec.ts b/tests/pw/tests/e2e/commission.spec.ts index 826030b3c5..009cad46d5 100644 --- a/tests/pw/tests/e2e/commission.spec.ts +++ b/tests/pw/tests/e2e/commission.spec.ts @@ -6,11 +6,12 @@ import { payloads } from '@utils/payloads'; import { dbUtils } from '@utils/dbUtils'; import { dbData } from '@utils/dbData'; -const { PRODUCT_ID } = process.env; +const { PRODUCT_ID, CUSTOMER_ID } = process.env; test.describe('Commission test', () => { let admin: CommissionPage; - let aPage: Page; + let vendor: CommissionPage; + let aPage: Page, vPage: Page; let apiUtils: ApiUtils; let subscriptionProductId: string; let sellerId: string; @@ -20,6 +21,10 @@ test.describe('Commission test', () => { aPage = await adminContext.newPage(); admin = new CommissionPage(aPage); + const vendorContext = await browser.newContext(data.auth.vendorAuth); + vPage = await vendorContext.newPage(); + vendor = new CommissionPage(vPage); + apiUtils = new ApiUtils(await request.newContext()); await dbUtils.setOptionValue(dbData.dokan.optionName.selling, dbData.dokan.sellingSettings); @@ -38,6 +43,7 @@ test.describe('Commission test', () => { // } await dbUtils.setOptionValue(dbData.dokan.optionName.selling, dbData.dokan.sellingSettings); await aPage.close(); + await vPage.close(); await apiUtils.dispose(); }); @@ -102,6 +108,43 @@ test.describe('Commission test', () => { await admin.viewCommissionMetaBox(orderId); }); - // todo: admin can view commission on product list, order list, and order details, sub order details on parent order - // todo: vendor can view earning on product list, product details, order list, and order details + test('admin can view sub orders meta-box on parent order details', { tag: ['@lite', '@admin'] }, async () => { + const [, , parentOrderId] = await apiUtils.createOrderWc(payloads.createMultiVendorOrder); + await admin.viewSubOrdersMetaBox(parentOrderId); + }); + + test('admin can view related orders meta-box on child order details', { tag: ['@lite', '@admin'] }, async () => { + const [, , parentOrderId] = await apiUtils.createOrderWc(payloads.createMultiVendorOrder); + const childOrderIds = await dbUtils.getChildOrderIds(parentOrderId); + await admin.viewRelatedOrdersMetaBox(childOrderIds[0] as string); + }); + + test('admin can view commission on product list', { tag: ['@lite', '@admin'] }, async () => { + await admin.viewCommissionOnProductList(); + }); + + test('admin can view commission on order list', { tag: ['@lite', '@admin'] }, async () => { + await admin.viewCommissionOnOrderList(); + }); + + test('vendor can view earning on product list', { tag: ['@lite', '@vendor'] }, async () => { + await vendor.vendorViewEarningOnProductList(); + }); + + test('vendor can view earning on product add page', { tag: ['@lite', '@vendor'] }, async () => { + await vendor.vendorViewEarningOnAddProductDetails(); + }); + + test('vendor can view earning on product edit page', { tag: ['@lite', '@vendor'] }, async () => { + await vendor.vendorViewEarningOnEditProductDetails(data.predefined.simpleProduct.product1.name); + }); + + test('vendor can view earning on order list', { tag: ['@lite', '@vendor'] }, async () => { + await vendor.vendorViewEarningOnOrderList(); + }); + + test('vendor can view earning on order details', { tag: ['@lite', '@vendor'] }, async () => { + const [, , orderId] = await apiUtils.createOrderWithStatus(PRODUCT_ID, { ...payloads.createOrder, customer_id: CUSTOMER_ID }, data.order.orderStatus.onhold, payloads.vendorAuth); + await vendor.vendorViewEarningOnOrderDetails(orderId); + }); }); diff --git a/tests/pw/utils/apiUtils.ts b/tests/pw/utils/apiUtils.ts index 13fb66ca80..fb13ea045f 100644 --- a/tests/pw/utils/apiUtils.ts +++ b/tests/pw/utils/apiUtils.ts @@ -2057,6 +2057,13 @@ export class ApiUtils { return [...order, productId]; } + // create multivendor order + async createMultivendorOrder(orderPayload: any, lineItemPayload?: any) { + const lineItems = await this.createLineItemsEnhanced(lineItemPayload); + const [, parentOrder, parentOrderId] = await this.createOrderWc({ ...orderPayload, line_items: lineItems }); + return [parentOrder, parentOrderId]; + } + // create order with status async createOrderWithStatus(product: string | object, order: any, status: string, auth?: auth): Promise<[APIResponse, responseBody, string, string]> { const [response, responseBody, orderId, productId] = await this.createOrder(product, order, auth); @@ -2066,6 +2073,7 @@ export class ApiUtils { // create line items async createLineItems(products = 1, quantities = [1], authors = [payloads.vendorAuth]) { + // todo: replace createLineItems with createLineItemsEnhanced and update tests const lineItems = []; for (let i = 0; i < products; i++) { @@ -2078,6 +2086,28 @@ export class ApiUtils { return lineItems; } + // create multivendor line items + async createLineItemsEnhanced(multivendorLineItem: { author: string; products: string | string[]; quantities: string | string[] }[]) { + const lineItems = []; + for (const item of multivendorLineItem) { + const { author, products, quantities } = item; + + // Validate lengths only if both `products` and `quantities` are arrays + if (Array.isArray(products) && Array.isArray(quantities) && products.length !== quantities.length) { + throw new Error('products and quantities must be the same length'); + } + + const quantitiesArray = Array.isArray(quantities) ? quantities : [quantities]; + const length = Array.isArray(products) ? products.length : Number(item.products); + for (let i = 0; i < length; i++) { + const productId = Array.isArray(products) ? products[i] : (await this.createProduct({ ...payloads.createProduct(), post_author: author }, payloads.adminAuth))[1]; + const quantity = quantitiesArray[i % quantitiesArray.length]; + lineItems.push({ product_id: productId, quantity: quantity }); + } + } + return lineItems; + } + // refund // create refund diff --git a/tests/pw/utils/payloads.ts b/tests/pw/utils/payloads.ts index f53b3fa258..0194a65271 100644 --- a/tests/pw/utils/payloads.ts +++ b/tests/pw/utils/payloads.ts @@ -4,7 +4,7 @@ import { dbData } from '@utils/dbData'; const basicAuth = (username: string, password: string) => 'Basic ' + Buffer.from(username + ':' + password).toString('base64'); -const { ADMIN, VENDOR, VENDOR2, VENDOR3, CUSTOMER, CUSTOMER2, ADMIN_PASSWORD, USER_PASSWORD, CUSTOMER_ID, VENDOR_ID, PRODUCT_ID, PRODUCT_ID_V2, TAG_ID, ATTRIBUTE_ID } = process.env; +const { ADMIN, VENDOR, VENDOR2, VENDOR3, CUSTOMER, CUSTOMER2, ADMIN_PASSWORD, USER_PASSWORD, CUSTOMER_ID, VENDOR_ID, VENDOR2_ID, PRODUCT_ID, PRODUCT_ID_V2, TAG_ID, ATTRIBUTE_ID } = process.env; export const payloads = { // wp @@ -400,6 +400,7 @@ export const payloads = { }), createProduct: () => ({ + // post_author: '', name: `${faker.commerce.productName()}_${faker.string.nanoid(5)} (Simple)`, type: 'simple', regular_price: faker.finance.amount({ min: 100, max: 200, dec: faker.helpers.arrayElement([0, 2]) }), @@ -1953,6 +1954,70 @@ export const payloads = { ], }, + createMultiVendorOrder: { + payment_method: 'bacs', + payment_method_title: 'Direct Bank Transfer', + set_paid: true, + customer_id: CUSTOMER_ID ?? 0, + billing: { + first_name: 'customer1', + last_name: 'c1', + address_1: 'abc street', + address_2: 'xyz street', + city: 'New York', + state: 'NY', + postcode: '10003', + country: 'US', + email: 'customer1@email.com', + phone: '(555) 555-5555', + }, + + shipping: { + first_name: 'customer1', + last_name: 'c1', + address_1: 'abc street', + address_2: 'xyz street', + city: 'New York', + state: 'NY', + postcode: '10003', + country: 'US', + }, + + line_items: [ + { + product_id: PRODUCT_ID ?? '', + quantity: 1, + }, + { + product_id: PRODUCT_ID_V2 ?? '', + quantity: 1, + }, + ], + + shipping_lines: [ + { + method_id: 'flat_rate', + method_title: 'Flat Rate', + total: '10.00', + }, + ], + + coupon_lines: [], + }, + + multivendorLineItems: [ + { + author: VENDOR_ID, + products: '3', // product Ids + quantities: '1', + }, + { + author: VENDOR2_ID, + products: '3', // product Ids + quantities: '1', + }, + ], + createOrderNote: { status: 'processing', note: 'test order note', From 3af31a094bbed3de765499965c9d592ecb640794 Mon Sep 17 00:00:00 2001 From: shashwata Halder Date: Sat, 7 Dec 2024 17:53:09 +0600 Subject: [PATCH 2/4] Add vendor coupon tests (#2470) * Add vendor coupon tests * Update test tags --- tests/pw/tests/api/calculation.spec.ts | 80 +++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/tests/pw/tests/api/calculation.spec.ts b/tests/pw/tests/api/calculation.spec.ts index 03507c933e..bce39aa7a5 100644 --- a/tests/pw/tests/api/calculation.spec.ts +++ b/tests/pw/tests/api/calculation.spec.ts @@ -373,7 +373,6 @@ test.describe.serial('fee recipient calculation test', () => { }); }); -// todo: vendor coupon tests [skipped wc order api doesn't support vendor coupon] test.describe.serial('marketplace coupon calculation test', () => { let apiUtils: ApiUtils; @@ -387,10 +386,10 @@ test.describe.serial('marketplace coupon calculation test', () => { }); test('marketplace coupon: single coupon', { tag: ['@lite'] }, async () => { - const [, , code1] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'percent' }, payloads.adminAuth); + const [, , couponCode] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'percent' }, payloads.adminAuth); // place order and assert order calculation - const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }] }, payloads.vendorAuth); + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); await assertOrderCalculation(order); }); @@ -425,18 +424,85 @@ test.describe.serial('marketplace coupon calculation test', () => { }); test('marketplace coupon: percent coupon', { tag: ['@lite'] }, async () => { - const [, , code1] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'percent' }, payloads.adminAuth); + const [, , couponCode] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'percent' }, payloads.adminAuth); // place order and assert order calculation - const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }] }, payloads.vendorAuth); + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); await assertOrderCalculation(order); }); test('marketplace coupon: fixed_cart coupon', { tag: ['@lite'] }, async () => { - const [, , code1] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'fixed_cart' }, payloads.adminAuth); + const [, , couponCode] = await apiUtils.createMarketPlaceCoupon({ ...payloads.createMarketPlaceCoupon(), discount_type: 'fixed_cart' }, payloads.adminAuth); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); +}); + +test.describe.serial('vendor coupon calculation test', () => { + let apiUtils: ApiUtils; + + test.beforeAll(async () => { + apiUtils = new ApiUtils(await request.newContext()); + await apiUtils.setUpTaxRate(payloads.enableTax, { ...payloads.createTaxRate, rate: '10' }); + await dbUtils.setOptionValue(dbData.dokan.optionName.selling, dbData.dokan.sellingSettings); + const store = await apiUtils.getSingleStore(VENDOR_ID); + await apiUtils.updateStore(VENDOR_ID, { ...store, ...payloads.vendorwiseCommission, admin_commission: '', admin_additional_fee: '' }); + await apiUtils.updateSingleWcSettingOptions('general', 'woocommerce_calc_discounts_sequentially', { value: 'no' }); + }); + + test('vendor coupon: single coupon', { tag: ['@pro'] }, async () => { + const [, , couponCode] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); + + test('vendor coupon: multiple coupon', { tag: ['@pro'] }, async () => { + const [, , code1] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + const [, , code2] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + await apiUtils.updateSingleWcSettingOptions('general', 'woocommerce_calc_discounts_sequentially', { value: 'no' }); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }, { code: code2 }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); + + test('vendor coupon: multiple coupon non-sequential', { tag: ['@pro'] }, async () => { + const [, , code1] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + const [, , code2] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + await apiUtils.updateSingleWcSettingOptions('general', 'woocommerce_calc_discounts_sequentially', { value: 'no' }); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }, { code: code2 }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); + + test('vendor coupon: multiple coupon sequential', { tag: ['@pro'] }, async () => { + const [, , code1] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + const [, , code2] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + await apiUtils.updateSingleWcSettingOptions('general', 'woocommerce_calc_discounts_sequentially', { value: 'yes' }); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }, { code: code2 }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); + + test('vendor coupon: percent coupon', { tag: ['@pro'] }, async () => { + const [, , couponCode] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'percent' }, payloads.vendorAuth); + + // place order and assert order calculation + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); + await assertOrderCalculation(order); + }); + + test('vendor coupon: fixed_cart coupon', { tag: ['@pro'] }, async () => { + const [, , couponCode] = await apiUtils.createCoupon([], { ...payloads.createCoupon(), discount_type: 'fixed_cart' }, payloads.vendorAuth); // place order and assert order calculation - const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: code1 }] }, payloads.vendorAuth); + const order = await apiUtils.createOrder(payloads.createProduct(), { ...payloads.createOrder, coupon_lines: [{ code: couponCode }] }, payloads.vendorAuth); await assertOrderCalculation(order); }); }); From 31194fafc2ecb74d34745d79a3f7956c480fc265 Mon Sep 17 00:00:00 2001 From: Osman Goni Sufy <47870515+osmansufy@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:50:19 +0600 Subject: [PATCH 3/4] Enhance/plugin header section redesign (#2461) * [initialization] * [design] upgrade * [centralized] pro-award svg icon * [refactor] [sanitize] code * isset args in award icon * [refactor] award icon * [update] zoom icon color in VendorCapabilities.vue * fix dokan_pro() check * removed dokan_pro() check * [responsive] header section * [hide] pro button when active not has license * [Skip] payment in Setup wizard --- assets/src/less/admin.less | 129 ++++++++++++++++++++----- src/admin/pages/VendorCapabilities.vue | 3 +- templates/admin-header.php | 64 +++++++++++- templates/svg-icons/pro-award.php | 19 ++++ 4 files changed, 185 insertions(+), 30 deletions(-) create mode 100644 templates/svg-icons/pro-award.php diff --git a/assets/src/less/admin.less b/assets/src/less/admin.less index c4514d2783..759d831440 100644 --- a/assets/src/less/admin.less +++ b/assets/src/less/admin.less @@ -4,32 +4,88 @@ .dokan-admin-header { background: #fff; - padding: 15px 15px 15px 22px; - margin: 0 0 0 -20px; - box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: space-between; + padding: 16px 24px; + margin: 20px 20px 0 2px; + border-radius: 8px; + box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.06); + .dokan-admin-header-content { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + .dokan-admin-logo-wrap { + display: flex; + flex: 1; .dokan-admin-header-logo { display: flex; align-items: center; img { - height: 25px; + height: 32px; width: auto; margin-right: 12px; } + } + + .dokan-version-tags { + display: flex; + align-items: center; + gap: 12px; + margin-right: 24px; + flex: 1; + .version-tag { + + border-radius: 20px; + font-size: 1rem; + line-height: 20px; + font-weight: 400; + padding: 0.5rem 1rem; + max-height: 2rem; + &.lite { + background: #FF9B5366; + color: #7B4E2E; + display: flex; + align-items: center; + } - span { - color: #F1634C; - background: #FFF4F2; - padding: 4px 12px; - font-size: 12px; - font-weight: 500; - font-family: "SF Pro Text", sans-serif; - border: 1px solid rgba(241, 99, 76, 0.2); - border-radius: 93px; + &.pro { + background: #D8D8FE; + color: @dokan-color; + display: flex; + align-items: center; + gap: 8px; + text-transform: capitalize; + font-weight: 450; + & .version-tag-pro-badge{ + background: @dokan-color; + color: white; + border-radius: 28px; + display: inline-flex; + align-items: center; + padding: 3px 9px; + font-size: 16px; + margin: -10px 0 -10px -10px; + } + } + } + } + } + .upgrade-button { + display: flex; + align-items: center; + gap: 8px; + background: @dokan-color; + color: white; + padding: 10px 20px; + border-radius: 6px; + text-decoration: none; + font-weight: 500; + transition: background-color 0.2s; + margin-left: auto; + &:hover { + background: @dokan-dark-purple; } } @@ -56,7 +112,6 @@ border-radius: 42px; cursor: pointer; transition: all .2s ease; - .notification-count { position: absolute; top: -5px; @@ -71,7 +126,6 @@ align-items: center; justify-content: center; } - .whats-new-pointer { position: absolute; top: 0; @@ -81,7 +135,7 @@ background-color: #ff5a40; border-radius: 53px; border: 2px solid #fff; - box-sizing: content-box; + box-sizing: content-box; } &:hover { @@ -137,10 +191,10 @@ } h3 { - margin: 0; - font-weight: bold; - font-size: 18px; - font-family: "SF Pro Text", sans-serif; + margin: 0; + font-weight: bold; + font-size: 18px; + font-family: "SF Pro Text", sans-serif; } .list-item { @@ -158,7 +212,7 @@ margin-bottom: 10px; &:last-child { - margin-bottom: 0; + margin-bottom: 0; } &.active { @@ -201,7 +255,7 @@ background-color: @dokan-light-gray; svg path { - fill:@dokan-color; + fill: @dokan-color; } } } @@ -217,6 +271,31 @@ } } +// Responsive styles +@media screen and (max-width: 782px) { + .dokan-admin-logo-wrap { + flex-direction: column; + } + + .dokan-admin-header { + margin: 10px 10px 0 2px; + padding: 12px 16px; + + .dokan-admin-header-content { + gap: 16px; + align-items:start; + } + + .dokan-version-tags { + margin: 12px 0; + flex-wrap: wrap; + } + + .upgrade-button { + margin: 0; + } + } +} .dokan-dashboard { .post-box-container { width: 49%; diff --git a/src/admin/pages/VendorCapabilities.vue b/src/admin/pages/VendorCapabilities.vue index cc6138a4cf..101365ee6e 100644 --- a/src/admin/pages/VendorCapabilities.vue +++ b/src/admin/pages/VendorCapabilities.vue @@ -157,6 +157,7 @@ export default {