From a6d2c0e0b43372227a9ae4bc48d1a53912d43b34 Mon Sep 17 00:00:00 2001 From: DediData <33499600+Dedi-Data@users.noreply.github.com> Date: Wed, 9 Dec 2020 13:18:17 +0330 Subject: [PATCH 01/32] Update pagination.php fix for RTL directions --- templates/loop/pagination.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/loop/pagination.php b/templates/loop/pagination.php index 9d61f8799cf..b524d19d14e 100644 --- a/templates/loop/pagination.php +++ b/templates/loop/pagination.php @@ -39,8 +39,8 @@ if ( $total <= 1 ) { 'add_args' => false, 'current' => max( 1, $current ), 'total' => $total, - 'prev_text' => '←', - 'next_text' => '→', + 'prev_text' => is_rtl() ? '→' : '←', + 'next_text' => is_rtl() ? '←' : '→', 'type' => 'list', 'end_size' => 3, 'mid_size' => 3, From a8834ee84cebeccf508e03f374062ec3746c5105 Mon Sep 17 00:00:00 2001 From: DediData <33499600+Dedi-Data@users.noreply.github.com> Date: Wed, 9 Dec 2020 13:21:54 +0330 Subject: [PATCH 02/32] Update single-product-reviews.php fix for RTL direction --- templates/single-product-reviews.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/single-product-reviews.php b/templates/single-product-reviews.php index 995a11ea348..4d6f12e5938 100644 --- a/templates/single-product-reviews.php +++ b/templates/single-product-reviews.php @@ -51,8 +51,8 @@ if ( ! comments_open() ) { apply_filters( 'woocommerce_comment_pagination_args', array( - 'prev_text' => '←', - 'next_text' => '→', + 'prev_text' => is_rtl() ? '→' : '←', + 'next_text' => is_rtl() ? '←' : '→', 'type' => 'list', ) ) From ed042b5a838fd74f5cf885bc3674f059b2bacabd Mon Sep 17 00:00:00 2001 From: Takashi Kitajima Date: Wed, 3 Feb 2021 14:21:43 +0900 Subject: [PATCH 03/32] The order of the full name returned by WC_Countries::get_formatted_address() `WC_Abstract_Order::get_formatted_billing_full_name()` and `WC_Abstract_Order::get_formatted_shipping_full_name()` displays the names in the correct order according to the language, but `WC_Countries::get_formatted_address()` does not. --- includes/class-wc-countries.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-countries.php b/includes/class-wc-countries.php index 58dc736e8a5..070d8afec5b 100644 --- a/includes/class-wc-countries.php +++ b/includes/class-wc-countries.php @@ -591,7 +591,7 @@ class WC_Countries { array( '{first_name}' => $args['first_name'], '{last_name}' => $args['last_name'], - '{name}' => $args['first_name'] . ' ' . $args['last_name'], + '{name}' => sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $args['first_name'], $args['last_name'] ), '{company}' => $args['company'], '{address_1}' => $args['address_1'], '{address_2}' => $args['address_2'], @@ -601,7 +601,7 @@ class WC_Countries { '{country}' => $full_country, '{first_name_upper}' => wc_strtoupper( $args['first_name'] ), '{last_name_upper}' => wc_strtoupper( $args['last_name'] ), - '{name_upper}' => wc_strtoupper( $args['first_name'] . ' ' . $args['last_name'] ), + '{name_upper}' => wc_strtoupper( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $args['first_name'], $args['last_name'] ) ), '{company_upper}' => wc_strtoupper( $args['company'] ), '{address_1_upper}' => wc_strtoupper( $args['address_1'] ), '{address_2_upper}' => wc_strtoupper( $args['address_2'] ), From 7f2163fdd8bf992f37e3643868f4addb4b8a2df3 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 14:44:23 +0100 Subject: [PATCH 04/32] Add new test shop browse search sort products --- tests/e2e/core-tests/CHANGELOG.md | 2 +- tests/e2e/core-tests/README.md | 1 + tests/e2e/core-tests/specs/index.js | 3 + ...ont-end-product-browse-search-sort.test.js | 110 ++++++++++++++++++ .../test-product-browse-search-sort.js | 6 + tests/e2e/utils/CHANGELOG.md | 1 + tests/e2e/utils/src/components.js | 39 +++++++ 7 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js create mode 100644 tests/e2e/specs/front-end/test-product-browse-search-sort.js diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index db19f4af04b..719f727605e 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -9,7 +9,7 @@ - Merchant Order Refund tests - Merchant Apply Coupon tests - Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99 - +- Shopper Shop Browse Search Sort - Shopper Checkout Apply Coupon diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index a75a039f18c..366db682595 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -63,6 +63,7 @@ The functions to access the core tests are: - `runCheckoutPageTest` - Shopper can complete checkout - `runMyAccountPageTest` - Shopper can access my account page - `runSingleProductPageTest` - Shopper can view single product page + - `runProductBrowseSearchSortTest` - Shopper can browse, search & sort products ## Contributing a new test diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 51ca772aee8..5ebe2cefe4e 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -15,6 +15,7 @@ const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupo const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); +const runProductBrowseSearchSortTest = require( './shopper/front-end-product-browse-search-sort.test' ); // Merchant tests const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' ); @@ -41,6 +42,7 @@ const runShopperTests = () => { runCheckoutPageTest(); runMyAccountPageTest(); runSingleProductPageTest(); + runProductBrowseSearchSortTest(); }; const runMerchantTests = () => { @@ -80,4 +82,5 @@ module.exports = { runOrderRefundTest, runOrderApplyCouponTest, runMerchantTests, + runProductBrowseSearchSortTest, }; diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js new file mode 100644 index 00000000000..1475c3eab52 --- /dev/null +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -0,0 +1,110 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ +/** + * Internal dependencies + */ +const { + shopper, + merchant, + createSimpleProductWithCategory, + uiUnblocked +} = require( '@woocommerce/e2e-utils' ); + +/** + * External dependencies + */ +const { + it, + describe, + beforeAll, +} = require( '@jest/globals' ); + +let variablePostIdValue; +const config = require( 'config' ); +const simpleProductName = config.get( 'products.simple.name' ); +const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99'; +const singleProductPrice2 = config.has('products.simple.price') ? config.get('products.simple.price') : '19.99'; +const singleProductPrice3 = config.has('products.simple.price') ? config.get('products.simple.price') : '29.99'; +const clothing = 'Clothing'; +const audio = 'Audio'; +const hardware = 'Hardware'; +const productTitle = 'ul.products > li.first > a > h2.woocommerce-loop-product__title'; + +const runProductBrowseSearchSortTest = () => { + describe('Search, browse by categories and sort items in the shop', () => { + beforeAll(async () => { + await merchant.login(); + + // Create 1st product with Clothing category + variablePostIdValue = await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing); + + // Create 2nd product with Audio category + await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio); + + // Create 3rd product with Hardware category + await createSimpleProductWithCategory(simpleProductName + ' 3', singleProductPrice3, hardware); + await merchant.logout(); + }); + + it('should let user search the store', async () => { + await shopper.login(); + await shopper.goToShop(); + + // Search for the 1st product + await expect(page).toFill('.search-field', simpleProductName + ' 1'); + await expect(page).toClick('.search-submit'); + await uiUnblocked(); + + // Make sure we're on the search results page + await expect(page.title()).resolves.toMatch('Search Results for “' + simpleProductName + ' 1”'); + + // Verify the results + await expect(page).toMatchElement('h2.entry-title', {text: simpleProductName + ' 1'}); + await expect(page).toClick('h2.entry-title', {text: simpleProductName + ' 1'}); + await uiUnblocked(); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); + }); + + it('should let user browse products by categories', async () => { + // Go to 1st product and click category name + await shopper.goToProduct(variablePostIdValue); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toClick('span.posted_in > a', {text: clothing}); + await uiUnblocked(); + + // Verify Clothing category page + await expect(page.title()).resolves.toMatch(clothing); + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); + + // Verify clicking on the product + await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); + await uiUnblocked(); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); + }); + + it('should let user sort the products in the shop', async () => { + await shopper.goToShop(); + + // Sort by price high to low + await page.select('.orderby', 'price-desc'); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, + {text: simpleProductName + ' 3'}); + + // Sort by price low to high + await page.select('.orderby', 'price'); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, + {text: simpleProductName + ' 1'}); + + // Sort by date of creation, latest to oldest + await page.select('.orderby', 'date'); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, + {text: simpleProductName + ' 3'}); + }); + }); +}; + +module.exports = runProductBrowseSearchSortTest; diff --git a/tests/e2e/specs/front-end/test-product-browse-search-sort.js b/tests/e2e/specs/front-end/test-product-browse-search-sort.js new file mode 100644 index 00000000000..ed12d1cb341 --- /dev/null +++ b/tests/e2e/specs/front-end/test-product-browse-search-sort.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runProductBrowseSearchSortTest } = require( '@woocommerce/e2e-core-tests' ); + +runProductBrowseSearchSortTest(); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 62aaae9edc1..448ecad5833 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -15,6 +15,7 @@ - `addProductToOrder( orderId, productName )` component which adds the provided productName to the passed in orderId - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. +- `createSimpleProductWithCategory` component which creates a simple product with categories, containing three parameters for title, price and category name. ## Changes diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 7ef45aeb128..0ec10849480 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -179,6 +179,44 @@ const createSimpleProduct = async () => { return product.id; } ; +/** + * Create simple product with categories + * + * @param productName Product's name which can be changed when writing a test + * @param productPrice Product's price which can be changed when writing a test + * @param categoryName Product's category which can be changed when writing a test + */ +const createSimpleProductWithCategory = async ( productName, productPrice, categoryName ) => { + // Go to "add product" page + await merchant.openNewProduct(); + + // Add title and regular price + await expect(page).toFill('#title', productName); + await expect(page).toClick('#_virtual'); + await clickTab('General'); + await expect(page).toFill('#_regular_price', productPrice); + + // Try to select the existing category if present already, otherwise add a new and select it + try { + const [checkbox] = await page.$x('//label[contains(text(), "'+categoryName+'")]'); + await checkbox.click(); + } catch (error) { + await expect(page).toClick('#product_cat-add-toggle'); + await expect(page).toFill('#newproduct_cat', categoryName); + await expect(page).toClick('#product_cat-add-submit'); + } + + // Publish the product + await expect(page).toClick('#publish'); + await uiUnblocked(); + await page.waitForSelector('.updated.notice', {text:'Product published.'}); + + // Get the product ID + const variablePostId = await page.$('#post_ID'); + let variablePostIdValue = (await(await variablePostId.getProperty('value')).jsonValue()); + return variablePostIdValue; +}; + /** * Create variable product. */ @@ -400,4 +438,5 @@ export { verifyAndPublish, addProductToOrder, createCoupon, + createSimpleProductWithCategory, }; From f6a19c1430ea4d0fb4de556ffd5364438cf0f1e6 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 14:51:17 +0100 Subject: [PATCH 05/32] Reorder scenarios within test --- ...ont-end-product-browse-search-sort.test.js | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 1475c3eab52..92bd63ea1ee 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -45,8 +45,27 @@ const runProductBrowseSearchSortTest = () => { await merchant.logout(); }); + it('should let user browse products by categories', async () => { + await shopper.goToShop(); + + // Go to 1st product and click category name + await shopper.goToProduct(variablePostIdValue); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toClick('span.posted_in > a', {text: clothing}); + await uiUnblocked(); + + // Verify Clothing category page + await expect(page.title()).resolves.toMatch(clothing); + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); + + // Verify clicking on the product + await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); + await uiUnblocked(); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); + }); + it('should let user search the store', async () => { - await shopper.login(); await shopper.goToShop(); // Search for the 1st product @@ -65,24 +84,6 @@ const runProductBrowseSearchSortTest = () => { await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); }); - it('should let user browse products by categories', async () => { - // Go to 1st product and click category name - await shopper.goToProduct(variablePostIdValue); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); - await expect(page).toClick('span.posted_in > a', {text: clothing}); - await uiUnblocked(); - - // Verify Clothing category page - await expect(page.title()).resolves.toMatch(clothing); - await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); - - // Verify clicking on the product - await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); - await uiUnblocked(); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); - await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); - }); - it('should let user sort the products in the shop', async () => { await shopper.goToShop(); From be56fa7772a04d85fd2878adaac270850dc3a49a Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 17:23:37 +0100 Subject: [PATCH 06/32] Reorder shopper tests --- tests/e2e/core-tests/specs/index.js | 2 +- ...ont-end-product-browse-search-sort.test.js | 59 +++++++++---------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 5ebe2cefe4e..0334342e9fc 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -36,13 +36,13 @@ const runSetupOnboardingTests = () => { }; const runShopperTests = () => { + runProductBrowseSearchSortTest(); runCartApplyCouponsTest(); runCartPageTest(); runCheckoutApplyCouponsTest(); runCheckoutPageTest(); runMyAccountPageTest(); runSingleProductPageTest(); - runProductBrowseSearchSortTest(); }; const runMerchantTests = () => { diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 92bd63ea1ee..16e0b0cda3d 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -27,7 +27,7 @@ const singleProductPrice3 = config.has('products.simple.price') ? config.get('pr const clothing = 'Clothing'; const audio = 'Audio'; const hardware = 'Hardware'; -const productTitle = 'ul.products > li.first > a > h2.woocommerce-loop-product__title'; +const productTitle = 'li.first > a > h2.woocommerce-loop-product__title'; const runProductBrowseSearchSortTest = () => { describe('Search, browse by categories and sort items in the shop', () => { @@ -45,27 +45,8 @@ const runProductBrowseSearchSortTest = () => { await merchant.logout(); }); - it('should let user browse products by categories', async () => { - await shopper.goToShop(); - - // Go to 1st product and click category name - await shopper.goToProduct(variablePostIdValue); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); - await expect(page).toClick('span.posted_in > a', {text: clothing}); - await uiUnblocked(); - - // Verify Clothing category page - await expect(page.title()).resolves.toMatch(clothing); - await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); - - // Verify clicking on the product - await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); - await uiUnblocked(); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); - await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); - }); - it('should let user search the store', async () => { + await shopper.login(); await shopper.goToShop(); // Search for the 1st product @@ -84,26 +65,44 @@ const runProductBrowseSearchSortTest = () => { await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); }); + it('should let user browse products by categories', async () => { + // Go to 1st product and click category name + await shopper.goToProduct(variablePostIdValue); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle0'}), + page.click('span.posted_in > a', {text: clothing}), + ]); + await uiUnblocked(); + + // Verify Clothing category page + await expect(page.title()).resolves.toMatch(clothing); + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); + + // Verify clicking on the product + await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); + await uiUnblocked(); + await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); + }); + it('should let user sort the products in the shop', async () => { await shopper.goToShop(); // Sort by price high to low await page.select('.orderby', 'price-desc'); - // Verify the first product in sort order - await expect(page).toMatchElement(productTitle, - {text: simpleProductName + ' 3'}); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'}); // Sort by price low to high await page.select('.orderby', 'price'); - // Verify the first product in sort order - await expect(page).toMatchElement(productTitle, - {text: simpleProductName + ' 1'}); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); // Sort by date of creation, latest to oldest await page.select('.orderby', 'date'); - // Verify the first product in sort order - await expect(page).toMatchElement(productTitle, - {text: simpleProductName + ' 3'}); + // Verify the first product in sort order + await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'}); }); }); }; From 961ce70a2b898415bdb88ea1b7d194d1490ccbc1 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 17:50:49 +0100 Subject: [PATCH 07/32] Fix issue after merging master --- tests/e2e/config/default.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index e01ee4e9a27..19693eece88 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", From 945d8bdf4a981a3d9fee85ff6823881dc6976036 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 18:07:20 +0100 Subject: [PATCH 08/32] Update comment --- .../specs/shopper/front-end-product-browse-search-sort.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 16e0b0cda3d..28d8ed58b28 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -1,4 +1,4 @@ -/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ +/* eslint-disable jest/no-export, jest/no-disabled-tests */ /** * Internal dependencies */ From 1d6d7273505c051d80ef9d6018830fddf52c5ed5 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 20:44:29 +0100 Subject: [PATCH 09/32] Revert back change and update code --- tests/e2e/core-tests/specs/index.js | 2 +- .../specs/shopper/front-end-product-browse-search-sort.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 0334342e9fc..5ebe2cefe4e 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -36,13 +36,13 @@ const runSetupOnboardingTests = () => { }; const runShopperTests = () => { - runProductBrowseSearchSortTest(); runCartApplyCouponsTest(); runCartPageTest(); runCheckoutApplyCouponsTest(); runCheckoutPageTest(); runMyAccountPageTest(); runSingleProductPageTest(); + runProductBrowseSearchSortTest(); }; const runMerchantTests = () => { diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 28d8ed58b28..3f2d64e7735 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -18,7 +18,6 @@ const { beforeAll, } = require( '@jest/globals' ); -let variablePostIdValue; const config = require( 'config' ); const simpleProductName = config.get( 'products.simple.name' ); const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99'; @@ -31,6 +30,7 @@ const productTitle = 'li.first > a > h2.woocommerce-loop-product__title'; const runProductBrowseSearchSortTest = () => { describe('Search, browse by categories and sort items in the shop', () => { + let variablePostIdValue; beforeAll(async () => { await merchant.login(); From 5c7fa67f35452767a1c10aeab981ea21fc1dd718 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Feb 2021 20:49:55 +0100 Subject: [PATCH 10/32] Reorder shopper tests --- tests/e2e/core-tests/specs/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 5ebe2cefe4e..a3523c17c40 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -9,13 +9,13 @@ const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setu const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ); // Shopper tests +const runProductBrowseSearchSortTest = require( './shopper/front-end-product-browse-search-sort.test' ); const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test'); const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); -const runProductBrowseSearchSortTest = require( './shopper/front-end-product-browse-search-sort.test' ); // Merchant tests const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' ); @@ -36,13 +36,13 @@ const runSetupOnboardingTests = () => { }; const runShopperTests = () => { + runProductBrowseSearchSortTest(); runCartApplyCouponsTest(); runCartPageTest(); runCheckoutApplyCouponsTest(); runCheckoutPageTest(); runMyAccountPageTest(); runSingleProductPageTest(); - runProductBrowseSearchSortTest(); }; const runMerchantTests = () => { From 2bc07e9b5554a4cff68f12801645e627c50b37bb Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Feb 2021 09:29:30 +0100 Subject: [PATCH 11/32] Fix spacing in code --- .../front-end-product-browse-search-sort.test.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 3f2d64e7735..67f0f89c3d0 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -46,7 +46,6 @@ const runProductBrowseSearchSortTest = () => { }); it('should let user search the store', async () => { - await shopper.login(); await shopper.goToShop(); // Search for the 1st product @@ -68,7 +67,6 @@ const runProductBrowseSearchSortTest = () => { it('should let user browse products by categories', async () => { // Go to 1st product and click category name await shopper.goToProduct(variablePostIdValue); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); await Promise.all([ page.waitForNavigation({waitUntil: 'networkidle0'}), page.click('span.posted_in > a', {text: clothing}), @@ -76,10 +74,7 @@ const runProductBrowseSearchSortTest = () => { await uiUnblocked(); // Verify Clothing category page - await expect(page.title()).resolves.toMatch(clothing); await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); - - // Verify clicking on the product await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); await uiUnblocked(); await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); @@ -91,17 +86,17 @@ const runProductBrowseSearchSortTest = () => { // Sort by price high to low await page.select('.orderby', 'price-desc'); - // Verify the first product in sort order + // Verify the first product in sort order await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'}); // Sort by price low to high await page.select('.orderby', 'price'); - // Verify the first product in sort order + // Verify the first product in sort order await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); // Sort by date of creation, latest to oldest await page.select('.orderby', 'date'); - // Verify the first product in sort order + // Verify the first product in sort order await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'}); }); }); From 3294897517c7f2c3730d8bda0fa5eddbc26afe8e Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Feb 2021 10:30:25 +0100 Subject: [PATCH 12/32] Add new shopper method and update test --- ...ont-end-product-browse-search-sort.test.js | 26 +++---------------- tests/e2e/utils/README.md | 1 + tests/e2e/utils/src/flows/shopper.js | 11 ++++++++ 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 67f0f89c3d0..f1695ce7aad 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -30,16 +30,12 @@ const productTitle = 'li.first > a > h2.woocommerce-loop-product__title'; const runProductBrowseSearchSortTest = () => { describe('Search, browse by categories and sort items in the shop', () => { - let variablePostIdValue; beforeAll(async () => { await merchant.login(); - // Create 1st product with Clothing category variablePostIdValue = await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing); - // Create 2nd product with Audio category await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio); - // Create 3rd product with Hardware category await createSimpleProductWithCategory(simpleProductName + ' 3', singleProductPrice3, hardware); await merchant.logout(); @@ -47,26 +43,11 @@ const runProductBrowseSearchSortTest = () => { it('should let user search the store', async () => { await shopper.goToShop(); - - // Search for the 1st product - await expect(page).toFill('.search-field', simpleProductName + ' 1'); - await expect(page).toClick('.search-submit'); - await uiUnblocked(); - - // Make sure we're on the search results page - await expect(page.title()).resolves.toMatch('Search Results for “' + simpleProductName + ' 1”'); - - // Verify the results - await expect(page).toMatchElement('h2.entry-title', {text: simpleProductName + ' 1'}); - await expect(page).toClick('h2.entry-title', {text: simpleProductName + ' 1'}); - await uiUnblocked(); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); - await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); + await shopper.searchForProduct(simpleProductName + ' 1'); }); it('should let user browse products by categories', async () => { - // Go to 1st product and click category name - await shopper.goToProduct(variablePostIdValue); + // Browse through Clothing category link await Promise.all([ page.waitForNavigation({waitUntil: 'networkidle0'}), page.click('span.posted_in > a', {text: clothing}), @@ -74,10 +55,11 @@ const runProductBrowseSearchSortTest = () => { await uiUnblocked(); // Verify Clothing category page + await page.waitForSelector(productTitle); await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); await uiUnblocked(); - await expect(page.title()).resolves.toMatch(simpleProductName + ' 1'); + await page.waitForSelector('h1.entry-title'); await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); }); diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index e04635ecb27..408f3e842ce 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -75,6 +75,7 @@ describe( 'Cart page', () => { | `productIsInCheckout` | `productTitle, quantity, total, cartSubtotal` | Verify product is in cart on checkout page | | `removeFromCart` | `productTitle` | Remove a product from the cart on the cart page | | `setCartQuantity` | `productTitle, quantityValue` | Change the quantity of a product on the cart page | +| `searchForProduct` | Searching for a product name and landing on its detail page | ### Page Utilities diff --git a/tests/e2e/utils/src/flows/shopper.js b/tests/e2e/utils/src/flows/shopper.js index 516a69e4df6..8e4acad61f2 100644 --- a/tests/e2e/utils/src/flows/shopper.js +++ b/tests/e2e/utils/src/flows/shopper.js @@ -137,6 +137,17 @@ const shopper = { await quantityInput.type( quantityValue.toString() ); }, + searchForProduct: async ( prouductName ) => { + await expect(page).toFill('.search-field', prouductName); + await expect(page).toClick('.search-submit'); + await page.waitForSelector('h2.entry-title'); + await expect(page).toMatchElement('h2.entry-title', {text: prouductName}); + await expect(page).toClick('h2.entry-title', {text: prouductName}); + await page.waitForSelector('h1.entry-title'); + await expect(page.title()).resolves.toMatch(prouductName); + await expect(page).toMatchElement('h1.entry-title', prouductName); + }, + /* * My Accounts flows. */ From c18cbcec02e11cd735843ed4f39b2141faaa3063 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Feb 2021 10:49:52 +0100 Subject: [PATCH 13/32] Remove variable from test --- .../specs/shopper/front-end-product-browse-search-sort.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index f1695ce7aad..50237daf053 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -33,7 +33,7 @@ const runProductBrowseSearchSortTest = () => { beforeAll(async () => { await merchant.login(); // Create 1st product with Clothing category - variablePostIdValue = await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing); + await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing); // Create 2nd product with Audio category await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio); // Create 3rd product with Hardware category From fff3904bbba3823ff4f9c901bca23769e4b15810 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 11 Feb 2021 10:30:39 +0100 Subject: [PATCH 14/32] Add new page utils and update coupon tests --- tests/e2e/config/default.json | 1 - .../shopper/front-end-cart-coupons.test.js | 47 +++++------------- .../front-end-checkout-coupons.test.js | 49 +++++-------------- tests/e2e/utils/CHANGELOG.md | 2 + tests/e2e/utils/README.md | 2 + tests/e2e/utils/src/page-utils.js | 34 +++++++++++++ 6 files changed, 64 insertions(+), 71 deletions(-) diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index e01ee4e9a27..19693eece88 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index 4b3188a7e13..e79cbded1e1 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -8,7 +8,8 @@ const { createCoupon, createSimpleProduct, uiUnblocked, - clearAndFillInput, + applyCoupon, + removeCoupon, } = require( '@woocommerce/e2e-utils' ); /** @@ -20,28 +21,6 @@ const { beforeAll, } = require( '@jest/globals' ); -/** - * Apply a coupon code to the cart. - * - * @param couponCode string - * @returns {Promise} - */ -const applyCouponToCart = async ( couponCode ) => { - await clearAndFillInput('#coupon_code', couponCode); - await expect(page).toClick('button', {text: 'Apply coupon'}); - await uiUnblocked(); -}; - -/** - * Remove one coupon from the cart. - * - * @returns {Promise} - */ -const removeCouponFromCart = async () => { - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); - await uiUnblocked(); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); -} const runCartApplyCouponsTest = () => { describe('Cart applying coupons', () => { let couponFixedCart; @@ -62,42 +41,42 @@ const runCartApplyCouponsTest = () => { }); it('allows customer to apply fixed cart coupon', async () => { - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('allows customer to apply percentage coupon', async () => { - await applyCouponToCart( couponPercentage ); + await applyCoupon( couponPercentage ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('allows customer to apply fixed product coupon', async () => { - await applyCouponToCart( couponFixedProduct ); + await applyCoupon( couponFixedProduct ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('prevents customer applying same coupon twice', async () => { - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); // Verify only one discount applied // This is a work around for Puppeteer inconsistently finding 'Coupon code already applied' await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); @@ -105,7 +84,7 @@ const runCartApplyCouponsTest = () => { }); it('allows customer to apply multiple coupons', async () => { - await applyCouponToCart( couponFixedProduct ); + await applyCoupon( couponFixedProduct ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total @@ -114,8 +93,8 @@ const runCartApplyCouponsTest = () => { }); it('restores cart total when coupons are removed', async () => { - await removeCouponFromCart(); - await removeCouponFromCart(); + await removeCoupon(); + await removeCoupon(); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); }); }); diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index abe006ef6d0..bcf8f7e20e9 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -8,7 +8,8 @@ const { createCoupon, createSimpleProduct, uiUnblocked, - clearAndFillInput, + applyCoupon, + removeCoupon, } = require( '@woocommerce/e2e-utils' ); /** @@ -20,30 +21,6 @@ const { beforeAll, } = require( '@jest/globals' ); -/** - * Apply a coupon code to the cart. - * - * @param couponCode string - * @returns {Promise} - */ -const applyCouponToCart = async ( couponCode ) => { - await expect(page).toClick('a', {text: 'Click here to enter your code'}); - await uiUnblocked(); - await clearAndFillInput('#coupon_code', couponCode); - await expect(page).toClick('button', {text: 'Apply coupon'}); - await uiUnblocked(); -}; - -/** - * Remove one coupon from the cart. - * - * @returns {Promise} - */ -const removeCouponFromCart = async () => { - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); - await uiUnblocked(); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); -} const runCheckoutApplyCouponsTest = () => { describe('Checkout coupons', () => { let couponFixedCart; @@ -64,7 +41,7 @@ const runCheckoutApplyCouponsTest = () => { }); it('allows customer to apply fixed cart coupon', async () => { - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Wait for page to expand total calculations to avoid flakyness @@ -73,31 +50,31 @@ const runCheckoutApplyCouponsTest = () => { // Verify discount applied and order total await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('allows customer to apply percentage coupon', async () => { - await applyCouponToCart( couponPercentage ); + await applyCoupon( couponPercentage ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('allows customer to apply fixed product coupon', async () => { - await applyCouponToCart( couponFixedProduct ); + await applyCoupon( couponFixedProduct ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCouponFromCart(); + await removeCoupon(); }); it('prevents customer applying same coupon twice', async () => { - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await applyCouponToCart( couponFixedCart ); + await applyCoupon( couponFixedCart ); // Verify only one discount applied // This is a work around for Puppeteer inconsistently finding 'Coupon code already applied' await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); @@ -105,14 +82,14 @@ const runCheckoutApplyCouponsTest = () => { }); it('allows customer to apply multiple coupons', async () => { - await applyCouponToCart( couponFixedProduct ); + await applyCoupon( couponFixedProduct ); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'}); }); it('restores cart total when coupons are removed', async () => { - await removeCouponFromCart(); - await removeCouponFromCart(); + await removeCoupon(); + await removeCoupon(); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); }); }); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 8b770fb8b4d..13126bf516f 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -16,6 +16,8 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field +- `applyCoupon( couponName )` util helper method which applies previously created coupon to cart or checkout +- `removeCoupon()` util helper method that removes a single coupon within cart or checkout ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 4b03105f644..6f85a9adee7 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -99,6 +99,8 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside +| `applyCoupon` | `couponName` | helper method which applies a coupon in cart or checkout +| `removeCoupon` | | helper method that removes a single coupon within cart or checkout ### Test Utilities diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 7679c15f2b7..1b209d5ed0c 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -209,6 +209,38 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f await page.keyboard.press('Enter'); }; +/** + * Apply a coupon code within cart or checkout. + * Method will try to apply a coupon in the checkout, otherwise will try to apply in the cart. + * + * @param couponCode string + * @returns {Promise} + */ +const applyCoupon = async ( couponCode ) => { + try { + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); + await clearAndFillInput('#coupon_code', couponCode); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); + } catch (error) { + await clearAndFillInput('#coupon_code', couponCode); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); + }; +}; + +/** + * Remove one coupon within cart or checkout. + * + * @returns {Promise} + */ +const removeCoupon = async () => { + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); +}; + export { clearAndFillInput, clickTab, @@ -225,4 +257,6 @@ export { moveAllItemsToTrash, evalAndClick, selectOptionInSelect2, + applyCoupon, + removeCoupon, }; From 01850a9cec88dd99a6fdb00db1e3f66fd157aaa4 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 11 Feb 2021 15:03:31 +0100 Subject: [PATCH 15/32] Merge branch 'master' into e2e-shopper-browse-search-sort --- .../specs/shopper/front-end-product-browse-search-sort.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index 50237daf053..c852cf1996e 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -6,7 +6,7 @@ const { shopper, merchant, createSimpleProductWithCategory, - uiUnblocked + uiUnblocked, } = require( '@woocommerce/e2e-utils' ); /** From 307a257781de512006484807e8df53dc613b3ff1 Mon Sep 17 00:00:00 2001 From: Takashi Kitajima Date: Fri, 12 Feb 2021 11:55:26 +0900 Subject: [PATCH 16/32] Add translator comment #29008 --- includes/class-wc-countries.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-countries.php b/includes/class-wc-countries.php index 070d8afec5b..3aeca1da8d0 100644 --- a/includes/class-wc-countries.php +++ b/includes/class-wc-countries.php @@ -591,7 +591,12 @@ class WC_Countries { array( '{first_name}' => $args['first_name'], '{last_name}' => $args['last_name'], - '{name}' => sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $args['first_name'], $args['last_name'] ), + '{name}' => sprintf( + /* translators: 1: first name 2: last name */ + _x( '%1$s %2$s', 'full name', 'woocommerce' ), + $args['first_name'], + $args['last_name'] + ), '{company}' => $args['company'], '{address_1}' => $args['address_1'], '{address_2}' => $args['address_2'], @@ -601,7 +606,14 @@ class WC_Countries { '{country}' => $full_country, '{first_name_upper}' => wc_strtoupper( $args['first_name'] ), '{last_name_upper}' => wc_strtoupper( $args['last_name'] ), - '{name_upper}' => wc_strtoupper( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $args['first_name'], $args['last_name'] ) ), + '{name_upper}' => wc_strtoupper( + sprintf( + /* translators: 1: first name 2: last name */ + _x( '%1$s %2$s', 'full name', 'woocommerce' ), + $args['first_name'], + $args['last_name'] + ) + ), '{company_upper}' => wc_strtoupper( $args['company'] ), '{address_1_upper}' => wc_strtoupper( $args['address_1'] ), '{address_2_upper}' => wc_strtoupper( $args['address_2'] ), From 812976c65c051c24e4961668a05eed5cee0a8c62 Mon Sep 17 00:00:00 2001 From: Tyler Paulson Date: Fri, 12 Feb 2021 12:12:27 -0500 Subject: [PATCH 17/32] Correct function summaries in the WC_Shipping_Rate class --- includes/class-wc-shipping-rate.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-shipping-rate.php b/includes/class-wc-shipping-rate.php index 1248a70a423..72bb0686d56 100644 --- a/includes/class-wc-shipping-rate.php +++ b/includes/class-wc-shipping-rate.php @@ -162,7 +162,7 @@ class WC_Shipping_Rate { } /** - * Set ID for the rate. This is usually a combination of the method and instance IDs. + * Get ID for the rate. This is usually a combination of the method and instance IDs. * * @since 3.2.0 * @return string @@ -172,7 +172,7 @@ class WC_Shipping_Rate { } /** - * Set shipping method ID the rate belongs to. + * Get shipping method ID the rate belongs to. * * @since 3.2.0 * @return string @@ -182,7 +182,7 @@ class WC_Shipping_Rate { } /** - * Set instance ID the rate belongs to. + * Get instance ID the rate belongs to. * * @since 3.2.0 * @return int @@ -192,7 +192,7 @@ class WC_Shipping_Rate { } /** - * Set rate label. + * Get rate label. * * @return string */ @@ -201,7 +201,7 @@ class WC_Shipping_Rate { } /** - * Set rate cost. + * Get rate cost. * * @since 3.2.0 * @return string @@ -211,7 +211,7 @@ class WC_Shipping_Rate { } /** - * Set rate taxes. + * Get rate taxes. * * @since 3.2.0 * @return array From 264bccf505c55bd96e6b0f01bc74c4d8fb95997d Mon Sep 17 00:00:00 2001 From: James Allan Date: Wed, 17 Feb 2021 11:42:30 +1000 Subject: [PATCH 18/32] Prevent displaying coupon form on checkout requiring login --- includes/wc-template-functions.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 9f37d99641e..4bdb5c16483 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -2292,12 +2292,14 @@ if ( ! function_exists( 'woocommerce_checkout_coupon_form' ) ) { * Output the Coupon form for the checkout. */ function woocommerce_checkout_coupon_form() { - wc_get_template( - 'checkout/form-coupon.php', - array( - 'checkout' => WC()->checkout(), - ) - ); + if ( is_user_logged_in() || WC()->checkout()->is_registration_enabled() || ! WC()->checkout()->is_registration_required() ) { + wc_get_template( + 'checkout/form-coupon.php', + array( + 'checkout' => WC()->checkout(), + ) + ); + } } } From 6284cbee3a5d52113b1f6ddf691f03f89be4d8d1 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 15 Feb 2021 19:41:35 -0800 Subject: [PATCH 19/32] Add a new dashboard widget to promote store setup --- assets/css/dashboard-finish-setup.scss | 55 +++++++ .../images/dashboard-widget-finish-setup.png | Bin 0 -> 5430 bytes .../class-wc-admin-dashboard-finish-setup.php | 154 ++++++++++++++++++ includes/admin/class-wc-admin.php | 1 + .../html-admin-dashboard-finish-setup.php | 29 ++++ 5 files changed, 239 insertions(+) create mode 100644 assets/css/dashboard-finish-setup.scss create mode 100644 assets/images/dashboard-widget-finish-setup.png create mode 100644 includes/admin/class-wc-admin-dashboard-finish-setup.php create mode 100644 includes/admin/views/html-admin-dashboard-finish-setup.php diff --git a/assets/css/dashboard-finish-setup.scss b/assets/css/dashboard-finish-setup.scss new file mode 100644 index 00000000000..223f66665bd --- /dev/null +++ b/assets/css/dashboard-finish-setup.scss @@ -0,0 +1,55 @@ +/** + * dashboard-finish-setup.scss + * Styles for WooCommerce dashboard finish setup widgets + * only loaded on the dashboard itself. + */ + +/** + * Imports + */ +@import "mixins"; +@import "variables"; +@import "fonts"; + +/** + * Styling begins + */ + +.dashboard-widget-finish-setup { + + .progress-wrapper { + border: 1px solid #757575; + border-radius: 10px; + font-size: 0.9em; + padding: 2px 8px 2px 5px; + display: inline-block; + } + + .progress-wrapper span { + position: relative; + top: -2px; + } + + .description div { + margin-top: 11px; + float: left; + width: 70%; + } + + .description img { + float: right; + width: 30%; + } + + .circle-progress { + + circle { + stroke: #f0f0f0; + stroke-width: 1px; + } + + .bar { + stroke: #949494; + } + } +} diff --git a/assets/images/dashboard-widget-finish-setup.png b/assets/images/dashboard-widget-finish-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..fcba8f5532a75dcc93288d3fdffd2f46cff58245 GIT binary patch literal 5430 zcmYLNWmp?Zv&NmG#U0uPr#KXMCloK<0L8r!pje8#OL4b@7jGdrMM`jYm*5htT<+=j z-TOSVvor6$Gqbb1zcvD>p@@$|g@c5Igs-e5_x^dFdma!h)Mrg{c&&kigmMg2(~*C6 zudc7JZ*I;nub%b)3j*(kqK>E4-XFu&)ZsAeR_Hn{MyIyrR&cF0&#mYKfm}23_UwP z-`w0vZ=7Tc{Kf6x^L%<~9b0J)us=_KPEJpc;PA&kk9+(3@bhc_sF|s$neF4th28V< z^%MHf9TcuV!y;l3*Vn(6j$>Ol*VZ@qf_gfpj~d1f3;K5bYu28-n35DDquKe;OI-vRh`$w6A8qvIqL)JIlMB!gOG z;*%qqw?Z0LNS%H}#w1N|p23e#4i1l=?}9%ZR$Jd>b~Er&m3 zSRi;HEGkY^%gVQEw!H&dR#}snnki!FvAezbA$?3UaTxnki(veUW6?}eX+>gEs*ri` zXJ3f3hc_j|o7mQ+Z_U#qV-to`P*gRWkLAj)gpK16Mb4Tu>i-_LwrI?!W$fBPf-+nSWRK;Xg;D}m@MGTiU4=lp^ zG-G>)JDaKt(qhAcUa2@dKXp#PUC#5Q;_+TX8|hCX;py%`*Ge>|qX6o{Onp^pUV5;$ zBK3%xiWmRb7i1CG3?k< zt$7OxiKt3hPFe@NFfyxZPohPNdCFH(9#>)8sE~PSu_{4asV?)a!|3JqU{-tvG@fh7 zvD_91><*foP=TpMZHbl|Q0Ap#EWPsDbZ^ zFT@2{IUkE-UkUyed)PnkrmGG8s5Wc}p*80NusFLXSvWo@UB#VgHnM zMXeH$I@sT-x|Nw;@RrA;9G~6F*)i;ng1NwOtxdUo$=v*uU$5A)mX4XSAaN18&B)|w z1W0XRuWEcsQG-ePXU_;6g$<=3)HnE{;ZDALw4}ZLa8V-D2x3- zq4N*P_$#!U}$1O^p+OJ4tE*bx3!QpR>*Y>#>9ZIfp3 zPg0MKhJj)HZem%%$~>-ltC5@oiri(sNFp1z!-T ziDaso0~h(~FR%gVjlr-wKhAdciF6X(4tZ220okh4aj7rt72wSJTu5~Y>3t_&eA2f2ta%;*F4FQBQHy&_KN(xZ8k% z9N*OMjAb|I*4pUJpT~mx>%ML(O`rFVdI@gDdJg(&dp@!H9SWs3f>w2FV_y@aizz_{ znHj)Z@BiY81{gy`MPH}_ng6|~s@cE!3m`OylQfeN>{EBD$LK~MLK9}EMywtvoMvKn zd*H*>(J`c+3bo-+kjpOCktoh9(!b=$;&c%E&>cCKADT4)lj4u8QLQ5$62rk}SB(=&WYc@?PH9(Mi7v8J?BL}1vM*To z%hOw8Tz#q|wyFN|Apflizps5{U<$_#FDrVPz@}R+U!l3tAc5G+B+<%@PO{~t44`M& z8WV4IAH)JO`s8!>8RpwP**?vQ5O|zsXN&1TYL^;2kT4ZS1WJ1mWy8YSB^0l%h{=#;NAsRD{W+U7pWlGYMc$jSMz`X#qvO?`A6)y4HH ztDwM^?NH=guVyzf8Fj|c2B0XiZs+j^M?N;`spKix+f{rJ3V2&q{|`mWY~qoq+qtEm zpP#C*_xPI$SmR!C{2pYVD2-HQKsZ*a%9DDPTW@w0RY0LvpEHh|uKEe?;>T-yav{=& zi2>4MFSCiwH~BnF{k_{@x}=;(@#S%~I%+hIMj~u^#jo~h>GF+28HFTaJQVL9 zFMB%j5zcEx8x~lT zs94f}YKe#u_a8cT=^fL;nPgPIOk`$@KvN@{*WPxl!wO9MDPEfK0?0fOl6tD6uZn0K zEwP+`PlL|uNdD3hb4-~F<+UMjR2$5VR4X<)Z|>?V4!MIF3#Vrth)z7P#R^(5 zqu$|wz+GNAfb`HQ-8QV_C3JMiZt$Yt!bjKk>`!^gq{6GlQz_x}M@_WbWpn%MjYB#h zVVI@}%Fev*JP2{ZG}t7f*-o8B<`s8eg7oTCSKn2?rZC>>m9QtKbS`fwYK75CE!*|l z=)2u+vuG$EJazJz#4$MJJ>p>~w^ZL9p?p%qfkEghT&Az^&-=YRZib+8rb*K%YW?4# z@BevHiwoH&lqx=3Ns%<%J?VR|KBW@a*Gyox7-in3`ilzT z!yX>EcP52=M|1|$=6Dwx@jVLi;#T1_7zu*|;6=n0a*hUrf!pH+_(gBY&W>QBm|`+B^=OtfMne(^D_t@e z3H(5t>Y-(YM_V45S{8dKU7DAqYdd$qNyHP~b=Vpa?PlpkXuM{L^uWzEj0C=Uk$MCS zT9Io@k$E*Yvt~$%XYgU7krPFri=3n|V`PvAg_*fBIUl$uZc3`~ZBI*)N$}D(rOH+pLTqk=o@dR69`$36XWZ(6`gX#fnI>kv*x>RTOk5Y+fC+1nAL$ z;RMFg3}}R`OpsV~-^{w7r_92ei2`R>MSwkN zAdol&Eu|nhxC~1x8jDSZgcJjv=;$Nm%WR_ix%;`#9H4-GQ{k%usp}DYn~z8?bl4W~QHQ3?IAvR>it=e_Zv(5^dn1Arx}Qw-7pmJ}2Et=+4lJyoU~m0Q z2Kfa@!b<_8pcltgK~M!MXHND84>#Fl%Sch|;EkB}VnRv7>mC!B=ZF%H%oO^tpqV8^ z8Lc0fIu!`a8}e29&b5UE-JC`yZq#MnKBtfgT)eT9*rWyl(Y?4h-gq6-BDHJ zvX#^G$Y83fe65n2h%e=dgO0`bBfumFP8V zWMYyO19SIWtgIC2oH#Y_020&ixBT}hB>(|~j4O;;N<{_>DIn0#N)!!l-p7H(89K2? zZ0*w;uQd2(nsDcgM6}YAiyBXW{!^zv_^f-xw6F!w*8XJ}N>IFtQd%;tgYQ!azCy)X z=?#J1s*7%?o<<8YW+?FN2qAh z3eZc`?QP|{_5ME18vhc;_bx1atUuOIuRo2kdl0rHa^vj0Qseq2k&$3;sW%9$Z@yV2 zHQ`7FHE zww319;+IkXVh4;DP&41sxv^X0%PN_HR$%@8r)g2a3^)ot``qex4!WN}9?3P9h(w9B zC^rihUr*8@WYuqWk-Zi&qkLvTkW1P#pf7+%Rd%sC?#Mh@Da}r`&?VXK)>#m)%%C0U zu4IrFG$fh|ZylKCh2}{mNJkF~(P8mU!p*NDM`IVEO_oe>YNe=VRxkt5g~f%=ID zlOqcgRDzAd(j7^wmTy3XjM!V3<&lF@+Jxqt5F$Rh-$*T%i}0#ME8EYMA=69CaLB{< zFA{}yxye8r@9mJ1zcF!MD@AY3`(Ki8s0zZY4K>o+y=Y#mMphPPaAM$~d%|lgHjxQH z8BIO&$EH1T%zzNKdhel5%yvy9sC!rm4|0%5 zS?o^puXE-S8d-xZK;z#T7HQPfR5bK;%$L!}mDfw9EGS)OQKdI75YCPzgE(#RDLd0? zQzK~QLL4JuH1*A#HXeHWY`7NLt_e)(6A&?NYN4Tv`ekx%Vtpybe0rk$qed<0o-FL0 zj<c~w;4!;N>?orNHvCni zs$*6zq805i{TTn4yL@dis_--0(3CJm%2Y|L5y*U5d`mnn?Zi`3*o~`~*GFL8O&7sb zz?=_q)ikY8k@we#ZJnwp(w-gL^wha;0%25cX~7$6qdo%0^Sfm0+?1z9tfQta1@pGu zbdgVDanLi<&>QeR8jq*6bcK>+V`T@so+px#W=vEW-fC5j=g8`Q0MiJ~yfKq8X7wn_>RRRko$6uLgk-y>o{LR+Z&T<>0 zN`AVdIIU@JxnN;ddXtE}y_-XCjwG{hu7Ej(4++D$J0XV-nv+!kpp+S&VB3de8I|+l zGE8woq7w&j{cYb}7ddvF(A9ujkG=T3!+o#=J`_!IPW0`0yv4hW1*mX+dNZff{q|;! zHBdGwK0YfxS|>$JrPjE{rwIIAjJrmqv!v}q?!!TU3}>hDO1S^L&8Ol1+0q?m+;>8q z%%u)6XVcY=)WI^-2J09J&6?TWeDWFvzNP>ciISANuPScLpS5Ol`P* zewlpwoz*%uF)zeBT~#U%hv@6&>6cKw)ccKY-3J-v#17#TUQTx#_BOZJL14eo# z9Y+{sz{jX>V(IqD_VxwHT0RgwNu6MK$c8)ZNPwlD#(S0^C}NLd3W3M`R>qxD&Fha( zT> array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard', + ), + 'products' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=products', + ), + 'tax' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=tax', + ), + 'shipping' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=shipping', + ), + 'appearance' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=appearance', + ), + ); + + /** + * WC_Admin_Dashboard_Finish_Setup constructor. + */ + public function __construct() { + if ( $this->should_display_widget() ) { + $this->populate_tasks(); + $this->hook_meta_box(); + } + } + + /** + * Hook meta_box + */ + public function hook_meta_box() { + $version = Constants::get_constant( 'WC_VERSION' ); + + wp_enqueue_style( 'wc-dashboard-finish-setup', WC()->plugin_url() . '/assets/css/dashboard-finish-setup.css', array(), $version ); + + add_meta_box( + 'wc_admin_dasbharod_finish_setup', + __( 'WooCommerce Setup', 'woocommerce' ), + array( $this, 'render_meta_box' ), + 'dashboard', + 'normal', + 'high' + ); + } + + /** + * Render meta box output. + */ + public function render_meta_box() { + $total_number_of_tasks = count( $this->tasks ); + $total_number_of_completed_tasks = count( $this->get_completed_tasks() ); + + $task = $this->get_next_task(); + if ( ! $task ) { + return; + } + + $button_link = $task['button_link']; + + // Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2). + $progress_percentage = ( $total_number_of_completed_tasks / $total_number_of_tasks ) * 100; + $circle_r = 6.5; + $circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) ); + + require_once __DIR__ . '/views/html-admin-dashboard-finish-setup.php'; + } + + /** + * Populate tasks from the database. + */ + private function populate_tasks() { + $tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() ); + foreach ( $tasks as $task ) { + if ( isset( $this->tasks[ $task ] ) ) { + $this->tasks[ $task ]['completed'] = true; + $this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] ); + } + } + } + + /** + * Return completed tasks + * + * @return array + */ + private function get_completed_tasks() { + return array_filter( + $this->tasks, + function( $task ) { + return $task['completed']; + } + ); + } + + /** + * Get the next task. + * + * @return array|null + */ + private function get_next_task() { + foreach ( $this->tasks as $task ) { + if ( false === $task['completed'] ) { + return $task; + } + } + + return null; + } + + /** + * Check to see if we should display the widget + * + * @return bool + */ + private function should_display_widget() { + return true !== get_option( 'woocommerce_task_list_complete' ) && true !== get_option( 'woocommerce_task_list_hidden' ); + } + } + +endif; + +return new WC_Admin_Dashboard_Finish_Setup(); diff --git a/includes/admin/class-wc-admin.php b/includes/admin/class-wc-admin.php index e136974d539..aad5ba39d41 100644 --- a/includes/admin/class-wc-admin.php +++ b/includes/admin/class-wc-admin.php @@ -94,6 +94,7 @@ class WC_Admin { switch ( $screen->id ) { case 'dashboard': case 'dashboard-network': + include __DIR__ . '/class-wc-admin-dashboard-finish-setup.php'; include __DIR__ . '/class-wc-admin-dashboard.php'; break; case 'options-permalink': diff --git a/includes/admin/views/html-admin-dashboard-finish-setup.php b/includes/admin/views/html-admin-dashboard-finish-setup.php new file mode 100644 index 00000000000..1310b611824 --- /dev/null +++ b/includes/admin/views/html-admin-dashboard-finish-setup.php @@ -0,0 +1,29 @@ + +
+ + + + + + Step of + + +
+
+ +
+
+ +
+
+
From bc3219e22e9e7bb7f00ef633bd025628038c6739 Mon Sep 17 00:00:00 2001 From: Moon Date: Wed, 17 Feb 2021 16:01:25 -0800 Subject: [PATCH 20/32] Add tests for the finish setup dashboard widget and clean up code --- .../class-wc-admin-dashboard-finish-setup.php | 33 +++-- .../html-admin-dashboard-finish-setup.php | 2 +- ...s-wc-admin-dashboard-finish-setup-test.php | 129 ++++++++++++++++++ 3 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php diff --git a/includes/admin/class-wc-admin-dashboard-finish-setup.php b/includes/admin/class-wc-admin-dashboard-finish-setup.php index 9b03d5d621a..61d102e9e09 100644 --- a/includes/admin/class-wc-admin-dashboard-finish-setup.php +++ b/includes/admin/class-wc-admin-dashboard-finish-setup.php @@ -51,24 +51,17 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Finish_Setup', false ) ) : * WC_Admin_Dashboard_Finish_Setup constructor. */ public function __construct() { - if ( $this->should_display_widget() ) { - $this->populate_tasks(); - $this->hook_meta_box(); - } + $this->should_display_widget() && $this->init(); } /** * Hook meta_box */ - public function hook_meta_box() { - $version = Constants::get_constant( 'WC_VERSION' ); - - wp_enqueue_style( 'wc-dashboard-finish-setup', WC()->plugin_url() . '/assets/css/dashboard-finish-setup.css', array(), $version ); - + public function init() { add_meta_box( 'wc_admin_dasbharod_finish_setup', __( 'WooCommerce Setup', 'woocommerce' ), - array( $this, 'render_meta_box' ), + array( $this, 'render' ), 'dashboard', 'normal', 'high' @@ -78,7 +71,12 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Finish_Setup', false ) ) : /** * Render meta box output. */ - public function render_meta_box() { + public function render() { + $version = Constants::get_constant( 'WC_VERSION' ); + wp_enqueue_style( 'wc-dashboard-finish-setup', WC()->plugin_url() . '/assets/css/dashboard-finish-setup.css', array(), $version ); + + $this->populate_tasks(); + $total_number_of_tasks = count( $this->tasks ); $total_number_of_completed_tasks = count( $this->get_completed_tasks() ); @@ -94,7 +92,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Finish_Setup', false ) ) : $circle_r = 6.5; $circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) ); - require_once __DIR__ . '/views/html-admin-dashboard-finish-setup.php'; + include __DIR__ . '/views/html-admin-dashboard-finish-setup.php'; } /** @@ -110,6 +108,15 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Finish_Setup', false ) ) : } } + /** + * Getter for $tasks + * + * @return array + */ + public function get_tasks() { + return $this->tasks; + } + /** * Return completed tasks * @@ -130,7 +137,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Finish_Setup', false ) ) : * @return array|null */ private function get_next_task() { - foreach ( $this->tasks as $task ) { + foreach ( $this->get_tasks() as $task ) { if ( false === $task['completed'] ) { return $task; } diff --git a/includes/admin/views/html-admin-dashboard-finish-setup.php b/includes/admin/views/html-admin-dashboard-finish-setup.php index 1310b611824..b61185d0218 100644 --- a/includes/admin/views/html-admin-dashboard-finish-setup.php +++ b/includes/admin/views/html-admin-dashboard-finish-setup.php @@ -15,7 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) { - Step of +
diff --git a/tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php b/tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php new file mode 100644 index 00000000000..21fd8dfc4c7 --- /dev/null +++ b/tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php @@ -0,0 +1,129 @@ +get_widget()->render(); + return ob_get_clean(); + } + + /** + * Tests widget does not get rendered when woocommerce_task_list_hidden or woocommerce_task_list_hidden + * is true. + * + * @dataProvider should_display_widget_data_provider + * + * @param array $options a set of options. + */ + public function test_widget_does_not_get_rendered( array $options ) { + global $wp_meta_boxes; + + foreach ( $options as $name => $value ) { + update_option( $name, $value ); + } + + $this->get_widget(); + $this->assertNull( $wp_meta_boxes ); + } + + /** + * Given both woocommerce_task_list_hidden and woocommerce_task_list_complete are false + * Then the widget should be added to the $wp_meta_boxes + */ + public function test_widget_gets_rendered_when_both_options_are_false() { + global $wp_meta_boxes; + update_option( 'woocommerce_task_list_complete', false ); + update_option( 'woocommerce_task_list_hidden', false ); + + $this->get_widget(); + $this->assertArrayHasKey( 'wc_admin_dasbharod_finish_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); + } + + /** + * Tests the widget output when 0 task has been completed. + */ + public function test_initial_widget_output() { + $html = $this->get_widget_output(); + + $required_strings = array( + 'Step 0 of 5', + 'You're almost there! Once you complete store setup you can start receiving orders.', + 'Start selling', + 'admin.php\?page=wc-admin&path=%2Fsetup-wizard', + ); + + foreach ( $required_strings as $required_string ) { + $this->assertRegexp( "/${required_string}/", $html ); + } + } + + /** + * Tests completed task count as it completes one by one + */ + public function test_widget_renders_completed_task_count() { + $completed_tasks = array(); + $tasks = $this->get_widget()->get_tasks(); + $tasks_count = count( $tasks ); + foreach ( $tasks as $key => $task ) { + array_push( $completed_tasks, $key ); + update_option( 'woocommerce_task_list_tracked_completed_tasks', $completed_tasks ); + $completed_tasks_count = count( $completed_tasks ); + // When all tasks are completed, assert that the widget output is empty. + // As widget won't be rendered when tasks are completed. + if ( $completed_tasks_count === $tasks_count ) { + $this->assertEmpty( $this->get_widget_output() ); + } else { + $this->assertRegexp( "/Step ${completed_tasks_count} of 5/", $this->get_widget_output() ); + } + } + } + + + /** + * Provides dataset that controls output of `should_display_widget` + */ + public function should_display_widget_data_provider() { + // these dataset should not render the widget + // we only want to render the widget when both options are set to false. + return array( + array( + array( + 'woocommerce_task_list_complete' => true, + 'woocommerce_task_list_hidden' => false, + ), + ), + array( + array( + 'woocommerce_task_list_complete' => false, + 'woocommerce_task_list_hidden' => true, + ), + ), + ); + } +} From 781c5e24d734d4f89bb83004806fe1a33184cf7a Mon Sep 17 00:00:00 2001 From: Moon Date: Wed, 17 Feb 2021 16:10:55 -0800 Subject: [PATCH 21/32] Status dashboard widget should be rendered only if onboarding tasks have been completed or hidden --- ...finish-setup.scss => dashboard-setup.scss} | 17 +- ...h-setup.png => dashboard-widget-setup.png} | Bin .../class-wc-admin-dashboard-finish-setup.php | 161 ------------- .../admin/class-wc-admin-dashboard-setup.php | 218 ++++++++++++++++++ includes/admin/class-wc-admin-dashboard.php | 11 +- includes/admin/class-wc-admin.php | 2 +- ...tup.php => html-admin-dashboard-setup.php} | 4 +- ...> class-wc-admin-dashboard-setup-test.php} | 35 +-- 8 files changed, 257 insertions(+), 191 deletions(-) rename assets/css/{dashboard-finish-setup.scss => dashboard-setup.scss} (78%) rename assets/images/{dashboard-widget-finish-setup.png => dashboard-widget-setup.png} (100%) delete mode 100644 includes/admin/class-wc-admin-dashboard-finish-setup.php create mode 100644 includes/admin/class-wc-admin-dashboard-setup.php rename includes/admin/views/{html-admin-dashboard-finish-setup.php => html-admin-dashboard-setup.php} (84%) rename tests/php/includes/admin/{class-wc-admin-dashboard-finish-setup-test.php => class-wc-admin-dashboard-setup-test.php} (77%) diff --git a/assets/css/dashboard-finish-setup.scss b/assets/css/dashboard-setup.scss similarity index 78% rename from assets/css/dashboard-finish-setup.scss rename to assets/css/dashboard-setup.scss index 223f66665bd..5a2d0fe6e37 100644 --- a/assets/css/dashboard-finish-setup.scss +++ b/assets/css/dashboard-setup.scss @@ -1,16 +1,9 @@ /** - * dashboard-finish-setup.scss + * dashboard-setup.scss * Styles for WooCommerce dashboard finish setup widgets * only loaded on the dashboard itself. */ -/** - * Imports - */ -@import "mixins"; -@import "variables"; -@import "fonts"; - /** * Styling begins */ @@ -19,9 +12,9 @@ .progress-wrapper { border: 1px solid #757575; - border-radius: 10px; + border-radius: 20px; font-size: 0.9em; - padding: 2px 8px 2px 5px; + padding: 2px 10px 2px 7px; display: inline-block; } @@ -44,12 +37,12 @@ .circle-progress { circle { - stroke: #f0f0f0; + stroke: #949494; stroke-width: 1px; } .bar { - stroke: #949494; + stroke: #f0f0f0; } } } diff --git a/assets/images/dashboard-widget-finish-setup.png b/assets/images/dashboard-widget-setup.png similarity index 100% rename from assets/images/dashboard-widget-finish-setup.png rename to assets/images/dashboard-widget-setup.png diff --git a/includes/admin/class-wc-admin-dashboard-finish-setup.php b/includes/admin/class-wc-admin-dashboard-finish-setup.php deleted file mode 100644 index 61d102e9e09..00000000000 --- a/includes/admin/class-wc-admin-dashboard-finish-setup.php +++ /dev/null @@ -1,161 +0,0 @@ - array( - 'completed' => false, - 'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard', - ), - 'products' => array( - 'completed' => false, - 'button_link' => 'admin.php?page=wc-admin&task=products', - ), - 'tax' => array( - 'completed' => false, - 'button_link' => 'admin.php?page=wc-admin&task=tax', - ), - 'shipping' => array( - 'completed' => false, - 'button_link' => 'admin.php?page=wc-admin&task=shipping', - ), - 'appearance' => array( - 'completed' => false, - 'button_link' => 'admin.php?page=wc-admin&task=appearance', - ), - ); - - /** - * WC_Admin_Dashboard_Finish_Setup constructor. - */ - public function __construct() { - $this->should_display_widget() && $this->init(); - } - - /** - * Hook meta_box - */ - public function init() { - add_meta_box( - 'wc_admin_dasbharod_finish_setup', - __( 'WooCommerce Setup', 'woocommerce' ), - array( $this, 'render' ), - 'dashboard', - 'normal', - 'high' - ); - } - - /** - * Render meta box output. - */ - public function render() { - $version = Constants::get_constant( 'WC_VERSION' ); - wp_enqueue_style( 'wc-dashboard-finish-setup', WC()->plugin_url() . '/assets/css/dashboard-finish-setup.css', array(), $version ); - - $this->populate_tasks(); - - $total_number_of_tasks = count( $this->tasks ); - $total_number_of_completed_tasks = count( $this->get_completed_tasks() ); - - $task = $this->get_next_task(); - if ( ! $task ) { - return; - } - - $button_link = $task['button_link']; - - // Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2). - $progress_percentage = ( $total_number_of_completed_tasks / $total_number_of_tasks ) * 100; - $circle_r = 6.5; - $circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) ); - - include __DIR__ . '/views/html-admin-dashboard-finish-setup.php'; - } - - /** - * Populate tasks from the database. - */ - private function populate_tasks() { - $tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() ); - foreach ( $tasks as $task ) { - if ( isset( $this->tasks[ $task ] ) ) { - $this->tasks[ $task ]['completed'] = true; - $this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] ); - } - } - } - - /** - * Getter for $tasks - * - * @return array - */ - public function get_tasks() { - return $this->tasks; - } - - /** - * Return completed tasks - * - * @return array - */ - private function get_completed_tasks() { - return array_filter( - $this->tasks, - function( $task ) { - return $task['completed']; - } - ); - } - - /** - * Get the next task. - * - * @return array|null - */ - private function get_next_task() { - foreach ( $this->get_tasks() as $task ) { - if ( false === $task['completed'] ) { - return $task; - } - } - - return null; - } - - /** - * Check to see if we should display the widget - * - * @return bool - */ - private function should_display_widget() { - return true !== get_option( 'woocommerce_task_list_complete' ) && true !== get_option( 'woocommerce_task_list_hidden' ); - } - } - -endif; - -return new WC_Admin_Dashboard_Finish_Setup(); diff --git a/includes/admin/class-wc-admin-dashboard-setup.php b/includes/admin/class-wc-admin-dashboard-setup.php new file mode 100644 index 00000000000..6806b1de5b1 --- /dev/null +++ b/includes/admin/class-wc-admin-dashboard-setup.php @@ -0,0 +1,218 @@ + array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard', + ), + 'products' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=products', + ), + 'woocommerce-payments' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&path=%2Fpayments%2Fconnect', + ), + 'payments' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=payments', + ), + 'tax' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=tax', + ), + 'shipping' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=shipping', + ), + 'appearance' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=appearance', + ), + ); + + /** + * # of completed tasks. + * + * @var int + */ + private $completed_tasks_count = 0; + + /** + * WC_Admin_Dashboard_Setup constructor. + */ + public function __construct() { + if ( $this->should_display_widget() ) { + $this->populate_general_tasks(); + $this->populate_payment_tasks(); + $this->completed_tasks_count = $this->get_completed_tasks_count(); + $this->init(); + } + } + + /** + * Hook meta_box + */ + public function init() { + add_meta_box( + 'wc_admin_dasbharod_setup', + __( 'WooCommerce Setup', 'woocommerce' ), + array( $this, 'render' ), + 'dashboard', + 'normal', + 'high' + ); + } + + /** + * Render meta box output. + */ + public function render() { + $version = Constants::get_constant( 'WC_VERSION' ); + wp_enqueue_style( 'wc-dashboard-setup', WC()->plugin_url() . '/assets/css/dashboard-setup.css', array(), $version ); + + $task = $this->get_next_task(); + if ( ! $task ) { + return; + } + + $button_link = $task['button_link']; + $completed_tasks_count = $this->completed_tasks_count; + $tasks_count = count( $this->tasks ); + + // Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2). + $progress_percentage = ( $completed_tasks_count / $tasks_count ) * 100; + $circle_r = 6.5; + $circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) ); + + include __DIR__ . '/views/html-admin-dashboard-setup.php'; + } + + /** + * Populate tasks from the database. + */ + private function populate_general_tasks() { + $tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() ); + foreach ( $tasks as $task ) { + if ( isset( $this->tasks[ $task ] ) ) { + $this->tasks[ $task ]['completed'] = true; + $this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] ); + } + } + } + + /** + * Getter for $tasks + * + * @return array + */ + public function get_tasks() { + return $this->tasks; + } + + /** + * Return # of completed tasks + */ + public function get_completed_tasks_count() { + $completed_tasks = array_filter( + $this->tasks, + function( $task ) { + return $task['completed']; + } + ); + + return count( $completed_tasks ); + } + + /** + * Get the next task. + * + * @return array|null + */ + private function get_next_task() { + foreach ( $this->get_tasks() as $task ) { + if ( false === $task['completed'] ) { + return $task; + } + } + + return null; + } + + /** + * Check to see if we should display the widget + * + * @return bool + */ + private function should_display_widget() { + return 'yes' !== get_option( 'woocommerce_task_list_complete' ) && 'yes' !== get_option( 'woocommerce_task_list_hidden' ); + } + + /** + * Populate payment tasks's visibility and completion + */ + private function populate_payment_tasks() { + $is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' ); + $country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0]; + + // woocommerce-payments requires its plugin activated and country must be US. + if ( ! $is_woo_payment_installed || 'US' !== $country ) { + unset( $this->tasks['woocommerce-payments'] ); + } + + // payments can't be used when woocommerce-payments exists and country is US. + if ( $is_woo_payment_installed || 'US' === $country ) { + unset( $this->tasks['payments'] ); + } + + if ( isset( $this->tasks['payments'] ) ) { + $gateways = WC()->payment_gateways->get_available_payment_gateways(); + $enabled_gateways = array_filter( + $gateways, + function ( $gateway ) { + return 'yes' === $gateway->enabled; + } + ); + $this->tasks['payments']['completed'] = ! empty( $enabled_gateways ); + } + + if ( isset( $this->tasks['woocommerce-payments'] ) ) { + $wc_pay_is_connected = false; + if ( class_exists( '\WC_Payments' ) ) { + $wc_payments_gateway = \WC_Payments::get_gateway(); + $wc_pay_is_connected = method_exists( $wc_payments_gateway, 'is_connected' ) + ? $wc_payments_gateway->is_connected() + : false; + } + $this->tasks['woocommerce-payments']['completed'] = $wc_pay_is_connected; + } + } + } + +endif; + +return new WC_Admin_Dashboard_Setup(); diff --git a/includes/admin/class-wc-admin-dashboard.php b/includes/admin/class-wc-admin-dashboard.php index 6f3435fb27f..62fdefb0d0b 100644 --- a/includes/admin/class-wc-admin-dashboard.php +++ b/includes/admin/class-wc-admin-dashboard.php @@ -24,7 +24,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : */ public function __construct() { // Only hook in admin parts if the user has admin access. - if ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) { + if ( $this->should_display_widget() && ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) ) { // If on network admin, only load the widget that works in that context and skip the rest. if ( is_multisite() && is_network_admin() ) { add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) ); @@ -57,6 +57,15 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : wp_add_dashboard_widget( 'woocommerce_network_orders', __( 'WooCommerce Network Orders', 'woocommerce' ), array( $this, 'network_orders' ) ); } + /** + * Check to see if we should display the widget. + * + * @return bool + */ + private function should_display_widget() { + return 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' ); + } + /** * Get top seller from DB. * diff --git a/includes/admin/class-wc-admin.php b/includes/admin/class-wc-admin.php index aad5ba39d41..0c2acf037f0 100644 --- a/includes/admin/class-wc-admin.php +++ b/includes/admin/class-wc-admin.php @@ -94,7 +94,7 @@ class WC_Admin { switch ( $screen->id ) { case 'dashboard': case 'dashboard-network': - include __DIR__ . '/class-wc-admin-dashboard-finish-setup.php'; + include __DIR__ . '/class-wc-admin-dashboard-setup.php'; include __DIR__ . '/class-wc-admin-dashboard.php'; break; case 'options-permalink': diff --git a/includes/admin/views/html-admin-dashboard-finish-setup.php b/includes/admin/views/html-admin-dashboard-setup.php similarity index 84% rename from includes/admin/views/html-admin-dashboard-finish-setup.php rename to includes/admin/views/html-admin-dashboard-setup.php index b61185d0218..62a2cdc5fa2 100644 --- a/includes/admin/views/html-admin-dashboard-finish-setup.php +++ b/includes/admin/views/html-admin-dashboard-setup.php @@ -15,7 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) { - +
@@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
- +
diff --git a/tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php similarity index 77% rename from tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php rename to tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php index 21fd8dfc4c7..878e938545a 100644 --- a/tests/php/includes/admin/class-wc-admin-dashboard-finish-setup-test.php +++ b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php @@ -1,22 +1,32 @@ get_widget()->render(); @@ -62,7 +71,7 @@ class WC_Admin_Dashboard_Finish_Setup_Test extends WC_Unit_Test_Case { update_option( 'woocommerce_task_list_hidden', false ); $this->get_widget(); - $this->assertArrayHasKey( 'wc_admin_dasbharod_finish_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); + $this->assertArrayHasKey( 'wc_admin_dasbharod_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); } /** @@ -109,19 +118,17 @@ class WC_Admin_Dashboard_Finish_Setup_Test extends WC_Unit_Test_Case { * Provides dataset that controls output of `should_display_widget` */ public function should_display_widget_data_provider() { - // these dataset should not render the widget - // we only want to render the widget when both options are set to false. return array( array( array( - 'woocommerce_task_list_complete' => true, - 'woocommerce_task_list_hidden' => false, + 'woocommerce_task_list_complete' => 'yes', + 'woocommerce_task_list_hidden' => 'no', ), ), array( array( - 'woocommerce_task_list_complete' => false, - 'woocommerce_task_list_hidden' => true, + 'woocommerce_task_list_complete' => 'no', + 'woocommerce_task_list_hidden' => 'yes', ), ), ); From 5c992339056116c74955218f782e1862598cee25 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 22 Feb 2021 19:46:31 -0800 Subject: [PATCH 22/32] Minor fixes * Removed extra double quotation from the image tag * Fixed typo in the init() method * Moved init() method body into the constructor -- a separate method is not necessary --- assets/css/dashboard-setup.scss | 14 +++++++---- .../admin/class-wc-admin-dashboard-setup.php | 23 +++++++------------ .../views/html-admin-dashboard-setup.php | 2 +- .../class-wc-admin-dashboard-setup-test.php | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/assets/css/dashboard-setup.scss b/assets/css/dashboard-setup.scss index 5a2d0fe6e37..cee2344428e 100644 --- a/assets/css/dashboard-setup.scss +++ b/assets/css/dashboard-setup.scss @@ -12,15 +12,17 @@ .progress-wrapper { border: 1px solid #757575; - border-radius: 20px; + border-radius: 16px; font-size: 0.9em; - padding: 2px 10px 2px 7px; + padding: 2px 8px 2px 8px; display: inline-block; + box-sizing: border-box; } .progress-wrapper span { position: relative; - top: -2px; + top: -3px; + color: #757575; } .description div { @@ -35,14 +37,16 @@ } .circle-progress { + margin-top: 1px; + margin-left: -3px; circle { - stroke: #949494; + stroke: #f0f0f0; stroke-width: 1px; } .bar { - stroke: #f0f0f0; + stroke: #949494; } } } diff --git a/includes/admin/class-wc-admin-dashboard-setup.php b/includes/admin/class-wc-admin-dashboard-setup.php index 6806b1de5b1..8fdbc1e94de 100644 --- a/includes/admin/class-wc-admin-dashboard-setup.php +++ b/includes/admin/class-wc-admin-dashboard-setup.php @@ -70,24 +70,17 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) : $this->populate_general_tasks(); $this->populate_payment_tasks(); $this->completed_tasks_count = $this->get_completed_tasks_count(); - $this->init(); + add_meta_box( + 'wc_admin_dashboard_setup', + __( 'WooCommerce Setup', 'woocommerce' ), + array( $this, 'render' ), + 'dashboard', + 'normal', + 'high' + ); } } - /** - * Hook meta_box - */ - public function init() { - add_meta_box( - 'wc_admin_dasbharod_setup', - __( 'WooCommerce Setup', 'woocommerce' ), - array( $this, 'render' ), - 'dashboard', - 'normal', - 'high' - ); - } - /** * Render meta box output. */ diff --git a/includes/admin/views/html-admin-dashboard-setup.php b/includes/admin/views/html-admin-dashboard-setup.php index 62a2cdc5fa2..ddc7b6b85f9 100644 --- a/includes/admin/views/html-admin-dashboard-setup.php +++ b/includes/admin/views/html-admin-dashboard-setup.php @@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
- +
diff --git a/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php index 878e938545a..82ebf87cc05 100644 --- a/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php +++ b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php @@ -71,7 +71,7 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case { update_option( 'woocommerce_task_list_hidden', false ); $this->get_widget(); - $this->assertArrayHasKey( 'wc_admin_dasbharod_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); + $this->assertArrayHasKey( 'wc_admin_dashboard_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); } /** From d013d7f7659245960e051bcb4138bf36b210e2ec Mon Sep 17 00:00:00 2001 From: Moon Date: Tue, 2 Mar 2021 16:06:10 -0800 Subject: [PATCH 23/32] Move init logic to should_display_widget --- includes/admin/class-wc-admin-dashboard.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/admin/class-wc-admin-dashboard.php b/includes/admin/class-wc-admin-dashboard.php index 62fdefb0d0b..4c5a9dd9143 100644 --- a/includes/admin/class-wc-admin-dashboard.php +++ b/includes/admin/class-wc-admin-dashboard.php @@ -24,7 +24,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : */ public function __construct() { // Only hook in admin parts if the user has admin access. - if ( $this->should_display_widget() && ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) ) { + if ( $this->should_display_widget() ) { // If on network admin, only load the widget that works in that context and skip the rest. if ( is_multisite() && is_network_admin() ) { add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) ); @@ -63,7 +63,9 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : * @return bool */ private function should_display_widget() { - return 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' ); + $has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ); + $task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' ); + return $task_completed_or_hidden && $has_permission; } /** From 3b24423b0ce538c3da9202ab289f7b45fc86b093 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 3 Mar 2021 14:57:10 +0100 Subject: [PATCH 24/32] Update removeCoupon to include coupon name --- .../shopper/front-end-cart-coupons.test.js | 22 +++++++++---------- .../front-end-checkout-coupons.test.js | 22 +++++++++---------- tests/e2e/utils/src/page-utils.js | 5 +++-- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index e79cbded1e1..b2b3f4888a3 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -41,42 +41,42 @@ const runCartApplyCouponsTest = () => { }); it('allows customer to apply fixed cart coupon', async () => { - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCoupon(); + await removeCoupon(couponFixedCart); }); it('allows customer to apply percentage coupon', async () => { - await applyCoupon( couponPercentage ); + await applyCoupon(couponPercentage); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); - await removeCoupon(); + await removeCoupon(couponPercentage); }); it('allows customer to apply fixed product coupon', async () => { - await applyCoupon( couponFixedProduct ); + await applyCoupon(couponFixedProduct); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCoupon(); + await removeCoupon(couponFixedProduct); }); it('prevents customer applying same coupon twice', async () => { - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); // Verify only one discount applied // This is a work around for Puppeteer inconsistently finding 'Coupon code already applied' await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); @@ -84,7 +84,7 @@ const runCartApplyCouponsTest = () => { }); it('allows customer to apply multiple coupons', async () => { - await applyCoupon( couponFixedProduct ); + await applyCoupon(couponFixedProduct); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total @@ -93,8 +93,8 @@ const runCartApplyCouponsTest = () => { }); it('restores cart total when coupons are removed', async () => { - await removeCoupon(); - await removeCoupon(); + await removeCoupon(couponFixedCart); + await removeCoupon(couponFixedProduct); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); }); }); diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index bcf8f7e20e9..e017c6d1739 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -41,7 +41,7 @@ const runCheckoutApplyCouponsTest = () => { }); it('allows customer to apply fixed cart coupon', async () => { - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Wait for page to expand total calculations to avoid flakyness @@ -50,31 +50,31 @@ const runCheckoutApplyCouponsTest = () => { // Verify discount applied and order total await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCoupon(); + await removeCoupon(couponFixedCart); }); it('allows customer to apply percentage coupon', async () => { - await applyCoupon( couponPercentage ); + await applyCoupon(couponPercentage); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Verify discount applied and order total await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); - await removeCoupon(); + await removeCoupon(couponPercentage); }); it('allows customer to apply fixed product coupon', async () => { - await applyCoupon( couponFixedProduct ); + await applyCoupon(couponFixedProduct); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); - await removeCoupon(); + await removeCoupon(couponFixedProduct); }); it('prevents customer applying same coupon twice', async () => { - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await applyCoupon( couponFixedCart ); + await applyCoupon(couponFixedCart); // Verify only one discount applied // This is a work around for Puppeteer inconsistently finding 'Coupon code already applied' await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); @@ -82,14 +82,14 @@ const runCheckoutApplyCouponsTest = () => { }); it('allows customer to apply multiple coupons', async () => { - await applyCoupon( couponFixedProduct ); + await applyCoupon(couponFixedProduct); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'}); }); it('restores cart total when coupons are removed', async () => { - await removeCoupon(); - await removeCoupon(); + await removeCoupon(couponFixedCart); + await removeCoupon(couponFixedProduct); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); }); }); diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 1b209d5ed0c..9ab8c7b3aa5 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -233,10 +233,11 @@ const applyCoupon = async ( couponCode ) => { /** * Remove one coupon within cart or checkout. * + * @param couponCode Coupon name. * @returns {Promise} */ -const removeCoupon = async () => { - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); +const removeCoupon = async ( couponCode ) => { + await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'}); await uiUnblocked(); await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); }; From afac2aefab4c3c84c49994a9ace465f32e37ff86 Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 3 Mar 2021 09:45:16 -0700 Subject: [PATCH 25/32] Added etests around merchant email flows --- tests/e2e/core-tests/specs/index.js | 3 + .../merchant/wp-admin-order-emails.test.js | 73 +++++++++++++++++++ tests/e2e/docker/initialize.sh | 3 + tests/e2e/specs/wp-admin/test-order-emails.js | 6 ++ tests/e2e/utils/src/components.js | 38 +++++++++- tests/e2e/utils/src/flows/merchant.js | 6 ++ tests/e2e/utils/src/page-utils.js | 16 ++++ 7 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js create mode 100644 tests/e2e/specs/wp-admin/test-order-emails.js diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 19d00828394..e793947a483 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -31,6 +31,7 @@ const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon const runProductEditDetailsTest = require( './merchant/wp-admin-product-edit-details.test' ); const runProductSearchTest = require( './merchant/wp-admin-product-search.test' ); const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' ); +const runMerchantOrderEmailsTest = require( './merchant/wp-admin-order-emails.test' ); // REST API tests const runExternalProductAPITest = require( './api/external-product.test' ); @@ -69,6 +70,7 @@ const runMerchantTests = () => { runProductEditDetailsTest(); runProductSearchTest(); runMerchantOrdersCustomerPaymentPage(); + runMerchantOrderEmailsTest(); } const runApiTests = () => { @@ -107,6 +109,7 @@ module.exports = { runProductEditDetailsTest, runProductSearchTest, runMerchantOrdersCustomerPaymentPage, + runMerchantOrderEmailsTest, runMerchantTests, runApiTests, }; diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js new file mode 100644 index 00000000000..d2d29076808 --- /dev/null +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js @@ -0,0 +1,73 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/no-standalone-expect */ +/** + * Internal dependencies + */ +const { + merchant, + updateOrder, + createSimpleOrder, + selectOrderAction, + deleteAllEmailLogs, +} = require( '@woocommerce/e2e-utils' ); + +const config = require( 'config' ); +const simpleProductName = config.get( 'products.simple.name' ); +const customerEmail = config.get( 'addresses.customer.billing.email' ); +const adminEmail = 'admin@woocommercecoree2etestsuite.com'; +const storeName = 'WooCommerce Core E2E Test Suite'; + +let orderId; + +const runMerchantOrderEmailsTest = () => { + + describe('Merchant > Order Action emails received', () => { + beforeAll( async () => { + await merchant.login(); + + // Clear out the existing email logs if any + await deleteAllEmailLogs(); + + orderId = await createSimpleOrder( 'Processing' ); + + await Promise.all( [ + // Select the billing email address field and add the customer billing email from the config + await page.click( 'div.order_data_column:nth-child(2) > h3:nth-child(1) > a:nth-child(1)' ), + await expect( page ).toFill( '#_billing_email', customerEmail ), + await updateOrder( 'Order updated.' ), + ] ); + } ); + + afterEach( async () => { + // Clear out any emails after each test + await deleteAllEmailLogs(); + } ); + + // New order emails are sent automatically when we create the simple order above, so let's verify we get these emails + it('can receive new order email', async () => { + await merchant.openEmailLog(); + await expect( page ).toMatchElement( '.column-receiver', { text: adminEmail } ); + await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } ); + } ); + + it('can resend new order notification', async () => { + await merchant.goToOrder( orderId ); + await selectOrderAction( 'send_order_details_admin' ); + + await merchant.openEmailLog(); + await expect( page ).toMatchElement( '.column-receiver', { text: adminEmail } ); + await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } ); + } ); + + it('can email invoice/order details to customer', async () => { + await merchant.goToOrder( orderId ); + await selectOrderAction( 'send_order_details' ); + + await merchant.openEmailLog(); + await expect( page ).toMatchElement( '.column-receiver', { text: customerEmail } ); + await expect( page ).toMatchElement( '.column-subject', { text: `Invoice for order #${orderId} on ${storeName}` } ); + } ); + + } ); +} + +module.exports = runMerchantOrderEmailsTest; diff --git a/tests/e2e/docker/initialize.sh b/tests/e2e/docker/initialize.sh index c5cbea47181..0a9592f5379 100755 --- a/tests/e2e/docker/initialize.sh +++ b/tests/e2e/docker/initialize.sh @@ -8,3 +8,6 @@ wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=pas # we cannot create API keys for the API, so we using basic auth, this plugin allows that. wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate + +# install the WP Mail Logging plugin to test emails +wp plugin install wp-mail-logging --activate diff --git a/tests/e2e/specs/wp-admin/test-order-emails.js b/tests/e2e/specs/wp-admin/test-order-emails.js new file mode 100644 index 00000000000..e0597074bda --- /dev/null +++ b/tests/e2e/specs/wp-admin/test-order-emails.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runMerchantOrderEmailsTest } = require( '@woocommerce/e2e-core-tests' ); + +runMerchantOrderEmailsTest(); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 04a7b4a3e02..dbc96765ac1 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -6,7 +6,7 @@ * Internal dependencies */ import { merchant } from './flows'; -import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick, selectOptionInSelect2 } from './page-utils'; +import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick, selectOptionInSelect2, setCheckbox } from './page-utils'; import factories from './factories'; const config = require( 'config' ); @@ -435,6 +435,40 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc return couponCode; }; +/** + * Update an order. + * + * @param noticeText The text that appears in the notice after updating the order. + */ +const updateOrder = async ( noticeText ) => { + // Wait for auto save + await page.waitFor( 2000 ); + + // PUpdate order + await expect( page ).toClick( 'button.save_order' ); + await page.waitForSelector( '.updated.notice' ); + + // Verify + await expect( page ).toMatchElement( '.updated.notice', { text: noticeText } ); +}; + +/** + * Delete all email logs in the WP Mail Logging plugin page. + */ +const deleteAllEmailLogs = async () => { + await merchant.openEmailLog(); + + // Make sure we have emails to delete. If we don't, this selector will return null. + if ( await page.$( '#bulk-action-selector-top' ) !== null ) { + await setCheckbox( '#cb-select-all-1' ); + await expect( page ).toSelect( '#bulk-action-selector-top', 'Delete' ); + await Promise.all( [ + page.click( '#doaction' ), + page.waitForNavigation( { waitUntil: 'networkidle0' } ), + ] ); + } +}; + export { completeOnboardingWizard, createSimpleProduct, @@ -444,4 +478,6 @@ export { verifyAndPublish, addProductToOrder, createCoupon, + updateOrder, + deleteAllEmailLogs, }; diff --git a/tests/e2e/utils/src/flows/merchant.js b/tests/e2e/utils/src/flows/merchant.js index df4fae99d68..242a42c171d 100644 --- a/tests/e2e/utils/src/flows/merchant.js +++ b/tests/e2e/utils/src/flows/merchant.js @@ -169,6 +169,12 @@ const merchant = { await expect( page ).toMatchElement( 'label[for="customer_user"] a[href*=user-edit]', { text: 'Profile' } ); } }, + + openEmailLog: async () => { + await page.goto( `${baseUrl}wp-admin/tools.php?page=wpml_plugin_log`, { + waitUntil: 'networkidle0', + } ); + } }; module.exports = merchant; diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 7679c15f2b7..c96ab2f6010 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -209,6 +209,21 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f await page.keyboard.press('Enter'); }; +/** + * + * Select and perform an order action in the `Order actions` postbox. + * + * @param {string} action The action to take on the order. + */ +const selectOrderAction = async ( action ) => { + await page.select( 'select[name=wc_order_action]', action ); + await Promise.all( [ + page.click( '.wc-reload' ), + page.waitForNavigation( { waitUntil: 'networkidle0' } ), + ] ); +} + + export { clearAndFillInput, clickTab, @@ -225,4 +240,5 @@ export { moveAllItemsToTrash, evalAndClick, selectOptionInSelect2, + selectOrderAction, }; From 4610d5cf162734662e2244f018e00cbadcb4bf8d Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 3 Mar 2021 09:57:11 -0700 Subject: [PATCH 26/32] Updated READMEs and CHANGELOGs --- tests/e2e/core-tests/CHANGELOG.md | 1 + tests/e2e/core-tests/README.md | 1 + tests/e2e/env/CHANGELOG.md | 1 + tests/e2e/utils/CHANGELOG.md | 4 ++++ tests/e2e/utils/README.md | 4 +++- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 9923b9a3111..348fb4c4371 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -16,6 +16,7 @@ - Merchant Orders Customer Checkout Page - Shopper Cart Apply Coupon - Shopper Variable product info updates on different variations +- Merchant order emails flow ## Fixed diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index cb16278e222..d744426dbca 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -58,6 +58,7 @@ The functions to access the core tests are: - `runProductEditDetailsTest` - Merchant can edit an existing product - `runProductSearchTest` - Merchant can search for a product and view it - `runMerchantOrdersCustomerPaymentPage` - Merchant can visit the customer payment page + - `runMerchantOrderEmailsTest` - Merchant can receive order emails and resend emails by Order Actions ### Shopper diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md index 94be2bfd1f8..8e6272ba44b 100644 --- a/tests/e2e/env/CHANGELOG.md +++ b/tests/e2e/env/CHANGELOG.md @@ -11,6 +11,7 @@ - support for custom container name - Insert a 12 hour delay in using new docker image tags - Package `bin` script `wc-e2e` +- WP Mail Log plugin as part of container initialization ## Fixed diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 3bb027f92f7..2457b00d649 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -17,6 +17,10 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field +- `selectOrderAction( action )` util helper method to selcct and initiate an order action in the Order Action postbox +- `merchant.openEmailLog()` go to the WP Mail Log page +- `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin +- `updateOrder()` util helper that clicks the `Update` button on an order ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 12f814aa992..540e2f8591d 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -54,6 +54,7 @@ describe( 'Cart page', () => { | `openSettings` | | Go to WooCommerce -> Settings | | `runSetupWizard` | | Open the onboarding profiler | | `updateOrderStatus` | `orderId, status` | Update the status of an order | +| `openEmailLog` | | Open the WP Mail Log page | ### Shopper `shopper` @@ -101,7 +102,8 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `verifyAndPublish` | `noticeText` | Verify that an item can be published | -| `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside +| `selectOptionInSelect2` | `selector, value` | helper method that searches for select2 type fields and select plus insert value inside | +| `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox | ### Test Utilities From d6b2a6caddc662f88e7410510b42e8cc5c272044 Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 3 Mar 2021 10:01:31 -0700 Subject: [PATCH 27/32] Fix minor type in changelog --- tests/e2e/utils/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 2457b00d649..77ce1b1a3f7 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -17,7 +17,7 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field -- `selectOrderAction( action )` util helper method to selcct and initiate an order action in the Order Action postbox +- `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox - `merchant.openEmailLog()` go to the WP Mail Log page - `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin - `updateOrder()` util helper that clicks the `Update` button on an order From c960edb786f577ad8b0e66308d24fce14f54c8f5 Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 3 Mar 2021 10:49:06 -0700 Subject: [PATCH 28/32] Code review feedback --- .../core-tests/specs/merchant/wp-admin-order-emails.test.js | 4 ++-- tests/e2e/utils/CHANGELOG.md | 2 +- tests/e2e/utils/README.md | 1 + tests/e2e/utils/src/components.js | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js index d2d29076808..ad18a8de6d1 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-emails.test.js @@ -4,7 +4,7 @@ */ const { merchant, - updateOrder, + clickUpdateOrder, createSimpleOrder, selectOrderAction, deleteAllEmailLogs, @@ -33,7 +33,7 @@ const runMerchantOrderEmailsTest = () => { // Select the billing email address field and add the customer billing email from the config await page.click( 'div.order_data_column:nth-child(2) > h3:nth-child(1) > a:nth-child(1)' ), await expect( page ).toFill( '#_billing_email', customerEmail ), - await updateOrder( 'Order updated.' ), + await clickUpdateOrder( 'Order updated.' ), ] ); } ); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 77ce1b1a3f7..70d1b5e95c4 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -20,7 +20,7 @@ - `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox - `merchant.openEmailLog()` go to the WP Mail Log page - `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin -- `updateOrder()` util helper that clicks the `Update` button on an order +- `clickUpdateOrder()` util helper that clicks the `Update` button on an order ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 540e2f8591d..ec21657a7e8 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -104,6 +104,7 @@ describe( 'Cart page', () => { | `verifyAndPublish` | `noticeText` | Verify that an item can be published | | `selectOptionInSelect2` | `selector, value` | helper method that searches for select2 type fields and select plus insert value inside | | `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox | +| `clickUpdateOrder` | | Helper method to click the Update button on the order details page ### Test Utilities diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index dbc96765ac1..57dff726b28 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -436,11 +436,11 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc }; /** - * Update an order. + * Click the Update button on the order details page. * * @param noticeText The text that appears in the notice after updating the order. */ -const updateOrder = async ( noticeText ) => { +const clickUpdateOrder = async ( noticeText ) => { // Wait for auto save await page.waitFor( 2000 ); @@ -478,6 +478,6 @@ export { verifyAndPublish, addProductToOrder, createCoupon, - updateOrder, + clickUpdateOrder, deleteAllEmailLogs, }; From be47060e86e7a403582e4d4022c389519e8d018f Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 3 Mar 2021 10:52:50 -0700 Subject: [PATCH 29/32] Add flag for clicking to update an order --- tests/e2e/utils/CHANGELOG.md | 2 +- tests/e2e/utils/README.md | 2 +- tests/e2e/utils/src/components.js | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 70d1b5e95c4..09af6a5f851 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -20,7 +20,7 @@ - `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox - `merchant.openEmailLog()` go to the WP Mail Log page - `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin -- `clickUpdateOrder()` util helper that clicks the `Update` button on an order +- `clickUpdateOrder( noticeText, waitForSave )` util helper that clicks the `Update` button on an order ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index ec21657a7e8..7871a416ea8 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -104,7 +104,7 @@ describe( 'Cart page', () => { | `verifyAndPublish` | `noticeText` | Verify that an item can be published | | `selectOptionInSelect2` | `selector, value` | helper method that searches for select2 type fields and select plus insert value inside | | `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox | -| `clickUpdateOrder` | | Helper method to click the Update button on the order details page +| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page ### Test Utilities diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 57dff726b28..8076ad0bd70 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -439,10 +439,12 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc * Click the Update button on the order details page. * * @param noticeText The text that appears in the notice after updating the order. + * @param waitForSave Optionally wait for auto save. */ -const clickUpdateOrder = async ( noticeText ) => { - // Wait for auto save - await page.waitFor( 2000 ); +const clickUpdateOrder = async ( noticeText, waitForSave = false ) => { + if ( waitForSave ) { + await page.waitFor( 2000 ); + } // PUpdate order await expect( page ).toClick( 'button.save_order' ); From 6fac4ab6d5e64ccad7879c2116ce35ad89d41e4f Mon Sep 17 00:00:00 2001 From: zhongruige Date: Thu, 4 Mar 2021 11:43:15 -0700 Subject: [PATCH 30/32] Added delete step to product tests --- tests/e2e/core-tests/specs/api/coupon.test.js | 4 ++-- tests/e2e/core-tests/specs/api/external-product.test.js | 5 +++++ tests/e2e/core-tests/specs/api/grouped-product.test.js | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/api/coupon.test.js b/tests/e2e/core-tests/specs/api/coupon.test.js index ec5200afa2b..6ba22bb4d3e 100644 --- a/tests/e2e/core-tests/specs/api/coupon.test.js +++ b/tests/e2e/core-tests/specs/api/coupon.test.js @@ -74,10 +74,10 @@ const runCouponApiTest = () => { it('can delete a coupon', async () => { // Delete the coupon - const deletedCoupon = await repository.delete( coupon.id ); + const status = await repository.delete( coupon.id ); // If the delete is successful, the response comes back truthy - expect( deletedCoupon ).toBeTruthy(); + expect( status ).toBeTruthy(); }); }); }; diff --git a/tests/e2e/core-tests/specs/api/external-product.test.js b/tests/e2e/core-tests/specs/api/external-product.test.js index 674cbdbae25..463f248b726 100644 --- a/tests/e2e/core-tests/specs/api/external-product.test.js +++ b/tests/e2e/core-tests/specs/api/external-product.test.js @@ -69,6 +69,11 @@ const runExternalProductAPITest = () => { const transformed = await repository.read( product.id ); expect( transformed ).toEqual( expect.objectContaining( transformedProperties ) ); }); + + it('can delete an external product', async () => { + const status = repository.delete( product.id ); + expect( status ).toBeTruthy(); + }); }); }; diff --git a/tests/e2e/core-tests/specs/api/grouped-product.test.js b/tests/e2e/core-tests/specs/api/grouped-product.test.js index 11dea1ba7a8..d1f12567305 100644 --- a/tests/e2e/core-tests/specs/api/grouped-product.test.js +++ b/tests/e2e/core-tests/specs/api/grouped-product.test.js @@ -71,11 +71,16 @@ const runGroupedProductAPITest = () => { expect( response.data ).toEqual( expect.objectContaining( rawProperties ) ); }); - it('can retrieve a transformed external product', async () => { + it('can retrieve a transformed grouped product', async () => { // Read product via the repository. const transformed = await repository.read( product.id ); expect( transformed ).toEqual( expect.objectContaining( baseGroupedProduct ) ); }); + + it('can delete a grouped product', async () => { + const status = repository.delete( product.id ); + expect( status ).toBeTruthy(); + }); }); }; From 5bf3e8ed9868762cd84c6cd990352dc1666bbc6c Mon Sep 17 00:00:00 2001 From: zhongruige Date: Thu, 4 Mar 2021 14:17:43 -0700 Subject: [PATCH 31/32] Code review feedback --- tests/e2e/core-tests/specs/index.js | 1 - .../wp-admin/{test-order-emails.js => order-emails.test.js} | 0 2 files changed, 1 deletion(-) rename tests/e2e/specs/wp-admin/{test-order-emails.js => order-emails.test.js} (100%) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index dc9db031d4c..2a59fed2696 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -73,7 +73,6 @@ const runMerchantTests = () => { runProductEditDetailsTest(); runProductSearchTest(); runMerchantOrdersCustomerPaymentPage(); - runMerchantOrderEmailsTest(); } const runApiTests = () => { diff --git a/tests/e2e/specs/wp-admin/test-order-emails.js b/tests/e2e/specs/wp-admin/order-emails.test.js similarity index 100% rename from tests/e2e/specs/wp-admin/test-order-emails.js rename to tests/e2e/specs/wp-admin/order-emails.test.js From 24167a5eedb3719f5c25a06018a725292fc50b96 Mon Sep 17 00:00:00 2001 From: Saggre Date: Fri, 5 Mar 2021 11:19:58 +0200 Subject: [PATCH 32/32] Fix typos in docs --- includes/abstracts/abstract-wc-payment-token.php | 2 +- includes/class-wc-order.php | 4 ++-- tests/legacy/unit-tests/checkout/checkout.php | 2 +- .../unit-tests/widgets/class-dummy-widget.php | 2 +- tests/php/includes/wc-stock-functions-tests.php | 16 ++++++++-------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/includes/abstracts/abstract-wc-payment-token.php b/includes/abstracts/abstract-wc-payment-token.php index ad958991981..6fc6f634d29 100644 --- a/includes/abstracts/abstract-wc-payment-token.php +++ b/includes/abstracts/abstract-wc-payment-token.php @@ -2,7 +2,7 @@ /** * Abstract payment tokens * - * Generic payment tokens functionality which can be extended by idividual types of payment tokens. + * Generic payment tokens functionality which can be extended by individual types of payment tokens. * * @class WC_Payment_Token * @package WooCommerce\Abstracts diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index f33c5db1b36..516f905b10b 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -907,7 +907,7 @@ class WC_Order extends WC_Abstract_Order { $address = WC()->countries->get_formatted_address( $raw_address ); /** - * Filter orders formatterd billing address. + * Filter orders formatted billing address. * * @since 3.8.0 * @param string $address Formatted billing address string. @@ -933,7 +933,7 @@ class WC_Order extends WC_Abstract_Order { } /** - * Filter orders formatterd shipping address. + * Filter orders formatted shipping address. * * @since 3.8.0 * @param string $address Formatted billing address string. diff --git a/tests/legacy/unit-tests/checkout/checkout.php b/tests/legacy/unit-tests/checkout/checkout.php index 73586cab609..953351a9350 100644 --- a/tests/legacy/unit-tests/checkout/checkout.php +++ b/tests/legacy/unit-tests/checkout/checkout.php @@ -141,7 +141,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case { } /** - * Test usage limit for guest users uasge limit per user is set. + * Test usage limit for guest users usage limit per user is set. * * @throws Exception When unable to create order. */ diff --git a/tests/legacy/unit-tests/widgets/class-dummy-widget.php b/tests/legacy/unit-tests/widgets/class-dummy-widget.php index f8aa56223d6..67ea55bfccb 100644 --- a/tests/legacy/unit-tests/widgets/class-dummy-widget.php +++ b/tests/legacy/unit-tests/widgets/class-dummy-widget.php @@ -18,7 +18,7 @@ class Dummy_Widget extends WC_Widget { * Output widget. * * @param mixed $args Arguments. - * @param WP_Widget $instance Intance of WP_Widget. + * @param WP_Widget $instance Instance of WP_Widget. * @return void */ public function widget( $args, $instance ) { diff --git a/tests/php/includes/wc-stock-functions-tests.php b/tests/php/includes/wc-stock-functions-tests.php index 56d2d0eeb13..d815eeee5d8 100644 --- a/tests/php/includes/wc-stock-functions-tests.php +++ b/tests/php/includes/wc-stock-functions-tests.php @@ -85,7 +85,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which reduces stock to another status which also reduces stock. + * Test inventory count after order status transitions which reduces stock to another status which also reduces stock. * Stock should have reduced once already, and should not reduce again. */ public function test_status_transition_stock_reduce_to_stock_reduce() { @@ -97,7 +97,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which reduces stock to another status which restores stock. + * Test inventory count after order status transitions which reduces stock to another status which restores stock. * Should should have already reduced once, and will increase again after transitioning. */ public function test_status_transition_stock_reduce_to_stock_restore() { @@ -109,7 +109,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which reduces stock to another status which don't affect inventory. + * Test inventory count after order status transitions which reduces stock to another status which don't affect inventory. * Stock should have already reduced, and will not change on transitioning. */ public function test_status_transition_stock_reduce_to_stock_no_effect() { @@ -133,7 +133,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which restores stock to another status which also restores stock. + * Test inventory count after order status transitions which restores stock to another status which also restores stock. * Stock should not have reduced, and will remain the same even after transition (i.e. should not be restocked again). */ public function test_status_transition_stock_restore_to_stock_restore() { @@ -145,7 +145,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which restores stock to another status which don't affect inventory. + * Test inventory count after order status transitions which restores stock to another status which don't affect inventory. * Stock should not have reduced, and will remain the same even after transition. */ public function test_status_transition_stock_restore_to_stock_no_effect() { @@ -157,7 +157,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which don't affect inventory stock to another status which reduces stock. + * Test inventory count after order status transitions which don't affect inventory stock to another status which reduces stock. * Stock would not have been affected, but will reduce after transition. */ public function test_status_transition_stock_no_effect_to_stock_reduce() { @@ -169,7 +169,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which don't affect inventory stock to another status which restores stock. + * Test inventory count after order status transitions which don't affect inventory stock to another status which restores stock. * Stock would not have been affected, and will not be restored after transition (since it was not reduced to begin with). */ public function test_status_transition_stock_no_effect_to_stock_restore() { @@ -181,7 +181,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which don't affect inventory stock to another status which also don't affect inventory. + * Test inventory count after order status transitions which don't affect inventory stock to another status which also don't affect inventory. * Stock levels will not change before or after the transition. */ public function test_status_transition_stock_no_effect_to_stock_no_effect() {