From c074c5580b8d5950601f4a0b9a1d35de16ff9a3a Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Wed, 4 Sep 2024 14:23:20 +0100 Subject: [PATCH 01/57] PTK: Check if theres a scheduled action before logging warning. (#51143) * Check if there is a scheduled action before logging warning --- .../changelog/fix-register-ptk-patterns-warning-log | 4 ++++ plugins/woocommerce/src/Blocks/BlockPatterns.php | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-register-ptk-patterns-warning-log diff --git a/plugins/woocommerce/changelog/fix-register-ptk-patterns-warning-log b/plugins/woocommerce/changelog/fix-register-ptk-patterns-warning-log new file mode 100644 index 00000000000..0f236c0c0fe --- /dev/null +++ b/plugins/woocommerce/changelog/fix-register-ptk-patterns-warning-log @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Only log PTK error if no action has been scheduled diff --git a/plugins/woocommerce/src/Blocks/BlockPatterns.php b/plugins/woocommerce/src/Blocks/BlockPatterns.php index 362fdae0446..70b7f137db9 100644 --- a/plugins/woocommerce/src/Blocks/BlockPatterns.php +++ b/plugins/woocommerce/src/Blocks/BlockPatterns.php @@ -141,9 +141,14 @@ class BlockPatterns { $patterns = $this->ptk_patterns_store->get_patterns(); if ( empty( $patterns ) ) { - wc_get_logger()->warning( - __( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ), - ); + // By only logging when patterns are empty and no fetch is scheduled, + // we ensure that warnings are only generated in genuinely problematic situations, + // such as when the pattern fetching mechanism has failed entirely. + if ( ! as_has_scheduled_action( 'fetch_patterns' ) ) { + wc_get_logger()->warning( + __( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ), + ); + } return; } From 6bf6f328546e35c143a5362a5bdeeafee5595f83 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Wed, 4 Sep 2024 14:34:00 +0100 Subject: [PATCH 02/57] CYS: Replace Nokul theme with Gizmo (#51113) * Replace Nokul theme with Gizmo in the CYS experience since it was retired --- .../update-cys-replace-nokul-with-gizmo | 4 +++ .../src/Admin/API/OnboardingThemes.php | 34 +++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-cys-replace-nokul-with-gizmo diff --git a/plugins/woocommerce/changelog/update-cys-replace-nokul-with-gizmo b/plugins/woocommerce/changelog/update-cys-replace-nokul-with-gizmo new file mode 100644 index 00000000000..26b88d40764 --- /dev/null +++ b/plugins/woocommerce/changelog/update-cys-replace-nokul-with-gizmo @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Replace retired theme Nokul with Gizmo in the CYS experience diff --git a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php index 6637e6f9c7b..bf0a9edfa8d 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php @@ -349,36 +349,36 @@ class OnboardingThemes extends \WC_REST_Data_Controller { 'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/luminate/' ), ), array( - 'name' => 'Nokul', + 'name' => 'Gizmo', /* translators: %d: price */ 'price' => sprintf( __( '$%d/year', 'woocommerce' ), 79 ), 'is_free' => false, 'color_palettes' => array( array( - 'title' => 'Foreground and background', - 'primary' => '#000000', - 'secondary' => '#f1eee2', + 'title' => 'Primary', + 'primary' => '#ff5833', + 'secondary' => '#ff5833', ), array( - 'title' => 'Foreground and secondary', - 'primary' => '#000000', - 'secondary' => '#999999', + 'title' => 'Foreground', + 'primary' => '#111111', + 'secondary' => '#111111', ), array( - 'title' => 'Foreground and accent', - 'primary' => '#000000', - 'secondary' => '#d82f16', + 'title' => 'Background', + 'primary' => '#FFFFFF', + 'secondary' => '#FFFFFF', ), array( - 'title' => 'Primary and background', - 'primary' => '#d9d0bf', - 'secondary' => '#f1eee2', + 'title' => 'Base', + 'primary' => '#595959', + 'secondary' => '#595959', ), ), - 'total_palettes' => 6, - 'slug' => 'nokul', - 'thumbnail_url' => 'https://woocommerce.com/wp-content/uploads/2022/11/Product-logo.jpg', - 'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/nokul/' ), + 'total_palettes' => 10, + 'slug' => 'gizmo', + 'thumbnail_url' => 'https://woocommerce.com/wp-content/uploads/2022/11/gizmo-regular-card-product-logo.jpg?w=900', + 'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/gizmo/' ), ), ); From 941f85bb77e29fc81587d9a4dc6c75e77e07b861 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:41:53 -0700 Subject: [PATCH 03/57] Delete changelog files based on PR 50970 (#51093) Delete changelog files for 50970 Co-authored-by: WooCommerce Bot --- .../changelog/update-woocommerce-shipping-promo-banner-188 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/update-woocommerce-shipping-promo-banner-188 diff --git a/plugins/woocommerce/changelog/update-woocommerce-shipping-promo-banner-188 b/plugins/woocommerce/changelog/update-woocommerce-shipping-promo-banner-188 deleted file mode 100644 index 51e8df0eb8f..00000000000 --- a/plugins/woocommerce/changelog/update-woocommerce-shipping-promo-banner-188 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update WooCommerce Shipping Promo Banner to install the latest version of WooCommerce Shipping instead of WCS&T. From 8f3e824596195937f582c6e1673cd6591a874b38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:42:00 -0700 Subject: [PATCH 04/57] Delete changelog files based on PR 51067 (#51084) Delete changelog files for 51067 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/51067-revert-50531 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/51067-revert-50531 diff --git a/plugins/woocommerce/changelog/51067-revert-50531 b/plugins/woocommerce/changelog/51067-revert-50531 deleted file mode 100644 index e65258f8afe..00000000000 --- a/plugins/woocommerce/changelog/51067-revert-50531 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak -Comment: This reverts an existing PR. - From 0f2fa403f1ad0dc26bdd82ec3e901cb1794201e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:42:18 -0700 Subject: [PATCH 05/57] Delete changelog files based on PR 51081 (#51107) Delete changelog files for 51081 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/fix-make-themes-api-safer | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-make-themes-api-safer diff --git a/plugins/woocommerce/changelog/fix-make-themes-api-safer b/plugins/woocommerce/changelog/fix-make-themes-api-safer deleted file mode 100644 index c07fa424df5..00000000000 --- a/plugins/woocommerce/changelog/fix-make-themes-api-safer +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Add check to ensure themes API is safe From 0ea76c2ffb2273ac2c9b9172f4c31c3ba84daae6 Mon Sep 17 00:00:00 2001 From: piinthecloud Date: Wed, 4 Sep 2024 19:11:35 +0200 Subject: [PATCH 06/57] fixes and updates (#51144) * fixes and updates * add changelog * update quotes * fix broken whitespace * Update plugins/woocommerce/changelog/update-contrib-docs-and-fixes Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com> --------- Co-authored-by: Jacklyn Biggin Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com> --- .../configuring_special_tax_scenarios.md | 2 +- docs/contributing-docs/contributing-docs.md | 6 ++++++ docs/docs-manifest.json | 14 +++++++------- .../guide-large-store.md | 8 ++++---- docs/performance/performance-best-practices.md | 2 +- .../register-product-collection.md | 8 ++++---- docs/theme-development/template-structure.md | 6 +++--- docs/ux-guidelines-extensions/navigation.md | 5 +---- .../changelog/update-contrib-docs-and-fixes | 4 ++++ .../src/StoreApi/docs/guiding-principles.md | 2 +- 10 files changed, 32 insertions(+), 25 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-contrib-docs-and-fixes diff --git a/docs/code-snippets/configuring_special_tax_scenarios.md b/docs/code-snippets/configuring_special_tax_scenarios.md index 1dc991844d8..34a92b2a640 100644 --- a/docs/code-snippets/configuring_special_tax_scenarios.md +++ b/docs/code-snippets/configuring_special_tax_scenarios.md @@ -38,7 +38,7 @@ function big_apple_get_tax_class( $tax_class, $product ) { Some merchants may require different tax rates to be applied based on a customer role to accommodate for wholesale status or tax exemption. -To enable this functionality, add the following snippet to your child theme's functions.php file or via a code snippet plugin. In this snippet, users with “administrator” capabilities will be assigned the **Zero rate tax class**. Adjust it according to your requirements. +To enable this functionality, add the following snippet to your child theme's functions.php file or via a code snippet plugin. In this snippet, users with "administrator" capabilities will be assigned the **Zero rate tax class**. Adjust it according to your requirements. ```php ) symbol named references. diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 6ef73f9e485..76b99e36923 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -414,7 +414,7 @@ "menu_title": "Configuring special tax scenarios", "tags": "code-snippet, tax", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/configuring_special_tax_scenarios.md", - "hash": "acce5111eb917b7381d9bdcb260c72870e89d723195b86522050032741c5107c", + "hash": "c2459568db70b97d9a901c83eecb7737746499fe03cc84133aab3cf981b5c96a", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/configuring_special_tax_scenarios.md", "id": "a8ab8b6734ba2ac5af7c6653635d15548abdab2a" }, @@ -594,7 +594,7 @@ "post_title": "Contributing Technical Documentation", "menu_title": "Contributing Docs", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/contributing-docs/contributing-docs.md", - "hash": "ee2eed4bc33ccbc4a84a2c73ee503f2df5a92183013e3f703bb439edab0a3fe3", + "hash": "6733ba23f82a6e784ef10e2862aa3afd8a61e32181e157f67ccabfc8354aa989", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/contributing-docs/contributing-docs.md", "id": "71c1a72bfd4d5ae6aa656d4264b1bf3beb6eca1c" } @@ -939,7 +939,7 @@ "menu_title": "Enable HPOS for large stores", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/high-performance-order-storage/guide-large-store.md", - "hash": "8bcae74d27e3a4ee9a902719c7e8d5aec4a4d82d7c14acd8665a72b9d4758181", + "hash": "1e144ac0d0a7c869093533bf94a1f218a42930a3f3edcbcfdd1210448243a992", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/guide-large-store.md", "id": "b6156ac7b77d75022867e9ebb968bc9c1c35f0da" }, @@ -1033,7 +1033,7 @@ "menu_title": "Performance best practices", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/performance/performance-best-practices.md", - "hash": "5af1f4e4085e85a1693390f40e238cbd6a4a0b7d5d304afdda935c34fed97c64", + "hash": "3c49b5553e99b64ecdbdad565866479f7af5e474dbbcc9aa36d711c02d8b0906", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/performance/performance-best-practices.md", "id": "35bda1cd7068d6179a9e46cca8d7dc2694d0df96" } @@ -1451,7 +1451,7 @@ { "post_title": "Template structure & Overriding templates via a theme", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/theme-development/template-structure.md", - "hash": "ff781eff7998ea93723f644bddd4f6da6f73c635bcfc3cd46950f03a8b83b26c", + "hash": "0d026ed5e9706b97dd03d6f7dc86aeb580b11549fa99c9d6f906bbca6f136a01", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/theme-development/template-structure.md", "id": "34bfebec9fc45e680976814928a7b8a1778af14e" }, @@ -1554,7 +1554,7 @@ "post_title": "WooCommerce Extension Guidelines - Navigation", "menu_title": "Navigation", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/ux-guidelines-extensions/navigation.md", - "hash": "f14cbd3750451934d173ec43aa996067699bdd8fc05f5c34097d1967a79145db", + "hash": "9c5313a31ebe26909a2cfaaa0bcfcdc9d5a0855ac0ddb9808832524be313ebb6", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/ux-guidelines-extensions/navigation.md", "id": "9922abbcea787a91b3360f49fa53d5402a604e6b" }, @@ -1804,5 +1804,5 @@ "categories": [] } ], - "hash": "2ecf48b6181dae0526b3858df889bce4e6ee425e10f2c43d151771d845c5a948" + "hash": "cbe507d24f0d6ae827c01a98fd2d7c6e8db74053a7b3db869c6941dd7442d250" } \ No newline at end of file diff --git a/docs/high-performance-order-storage/guide-large-store.md b/docs/high-performance-order-storage/guide-large-store.md index 75583f42615..dd2b40d159e 100644 --- a/docs/high-performance-order-storage/guide-large-store.md +++ b/docs/high-performance-order-storage/guide-large-store.md @@ -87,7 +87,7 @@ Also, we didn't see a noticeable negative performance impact when keeping synchr ### Switch to HPOS as authoritative -It's time to switch to HPOS. Go to **WooCommerce > Settings > Advanced > Features** and set HPOS to be authoritative (select “**Use the WooCommerce orders tables**"). +It's time to switch to HPOS. Go to **WooCommerce > Settings > Advanced > Features** and set HPOS to be authoritative (select "**Use the WooCommerce orders tables**"). As mentioned above, don't turn off synchronization yet. If there are any issues, the system can be instantaneously reverted to the posts table, resulting in no downtime. @@ -107,7 +107,7 @@ We disable sync on read first because it demands more resources. If your site is ### Switch off sync on write -If everything is working as expected, you can disable sync on write as well. Given sync on read was already disabled, you can disable sync altogether from the settings. As usual, go to **WooCommerce > Settings > Advanced > Features**, and uncheck **“Enable compatibility mode"**. +If everything is working as expected, you can disable sync on write as well. Given sync on read was already disabled, you can disable sync altogether from the settings. As usual, go to **WooCommerce > Settings > Advanced > Features**, and uncheck **"Enable compatibility mode"**. On our high-volume site, we fully disabled sync after 1 week. We still run some manual synchronization (via `wp wc cot sync`) periodically so that we have the opportunity to fall back to posts immediately should anything happen. @@ -118,11 +118,11 @@ Now with synchronization fully disabled, test out various critical flows, check ### Review: Phase 3 Checklist 1. [ ] Plan to be online and monitoring your live site for a period of time. -2. [ ] Enable synchronization with posts set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select “**Use the WordPress posts tables**". +2. [ ] Enable synchronization with posts set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select "**Use the WordPress posts tables**". 3. [ ] Start migration via CLI using the `wp wc cot sync` command. 4. [ ] Monitor for errors during migration; halt or resume as necessary. 5. [ ] Verify migrated data integrity using the verify command `wp wc cot verify_cot_data`. -6. [ ] Enable synchronization with HPOS set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select “Use the **WooCommerce orders tables**". +6. [ ] Enable synchronization with HPOS set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select "Use the **WooCommerce orders tables**". 7. [ ] Test all critical flows, perform checkouts with multiple payment methods, and verify order data accuracy. 8. [ ] Monitor support tickets for any issues. 9. [ ] Disable synchronization on read using the provided snippet: `add_filter( 'woocommerce_hpos_enable_sync_on_read', '__return_false' );` diff --git a/docs/performance/performance-best-practices.md b/docs/performance/performance-best-practices.md index a88896370c4..b47f828754e 100644 --- a/docs/performance/performance-best-practices.md +++ b/docs/performance/performance-best-practices.md @@ -20,7 +20,7 @@ For WooCommerce extensions, performance optimization means ensuring that your co ## Benchmarking Performance -Setting clear performance benchmarks is essential for development and continuous improvement of WooCommerce extensions. A recommended performance standard is achieving a Chrome Core Web Vitals "Performance" score of 90 or above on Woo Express, using tools like the [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/). +Setting clear performance benchmarks is essential for development and continuous improvement of WooCommerce extensions. A recommended performance standard is achieving a Chrome Core Web Vitals "Performance" score of 90 or above on a simple Woo site, using tools like the [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/). ### Using Accessible Tools for Benchmarking diff --git a/docs/product-collection-block/register-product-collection.md b/docs/product-collection-block/register-product-collection.md index 9f96f791262..677668ba71e 100644 --- a/docs/product-collection-block/register-product-collection.md +++ b/docs/product-collection-block/register-product-collection.md @@ -6,20 +6,20 @@ tags: how-to # Register Product Collection -The `__experimentalRegisterProductCollection` function is part of the `@woocommerce/blocks-registry` package. This function allows third party developers to register a new collection. This function accepts most of the arguments that are accepted by [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation). +The `__experimentalRegisterProductCollection` function is part of the `@woocommerce/blocks-registry` package. This function allows third party developers to register a new collection. This function accepts most of the arguments that are accepted by [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation). > [!WARNING] > It's experimental and may change in the future. Please use it with caution. **There are two ways to use this function:** -1. Using `@woocommerce/dependency-extraction-webpack-plugin` in a Webpack configuration: This will allow you to import the function from the package & use it in your code. For example: +1. Using `@woocommerce/dependency-extraction-webpack-plugin` in a Webpack configuration: This will allow you to import the function from the package & use it in your code. For example: ```tsx import { __experimentalRegisterProductCollection } from "@woocommerce/blocks-registry"; ``` -2. Using the global `wc` object: This will allow you to use the function using the global JS object without importing it. For example: +2. Using the global `wc` object: This will allow you to use the function using the global JS object without importing it. For example: ```tsx wc.wcBlocksRegistry.__experimentalRegisterProductCollection({ @@ -113,7 +113,7 @@ __experimentalRegisterProductCollection({ }); ``` -As you can see in the example above, we are registering a new collection with the name `woocommerce/product-collection/my-custom-collection` & title `My Custom Collection`. Here is screenshot of how it will look like: +As you can see in the example above, we are registering a new collection with the name `woocommerce/product-collection/my-custom-collection` & title `My Custom Collection`. Here is screenshot of how it will look like: ![image](https://github.com/woocommerce/woocommerce/assets/16707866/7fddbc02-a6cd-494e-b2f4-13dd5ef9cf96) ### Example 2: Register a new collection with a preview diff --git a/docs/theme-development/template-structure.md b/docs/theme-development/template-structure.md index 4688eb53875..888cd31ca11 100644 --- a/docs/theme-development/template-structure.md +++ b/docs/theme-development/template-structure.md @@ -22,7 +22,7 @@ Below is video walkthrough showing how one may go about updating the template fi ## Template list -The various template files on your WooCommerce site can be found via an FTP client or your hosts file manager, in `/wp-content/plugins/woocommerce/templates/`. Below are links to the current and earlier versions of the WooCommerce template files on Github, where you can view the code exactly as it appears in those files: +The various template files on your WooCommerce site can be found via an FTP client or your hosts file manager, in `/wp-content/plugins/woocommerce/templates/`. Below are links to the current and earlier versions of the WooCommerce template files on Github, where you can view the code exactly as it appears in those files: | Latest Version | Files | | -------------- | ----- | @@ -96,7 +96,7 @@ Below are the links to the files of all major previous WooCommerce versions: ## Changing Templates via Hooks -When you open a template file, you will notice they all contain _hooks_ that allow you to add/move content without needing to edit template files themselves. Hooks are a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. This method allows implementing a code snippet that “hooks” into a particular a theme location. It avoids upgrade issues, as the template files can be left completely untouched and doesn't require a child theme to be configured. +When you open a template file, you will notice they all contain _hooks_ that allow you to add/move content without needing to edit template files themselves. Hooks are a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. This method allows implementing a code snippet that "hooks" into a particular a theme location. It avoids upgrade issues, as the template files can be left completely untouched and doesn't require a child theme to be configured. Let's take a look at [/wp-content/plugins/woocommerce/templates/emails/admin-new-order.php](https://github.com/woocommerce/woocommerce/blob/8.9.0/plugins/woocommerce/templates/emails/admin-new-order.php) and see what a hook looks like. Starting on line 30, we see the following code, which is responsible for producing the order details section of the New Order email. @@ -137,7 +137,7 @@ The copied file will now override the WooCommerce default template file, so you --- -**Note** A (desirable) side-effect of your templates being upgrade-safe is that WooCommerce core templates will update, but your custom overrides will not. You may occassionally see notices in your System Status report that says, e.g. “version 3.5.0 is out of date. The core version is 3.7.0″. Should that happen, follow the Fixing Outdated WooCommerce Templates guide to bring them in line. +**Note** A (desirable) side-effect of your templates being upgrade-safe is that WooCommerce core templates will update, but your custom overrides will not. You may occassionally see notices in your System Status report that says, e.g. "version 3.5.0 is out of date. The core version is 3.7.0". Should that happen, follow the Fixing Outdated WooCommerce Templates guide to bring them in line. --- diff --git a/docs/ux-guidelines-extensions/navigation.md b/docs/ux-guidelines-extensions/navigation.md index 428d63bdca9..52b43a9b556 100644 --- a/docs/ux-guidelines-extensions/navigation.md +++ b/docs/ux-guidelines-extensions/navigation.md @@ -7,10 +7,7 @@ menu_title: Navigation Examples: -- If your extension is extending a component within WooCommerce, it should live within either the Extensions navigation drawer (in Woo Express stores), or directly within that category's section. - -Extensions drawer (Woo Express) -![Navigation extensions drawer](https://developer.woocommerce.com/docs/wp-content/uploads/sites/3/2024/01/Image-1224x572-1.png) +- If your extension is extending a component within WooCommerce, it should live directly within that category's section. ![Navigation category](https://developer.woocommerce.com/docs/wp-content/uploads/sites/3/2024/01/Image-1242x764-1.png) diff --git a/plugins/woocommerce/changelog/update-contrib-docs-and-fixes b/plugins/woocommerce/changelog/update-contrib-docs-and-fixes new file mode 100644 index 00000000000..8103b856e85 --- /dev/null +++ b/plugins/woocommerce/changelog/update-contrib-docs-and-fixes @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Update docs diff --git a/plugins/woocommerce/src/StoreApi/docs/guiding-principles.md b/plugins/woocommerce/src/StoreApi/docs/guiding-principles.md index 0ce28e33ecb..277c0090b4d 100644 --- a/plugins/woocommerce/src/StoreApi/docs/guiding-principles.md +++ b/plugins/woocommerce/src/StoreApi/docs/guiding-principles.md @@ -22,7 +22,7 @@ Well-defined schema also provides a layer of security, as it enables us to valid When defining schema, take note of the [WordPress REST API handbook](https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/) which documents available properties and types, as well as the [JSON schema standard](http://json-schema.org/). In addition to this: - Properties should use snake_case 🐍 -- Ambiguous terms should be avoided, and property names should try to use understandable language, rather than “WooCommerce” terminology or setting names +- Ambiguous terms should be avoided, and property names should try to use understandable language, rather than "WooCommerce" terminology or setting names - Properties should be defined using US English, but the descriptions of fields should be localized - Multiple types are permitted, for example, using a `null` type if a value is not applicable - `sanitize_callback` and `validate_callback` are encouraged where possible to ensure data is received in the correct format before processing requests From a5623099ddec1118d913065c3dd20f48335547ae Mon Sep 17 00:00:00 2001 From: Ivan Stojadinov Date: Wed, 4 Sep 2024 21:53:59 +0200 Subject: [PATCH 07/57] [e2e] Move Pressable and WPCOM to release checks instead of daily checks (#51148) * Move Pressable and WPCOM to release checks instead of daily checks * Add changefile(s) from automation for the following project(s): woocommerce --------- Co-authored-by: github-actions --- .../changelog/51148-e2e-external-sites-run-on-release-checks | 4 ++++ plugins/woocommerce/package.json | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/51148-e2e-external-sites-run-on-release-checks diff --git a/plugins/woocommerce/changelog/51148-e2e-external-sites-run-on-release-checks b/plugins/woocommerce/changelog/51148-e2e-external-sites-run-on-release-checks new file mode 100644 index 00000000000..91f6f869a9d --- /dev/null +++ b/plugins/woocommerce/changelog/51148-e2e-external-sites-run-on-release-checks @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Move Pressable and WPCOM e2e runss to release-checks instead of daily-checks \ No newline at end of file diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index f817cad37ce..28129dc3d7d 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -560,7 +560,7 @@ "shardingArguments": [], "changes": [], "events": [ - "daily-checks", + "release-checks", "on-demand" ], "report": { @@ -576,7 +576,7 @@ "shardingArguments": [], "changes": [], "events": [ - "daily-checks", + "release-checks", "on-demand" ], "report": { From 18ab90badd71d43ad1a132fba84d3b93508466f4 Mon Sep 17 00:00:00 2001 From: "Veljko V." Date: Wed, 4 Sep 2024 22:09:04 +0200 Subject: [PATCH 08/57] [e2e tests] External sites - update /shopper tests, part 2 (#51100) * Update shopper tests, external config and add tags * Update WPCOM config to include shopper tests --- .../e2e-fix-compatibility-external-envs-part2 | 4 + .../default-pressable/playwright.config.js | 17 ++++ .../envs/default-wpcom/playwright.config.js | 22 ++++- .../tests/shopper/launch-your-store.spec.js | 84 ++++++++++--------- .../shop-products-filter-by-price.spec.js | 9 +- .../shopper/shop-title-after-deletion.spec.js | 12 +++ .../tests/shopper/wordpress-post.spec.js | 8 +- .../woocommerce/tests/e2e-pw/utils/editor.js | 1 + 8 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 plugins/woocommerce/changelog/e2e-fix-compatibility-external-envs-part2 diff --git a/plugins/woocommerce/changelog/e2e-fix-compatibility-external-envs-part2 b/plugins/woocommerce/changelog/e2e-fix-compatibility-external-envs-part2 new file mode 100644 index 00000000000..682518cc821 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-fix-compatibility-external-envs-part2 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update /shopper tests (second part), so they are passing against Pressable env. \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js index b0747f37c30..48f83f3e12b 100644 --- a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js @@ -21,6 +21,23 @@ config = { '**/merchant/create-checkout-block.spec.js', '**/merchant/create-coupon.spec.js', '**/merchant/create-order.spec.js', + '**/shopper/checkout-create-account.spec.js', + '**/shopper/checkout-login.spec.js', + '**/shopper/checkout.spec.js', + '**/shopper/dashboard-access.spec.js', + '**/shopper/mini-cart.spec.js', + '**/shopper/my-account-addresses.spec.js', + '**/shopper/my-account-create-account.spec.js', + '**/shopper/my-account-downloads.spec.js', + '**/shopper/my-account-pay-order.spec.js', + '**/shopper/my-account.spec.js', + '**/shopper/order-email-receiving.spec.js', + '**/shopper/product-grouped.spec.js', + '**/shopper/product-simple.spec.js', + '**/shopper/product-tags-attributes.spec.js', + '**/shopper/product-variable.spec.js', + '**/shopper/shop-search-browse-sort.spec.js', + '**/shopper/shop-title-after-deletion.spec.js', ], grepInvert: /@skip-on-default-pressable/, }, diff --git a/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js index 179fca7b471..3a566c9a531 100644 --- a/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js @@ -7,7 +7,27 @@ config = { { name: 'default wpcom', use: { ...devices[ 'Desktop Chrome' ] }, - testMatch: '**basic.spec.js', + testMatch: [ + '**/basic.spec.js', + '**/shopper/checkout-create-account.spec.js', + '**/shopper/checkout-login.spec.js', + '**/shopper/checkout.spec.js', + '**/shopper/dashboard-access.spec.js', + '**/shopper/mini-cart.spec.js', + '**/shopper/my-account-addresses.spec.js', + '**/shopper/my-account-create-account.spec.js', + '**/shopper/my-account-downloads.spec.js', + '**/shopper/my-account-pay-order.spec.js', + '**/shopper/my-account.spec.js', + '**/shopper/order-email-receiving.spec.js', + '**/shopper/product-grouped.spec.js', + '**/shopper/product-simple.spec.js', + '**/shopper/product-tags-attributes.spec.js', + '**/shopper/product-variable.spec.js', + '**/shopper/shop-search-browse-sort.spec.js', + '**/shopper/shop-title-after-deletion.spec.js', + ], + grepInvert: /@skip-on-default-wpcom/, }, ], }; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/launch-your-store.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/launch-your-store.spec.js index 5f85238a598..5db7a5ebd3b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/launch-your-store.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/launch-your-store.spec.js @@ -72,43 +72,51 @@ async function runComingSoonTests( themeContext = '' ) { } ); } -test.describe( 'Launch Your Store front end - logged out', () => { - test.afterAll( async ( { baseURL } ) => { - try { - await setOption( - request, - baseURL, - 'woocommerce_coming_soon', - 'no' +test.describe( + 'Launch Your Store front end - logged out', + { tag: [ '@skip-on-default-wpcom', '@skip-on-default-pressable' ] }, + () => { + test.afterAll( async ( { baseURL } ) => { + try { + await setOption( + request, + baseURL, + 'woocommerce_coming_soon', + 'no' + ); + } catch ( error ) { + console.log( error ); + } + } ); + + test.describe( 'Block Theme (Twenty Twenty Four)', () => { + test.beforeAll( async () => { + await activateTheme( 'twentytwentyfour' ); + } ); + + test.afterAll( async () => { + // Reset theme to the default. + await activateTheme( DEFAULT_THEME ); + } ); + + runComingSoonTests( test.step, test.use ); + } ); + + test.describe( 'Classic Theme (Storefront)', () => { + test.beforeAll( async () => { + await activateTheme( 'storefront' ); + } ); + + test.afterAll( async () => { + // Reset theme to the default. + await activateTheme( DEFAULT_THEME ); + } ); + + runComingSoonTests( + test.step, + test.use, + 'Classic Theme (Storefront)' ); - } catch ( error ) { - console.log( error ); - } - } ); - - test.describe( 'Block Theme (Twenty Twenty Four)', () => { - test.beforeAll( async () => { - await activateTheme( 'twentytwentyfour' ); } ); - - test.afterAll( async () => { - // Reset theme to the default. - await activateTheme( DEFAULT_THEME ); - } ); - - runComingSoonTests( test.step, test.use ); - } ); - - test.describe( 'Classic Theme (Storefront)', () => { - test.beforeAll( async () => { - await activateTheme( 'storefront' ); - } ); - - test.afterAll( async () => { - // Reset theme to the default. - await activateTheme( DEFAULT_THEME ); - } ); - - runComingSoonTests( test.step, test.use, 'Classic Theme (Storefront)' ); - } ); -} ); + } +); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js index 61fd3df02ba..ddf1a2d4735 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js @@ -24,7 +24,14 @@ const test = baseTest.extend( { test.describe( 'Filter items in the shop by product price', - { tag: [ '@payments', '@services' ] }, + { + tag: [ + '@payments', + '@services', + '@skip-on-default-wpcom', + '@skip-on-default-pressable', + ], + }, () => { test.beforeAll( async ( { api } ) => { // add products diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js index 5c361b1ed50..f0b75c15854 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js @@ -30,6 +30,18 @@ test.describe( await page .getByText( '1 page restored from the Trash.' ) .waitFor( { state: 'visible' } ); + await page.goto( 'wp-admin/edit.php?post_type=page' ); + await page.getByRole( 'cell', { name: '“Shop” (Edit)' } ).hover(); + await page + .getByLabel( 'Quick edit “Shop” inline' ) + .click( { force: true } ); + await page + .getByLabel( 'Status Published Pending' ) + .selectOption( 'publish', { exact: true } ); + await page.getByRole( 'button', { name: 'Update' } ).click(); + await page + .locator( '#a11y-speak-polite', { hasText: 'Changes saved.' } ) + .waitFor(); } ); test( 'Check the title of the shop page after the page has been deleted', async ( { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/wordpress-post.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/wordpress-post.spec.js index fd6f46e0497..a9a3407d06f 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/wordpress-post.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/wordpress-post.spec.js @@ -6,7 +6,13 @@ const test = baseTest.extend( { test( 'logged-in customer can comment on a post', - { tag: [ '@non-critical' ] }, + { + tag: [ + '@non-critical', + '@skip-on-default-wpcom', + '@skip-on-default-pressable', + ], + }, async ( { page } ) => { await page.goto( 'hello-world/' ); await expect( diff --git a/plugins/woocommerce/tests/e2e-pw/utils/editor.js b/plugins/woocommerce/tests/e2e-pw/utils/editor.js index d2cf9d6dfe3..c837a59594e 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/editor.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/editor.js @@ -45,6 +45,7 @@ const goToPageEditor = async ( { page } ) => { ); await page.goto( 'wp-admin/post-new.php?post_type=page' ); await disableWelcomeModal( { page } ); + await closeChoosePatternModal( { page } ); await responsePromise; }; From aaae67f03266a2b18093f8a3bf0bfdf21bc9e862 Mon Sep 17 00:00:00 2001 From: Jacklyn Biggin Date: Wed, 4 Sep 2024 16:25:24 -0400 Subject: [PATCH 09/57] Fix incorrectly rendered HTML tags in docs (#50966) * Fix incorrectly rendering custom fields docs page * Fix additional incorrectly rendering html tags in docs --- ...ing-a-custom-field-to-variable-products.md | 56 +++---- .../before-login--register-form.md | 22 +-- docs/docs-manifest.json | 12 +- .../handling-merchant-onboarding.md | 142 +++++++++--------- 4 files changed, 116 insertions(+), 116 deletions(-) diff --git a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md index e955b142144..3b7a53c9fe8 100644 --- a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md +++ b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md @@ -59,7 +59,7 @@ For our Woo extension, we'll be appending our field right at the end with `wooco Let's get started with creating a new class which will hold the code for the field. Add a new file with the name `class-product-fields.php` to the `/includes/admin/` folder. Within the class, we add our namespace, an abort if anyone tries to call the file directly and a \_\_construct method which calls the `hooks()` method: ```php -hooks(); + $this->hooks(); } private function hooks() {} @@ -93,19 +93,19 @@ With the class set up and being called, we can create a function to add the cust ```php public function add_field() { global $product_object; - ?> -
- '_new_stock_information', - 'label' => __( 'New Stock', 'woo_product_field' ), - 'description' => __( 'Information shown in store', 'woo_product_field' ), - 'desc_tip' => true, - 'value' => $product_object->get_meta( '_new_stock_information' ) + 'id' => '_new_stock_information', + 'label' => __( 'New Stock', 'woo_product_field' ), + 'description' => __( 'Information shown in store', 'woo_product_field' ), + 'desc_tip' => true, + 'value' => $product_object->get_meta( '_new_stock_information' ) ) - ); ?> -
- update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'] ) ); - $product->save_meta_data(); + $product->update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'] ) ); + $product->save_meta_data(); } } ``` @@ -142,12 +142,12 @@ If we add data and save the product, then the new meta data is inserted into the At this point you have a working extension that saves a custom field for a product as product meta. Showing the field in the store -If we want to display the new field in our store, then we can do this with the `get_meta()` method of the Woo product class: `$product->get_meta( '\_new_stock_information' )` +If we want to display the new field in our store, then we can do this with the `get_meta()` method of the Woo product class: `$product->get_meta( '\_new_stock_information' )` Let's get started by creating a new file /includes/class-product.php. You may have noticed that this is outside the `/admin/` folder as this code will run in the front. So when we set up the class, we also adjust the namespace accordingly: ```php -hooks(); + $this->hooks(); } private function hooks() { } @@ -190,9 +190,9 @@ In our function we output the stock information with the [appropriate escape fun ```php public function add_stock_info() { global $product; - ?> -

get_meta( '_new_stock_information' ) ); ?>

- ID ); + $variation_product = wc_get_product( $variation->ID ); woocommerce_wp_text_input( array( - 'id' => '\_new_stock_information' . '[' . $loop . ']', - 'label' => \_\_( 'New Stock Information', 'woo_product_field' ), - 'wrapper_class' => 'form-row form-row-full', - 'value' => $variation_product->get_meta( '\_new_stock_information' ) + 'id' => '\_new_stock_information' . '[' . $loop . ']', + 'label' => \_\_( 'New Stock Information', 'woo_product_field' ), + 'wrapper_class' => 'form-row form-row-full', + 'value' => $variation_product->get_meta( '\_new_stock_information' ) ) ); } @@ -242,8 +242,8 @@ For saving we use: public function save_variation_field( $variation_id, $i ) { if ( isset( $_POST['_new_stock_information'][$i] ) ) { $variation_product = wc_get_product( $variation_id ); - $variation_product->update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'][$i] ) ); - $variation_product->save_meta_data(); + $variation_product->update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'][$i] ) ); + $variation_product->save_meta_data(); } } ``` diff --git a/docs/code-snippets/before-login--register-form.md b/docs/code-snippets/before-login--register-form.md index ca6e9dcfdc2..915f2cc5f7c 100644 --- a/docs/code-snippets/before-login--register-form.md +++ b/docs/code-snippets/before-login--register-form.md @@ -14,17 +14,17 @@ if ( ! function_exists( 'YOUR_PREFIX_login_message' ) ) { */ function YOUR_PREFIX_login_message() { if ( get_option( 'woocommerce_enable_myaccount_registration' ) == 'yes' ) { - ?> -
-

-
    -
  • -
  • -
  • -
  • -
-
- { +const Task = ( { onComplete, task, query } ) => { // Implement your task UI/feature here. - return
; + return <div></div>; }; registerPlugin( 'add-task-content', { - render: () => ( - - { ( { onComplete, query, task } ) => ( - + render: () => ( + <WooOnboardingTask id="my-task"> + { ( { onComplete, query, task } ) => ( + <Task onComplete={ onComplete } task={ task } query={ query } /> ) } - + </WooOnboardingTask> ), } ); registerPlugin( 'add-task-list-item', { scope: 'woocommerce-tasks', - render: () => ( - - { ( { defaultTaskItem: DefaultTaskItem } ) => ( + render: () => ( + <WooOnboardingTaskListItem id="my-task"> + { ( { defaultTaskItem: DefaultTaskItem } ) => ( // Add a custom wrapper around the default task item. -
- -
+ > + <DefaultTaskItem /> + </div> ) } -
+ </WooOnboardingTaskListItem> ), } ); ``` @@ -168,37 +168,37 @@ import { registerPlugin } from '@wordpress/plugins'; Next, we create a [functional component](https://reactjs.org/docs/components-and-props.html) that returns our task card. The intermixed JavaScript/HTML syntax we're using here is called JSX. If you're unfamiliar with it, you can [read more about it in the React docs](https://reactjs.org/docs/introducing-jsx.html). ```js -const Task = ( { onComplete, task } ) => { +const Task = ( { onComplete, task } ) => { const { actionTask } = useDispatch( ONBOARDING_STORE_NAME ); const { isActioned } = task; return ( - - + <Card className="woocommerce-task-card"> + <CardBody> { __( "This task's completion status is dependent on being actioned. The action button below will action this task, while the complete button will optimistically complete the task in the task list and redirect back to the task list. Note that in this example, the task must be actioned for completion to persist.", 'plugin-domain' ) }{ ' ' } -
-
+ <br /> + <br /> { __( 'Task actioned status: ', 'plugin-domain' ) }{ ' ' } { isActioned ? 'actioned' : 'not actioned' } -
-
-
- - -
-
-
+ </button> + </div> + </CardBody> + </Card> ); }; ``` @@ -211,14 +211,14 @@ Next, we register the Task component as a plugin named "add-task-content" using ```js registerPlugin( 'add-task-content', { - render: () => ( + render: () => ( { ( { onComplete, // eslint-disable-next-line @typescript-eslint/no-unused-vars query, task, - } ) => } + } ) => } ), scope: 'woocommerce-tasks', @@ -232,20 +232,20 @@ Finally, we register another plugin named "my-task-list-item-plugin." This plugi ```js registerPlugin( 'my-task-list-item-plugin', { scope: 'woocommerce-tasks', - render: () => ( - - { ( { defaultTaskItem: DefaultTaskItem } ) => ( + render: () => ( + <WooOnboardingTaskListItem id="my-task"> + { ( { defaultTaskItem: DefaultTaskItem } ) => ( // Add a custom wrapper around the default task item. -
- -
+ > + <DefaultTaskItem /> + </div> ) } -
+ </WooOnboardingTaskListItem> ), } ); ``` @@ -344,7 +344,7 @@ import { addFilter } from '@wordpress/hooks'; addFilter( 'woocommerce_admin_homescreen_quicklinks', 'my-extension', - ( quickLinks ) => { + ( quickLinks ) => { return [ ...quickLinks, { @@ -374,7 +374,7 @@ Despite being a part of the new React-powered admin experience in WooCommerce, A The recommended approach for using Admin Notes is to encapsulate your note within its own class that uses the [NoteTraits](https://github.com/woocommerce/woocommerce-admin/blob/831c9ff13a862f22cf53d3ae676daeabbefe90ad/src/Notes/NoteTraits.php) trait included with WooCommerce Admin. Below is a simple example of what this might look like: ```php -set_title( 'Getting Started' ); + $note->set_title( 'Getting Started' ); // Set our note's content. - $note->set_content( + $note->set_content( sprintf( 'Extension activated on %s.', $activated_time_formatted ) @@ -436,41 +436,41 @@ class ExampleNote { // You can use this property to re-localize notes on the fly, but // that is just one use. You can store other data here too. This // is backed by a longtext column in the database. - $note->set_content_data( (object) array( - 'getting_started' => true, - 'activated' => $activated_time, - 'activated_formatted' => $activated_time_formatted + $note->set_content_data( (object) array( + 'getting_started' => true, + 'activated' => $activated_time, + 'activated_formatted' => $activated_time_formatted ) ); // Set the type of the note. Note types are defined as enum-style // constants in the Note class. Available note types are: // error, warning, update, info, marketing. - $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); + $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); // Set the type of layout the note uses. Supported layout types are: // 'banner', 'plain', 'thumbnail' - $note->set_layout( 'plain' ); + $note->set_layout( 'plain' ); // Set the image for the note. This property renders as the src // attribute for an img tag, so use a string here. - $note->set_image( '' ); + $note->set_image( '' ); // Set the note name and source. You should store your extension's // name (slug) in the source property of the note. You can use // the name property of the note to support multiple sub-types of // notes. This also gives you a handy way of namespacing your notes. - $note->set_source( 'inbox-note-example'); - $note->set_name( self::NOTE_NAME ); + $note->set_source( 'inbox-note-example'); + $note->set_name( self::NOTE_NAME ); // Add action buttons to the note. A note can support 0, 1, or 2 actions. // The first parameter is the action name, which can be used for event handling. // The second parameter renders as the label for the button. // The third parameter is an optional URL for actions that require navigation. - $note->add_action( + $note->add_action( 'settings', 'Open Settings', '?page=wc-settings&tab=general' ); - $note->add_action( + $note->add_action( 'learn_more', 'Learn More', 'https://example.com' ); @@ -559,12 +559,12 @@ Next, we'll instantiate a new `Note` object. Once we have an instance of the Note class, we can work with its API to set its properties, starting with its title. -`$note->set_title( 'Getting Started' );` +`$note->set_title( 'Getting Started' );` Then we'll use some of the timestamp data we collected above to set the note's content. ```php -$note->set_content( +$note->set_content( sprintf( 'Extension activated on %s.', $activated_time_formatted ) @@ -574,41 +574,41 @@ $note->set_content( In addition to regular content, notes also support structured content using the `content_data` property. You can use this property to re-localize notes on the fly, but that is just one use case. You can store other data here too. This is backed by a `longtext` column in the database. ```php -$note->set_content_data( (object) array( - 'getting_started' => true, - 'activated' => $activated_time, - 'activated_formatted' => $activated_time_formatted +$note->set_content_data( (object) array( + 'getting_started' => true, + 'activated' => $activated_time, + 'activated_formatted' => $activated_time_formatted ) ); ``` Next, we'll set the note's `type` property. Note types are defined as enum-style class constants in the `Note` class. Available note types are _error_, _warning_, _update_, _info_, and _marketing_. When selecting a note type, be aware that the _error_ and _update_ result in the note being shown as a Store Alert, not in the Inbox. It's best to avoid using these types of notes unless you absolutely need to. -`$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );` +`$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );` Admin Notes also support a few different layouts. You can specify `banner`, `plain`, or `thumbnail` as the layout. If you're interested in seeing the different layouts in action, take a look at [this simple plugin](https://gist.github.com/octaedro/864315edaf9c6a2a6de71d297be1ed88) that you can install to experiment with them. We'll choose `plain` as our layout, but it's also the default, so we could leave this property alone and the effect would be the same. -`$note->set_layout( 'plain' );` +`$note->set_layout( 'plain' );` If you have an image that you want to add to your Admin Note, you can specify it using the `set_image` function. This property ultimately renders as the `src` attribute on an `img` tag, so use a string here. -`$note->set_image( '' );` +`$note->set_image( '' );` Next, we'll set the values for our Admin Note's `name` and `source` properties. As a best practice, you should store your extension's name (i.e. its slug) in the `source` property of the note. You can use the `name` property to support multiple sub-types of notes. This gives you a handy way of namespacing your notes and managing them at both a high and low level. ```php -$note->set_source( 'inbox-note-example'); -$note->set_name( self::NOTE_NAME ); +$note->set_source( 'inbox-note-example'); +$note->set_name( self::NOTE_NAME ); ``` Admin Notes can support 0, 1, or 2 actions (buttons). You can use these actions to capture events that trigger asynchronous processes or help the merchant navigate to a particular view to complete a step, or even simply to provide an external link for further information. The `add_action()` function takes up to three arguments. The first is the action name, which can be used for event handling, the second renders as a label for the action's button, and the third is an optional URL for actions that require navigation. ```php -$note->add_action( +$note->add_action( 'settings', 'Open Settings', '?page=wc-settings&tab=general' ); -$note->add_action( +$note->add_action( 'learn_more', 'Learn More', 'https://example.com' ); ``` From dcbc4dc588b0581e73c9f664f2023a6eeedebbce Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:14:32 +0800 Subject: [PATCH 10/57] dev: update webpack build to dynamiclly fetch wp-admin-scripts (#51133) * dev: update webpack build to dynamiclly fetch wp-admin-scripts * Update README.md * Update README.md --- .../client/wp-admin-scripts/README.md | 17 +++++- plugins/woocommerce-admin/webpack.config.js | 55 +++++++------------ .../dev-webpack-dynamic-wp-admin-scripts | 4 ++ 3 files changed, 37 insertions(+), 39 deletions(-) create mode 100644 plugins/woocommerce/changelog/dev-webpack-dynamic-wp-admin-scripts diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/README.md b/plugins/woocommerce-admin/client/wp-admin-scripts/README.md index e3fd58aeb88..71551988cee 100644 --- a/plugins/woocommerce-admin/client/wp-admin-scripts/README.md +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/README.md @@ -1,5 +1,16 @@ -Scripts located in this directory are meant to be loaded on wp-admin pages outside the context of WooCommerce Admin, such as the post editor. Adding the script name to `wpAdminScripts` in the Webpack config will automatically build these scripts. +# WP Admin Scripts -Scripts must be manually enqueued with any necessary dependencies. For example, `onboarding-homepage-notice` uses the WooCommerce navigation package: +Scripts located in this directory are meant to be loaded on wp-admin pages outside the context of WooCommerce Admin, such as the post editor. + +Each subdirectory of this directory is automatically added as an entrypoint in the [webpack build script](../../../../plugins/woocommerce-admin/webpack.config.js#L71) of WooCommerce Admin. When they are built, each one results in a pair of `.asset.php` and `.js` file in the `build\wp-admin-scripts\` path, but they will not be automatically loaded on every PHP page. + +The `.asset.php` file contains a list of automatically detected dependencies generated by the Dependency Extraction Webpack Plugin, so JS dependencies are not required to be manually enqueued as long as the `WCAdminAssets::register_script()` function is used to enqueue the script. + +As an example, the `payment-method-promotions` wp-admin-scripts has generated `payment-method-promotions.asset.php` with the contents below: + +` array('react', 'wc-components', 'wc-experimental', 'wc-settings', 'wc-store-data', 'wc-tracks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'e64b1e69145febe07734');` + +And is registered as a script like so: [`plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php`](../../../../plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php#L179), + +`WCAdminAssets::register_script( 'wp-admin-scripts', 'payment-method-promotions', true );` -`wp_enqueue_script( 'onboarding-homepage-notice', Loader::get_url( 'wp-scripts/onboarding-homepage-notice.js' ), array( 'wc-navigation' ) );` \ No newline at end of file diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 469d48e3f3a..ad264135d62 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -3,6 +3,7 @@ */ const { get } = require( 'lodash' ); const path = require( 'path' ); +const fs = require( 'fs' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); const BundleAnalyzerPlugin = @@ -25,7 +26,22 @@ const WC_ADMIN_PHASE = process.env.WC_ADMIN_PHASE || 'development'; const isHot = Boolean( process.env.HOT ); const isProduction = NODE_ENV === 'production'; +const getSubdirectoriesAt = ( searchPath ) => { + const dir = path.resolve( searchPath ); + return fs + .readdirSync( dir, { withFileTypes: true } ) + .filter( ( entry ) => entry.isDirectory() ) + .map( ( entry ) => entry.name ); +}; + +const WC_ADMIN_PACKAGES_DIR = '../../packages/js'; +const WP_ADMIN_SCRIPTS_DIR = './client/wp-admin-scripts'; + +// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin +// See ./client/wp-admin-scripts/README.md for more details +const wpAdminScripts = getSubdirectoriesAt( WP_ADMIN_SCRIPTS_DIR ); // automatically include all subdirs const wcAdminPackages = [ + // we use a whitelist for this instead of dynamically generating it because not all folders are packages meant for consumption 'admin-layout', 'components', 'csv-export', @@ -44,49 +60,16 @@ const wcAdminPackages = [ 'product-editor', 'remote-logging', ]; -// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin -// See ./client/wp-admin-scripts/README.md for more details -const wpAdminScripts = [ - 'marketing-coupons', - 'onboarding-homepage-notice', - 'onboarding-product-notice', - 'onboarding-product-import-notice', - 'onboarding-tax-notice', - 'print-shipping-label-banner', - 'beta-features-tracking-modal', - 'payment-method-promotions', - 'onboarding-load-sample-products-notice', - 'product-tracking', - 'add-term-tracking', - 'attributes-tracking', - 'category-tracking', - 'tags-tracking', - 'product-tour', - 'wc-addons-tour', - 'settings-tracking', - 'order-tracking', - 'product-import-tracking', - 'variable-product-tour', - 'product-category-metabox', - 'shipping-settings-region-picker', - 'command-palette', - 'command-palette-analytics', - 'woo-connect-notice', - 'woo-plugin-update-connect-notice', - 'woo-enable-autorenew', - 'woo-renew-subscription', - 'woo-subscriptions-notice', - 'woo-product-usage-notice', -]; + const getEntryPoints = () => { const entryPoints = { app: './client/index.js', }; wcAdminPackages.forEach( ( name ) => { - entryPoints[ name ] = `../../packages/js/${ name }`; + entryPoints[ name ] = `${ WC_ADMIN_PACKAGES_DIR }/${ name }`; } ); wpAdminScripts.forEach( ( name ) => { - entryPoints[ name ] = `./client/wp-admin-scripts/${ name }`; + entryPoints[ name ] = `${ WP_ADMIN_SCRIPTS_DIR }/${ name }`; } ); return entryPoints; }; diff --git a/plugins/woocommerce/changelog/dev-webpack-dynamic-wp-admin-scripts b/plugins/woocommerce/changelog/dev-webpack-dynamic-wp-admin-scripts new file mode 100644 index 00000000000..f3f28f026e2 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-webpack-dynamic-wp-admin-scripts @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Updated webpack build script for wc admin so that the wp-admin-scripts are dynamically fetched from the fs instead of a hard list From ccd5669c2175586cefea83e6d0f4eddd363947c3 Mon Sep 17 00:00:00 2001 From: Ivan Stojadinov Date: Thu, 5 Sep 2024 10:56:48 +0200 Subject: [PATCH 11/57] [e2e tests] External sites - update /merchant tests, part 2 (#51016) * Handle error for api.delete( coupons/ ) * Choose any product that contains "product" in its name * Expand Pressable test suite * Add changefile(s) from automation for the following project(s): woocommerce * Add missing `throw` error * Make product name more unique with `Date.now()` --------- Co-authored-by: github-actions --- ...016-e2e-external-sites-update-merchant-tests-pt2 | 4 ++++ .../envs/default-pressable/playwright.config.js | 5 +++++ .../merchant/create-restricted-coupons.spec.js | 13 +++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/51016-e2e-external-sites-update-merchant-tests-pt2 diff --git a/plugins/woocommerce/changelog/51016-e2e-external-sites-update-merchant-tests-pt2 b/plugins/woocommerce/changelog/51016-e2e-external-sites-update-merchant-tests-pt2 new file mode 100644 index 00000000000..23131053e0c --- /dev/null +++ b/plugins/woocommerce/changelog/51016-e2e-external-sites-update-merchant-tests-pt2 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update /merchant tests (second five files), so they are passing against Pressable env. \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js index 48f83f3e12b..058ae9aef7f 100644 --- a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js @@ -16,6 +16,11 @@ config = { '**/admin-marketing/**/*.spec.js', '**/admin-tasks/**/*.spec.js', '**/customize-store/**/*.spec.js', + '**/merchant/create-page.spec.js', + '**/merchant/create-post.spec.js', + '**/merchant/create-restricted-coupons.spec.js', + '**/merchant/create-shipping-classes.spec.js', + '**/merchant/create-shipping-zones.spec.js', '**/merchant/command-palette.spec.js', '**/merchant/create-cart-block.spec.js', '**/merchant/create-checkout-block.spec.js', diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-restricted-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-restricted-coupons.spec.js index dd6941c6cc9..73ee10db158 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-restricted-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-restricted-coupons.spec.js @@ -74,15 +74,24 @@ const test = baseTest.extend( { coupon: async ( { api }, use ) => { const coupon = {}; await use( coupon ); - await api.delete( `coupons/${ coupon.id }`, { force: true } ); + await api + .delete( `coupons/${ coupon.id }`, { force: true } ) + .then( ( response ) => { + console.log( 'Delete successful:', response.data ); + } ) + .catch( ( error ) => { + console.log( 'Error response data:', error.response.data ); + throw new Error( error.response.data ); + } ); }, product: async ( { api }, use ) => { let product = {}; + const productName = `Product ${ Date.now() }`; await api .post( 'products', { - name: 'Product', + name: productName, regular_price: '100', } ) .then( ( response ) => { From 5a5b1f04785dd90df2cfc47b907f15b94bc17a11 Mon Sep 17 00:00:00 2001 From: Ivan Stojadinov Date: Thu, 5 Sep 2024 11:19:23 +0200 Subject: [PATCH 12/57] [e2e tests] External sites - update /merchant tests, part 3 (#51158) * Skip `Merchant can view a list of all customers, filter and download` * Skip `can receive new order email` * Expand Pressable run with new set of /merchant tests * Move `should filter by All` into for loop with existing tests * Wait between two actions with `networkidle` * Skip two tests on Pressable * Add changefile(s) from automation for the following project(s): woocommerce * Remove `networkidle` and use class `current` for the selected filter * Fix ESLint error --------- Co-authored-by: github-actions --- ...e-external-sites-update-merchant-tests-pt3 | 4 + .../default-pressable/playwright.config.js | 15 + .../create-woocommerce-blocks.spec.js | 2 +- .../create-woocommerce-patterns.spec.js | 2 +- .../tests/merchant/customer-list.spec.js | 257 +++++++++--------- .../tests/merchant/order-emails.spec.js | 98 ++++--- .../merchant/order-status-filter.spec.js | 17 +- 7 files changed, 214 insertions(+), 181 deletions(-) create mode 100644 plugins/woocommerce/changelog/51158-e2e-external-sites-update-merchant-tests-pt3 diff --git a/plugins/woocommerce/changelog/51158-e2e-external-sites-update-merchant-tests-pt3 b/plugins/woocommerce/changelog/51158-e2e-external-sites-update-merchant-tests-pt3 new file mode 100644 index 00000000000..c9c03552774 --- /dev/null +++ b/plugins/woocommerce/changelog/51158-e2e-external-sites-update-merchant-tests-pt3 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update /merchant tests so they are passing against Pressable env. \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js index 058ae9aef7f..1817c3097c6 100644 --- a/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/envs/default-pressable/playwright.config.js @@ -26,6 +26,21 @@ config = { '**/merchant/create-checkout-block.spec.js', '**/merchant/create-coupon.spec.js', '**/merchant/create-order.spec.js', + '**/merchant/create-woocommerce-blocks.spec.js', + '**/merchant/create-woocommerce-patterns.spec.js', + '**/merchant/customer-list.spec.js', + '**/merchant/customer-payment-page.spec.js', + '**/merchant/launch-your-store.spec.js', + '**/merchant/lost-password.spec.js', + '**/merchant/order-bulk-edit.spec.js', + '**/merchant/order-coupon.spec.js', + '**/merchant/order-edit.spec.js', + '**/merchant/order-emails.spec.js', + '**/merchant/order-refund.spec.js', + '**/merchant/order-search.spec.js', + '**/merchant/order-status-filter.spec.js', + '**/merchant/page-loads.spec.js', + '**/merchant/product-create-simple.spec.js', '**/shopper/checkout-create-account.spec.js', '**/shopper/checkout-login.spec.js', '**/shopper/checkout.spec.js', diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js index ed5768ae08e..7ade737367f 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js @@ -52,7 +52,7 @@ const test = baseTest.extend( { test.describe( 'Add WooCommerce Blocks Into Page', - { tag: [ '@gutenberg', '@services' ] }, + { tag: [ '@gutenberg', '@services', '@skip-on-default-pressable' ] }, () => { test.beforeAll( async ( { api } ) => { // add product attribute diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js index 27071e6cb29..ee6d56b0d9c 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js @@ -28,7 +28,7 @@ const test = baseTest.extend( { test.describe( 'Add WooCommerce Patterns Into Page', - { tag: [ '@gutenberg', '@services' ] }, + { tag: [ '@gutenberg', '@services', '@skip-on-default-pressable' ] }, () => { test( 'can insert WooCommerce patterns into page', async ( { page, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js index 68a029eb9a6..1b8cb58846a 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js @@ -83,144 +83,157 @@ test.describe( 'Merchant > Customer List', { tag: '@services' }, () => { await context.route( '**/users/**', ( route ) => route.abort() ); } ); - test( 'Merchant can view a list of all customers, filter and download', async ( { - page, - customers, - } ) => { - await test.step( 'Go to the customers reports page', async () => { - const responsePromise = page.waitForResponse( - '**/wp-json/wc-analytics/reports/customers?orderby**' - ); - await page.goto( - '/wp-admin/admin.php?page=wc-admin&path=%2Fcustomers' - ); - await responsePromise; - } ); + test( + 'Merchant can view a list of all customers, filter and download', + { tag: '@skip-on-default-pressable' }, + async ( { page, customers } ) => { + await test.step( 'Go to the customers reports page', async () => { + const responsePromise = page.waitForResponse( + '**/wp-json/wc-analytics/reports/customers?orderby**' + ); + await page.goto( + '/wp-admin/admin.php?page=wc-admin&path=%2Fcustomers' + ); + await responsePromise; + } ); - // may have more than 3 customers due to guest orders - // await test.step( 'Check that 3 customers are displayed', async () => { - // await expect( - // page.getByText( '3customers0Average orders$0.' ) - // ).toBeVisible(); - // } ); + // may have more than 3 customers due to guest orders + // await test.step( 'Check that 3 customers are displayed', async () => { + // await expect( + // page.getByText( '3customers0Average orders$0.' ) + // ).toBeVisible(); + // } ); - await test.step( 'Check that the customers are displayed in the list', async () => { - for ( const customer of customers ) { - await expect( - page.getByRole( 'link', { name: customer.email } ) - ).toBeVisible(); - } - } ); + await test.step( 'Check that the customers are displayed in the list', async () => { + for ( const customer of customers ) { + await expect( + page.getByRole( 'link', { name: customer.email } ) + ).toBeVisible(); + } + } ); - await test.step( 'Check that the customer list can be filtered by first name', async () => { - let x = 1; - for ( const customer of customers ) { + await test.step( 'Check that the customer list can be filtered by first name', async () => { + let x = 1; + for ( const customer of customers ) { + await page + .getByRole( 'combobox', { + expanded: false, + disabled: false, + } ) + .click(); + await page + .getByRole( 'combobox', { + expanded: false, + disabled: false, + } ) + .pressSequentially( + `${ customer.first_name } ${ customer.last_name }` + ); + await page + .getByRole( 'option', { + name: `All customers with names that include ${ customer.first_name } ${ customer.last_name }`, + exact: true, + } ) + .waitFor(); + await page + .getByRole( 'option', { + name: `${ customer.first_name } ${ customer.last_name }`, + exact: true, + } ) + .waitFor(); + await page + .getByRole( 'option', { + name: `All customers with names that include ${ customer.first_name } ${ customer.last_name }`, + exact: true, + } ) + .click( { delay: 300 } ); + await expect( + page.getByRole( 'link', { name: customer.email } ) + ).toBeVisible(); + await expect( + page.getByText( `${ x }customer` ) + ).toBeVisible(); + x++; + } + await page.getByRole( 'button', { name: 'Clear all' } ).click(); + } ); + + await test.step( 'Hide and display columns', async () => { await page - .getByRole( 'combobox', { - expanded: false, - disabled: false, + .getByRole( 'button', { + name: 'Choose which values to display', } ) .click(); + // hide a few columns + await page.getByRole( 'menu' ).getByText( 'Username' ).click(); await page - .getByRole( 'combobox', { - expanded: false, - disabled: false, - } ) - .pressSequentially( - `${ customer.first_name } ${ customer.last_name }` - ); + .getByRole( 'menu' ) + .getByText( 'Last active' ) + .click(); await page - .getByRole( 'option', { - name: `All customers with names that include ${ customer.first_name } ${ customer.last_name }`, - exact: true, - } ) - .waitFor(); - await page - .getByRole( 'option', { - name: `${ customer.first_name } ${ customer.last_name }`, - exact: true, - } ) - .waitFor(); - await page - .getByRole( 'option', { - name: `All customers with names that include ${ customer.first_name } ${ customer.last_name }`, - exact: true, - } ) - .click( { delay: 300 } ); + .getByRole( 'menu' ) + .getByText( 'Total spend' ) + .click(); + + // click to close the menu + await page.getByText( 'Show:' ).click(); + await expect( - page.getByRole( 'link', { name: customer.email } ) + page.getByRole( 'columnheader', { name: 'Username' } ) + ).toBeHidden(); + await expect( + page.getByRole( 'columnheader', { name: 'Last active' } ) + ).toBeHidden(); + await expect( + page.getByRole( 'columnheader', { name: 'Total spend' } ) + ).toBeHidden(); + + // show the columns again + await page + .getByRole( 'button', { + name: 'Choose which values to display', + } ) + .click(); + await page.getByRole( 'menu' ).getByText( 'Username' ).click(); + await page + .getByRole( 'menu' ) + .getByText( 'Last active' ) + .click(); + await page + .getByRole( 'menu' ) + .getByText( 'Total spend' ) + .click(); + + // click to close the menu + await page.getByText( 'Show:' ).click(); + + await expect( + page.getByRole( 'columnheader', { name: 'Username' } ) ).toBeVisible(); await expect( - page.getByText( `${ x }customer` ) + page.getByRole( 'columnheader', { name: 'Last active' } ) ).toBeVisible(); - x++; - } - await page.getByRole( 'button', { name: 'Clear all' } ).click(); - } ); + await expect( + page.getByRole( 'columnheader', { name: 'Total spend' } ) + ).toBeVisible(); + } ); - await test.step( 'Hide and display columns', async () => { - await page - .getByRole( 'button', { - name: 'Choose which values to display', - } ) - .click(); - // hide a few columns - await page.getByRole( 'menu' ).getByText( 'Username' ).click(); - await page.getByRole( 'menu' ).getByText( 'Last active' ).click(); - await page.getByRole( 'menu' ).getByText( 'Total spend' ).click(); + await test.step( 'Download the customer list', async () => { + const downloadPromise = page.waitForEvent( 'download' ); + await page.getByRole( 'button', { name: 'Download' } ).click(); + const download = await downloadPromise; - // click to close the menu - await page.getByText( 'Show:' ).click(); + const today = new Date(); + const year = today.getFullYear(); + const month = String( today.getMonth() + 1 ).padStart( 2, '0' ); + const day = String( today.getDate() ).padStart( 2, '0' ); - await expect( - page.getByRole( 'columnheader', { name: 'Username' } ) - ).toBeHidden(); - await expect( - page.getByRole( 'columnheader', { name: 'Last active' } ) - ).toBeHidden(); - await expect( - page.getByRole( 'columnheader', { name: 'Total spend' } ) - ).toBeHidden(); + const filename = `customers_${ year }-${ month }-${ day }_orderby-date-last-active_order-desc_page-wc-admin_path--customers.csv`; - // show the columns again - await page - .getByRole( 'button', { - name: 'Choose which values to display', - } ) - .click(); - await page.getByRole( 'menu' ).getByText( 'Username' ).click(); - await page.getByRole( 'menu' ).getByText( 'Last active' ).click(); - await page.getByRole( 'menu' ).getByText( 'Total spend' ).click(); - - // click to close the menu - await page.getByText( 'Show:' ).click(); - - await expect( - page.getByRole( 'columnheader', { name: 'Username' } ) - ).toBeVisible(); - await expect( - page.getByRole( 'columnheader', { name: 'Last active' } ) - ).toBeVisible(); - await expect( - page.getByRole( 'columnheader', { name: 'Total spend' } ) - ).toBeVisible(); - } ); - - await test.step( 'Download the customer list', async () => { - const downloadPromise = page.waitForEvent( 'download' ); - await page.getByRole( 'button', { name: 'Download' } ).click(); - const download = await downloadPromise; - - const today = new Date(); - const year = today.getFullYear(); - const month = String( today.getMonth() + 1 ).padStart( 2, '0' ); - const day = String( today.getDate() ).padStart( 2, '0' ); - - const filename = `customers_${ year }-${ month }-${ day }_orderby-date-last-active_order-desc_page-wc-admin_path--customers.csv`; - - await expect( download.suggestedFilename() ).toBe( filename ); - } ); - } ); + await expect( download.suggestedFilename() ).toBe( filename ); + } ); + } + ); test( 'Merchant can view a single customer', async ( { page, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-emails.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-emails.spec.js index 27c6b7ea7cd..b5896e537b3 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-emails.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-emails.spec.js @@ -71,53 +71,63 @@ test.describe( } ); } ); - test( 'can receive new order email', async ( { page, baseURL } ) => { - // New order emails are sent automatically when we create a simple order. Verify that we get these. - // Need to create a new order for this test because we clear logs before each run. - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); - await api - .post( 'orders', { - status: 'processing', - billing: customerBilling, - } ) - .then( ( response ) => { - newOrderId = response.data.id; + test( + 'can receive new order email', + { tag: '@skip-on-default-pressable' }, + async ( { page, baseURL } ) => { + // New order emails are sent automatically when we create a simple order. Verify that we get these. + // Need to create a new order for this test because we clear logs before each run. + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', } ); - // search to narrow it down to just the messages we want - await page.goto( - `/wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( - customerBilling.email - ) }` - ); - await expect( - page.locator( 'td.column-receiver >> nth=1' ) - ).toContainText( admin.email ); - await expect( - page.locator( 'td.column-subject >> nth=1' ) - ).toContainText( `[${ storeName }]: New order #${ newOrderId }` ); + await api + .post( 'orders', { + status: 'processing', + billing: customerBilling, + } ) + .then( ( response ) => { + newOrderId = response.data.id; + } ); + // search to narrow it down to just the messages we want + await page.goto( + `/wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerBilling.email + ) }` + ); + await expect( + page.locator( 'td.column-receiver >> nth=1' ) + ).toContainText( admin.email ); + await expect( + page.locator( 'td.column-subject >> nth=1' ) + ).toContainText( + `[${ storeName }]: New order #${ newOrderId }` + ); - // look at order email contents - await page - .getByRole( 'button', { name: 'View log' } ) - .last() - .click(); + // look at order email contents + await page + .getByRole( 'button', { name: 'View log' } ) + .last() + .click(); - await expect( - page.getByText( 'Receiver wordpress@example.com' ) - ).toBeVisible(); - await expect( - page.getByText( 'Subject [WooCommerce Core E2E' ) - ).toBeVisible(); - await page.getByRole( 'link', { name: 'json' } ).click(); - await expect( - page.locator( '#wp-mail-logging-modal-content-body-content' ) - ).toContainText( 'You’ve received the following order from :' ); - } ); + await expect( + page.getByText( 'Receiver wordpress@example.com' ) + ).toBeVisible(); + await expect( + page.getByText( 'Subject [WooCommerce Core E2E' ) + ).toBeVisible(); + await page.getByRole( 'link', { name: 'json' } ).click(); + await expect( + page.locator( + '#wp-mail-logging-modal-content-body-content' + ) + ).toContainText( + 'You’ve received the following order from :' + ); + } + ); test( 'can receive completed email', async ( { page, baseURL } ) => { // Completed order emails are sent automatically when an order's payment is completed. diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-status-filter.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-status-filter.spec.js index 31030d379ef..2ecf45d1c96 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-status-filter.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-status-filter.spec.js @@ -6,6 +6,7 @@ const statusColumnTextSelector = 'mark.order-status > span'; // Define order statuses to filter against const orderStatus = [ + [ 'All', 'all' ], [ 'Pending payment', 'wc-pending' ], [ 'Processing', 'wc-processing' ], [ 'On hold', 'wc-on-hold' ], @@ -55,19 +56,6 @@ test.describe( await api.post( 'orders/batch', { delete: [ ...orderBatchId ] } ); } ); - test( 'should filter by All', async ( { page } ) => { - await page.goto( '/wp-admin/admin.php?page=wc-orders' ); - - await page.locator( 'li.all > a' ).click(); - // because tests are running in parallel, we can't know how many orders there - // are beyond the ones we created here. - for ( let i = 0; i < orderStatus.length; i++ ) { - const statusTag = 'text=' + orderStatus[ i ][ 0 ]; - const countElements = await page.locator( statusTag ).count(); - await expect( countElements ).toBeGreaterThan( 0 ); - } - } ); - for ( let i = 0; i < orderStatus.length; i++ ) { test( `should filter by ${ orderStatus[ i ][ 0 ] }`, async ( { page, @@ -75,6 +63,9 @@ test.describe( await page.goto( '/wp-admin/admin.php?page=wc-orders' ); await page.locator( `li.${ orderStatus[ i ][ 1 ] }` ).click(); + await expect( + page.locator( `li.${ orderStatus[ i ][ 1 ] } > a.current` ) + ).toBeVisible(); const countElements = await page .locator( statusColumnTextSelector ) .count(); From a7231863c014a95602f5932f702171465fa7bcf2 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:52:35 +0200 Subject: [PATCH 13/57] Product Collection: Trigger `wc-blocks_viewed_product` JS event (#51156) * Define the event * Add action sending an event in PC store * Add directives and context to Product Template li element * Use on--click directive in ProductImage * Use on--click directive in Product Title * Use on--click directive in Product Button * Add changelog * Add E2E tests * Update docs * Update blocks reference and docs manifest * Update m,anifest * Fix mistake in docs * Regenerate docs manifest * Fix lint * Extractb new tests to a separate file --- docs/building-a-woo-store/block-references.md | 100 ++++++------- docs/docs-manifest.json | 6 +- docs/product-collection-block/dom-events.md | 28 +++- .../assets/js/base/utils/legacy-events.ts | 11 ++ .../js/blocks/product-collection/frontend.tsx | 15 +- .../extensibility-events.block_theme.spec.ts | 139 ++++++++++++++++++ .../product-collection.block_theme.spec.ts | 66 --------- .../add-48861-simpler-approach-for-events | 4 + .../src/Blocks/BlockTypes/ProductButton.php | 6 +- .../Blocks/BlockTypes/ProductCollection.php | 31 ++++ .../src/Blocks/BlockTypes/ProductImage.php | 7 +- .../src/Blocks/BlockTypes/ProductTemplate.php | 30 +++- 12 files changed, 316 insertions(+), 127 deletions(-) create mode 100644 plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts create mode 100644 plugins/woocommerce/changelog/add-48861-simpler-approach-for-events diff --git a/docs/building-a-woo-store/block-references.md b/docs/building-a-woo-store/block-references.md index 5ae993bb975..5ec221ebed1 100644 --- a/docs/building-a-woo-store/block-references.md +++ b/docs/building-a-woo-store/block-references.md @@ -1109,16 +1109,16 @@ The contents of this block will display when there are no products found. - **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** -## Product Filter (Experimental) - woocommerce/product-filter +## Product Filters (Experimental) - woocommerce/product-filters -A block that adds product filters to the product collection. +Let shoppers filter products displayed on the page. -- **Name:** woocommerce/product-filter +- **Name:** woocommerce/product-filters - **Category:** woocommerce -- **Ancestor:** woocommerce/product-filters +- **Ancestor:** - **Parent:** -- **Supports:** ~~html~~, ~~inserter~~, ~~reusable~~ -- **Attributes:** attributeId, filterType, heading, isPreview +- **Supports:** align, color (background, text), interactivity, layout (allowJustification, allowOrientation, allowVerticalAlignment, default, ~~allowInheriting~~), spacing (blockGap), typography (fontSize, textAlign), ~~inserter~~, ~~multiple~~ +- **Attributes:** overlay, overlayButtonStyle, overlayIcon, overlayIconSize ## Filter Options - woocommerce/product-filter-active @@ -1153,50 +1153,6 @@ Allows shoppers to reset this filter. - **Supports:** interactivity, ~~inserter~~ - **Attributes:** -## Filter Options - woocommerce/product-filter-price - -Enable customers to filter the product collection by choosing a price range. - -- **Name:** woocommerce/product-filter-price -- **Category:** woocommerce -- **Ancestor:** woocommerce/product-filter -- **Parent:** -- **Supports:** interactivity, ~~inserter~~ -- **Attributes:** inlineInput, showInputFields - -## Filter Options - woocommerce/product-filter-rating - -Enable customers to filter the product collection by rating. - -- **Name:** woocommerce/product-filter-rating -- **Category:** woocommerce -- **Ancestor:** woocommerce/product-filter -- **Parent:** -- **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~ -- **Attributes:** className, displayStyle, isPreview, selectType, showCounts - -## Filter Options - woocommerce/product-filter-stock-status - -Enable customers to filter the product collection by stock status. - -- **Name:** woocommerce/product-filter-stock-status -- **Category:** woocommerce -- **Ancestor:** woocommerce/product-filter -- **Parent:** -- **Supports:** color (text, ~~background~~), interactivity, ~~html~~, ~~inserter~~, ~~multiple~~ -- **Attributes:** className, displayStyle, isPreview, selectType, showCounts - -## Product Filters (Experimental) - woocommerce/product-filters - -Let shoppers filter products displayed on the page. - -- **Name:** woocommerce/product-filters -- **Category:** woocommerce -- **Ancestor:** -- **Parent:** -- **Supports:** align, color (background, text), interactivity, layout (allowJustification, allowOrientation, allowVerticalAlignment, default, ~~allowInheriting~~), spacing (blockGap), typography (fontSize, textAlign), ~~inserter~~, ~~multiple~~ -- **Attributes:** overlay, overlayButtonStyle, overlayIcon, overlayIconSize - ## Product Filters Overlay (Experimental) - woocommerce/product-filters-overlay Display product filters in an overlay on top of a page. @@ -1219,6 +1175,50 @@ Display overlay navigation controls. - **Supports:** align (center, left, right), color (background, text), layout (default, ~~allowEditing~~), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~inserter~~ - **Attributes:** align, buttonStyle, iconSize, navigationStyle, overlayMode, style, triggerType +## Filter Options - woocommerce/product-filter-price + +Enable customers to filter the product collection by choosing a price range. + +- **Name:** woocommerce/product-filter-price +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity, ~~inserter~~ +- **Attributes:** inlineInput, showInputFields + +## Product Filter (Experimental) - woocommerce/product-filter + +A block that adds product filters to the product collection. + +- **Name:** woocommerce/product-filter +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filters +- **Parent:** +- **Supports:** inserter, ~~html~~, ~~reusable~~ +- **Attributes:** attributeId, filterType, heading, isPreview + +## Filter Options - woocommerce/product-filter-rating + +Enable customers to filter the product collection by rating. + +- **Name:** woocommerce/product-filter-rating +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~ +- **Attributes:** className, displayStyle, isPreview, selectType, showCounts + +## Filter Options - woocommerce/product-filter-stock-status + +Enable customers to filter the product collection by stock status. + +- **Name:** woocommerce/product-filter-stock-status +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** color (text, ~~background~~), interactivity, ~~html~~, ~~inserter~~ +- **Attributes:** className, displayStyle, isPreview, selectType, showCounts + ## Product Gallery (Beta) - woocommerce/product-gallery Showcase your products relevant images and media. diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 5de8f35e6aa..360754af0a1 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -74,7 +74,7 @@ "post_title": "Blocks reference", "menu_title": "Blocks Reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/building-a-woo-store/block-references.md", - "hash": "329f17097ce67074a915d7814b2363e8b9e908910c1f7b196c8f4fd8594cc55c", + "hash": "9bbd3555641a70a0d7c24c818323a9270e437a6446998de9a6506e0c2ed6ddf5", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/block-references.md", "id": "1fbe91d7fa4fafaf35f0297e4cee1e7958756aed" }, @@ -1059,7 +1059,7 @@ "menu_title": "DOM Events", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-collection-block/dom-events.md", - "hash": "fbad20bc55cc569161e80478c0789db3c34cf35513e669554af36db1de967a26", + "hash": "59a4b49eb146774d33229bc60ab7d8f74381493f6e7089ca8f0e2d0eb433a7a4", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-collection-block/dom-events.md", "id": "c8d247b91472740075871e6b57a9583d893ac650" } @@ -1804,5 +1804,5 @@ "categories": [] } ], - "hash": "212688b70a2dd0e70819746e6ffc033bc2279cb5b7b6350f377bbc3bc28c080f" + "hash": "12e9abfbcdbeae7dd5cc12dc3af3818332f272cb4b3ad12993cc010299009013" } \ No newline at end of file diff --git a/docs/product-collection-block/dom-events.md b/docs/product-collection-block/dom-events.md index cc49b4bb8b9..940e49a929d 100644 --- a/docs/product-collection-block/dom-events.md +++ b/docs/product-collection-block/dom-events.md @@ -10,13 +10,13 @@ tags: how-to This event is triggered when Product Collection block was rendered or re-rendered (e.g. due to page change). -### `detail` parameters +### `wc-blocks_product_list_rendered` - `detail` parameters | Parameter | Type | Default value | Description | | ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. | -### Example usage +### `wc-blocks_product_list_rendered` - Example usage ```javascript window.document.addEventListener( @@ -27,3 +27,27 @@ window.document.addEventListener( } ); ``` + +## Event: `wc-blocks_viewed_product` + +This event is triggered when some blocks are clicked in order to view product (redirect to product page). + +### `wc-blocks_viewed_product` - `detail` parameters + +| Parameter | Type | Default value | Description | +| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. | +| `productId` | number | | Product ID | + +### `wc-blocks_viewed_product` Example usage + +```javascript +window.document.addEventListener( + 'wc-blocks_viewed_product', + ( e ) => { + const { collection, productId } = e.detail; + console.log( collection ) // -> collection name, e.g. "woocommerce/product-collection/featured" or undefined for default one + console.log( productId ) // -> product ID, e.g. 34 + } +); +``` diff --git a/plugins/woocommerce-blocks/assets/js/base/utils/legacy-events.ts b/plugins/woocommerce-blocks/assets/js/base/utils/legacy-events.ts index c2607db8c6d..bb96223ff00 100644 --- a/plugins/woocommerce-blocks/assets/js/base/utils/legacy-events.ts +++ b/plugins/woocommerce-blocks/assets/js/base/utils/legacy-events.ts @@ -70,6 +70,17 @@ export const triggerProductListRenderedEvent = ( payload: { } ); }; +export const triggerViewedProductEvent = ( payload: { + collection?: CoreCollectionNames | string; + productId: number; +} ): void => { + dispatchEvent( 'wc-blocks_viewed_product', { + bubbles: true, + cancelable: true, + detail: payload, + } ); +}; + /** * Function that listens to a jQuery event and dispatches a native JS event. * Useful to convert WC Core events into events that can be read by blocks. diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/frontend.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/frontend.tsx index 12b5f386209..829a73b67bb 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/frontend.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/frontend.tsx @@ -8,7 +8,10 @@ import { getElement, getContext, } from '@woocommerce/interactivity'; -import { triggerProductListRenderedEvent } from '@woocommerce/base-utils'; +import { + triggerProductListRenderedEvent, + triggerViewedProductEvent, +} from '@woocommerce/base-utils'; /** * Internal dependencies @@ -17,6 +20,8 @@ import { CoreCollectionNames } from './types'; import './style.scss'; export type ProductCollectionStoreContext = { + // Available on the
  • product element and deeper + productId?: number; isPrefetchNextOrPreviousLink: boolean; animation: 'start' | 'finish'; accessibilityMessage: string; @@ -164,6 +169,14 @@ const productCollectionStore = { yield prefetch( ref.href ); } }, + *viewProduct() { + const { collection, productId } = + getContext< ProductCollectionStoreContext >(); + + if ( productId ) { + triggerViewedProductEvent( { collection, productId } ); + } + }, }, callbacks: { /** diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts new file mode 100644 index 00000000000..15aafd1f88a --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage from './product-collection.page'; + +const test = base.extend< { pageObject: ProductCollectionPage } >( { + pageObject: async ( { page, admin, editor }, use ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + } ); + await use( pageObject ); + }, +} ); + +test.describe( 'Product Collection - extensibility JS events', () => { + test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( { + pageObject, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await page.addInitScript( () => { + let eventFired = 0; + window.document.addEventListener( + 'wc-blocks_product_list_rendered', + ( e ) => { + const { collection } = e.detail; + window.eventPayload = collection; + window.eventFired = ++eventFired; + } + ); + } ); + + await pageObject.publishAndGoToFrontend(); + + await expect + .poll( async () => await page.evaluate( 'window.eventPayload' ) ) + .toBe( undefined ); + await expect + .poll( async () => await page.evaluate( 'window.eventFired' ) ) + .toBe( 1 ); + + await page.getByRole( 'link', { name: 'Next Page' } ).click(); + + await expect + .poll( async () => await page.evaluate( 'window.eventFired' ) ) + .toBe( 2 ); + } ); + + test( 'emits one wc-blocks_product_list_rendered event per block', async ( { + pageObject, + page, + } ) => { + // Adding three blocks in total + await pageObject.createNewPostAndInsertBlock(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost(); + + await page.addInitScript( () => { + let eventFired = 0; + window.document.addEventListener( + 'wc-blocks_product_list_rendered', + () => { + window.eventFired = ++eventFired; + } + ); + } ); + + await pageObject.publishAndGoToFrontend(); + + await expect + .poll( async () => await page.evaluate( 'window.eventFired' ) ) + .toBe( 3 ); + } ); + + test.describe( 'wc-blocks_viewed_product is emitted', () => { + let promise: Promise< { productId?: number; collection?: string } >; + + test.beforeEach( async ( { page, pageObject } ) => { + await pageObject.createNewPostAndInsertBlock( 'featured' ); + + promise = new Promise( ( resolve ) => { + void page.exposeFunction( 'resolvePayload', resolve ); + void page.addInitScript( () => { + window.document.addEventListener( + 'wc-blocks_viewed_product', + ( e ) => { + window.resolvePayload( e.detail ); + } + ); + } ); + } ); + + await pageObject.publishAndGoToFrontend(); + } ); + + test( 'when Product Image is clicked', async ( { page } ) => { + await page + .locator( '[data-block-name="woocommerce/product-image"]' ) + .nth( 0 ) + .click(); + + const { collection, productId } = await promise; + expect( collection ).toEqual( + 'woocommerce/product-collection/featured' + ); + expect( productId ).toEqual( expect.any( Number ) ); + } ); + + test( 'when Product Title is clicked', async ( { page } ) => { + await page.locator( '.wp-block-post-title' ).nth( 0 ).click(); + + const { collection, productId } = await promise; + expect( collection ).toEqual( + 'woocommerce/product-collection/featured' + ); + expect( productId ).toEqual( expect.any( Number ) ); + } ); + + test( 'when Add to Cart Anchor is clicked', async ( { page } ) => { + await page.getByLabel( 'Select options for “V-Neck T-' ).click(); + + const { collection, productId } = await promise; + expect( collection ).toEqual( + 'woocommerce/product-collection/featured' + ); + expect( productId ).toEqual( expect.any( Number ) ); + } ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index e38db1b526a..bb4f6bd8f75 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -901,72 +901,6 @@ test.describe( 'Product Collection', () => { await expect( products ).toHaveText( expectedProducts ); } ); } ); - - test.describe( 'Extensibility - JS events', () => { - test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( { - pageObject, - page, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await page.addInitScript( () => { - let eventFired = 0; - window.document.addEventListener( - 'wc-blocks_product_list_rendered', - ( e ) => { - const { collection } = e.detail; - window.eventPayload = collection; - window.eventFired = ++eventFired; - } - ); - } ); - - await pageObject.publishAndGoToFrontend(); - - await expect - .poll( - async () => await page.evaluate( 'window.eventPayload' ) - ) - .toBe( undefined ); - await expect - .poll( async () => await page.evaluate( 'window.eventFired' ) ) - .toBe( 1 ); - - await page.getByRole( 'link', { name: 'Next Page' } ).click(); - - await expect - .poll( async () => await page.evaluate( 'window.eventFired' ) ) - .toBe( 2 ); - } ); - - test( 'emits one wc-blocks_product_list_rendered event per block', async ( { - pageObject, - page, - } ) => { - // Adding three blocks in total - await pageObject.createNewPostAndInsertBlock(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost(); - - await page.addInitScript( () => { - let eventFired = 0; - window.document.addEventListener( - 'wc-blocks_product_list_rendered', - () => { - window.eventFired = ++eventFired; - } - ); - } ); - - await pageObject.publishAndGoToFrontend(); - - await expect - .poll( async () => await page.evaluate( 'window.eventFired' ) ) - .toBe( 3 ); - } ); - } ); } ); test.describe( 'Testing "usesReference" argument in "registerProductCollection"', () => { diff --git a/plugins/woocommerce/changelog/add-48861-simpler-approach-for-events b/plugins/woocommerce/changelog/add-48861-simpler-approach-for-events new file mode 100644 index 00000000000..10176409154 --- /dev/null +++ b/plugins/woocommerce/changelog/add-48861-simpler-approach-for-events @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Product Elements send a JS event when user attempts to view a product diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php index 87430430876..1edf068e1eb 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php @@ -176,6 +176,10 @@ class ProductButton extends AbstractBlock { data-wc-class--loading="context.isLoading" '; + $anchor_directive = ' + data-wc-on--click="woocommerce/product-collection::actions.viewProduct" + '; + $span_button_directives = ' data-wc-text="state.addToCartText" data-wc-class--wc-block-slide-in="state.slideInAnimation" @@ -219,7 +223,7 @@ class ProductButton extends AbstractBlock { '{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '', '{add_to_cart_text}' => esc_html( $initial_product_text ), '{div_directives}' => $is_ajax_button ? $div_directives : '', - '{button_directives}' => $is_ajax_button ? $button_directives : '', + '{button_directives}' => $is_ajax_button ? $button_directives : $anchor_directive, '{span_button_directives}' => $is_ajax_button ? $span_button_directives : '', '{view_cart_html}' => $is_ajax_button ? $this->get_view_cart_html() : '', ) diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php index 09fe61976fa..8f4c3ecfda1 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php @@ -117,6 +117,7 @@ class ProductCollection extends AbstractBlock { // Interactivity API: Add navigation directives to the product collection block. add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 ); add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 ); + add_filter( 'render_block_core/post-title', array( $this, 'add_product_title_click_event_directives' ), 10, 3 ); add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 ); @@ -408,6 +409,36 @@ class ProductCollection extends AbstractBlock { return $block_content; } + /** + * Add interactivity to the Product Title block within Product Collection. + * This enables the triggering of a custom event when the product title is clicked. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param \WP_Block $instance The block instance. + * @return string Modified block content with added interactivity. + */ + public function add_product_title_click_event_directives( $block_content, $block, $instance ) { + $namespace = $instance->attributes['__woocommerceNamespace'] ?? ''; + $is_product_title_block = 'woocommerce/product-collection/product-title' === $namespace; + $is_link = $instance->attributes['isLink'] ?? false; + + // Only proceed if the block is a Product Title (Post Title variation) block. + if ( $is_product_title_block && $is_link ) { + $p = new \WP_HTML_Tag_Processor( $block_content ); + $p->next_tag( array( 'class_name' => 'wp-block-post-title' ) ); + $is_anchor = $p->next_tag( array( 'tag_name' => 'a' ) ); + + if ( $is_anchor ) { + $p->set_attribute( 'data-wc-on--click', 'woocommerce/product-collection::actions.viewProduct' ); + + $block_content = $p->get_updated_html(); + } + } + + return $block_content; + } + /** * Process pagination links within the block content. * diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php index 567c43807ad..9aadea3a948 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php @@ -125,12 +125,15 @@ class ProductImage extends AbstractBlock { private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) { $product_permalink = $product->get_permalink(); - $pointer_events = false === $attributes['showProductLink'] ? 'pointer-events: none;' : ''; + $is_link = true === $attributes['showProductLink']; + $pointer_events = $is_link ? '' : 'pointer-events: none;'; + $directive = $is_link ? 'data-wc-on--click="woocommerce/product-collection::actions.viewProduct"' : ''; return sprintf( - '%3$s %4$s', + '%4$s %5$s', $product_permalink, $pointer_events, + $directive, $on_sale_badge, $product_image ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php index 583f6a464c2..64b0dd14b56 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php @@ -85,6 +85,7 @@ class ProductTemplate extends AbstractBlock { // Get an instance of the current Post Template block. $block_instance = $block->parsed_block; + $product_id = get_the_ID(); // Set the block name to one that does not correspond to an existing registered block. // This ensures that for the inner instances of the Post Template block, we do not render any block supports. @@ -97,14 +98,39 @@ class ProductTemplate extends AbstractBlock { $block_instance, array( 'postType' => get_post_type(), - 'postId' => get_the_ID(), + 'postId' => $product_id, ) ) )->render( array( 'dynamic' => false ) ); + $interactive = array( + 'namespace' => 'woocommerce/product-collection', + ); + + $context = array( + 'productId' => $product_id, + ); + + $li_directives = ' + data-wc-interactive=\'' . wp_json_encode( $interactive, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\' + data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\' + data-wc-key="product-item-' . $product_id . '" + '; + // Wrap the render inner blocks in a `li` element with the appropriate post classes. $post_classes = implode( ' ', get_post_class( 'wc-block-product' ) ); - $content .= '
  • ' . $block_content . '
  • '; + $content .= strtr( + '
  • + {content} +
  • ', + array( + '{classes}' => esc_attr( $post_classes ), + '{li_directives}' => $li_directives, + '{content}' => $block_content, + ) + ); } /* From d2fb60a5d12705cc984a79151c1a284b2af96a52 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Thu, 5 Sep 2024 17:11:42 +0200 Subject: [PATCH 14/57] Change label `Browse` to `Extensions` (#51140) * Change label `Browse` to `Extensions` * Changelog --- .../client/marketplace/components/tabs/tabs.tsx | 2 +- .../changelog/51140-update-in-app-marketplace-label | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/51140-update-in-app-marketplace-label diff --git a/plugins/woocommerce-admin/client/marketplace/components/tabs/tabs.tsx b/plugins/woocommerce-admin/client/marketplace/components/tabs/tabs.tsx index 5fba21cc55b..1f852a2b982 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/tabs/tabs.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/tabs/tabs.tsx @@ -50,7 +50,7 @@ const tabs: Tabs = { }, extensions: { name: 'extensions', - title: __( 'Browse', 'woocommerce' ), + title: __( 'Extensions', 'woocommerce' ), showUpdateCount: false, updateCount: 0, }, diff --git a/plugins/woocommerce/changelog/51140-update-in-app-marketplace-label b/plugins/woocommerce/changelog/51140-update-in-app-marketplace-label new file mode 100644 index 00000000000..582d3801dbd --- /dev/null +++ b/plugins/woocommerce/changelog/51140-update-in-app-marketplace-label @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: Change label `Browse` to `Extensions` + From 61d41adf7957b269fec3f4931116476db4fe2671 Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:53:00 +0100 Subject: [PATCH 15/57] [k6 tests] Fix broken perf tests - updated page titles (#51173) --- .../k6-fix-broken-tests-update-page-titles | 4 + .../woocommerce/tests/performance/config.js | 3 + .../woocommerce/tests/performance/headers.js | 101 +++---- .../requests/merchant/add-order.js | 262 ++++++++++++------ .../requests/merchant/home-wc-admin.js | 7 +- .../requests/shopper/cart-apply-coupon.js | 25 +- .../requests/shopper/cart-remove-item.js | 18 +- .../performance/requests/shopper/cart.js | 30 +- .../requests/shopper/category-page.js | 27 +- .../shopper/checkout-customer-login.js | 73 ++--- .../requests/shopper/checkout-guest.js | 66 ++--- .../performance/requests/shopper/home.js | 23 +- .../requests/shopper/my-account-orders.js | 81 ++---- .../requests/shopper/my-account.js | 30 +- .../requests/shopper/search-product.js | 26 +- .../performance/requests/shopper/shop-page.js | 32 +-- .../requests/shopper/single-product.js | 26 +- .../woocommerce/tests/performance/utils.js | 30 ++ 18 files changed, 435 insertions(+), 429 deletions(-) create mode 100644 plugins/woocommerce/changelog/k6-fix-broken-tests-update-page-titles create mode 100644 plugins/woocommerce/tests/performance/utils.js diff --git a/plugins/woocommerce/changelog/k6-fix-broken-tests-update-page-titles b/plugins/woocommerce/changelog/k6-fix-broken-tests-update-page-titles new file mode 100644 index 00000000000..4f73f5cc6c2 --- /dev/null +++ b/plugins/woocommerce/changelog/k6-fix-broken-tests-update-page-titles @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + + diff --git a/plugins/woocommerce/tests/performance/config.js b/plugins/woocommerce/tests/performance/config.js index 30dd0d2c4a7..d4638940de0 100644 --- a/plugins/woocommerce/tests/performance/config.js +++ b/plugins/woocommerce/tests/performance/config.js @@ -2,6 +2,9 @@ export const base_url = __ENV.URL || 'http://localhost:8086'; export const base_host = __ENV.HOST || 'localhost:8086'; +export const STORE_NAME = __ENV.STORE_NAME || 'WooCommerce Core E2E Test Suite'; +export const FOOTER_TEXT = 'Built with WooCommerce'; + export const admin_username = __ENV.A_USER || 'admin'; export const admin_password = __ENV.A_PW || 'password'; export const admin_acc_login = __ENV.A_ACC_LOGIN || false; diff --git a/plugins/woocommerce/tests/performance/headers.js b/plugins/woocommerce/tests/performance/headers.js index b73fda1b72b..0010a844b0a 100644 --- a/plugins/woocommerce/tests/performance/headers.js +++ b/plugins/woocommerce/tests/performance/headers.js @@ -1,73 +1,76 @@ +/** + * Internal dependencies + */ import { base_host, base_url } from './config.js'; const htmlRequestHeader = { - accept: - 'text/html,application/xhtml+xml,application/xml;' + - 'q=0.9,image/avif,image/webp,image/apng,*/*;' + - 'q=0.8,application/signed-exchange;v=b3;q=0.9' - }; + accept: + 'text/html,application/xhtml+xml,application/xml;' + + 'q=0.9,image/avif,image/webp,image/apng,*/*;' + + 'q=0.8,application/signed-exchange;v=b3;q=0.9', +}; const jsonRequestHeader = { - accept: 'application/json, text/javascript, */*; q=0.01' - }; + accept: 'application/json, text/javascript, */*; q=0.01', +}; const jsonAPIRequestHeader = { - accept: 'application/json, */*;q=0.1' - }; + accept: 'application/json, */*;q=0.1', +}; const allRequestHeader = { - accept: '*//*' - }; + accept: '*//*', +}; const commonRequestHeaders = { - 'accept-language': 'en-US,en;q=0.9' - }; + 'accept-language': 'en-US,en;q=0.9', +}; const commonGetRequestHeaders = { - connection: 'keep-alive', - host: `${base_host}`, - 'sec-fetch-dest': 'document', - 'sec-fetch-mode': 'navigate', - 'sec-fetch-site': 'same-origin', - 'sec-fetch-user': '?1', - 'upgrade-insecure-requests': '1' - }; + connection: 'keep-alive', + host: `${ base_host }`, + 'sec-fetch-dest': 'document', + 'sec-fetch-mode': 'navigate', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'upgrade-insecure-requests': '1', +}; const commonAPIGetRequestHeaders = { - connection: 'keep-alive', - host: `${base_host}`, - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - }; + connection: 'keep-alive', + host: `${ base_host }`, + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', +}; const contentTypeRequestHeader = { - 'content-type': 'application/x-www-form-urlencoded' - }; + 'content-type': 'application/x-www-form-urlencoded', +}; const commonPostRequestHeaders = { - origin: `${base_url}`, - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'x-requested-with': 'XMLHttpRequest' - }; + origin: `${ base_url }`, + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'x-requested-with': 'XMLHttpRequest', +}; const commonNonStandardHeaders = { - 'sec-ch-ua': - '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"', - 'sec-ch-ua-mobile': '?0' - }; + 'sec-ch-ua': + '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"', + 'sec-ch-ua-mobile': '?0', +}; export { - htmlRequestHeader, - jsonRequestHeader, - jsonAPIRequestHeader, - allRequestHeader, - commonRequestHeaders, - commonGetRequestHeaders, - commonAPIGetRequestHeaders, - contentTypeRequestHeader, - commonPostRequestHeaders, - commonNonStandardHeaders, + htmlRequestHeader, + jsonRequestHeader, + jsonAPIRequestHeader, + allRequestHeader, + commonRequestHeaders, + commonGetRequestHeaders, + commonAPIGetRequestHeaders, + contentTypeRequestHeader, + commonPostRequestHeaders, + commonNonStandardHeaders, }; diff --git a/plugins/woocommerce/tests/performance/requests/merchant/add-order.js b/plugins/woocommerce/tests/performance/requests/merchant/add-order.js index dbda889f742..139b5ce7fbd 100644 --- a/plugins/woocommerce/tests/performance/requests/merchant/add-order.js +++ b/plugins/woocommerce/tests/performance/requests/merchant/add-order.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -65,17 +64,15 @@ if ( hpos_status === true ) { } else { admin_new_order_base = 'post-new.php?post_type=shop_order'; admin_update_order_base = 'post.php'; - admin_new_order_assert = 'Add new order'; + admin_new_order_assert = 'Add new order'; admin_open_order_assert = 'Edit order'; admin_created_order_assert = 'Order updated.'; admin_update_order_assert = 'Order updated.'; } -const date = new Date(); -const order_date = date.toJSON().slice( 0, 10 ); +const global_order_date = new Date().toJSON().slice( 0, 10 ); export function addOrder( includeTests = {} ) { - let response; let ajax_nonce_add_meta; let wpnonce; let closed_postboxes_nonce; @@ -87,7 +84,8 @@ export function addOrder( includeTests = {} ) { let api_x_wp_nonce; let apiNonceHeader; let heartbeat_nonce; - let includedTests = Object.assign( { + const includedTests = Object.assign( + { create: true, heartbeat: true, open: true, @@ -106,7 +104,7 @@ export function addOrder( includeTests = {} ) { commonNonStandardHeaders ); - response = http.get( + const response = http.get( `${ base_url }/wp-admin/${ admin_new_order_base }`, { headers: requestHeaders, @@ -115,8 +113,8 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Add new order' header": ( response ) => - response.body.includes( `${ admin_new_order_assert }` ), + "body contains: 'Add new order' header": ( r ) => + r.body.includes( `${ admin_new_order_assert }` ), } ); // Correlate nonce values for use in subsequent requests. @@ -190,18 +188,18 @@ export function addOrder( includeTests = {} ) { commonNonStandardHeaders ); - response = http.get( + const tasksResponse = http.get( `${ base_url }/wp-json/wc-admin/onboarding/tasks?_locale=user`, { headers: requestHeaders, tags: { name: 'Merchant - wc-admin/onboarding/tasks?' }, } ); - check( response, { + check( tasksResponse, { 'is status 200': ( r ) => r.status === 200, } ); - response = http.get( + const notesResponse = http.get( `${ base_url }/wp-json/wc-analytics/admin/notes?page=1&per_page=25&` + `type=error%2Cupdate&status=unactioned&_locale=user`, { @@ -209,11 +207,11 @@ export function addOrder( includeTests = {} ) { tags: { name: 'Merchant - wc-analytics/admin/notes?' }, } ); - check( response, { + check( notesResponse, { 'is status 200': ( r ) => r.status === 200, } ); - response = http.get( + const optionsResponse = http.get( `${ base_url }/wp-json/wc-admin/options?options=woocommerce_ces_tracks_queue&_locale=user`, { headers: requestHeaders, @@ -222,7 +220,7 @@ export function addOrder( includeTests = {} ) { }, } ); - check( response, { + check( optionsResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); @@ -239,7 +237,7 @@ export function addOrder( includeTests = {} ) { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/wp-admin/admin-ajax.php`, `_nonce=${ heartbeat_nonce }&action=heartbeat&has_focus=true&interval=15&screen_id=shop_order`, { @@ -252,7 +250,9 @@ export function addOrder( includeTests = {} ) { } ); } ); - sleep( randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) ); + sleep( + randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) + ); } if ( includedTests.create ) { @@ -266,13 +266,18 @@ export function addOrder( includeTests = {} ) { commonNonStandardHeaders ); - const date = new Date(); - const order_date = date.toJSON().slice( 0, 10 ); + const order_date = new Date().toJSON().slice( 0, 10 ); const orderParams = new URLSearchParams( [ [ '_ajax_nonce-add-meta', `${ ajax_nonce_add_meta }` ], - [ '_billing_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_billing_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_billing_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_billing_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_billing_city', `${ addresses_guest_billing_city }` ], [ '_billing_company', `${ addresses_guest_billing_company }` ], [ '_billing_country', `${ addresses_guest_billing_country }` ], @@ -281,12 +286,24 @@ export function addOrder( includeTests = {} ) { '_billing_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_billing_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_billing_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_billing_phone', `${ addresses_guest_billing_phone }` ], - [ '_billing_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_billing_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_billing_state', `${ addresses_guest_billing_state }` ], - [ '_shipping_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_shipping_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_shipping_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_shipping_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_shipping_city', `${ addresses_guest_billing_city }` ], [ '_shipping_company', `${ addresses_guest_billing_company }` ], [ '_shipping_country', `${ addresses_guest_billing_country }` ], @@ -294,9 +311,15 @@ export function addOrder( includeTests = {} ) { '_shipping_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_shipping_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_shipping_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_shipping_phone', `${ addresses_guest_billing_phone }` ], - [ '_shipping_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_shipping_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_shipping_state', `${ addresses_guest_billing_state }` ], [ '_payment_method', `${ payment_method }` ], [ '_transaction_id', '' ], @@ -337,8 +360,14 @@ export function addOrder( includeTests = {} ) { const hposOrderParams = new URLSearchParams( [ [ '_ajax_nonce-add-meta', `${ ajax_nonce_add_meta }` ], - [ '_billing_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_billing_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_billing_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_billing_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_billing_city', `${ addresses_guest_billing_city }` ], [ '_billing_company', `${ addresses_guest_billing_company }` ], [ '_billing_country', `${ addresses_guest_billing_country }` ], @@ -347,12 +376,24 @@ export function addOrder( includeTests = {} ) { '_billing_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_billing_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_billing_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_billing_phone', `${ addresses_guest_billing_phone }` ], - [ '_billing_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_billing_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_billing_state', `${ addresses_guest_billing_state }` ], - [ '_shipping_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_shipping_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_shipping_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_shipping_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_shipping_city', `${ addresses_guest_billing_city }` ], [ '_shipping_company', `${ addresses_guest_billing_company }` ], [ '_shipping_country', `${ addresses_guest_billing_country }` ], @@ -360,9 +401,15 @@ export function addOrder( includeTests = {} ) { '_shipping_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_shipping_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_shipping_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_shipping_phone', `${ addresses_guest_billing_phone }` ], - [ '_shipping_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_shipping_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_shipping_state', `${ addresses_guest_billing_state }` ], [ '_payment_method', `${ payment_method }` ], [ '_transaction_id', '' ], @@ -397,7 +444,7 @@ export function addOrder( includeTests = {} ) { admin_update_order_params = orderParams.toString(); } - response = http.post( + const response = http.post( `${ base_url }/wp-admin/${ admin_update_order }`, admin_update_order_params.toString(), { @@ -407,14 +454,16 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Edit order' header": ( response ) => - response.body.includes( `${ admin_open_order_assert }` ), - "body contains: 'Order updated' confirmation": ( response ) => - response.body.includes( `${ admin_created_order_assert }` ), + "body contains: 'Edit order' header": ( r ) => + r.body.includes( `${ admin_open_order_assert }` ), + "body contains: 'Order updated' confirmation": ( r ) => + r.body.includes( `${ admin_created_order_assert }` ), } ); } ); - sleep( randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) ); + sleep( + randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) + ); } if ( includedTests.open ) { @@ -433,7 +482,7 @@ export function addOrder( includeTests = {} ) { admin_open_order_base = `${ admin_update_order_base }?post=${ post_id }`; } - response = http.get( + const response = http.get( `${ base_url }/wp-admin/${ admin_open_order_base }&action=edit`, { headers: requestHeaders, @@ -442,12 +491,14 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Edit order' header": ( response ) => - response.body.includes( `${ admin_open_order_assert }` ), + "body contains: 'Edit order' header": ( r ) => + r.body.includes( `${ admin_open_order_assert }` ), } ); } ); - sleep( randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) ); + sleep( + randomIntBetween( `${ think_time_min }`, `${ think_time_max }` ) + ); } if ( includedTests.update ) { @@ -463,8 +514,14 @@ export function addOrder( includeTests = {} ) { const orderParams = new URLSearchParams( [ [ '_ajax_nonce-add-meta', `${ ajax_nonce_add_meta }` ], - [ '_billing_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_billing_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_billing_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_billing_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_billing_city', `${ addresses_guest_billing_city }` ], [ '_billing_company', `${ addresses_guest_billing_company }` ], [ '_billing_country', `${ addresses_guest_billing_country }` ], @@ -473,12 +530,24 @@ export function addOrder( includeTests = {} ) { '_billing_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_billing_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_billing_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_billing_phone', `${ addresses_guest_billing_phone }` ], - [ '_billing_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_billing_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_billing_state', `${ addresses_guest_billing_state }` ], - [ '_shipping_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_shipping_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_shipping_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_shipping_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_shipping_city', `${ addresses_guest_billing_city }` ], [ '_shipping_company', `${ addresses_guest_billing_company }` ], [ '_shipping_country', `${ addresses_guest_billing_country }` ], @@ -486,9 +555,15 @@ export function addOrder( includeTests = {} ) { '_shipping_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_shipping_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_shipping_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_shipping_phone', `${ addresses_guest_billing_phone }` ], - [ '_shipping_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_shipping_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_shipping_state', `${ addresses_guest_billing_state }` ], [ '_payment_method', `${ payment_method }` ], [ '_transaction_id', '' ], @@ -503,7 +578,7 @@ export function addOrder( includeTests = {} ) { [ 'metakeyinput', '' ], [ 'metakeyselect', '%23NONE%23' ], [ 'metavalue', '' ], - [ 'order_date', `${ order_date }` ], + [ 'order_date', `${ global_order_date }` ], [ 'order_date_hour', '01' ], [ 'order_date_minute', '01' ], [ 'order_date_second', '01' ], @@ -528,8 +603,14 @@ export function addOrder( includeTests = {} ) { const hposOrderParams = new URLSearchParams( [ [ '_ajax_nonce-add-meta', `${ ajax_nonce_add_meta }` ], - [ '_billing_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_billing_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_billing_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_billing_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_billing_city', `${ addresses_guest_billing_city }` ], [ '_billing_company', `${ addresses_guest_billing_company }` ], [ '_billing_country', `${ addresses_guest_billing_country }` ], @@ -538,12 +619,24 @@ export function addOrder( includeTests = {} ) { '_billing_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_billing_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_billing_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_billing_phone', `${ addresses_guest_billing_phone }` ], - [ '_billing_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_billing_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_billing_state', `${ addresses_guest_billing_state }` ], - [ '_shipping_address_1', `${ addresses_guest_billing_address_1 }` ], - [ '_shipping_address_2', `${ addresses_guest_billing_address_2 }` ], + [ + '_shipping_address_1', + `${ addresses_guest_billing_address_1 }`, + ], + [ + '_shipping_address_2', + `${ addresses_guest_billing_address_2 }`, + ], [ '_shipping_city', `${ addresses_guest_billing_city }` ], [ '_shipping_company', `${ addresses_guest_billing_company }` ], [ '_shipping_country', `${ addresses_guest_billing_country }` ], @@ -551,9 +644,15 @@ export function addOrder( includeTests = {} ) { '_shipping_first_name', `${ addresses_guest_billing_first_name }`, ], - [ '_shipping_last_name', `${ addresses_guest_billing_last_name }` ], + [ + '_shipping_last_name', + `${ addresses_guest_billing_last_name }`, + ], [ '_shipping_phone', `${ addresses_guest_billing_phone }` ], - [ '_shipping_postcode', `${ addresses_guest_billing_postcode }` ], + [ + '_shipping_postcode', + `${ addresses_guest_billing_postcode }`, + ], [ '_shipping_state', `${ addresses_guest_billing_state }` ], [ '_payment_method', `${ payment_method }` ], [ '_transaction_id', '' ], @@ -564,7 +663,7 @@ export function addOrder( includeTests = {} ) { [ 'excerpt', '' ], [ 'metakeyinput', '' ], [ 'metavalue', '' ], - [ 'order_date', `${ order_date }` ], + [ 'order_date', `${ global_order_date }` ], [ 'order_date_hour', '01' ], [ 'order_date_minute', '01' ], [ 'order_date_second', '01' ], @@ -588,7 +687,7 @@ export function addOrder( includeTests = {} ) { admin_update_order_id = `${ admin_open_order_base }`; } - response = http.post( + const response = http.post( `${ base_url }/wp-admin/${ admin_update_order_id }`, admin_update_order_params.toString(), { @@ -598,8 +697,8 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Order updated' confirmation": ( response ) => - response.body.includes( `${ admin_update_order_assert }` ), + "body contains: 'Order updated' confirmation": ( r ) => + r.body.includes( `${ admin_update_order_assert }` ), } ); } ); } @@ -616,8 +715,7 @@ export function addOrder( includeTests = {} ) { commonNonStandardHeaders ); - const date = new Date(); - const order_date = date.toJSON().slice( 0, 10 ); + const order_date = new Date().toJSON().slice( 0, 10 ); const orderParams = new URLSearchParams( [ [ '_ajax_nonce-add-meta', `${ ajax_nonce_add_meta }` ], @@ -747,7 +845,7 @@ export function addOrder( includeTests = {} ) { admin_update_order_params = orderParams.toString(); } - response = http.post( + const response = http.post( `${ base_url }/wp-admin/${ admin_update_order }`, admin_update_order_params.toString(), { @@ -757,10 +855,10 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Edit order' header": ( response ) => - response.body.includes( `${ admin_open_order_assert }` ), - "body contains: 'Order updated' confirmation": ( response ) => - response.body.includes( `${ admin_created_order_assert }` ), + "body contains: 'Edit order' header": ( r ) => + r.body.includes( `${ admin_open_order_assert }` ), + "body contains: 'Order updated' confirmation": ( r ) => + r.body.includes( `${ admin_created_order_assert }` ), } ); } ); @@ -781,7 +879,7 @@ export function addOrder( includeTests = {} ) { admin_open_order_base = `${ admin_update_order_base }?post=${ post_id }`; } - response = http.get( + const response = http.get( `${ base_url }/wp-admin/${ admin_open_order_base }&action=edit`, { headers: requestHeaders, @@ -790,8 +888,8 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Edit order' header": ( response ) => - response.body.includes( `${ admin_open_order_assert }` ), + "body contains: 'Edit order' header": ( r ) => + r.body.includes( `${ admin_open_order_assert }` ), } ); } ); @@ -849,7 +947,7 @@ export function addOrder( includeTests = {} ) { [ 'metakeyinput', '' ], [ 'metakeyselect', '%23NONE%23' ], [ 'metavalue', '' ], - [ 'order_date', `${ order_date }` ], + [ 'order_date', `${ global_order_date }` ], [ 'order_date_hour', '01' ], [ 'order_date_minute', '01' ], [ 'order_date_second', '01' ], @@ -910,7 +1008,7 @@ export function addOrder( includeTests = {} ) { [ 'excerpt', '' ], [ 'metakeyinput', '' ], [ 'metavalue', '' ], - [ 'order_date', `${ order_date }` ], + [ 'order_date', `${ global_order_date }` ], [ 'order_date_hour', '01' ], [ 'order_date_minute', '01' ], [ 'order_date_second', '01' ], @@ -934,7 +1032,7 @@ export function addOrder( includeTests = {} ) { admin_update_order_id = `${ admin_open_order_base }`; } - response = http.post( + const response = http.post( `${ base_url }/wp-admin/${ admin_update_order_id }`, admin_update_order_params.toString(), { @@ -944,11 +1042,11 @@ export function addOrder( includeTests = {} ) { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Order updated' confirmation": ( response ) => - response.body.includes( `${ admin_update_order_assert }` ), + "body contains: 'Order updated' confirmation": ( r ) => + r.body.includes( `${ admin_update_order_assert }` ), } ); } ); -}; +} export default function () { addOrder(); diff --git a/plugins/woocommerce/tests/performance/requests/merchant/home-wc-admin.js b/plugins/woocommerce/tests/performance/requests/merchant/home-wc-admin.js index f4ac2d8b7cd..240a15c9b84 100644 --- a/plugins/woocommerce/tests/performance/requests/merchant/home-wc-admin.js +++ b/plugins/woocommerce/tests/performance/requests/merchant/home-wc-admin.js @@ -27,7 +27,8 @@ export function homeWCAdmin( includeTests = {} ) { let response; let api_x_wp_nonce; let apiNonceHeader; - let includedTests = Object.assign( { + const includedTests = Object.assign( + { orders: true, other: true, products: true, @@ -51,8 +52,8 @@ export function homeWCAdmin( includeTests = {} ) { } ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: current page is 'Home'": ( response ) => - response.body.includes( 'aria-current="page">Home' ), + "body contains: current page is 'Home'": ( r ) => + r.body.includes( 'aria-current="page">Home' ), } ); // Correlate nonce values for use in subsequent requests. diff --git a/plugins/woocommerce/tests/performance/requests/shopper/cart-apply-coupon.js b/plugins/woocommerce/tests/performance/requests/shopper/cart-apply-coupon.js index 89007965f15..4e43a1bb3df 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/cart-apply-coupon.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/cart-apply-coupon.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -32,7 +31,6 @@ import { } from '../../headers.js'; export function cartApplyCoupon() { - let response; let apply_coupon_nonce; // let item_name; let woocommerce_cart_nonce; @@ -46,7 +44,7 @@ export function cartApplyCoupon() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=add_to_cart`, { product_sku: `${ product_sku }`, @@ -74,15 +72,14 @@ export function cartApplyCoupon() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/cart`, { + const response = http.get( `${ base_url }/cart`, { headers: requestheaders, tags: { name: 'Shopper - View Cart' }, } ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body does not contain: 'your cart is currently empty'": ( - response - ) => ! response.body.includes( 'Your cart is currently empty.' ), + "body does not contain: 'your cart is currently empty'": ( r ) => + ! r.body.includes( 'Your cart is currently empty.' ), } ); // Correlate cart item value for use in subsequent requests. @@ -111,7 +108,7 @@ export function cartApplyCoupon() { contentTypeRequestHeader ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=apply_coupon`, { coupon_code: `${ coupon_code }`, @@ -125,11 +122,11 @@ export function cartApplyCoupon() { check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'Coupon code applied successfully'": ( response ) => - response.body.includes( 'Coupon code applied successfully' ), + "body contains: 'Coupon code applied successfully'": ( r ) => + r.body.includes( 'Coupon code applied successfully' ), } ); - response = http.post( + const cartResponse = http.post( `${ base_url }/cart`, { _wp_http_referer: '%2Fcart', @@ -142,10 +139,10 @@ export function cartApplyCoupon() { tags: { name: 'Shopper - Update Cart' }, } ); - check( response, { + check( cartResponse, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'woocommerce-remove-coupon' class": ( response ) => - response.body.includes( 'class="woocommerce-remove-coupon"' ), + "body contains: 'woocommerce-remove-coupon' class": ( r ) => + r.body.includes( 'class="woocommerce-remove-coupon"' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/cart-remove-item.js b/plugins/woocommerce/tests/performance/requests/shopper/cart-remove-item.js index 355942e4ce4..c76b0444e0e 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/cart-remove-item.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/cart-remove-item.js @@ -1,5 +1,4 @@ -/* eslint-disable no-shadow */ -/* eslint-disable import/no-unresolved */ +// eslint-disable import/no-unresolved /** * External dependencies */ @@ -30,7 +29,6 @@ import { } from '../../headers.js'; export function cartRemoveItem() { - let response; let item_to_remove; let wpnonce; @@ -43,7 +41,7 @@ export function cartRemoveItem() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=add_to_cart`, { product_sku: `${ product_sku }`, @@ -71,15 +69,14 @@ export function cartRemoveItem() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/cart`, { + const response = http.get( `${ base_url }/cart`, { headers: requestheaders, tags: { name: 'Shopper - View Cart' }, } ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body does not contain: 'your cart is currently empty'": ( - response - ) => ! response.body.includes( 'Your cart is currently empty.' ), + "body does not contain: 'your cart is currently empty'": ( r ) => + ! r.body.includes( 'Your cart is currently empty.' ), } ); // Correlate cart item value for use in subsequent requests. @@ -98,7 +95,7 @@ export function cartRemoveItem() { commonNonStandardHeaders ); - response = http.get( + const response = http.get( `${ base_url }/cart?remove_item=${ item_to_remove }&_wpnonce=${ wpnonce }`, { headers: requestheaders, @@ -107,8 +104,7 @@ export function cartRemoveItem() { ); check( response, { 'is status 200': ( r ) => r.status === 200, - "body contains: 'removed'": ( response ) => - response.body.includes( ' removed.' ), + "body contains: 'removed'": ( r ) => r.body.includes( ' removed.' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/cart.js b/plugins/woocommerce/tests/performance/requests/shopper/cart.js index 21f38e2956b..279cf18635c 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/cart.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/cart.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -16,6 +15,8 @@ import { product_id, think_time_min, think_time_max, + STORE_NAME, + FOOTER_TEXT, } from '../../config.js'; import { htmlRequestHeader, @@ -27,8 +28,6 @@ import { } from '../../headers.js'; export function cart() { - let response; - group( 'Product Page Add to cart', function () { const requestheaders = Object.assign( {}, @@ -38,7 +37,7 @@ export function cart() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=add_to_cart`, { product_sku: `${ product_sku }`, @@ -66,26 +65,19 @@ export function cart() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/cart`, { + const response = http.get( `${ base_url }/cart`, { headers: requestheaders, tags: { name: 'Shopper - View Cart' }, } ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'title is: "Cart – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Cart – WooCommerce Core E2E Test Suite', - "body does not contain: 'your cart is currently empty'": ( - response - ) => ! response.body.includes( 'Your cart is currently empty.' ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + [ `title is: "Cart – ${ STORE_NAME }"` ]: ( r ) => + r.html().find( 'head title' ).text() === + `Cart – ${ STORE_NAME }`, + "body does not contain: 'your cart is currently empty'": ( r ) => + ! r.body.includes( 'Your cart is currently empty.' ), + 'footer contains: Built with WooCommerce': ( r ) => + r.html().find( 'body footer' ).text().includes( FOOTER_TEXT ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/category-page.js b/plugins/woocommerce/tests/performance/requests/shopper/category-page.js index e5be767659b..f9495898848 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/category-page.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/category-page.js @@ -1,9 +1,8 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies */ -import { sleep, check, group } from 'k6'; +import { sleep, group } from 'k6'; import http from 'k6/http'; import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js'; @@ -15,6 +14,8 @@ import { think_time_min, think_time_max, product_category, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -22,6 +23,7 @@ import { commonGetRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function categoryPage() { let response; @@ -42,23 +44,10 @@ export function categoryPage() { tags: { name: 'Shopper - Category Page' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "Accessories – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Accessories – WooCommerce Core E2E Test Suite', - "body contains: Category's title": ( response ) => - response.body.includes( - `

    ${ product_category }

    ` - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + checkResponse( response, 200, { + title: `Accessories – ${ STORE_NAME }`, + body: `

    ${ product_category }

    `, + footer: FOOTER_TEXT, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js b/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js index 57b4c898c0e..4be19fc4824 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -37,6 +36,8 @@ import { payment_method, think_time_min, think_time_max, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -47,9 +48,9 @@ import { commonPostRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function checkoutCustomerLogin() { - let response; let woocommerce_process_checkout_nonce_customer; let woocommerce_login_nonce; let update_order_review_nonce_guest; @@ -64,27 +65,15 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/checkout`, { + const response = http.get( `${ base_url }/checkout`, { headers: requestHeaders, tags: { name: 'Shopper - View Checkout' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Checkout – WooCommerce Core E2E Test Suite', - 'body contains checkout class': ( response ) => - response.body.includes( - 'class="checkout woocommerce-checkout"' - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + + checkResponse( response, 200, { + title: `Checkout – ${ STORE_NAME }`, + body: 'class="checkout woocommerce-checkout"', + footer: FOOTER_TEXT, } ); // Correlate nonce values for use in subsequent requests. @@ -107,7 +96,7 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.post( + const updateResponse = http.post( `${ base_url }/?wc-ajax=update_order_review`, { security: `${ update_order_review_nonce_guest }`, @@ -131,7 +120,7 @@ export function checkoutCustomerLogin() { tags: { name: 'Shopper - wc-ajax=update_order_review' }, } ); - check( response, { + check( updateResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); @@ -147,7 +136,7 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/checkout`, { username: `${ customer_username }`, @@ -186,7 +175,7 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.post( + const updateResponse = http.post( `${ base_url }/?wc-ajax=update_order_review`, { security: `${ update_order_review_nonce_customer }`, @@ -210,7 +199,7 @@ export function checkoutCustomerLogin() { tags: { name: 'Shopper - wc-ajax=update_order_review' }, } ); - check( response, { + check( updateResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); @@ -226,7 +215,7 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=checkout`, { billing_first_name: `${ addresses_customer_billing_first_name }`, @@ -252,8 +241,8 @@ export function checkoutCustomerLogin() { ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'body contains: order-received': ( response ) => - response.body.includes( 'order-received' ), + 'body contains: order-received': ( r ) => + r.body.includes( 'order-received' ), } ); } ); @@ -268,28 +257,14 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/checkout/order-received/`, { + const response = http.get( `${ base_url }/checkout/order-received/`, { headers: requestHeaders, tags: { name: 'Shopper - Order Received' }, } ); - check( response, { - 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Checkout – WooCommerce Core E2E Test Suite', - "body contains: 'Thank you. Your order has been received.'": ( - response - ) => - response.body.includes( - 'Thank you. Your order has been received.' - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + checkResponse( response, 200, { + title: `Order received – ${ STORE_NAME }`, + body: 'Thank you. Your order has been received.', + footer: FOOTER_TEXT, } ); const requestHeadersPost = Object.assign( @@ -300,14 +275,14 @@ export function checkoutCustomerLogin() { commonNonStandardHeaders ); - response = http.post( + const refreshResponse = http.post( `${ base_url }/?wc-ajax=get_refreshed_fragments`, { headers: requestHeadersPost, tags: { name: 'Shopper - wc-ajax=get_refreshed_fragments' }, } ); - check( response, { + check( refreshResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js b/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js index ef41cce033c..a5f2e09b201 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -29,6 +28,8 @@ import { payment_method, think_time_min, think_time_max, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -39,9 +40,9 @@ import { commonPostRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function checkoutGuest() { - let response; let woocommerce_process_checkout_nonce_guest; let update_order_review_nonce_guest; @@ -54,27 +55,14 @@ export function checkoutGuest() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/checkout`, { + const response = http.get( `${ base_url }/checkout`, { headers: requestHeaders, tags: { name: 'Shopper - View Checkout' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Checkout – WooCommerce Core E2E Test Suite', - "body contains: 'woocommerce-checkout' class": ( response ) => - response.body.includes( - 'class="checkout woocommerce-checkout"' - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + checkResponse( response, 200, { + title: `Checkout – ${ STORE_NAME }`, + body: 'class="checkout woocommerce-checkout"', + footer: FOOTER_TEXT, } ); // Correlate nonce values for use in subsequent requests. @@ -97,7 +85,7 @@ export function checkoutGuest() { commonNonStandardHeaders ); - response = http.post( + const updateResponse = http.post( `${ base_url }/?wc-ajax=update_order_review`, { security: `${ update_order_review_nonce_guest }`, @@ -121,7 +109,7 @@ export function checkoutGuest() { tags: { name: 'Shopper - wc-ajax=update_order_review' }, } ); - check( response, { + check( updateResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); @@ -137,7 +125,7 @@ export function checkoutGuest() { commonNonStandardHeaders ); - response = http.post( + const response = http.post( `${ base_url }/?wc-ajax=checkout`, { billing_first_name: `${ addresses_guest_billing_first_name }`, @@ -163,8 +151,8 @@ export function checkoutGuest() { ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'body contains: order-received': ( response ) => - response.body.includes( 'order-received' ), + 'body contains: order-received': ( r ) => + r.body.includes( 'order-received' ), } ); } ); @@ -179,28 +167,14 @@ export function checkoutGuest() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/checkout/order-received/`, { + const response = http.get( `${ base_url }/checkout/order-received/`, { headers: requestHeaders, tags: { name: 'Shopper - Order Received' }, } ); - check( response, { - 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Checkout – WooCommerce Core E2E Test Suite', - "body contains: 'Thank you. Your order has been received.'": ( - response - ) => - response.body.includes( - 'Thank you. Your order has been received.' - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + checkResponse( response, 200, { + title: `Order received – ${ STORE_NAME }`, + body: 'Thank you. Your order has been received.', + footer: FOOTER_TEXT, } ); const requestHeadersPost = Object.assign( @@ -211,14 +185,14 @@ export function checkoutGuest() { commonNonStandardHeaders ); - response = http.post( + const refreshResponse = http.post( `${ base_url }/?wc-ajax=get_refreshed_fragments`, { headers: requestHeadersPost, tags: { name: 'Shopper - wc-ajax=get_refreshed_fragments' }, } ); - check( response, { + check( refreshResponse, { 'is status 200': ( r ) => r.status === 200, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/home.js b/plugins/woocommerce/tests/performance/requests/shopper/home.js index 7a7b8ba0498..dd5120d0972 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/home.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/home.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -10,7 +9,13 @@ import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js'; /** * Internal dependencies */ -import { base_url, think_time_min, think_time_max } from '../../config.js'; +import { + base_url, + think_time_min, + think_time_max, + FOOTER_TEXT, + STORE_NAME, +} from '../../config.js'; import { htmlRequestHeader, commonRequestHeaders, @@ -34,17 +39,13 @@ export function homePage() { headers: requestHeaders, tags: { name: 'Shopper - Site Root' }, } ); + check( response, { 'is status 200': ( r ) => r.status === 200, - 'title is: "WooCommerce Core E2E Test Suite"': ( response ) => - response.html().find( 'head title' ).text() === - 'WooCommerce Core E2E Test Suite', - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + [ `title is: "${ STORE_NAME }"` ]: ( r ) => + r.html().find( 'head title' ).text() === STORE_NAME, + 'footer contains: Built with WooCommerce': ( r ) => + r.html().find( 'body footer' ).text().includes( FOOTER_TEXT ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js b/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js index 54918a8aed4..789a3eb185f 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js @@ -1,9 +1,8 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies */ -import { sleep, check, group } from 'k6'; +import { sleep, group } from 'k6'; import http from 'k6/http'; import { randomIntBetween, @@ -13,16 +12,22 @@ import { /** * Internal dependencies */ -import { base_url, think_time_min, think_time_max } from '../../config.js'; +import { + base_url, + think_time_min, + think_time_max, + STORE_NAME, + FOOTER_TEXT, +} from '../../config.js'; import { htmlRequestHeader, commonRequestHeaders, commonGetRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function myAccountOrders() { - let response; let my_account_order_id; group( 'My Account', function () { @@ -34,27 +39,14 @@ export function myAccountOrders() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/my-account`, { + const response = http.get( `${ base_url }/my-account`, { headers: requestHeaders, tags: { name: 'Shopper - My Account' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "My account – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'My account – WooCommerce Core E2E Test Suite', - 'body contains: my account welcome message': ( response ) => - response.body.includes( - 'From your account dashboard you can view' - ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + checkResponse( response, 200, { + title: `My account – ${ STORE_NAME }`, + body: 'From your account dashboard you can view', + footer: FOOTER_TEXT, } ); } ); @@ -69,26 +61,17 @@ export function myAccountOrders() { commonNonStandardHeaders ); - response = http.get( `${ base_url }/my-account/orders/`, { + const response = http.get( `${ base_url }/my-account/orders/`, { headers: requestHeaders, tags: { name: 'Shopper - My Account Orders' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "My account – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'My account – WooCommerce Core E2E Test Suite', - "body contains: 'Orders' title": ( response ) => - response.body.includes( '>Orders' ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + + checkResponse( response, 200, { + title: `Orders – ${ STORE_NAME }`, + body: '>Orders', + footer: FOOTER_TEXT, } ); + my_account_order_id = findBetween( response.body, 'my-account/view-order/', @@ -107,28 +90,18 @@ export function myAccountOrders() { commonNonStandardHeaders ); - response = http.get( + const response = http.get( `${ base_url }/my-account/view-order/${ my_account_order_id }`, { headers: requestHeaders, tags: { name: 'Shopper - My Account Open Order' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "My account – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'My account – WooCommerce Core E2E Test Suite', - "body contains: 'Order number' title": ( response ) => - response.body.includes( `${ my_account_order_id }` ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + + checkResponse( response, 200, { + title: `My account – ${ STORE_NAME }`, + body: my_account_order_id, + footer: FOOTER_TEXT, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/my-account.js b/plugins/woocommerce/tests/performance/requests/shopper/my-account.js index c39626fa5af..ec1f70c930b 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/my-account.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/my-account.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -16,6 +15,8 @@ import { customer_password, think_time_min, think_time_max, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -25,6 +26,7 @@ import { commonPostRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function myAccount() { let response; @@ -43,21 +45,11 @@ export function myAccount() { headers: requestHeaders, tags: { name: 'Shopper - My Account Login Page' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title is: "My account – WooCommerce Core E2E Test Suite"': ( - response - ) => - response.html().find( 'head title' ).text() === - 'My account – WooCommerce Core E2E Test Suite', - "body contains: 'My account' title": ( response ) => - response.body.includes( '>My account' ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + + checkResponse( response, 200, { + title: `My account – ${ STORE_NAME }`, + body: '>My account', + footer: FOOTER_TEXT, } ); // Correlate nonce value for use in subsequent requests. @@ -95,10 +87,8 @@ export function myAccount() { ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'body contains: my account welcome message': ( response ) => - response.body.includes( - 'From your account dashboard you can view' - ), + 'body contains: my account welcome message': ( r ) => + r.body.includes( 'From your account dashboard you can view' ), } ); const requestHeadersPost = Object.assign( diff --git a/plugins/woocommerce/tests/performance/requests/shopper/search-product.js b/plugins/woocommerce/tests/performance/requests/shopper/search-product.js index edbdd662266..5bd6a5fd168 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/search-product.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/search-product.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -15,6 +14,8 @@ import { product_search_term, think_time_min, think_time_max, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -44,25 +45,18 @@ export function searchProduct() { ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'title matches: Search Results for {product_search_term} – WooCommerce Core E2E Test Suite': - ( response ) => { - const title_actual = response - .html() - .find( 'head title' ) - .text(); + [ `title matches: Search Results for {product_search_term} – ${ STORE_NAME }` ]: + ( r ) => { + const title_actual = r.html().find( 'head title' ).text(); const title_expected = new RegExp( - `Search Results for .${ product_search_term }. – WooCommerce Core E2E Test Suite` + `Search Results for .${ product_search_term }. – ${ STORE_NAME }` ); return title_actual.match( title_expected ); }, - "body contains: 'Search results' title": ( response ) => - response.body.includes( 'Search results:' ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + "body contains: 'Search results' title": ( r ) => + r.body.includes( 'Search results:' ), + 'footer contains: Built with WooCommerce': ( r ) => + r.html().find( 'body footer' ).text().includes( FOOTER_TEXT ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js b/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js index c1c9e626be8..aa79e45f47a 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js @@ -1,22 +1,28 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies */ -import { sleep, check, group } from 'k6'; +import { sleep, group } from 'k6'; import http from 'k6/http'; import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js'; /** * Internal dependencies */ -import { base_url, think_time_min, think_time_max } from '../../config.js'; +import { + base_url, + think_time_min, + think_time_max, + FOOTER_TEXT, + STORE_NAME, +} from '../../config.js'; import { htmlRequestHeader, commonRequestHeaders, commonGetRequestHeaders, commonNonStandardHeaders, } from '../../headers.js'; +import { checkResponse } from '../../utils.js'; export function shopPage() { let response; @@ -34,22 +40,10 @@ export function shopPage() { headers: requestHeaders, tags: { name: 'Shopper - Shop Page' }, } ); - check( response, { - 'is status 200': ( r ) => r.status === 200, - 'title equals: Shop – WooCommerce Core E2E Test Suite': ( - response - ) => - response.html().find( 'head title' ).text() === - 'Shop – WooCommerce Core E2E Test Suite', - 'body contains: woocommerce-products-header': ( response ) => - response.body.includes( - '
    ' - ), - 'body contains: woocommerce-loop-product__title': ( response ) => - response - .html() - .find( '.woocommerce-loop-product__title' ) - .toArray().length > 0, + checkResponse( response, 200, { + title: `Shop – ${ STORE_NAME }`, + body: '
    ', + footer: FOOTER_TEXT, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/single-product.js b/plugins/woocommerce/tests/performance/requests/shopper/single-product.js index fa90b42a9be..e4e9d7ef065 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/single-product.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/single-product.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /** * External dependencies @@ -16,6 +15,8 @@ import { product_sku, think_time_min, think_time_max, + FOOTER_TEXT, + STORE_NAME, } from '../../config.js'; import { htmlRequestHeader, @@ -42,27 +43,18 @@ export function singleProduct() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, - 'title is: {product_url} – WooCommerce Core E2E Test Suite': ( - response - ) => { - const title_actual = response - .html() - .find( 'head title' ) - .text(); + [ `title is: ${ product_url } – ${ STORE_NAME }` ]: ( r ) => { + const title_actual = r.html().find( 'head title' ).text(); const title_expected = new RegExp( - `${ product_url } – WooCommerce Core E2E Test Suite`, + `${ product_url } – ${ STORE_NAME }`, 'i' ); return title_actual.match( title_expected ); }, - 'body contains: product SKU': ( response ) => - response.body.includes( `class="sku">${ product_sku }` ), - 'footer contains: Built with WooCommerce': ( response ) => - response - .html() - .find( 'body footer' ) - .text() - .includes( 'Built with WooCommerce' ), + 'body contains: product SKU': ( r ) => + r.body.includes( `class="sku">${ product_sku }` ), + 'footer contains: Built with WooCommerce': ( r ) => + r.html().find( 'body footer' ).text().includes( FOOTER_TEXT ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/utils.js b/plugins/woocommerce/tests/performance/utils.js new file mode 100644 index 00000000000..7c40a25ef66 --- /dev/null +++ b/plugins/woocommerce/tests/performance/utils.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { check } from 'k6'; + +/** + * Internal dependencies + */ + +const checkResponse = ( + response, + statusCode, + page = { + title: '', + body: '', + footer: '', + } +) => { + check( response, { + [ `is status ${ statusCode }` ]: ( r ) => r.status === statusCode, + [ `title is: "${ page.title }"` ]: ( r ) => + r.html().find( 'head title' ).text() === page.title, + [ `body contains: ${ page.body }` ]: ( r ) => + r.body.includes( page.body ), + [ `footer contains: ${ page.footer }` ]: ( r ) => + r.html().find( 'body footer' ).text().includes( page.footer ), + } ); +}; + +export { checkResponse }; From a7b957dbce68fbcdf56c2a3713d71f90a1962851 Mon Sep 17 00:00:00 2001 From: George Jipa Date: Thu, 5 Sep 2024 22:56:48 +0300 Subject: [PATCH 16/57] Fix `Customers\DataStore::anonymize_customer` use of WC_Order instance as int (#38201) --- plugins/woocommerce/changelog/fix-anonymize-customer | 4 ++++ .../woocommerce/includes/class-wc-privacy-erasers.php | 2 +- .../src/Admin/API/Reports/Customers/DataStore.php | 10 +++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-anonymize-customer diff --git a/plugins/woocommerce/changelog/fix-anonymize-customer b/plugins/woocommerce/changelog/fix-anonymize-customer new file mode 100644 index 00000000000..82b4668282c --- /dev/null +++ b/plugins/woocommerce/changelog/fix-anonymize-customer @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Make Admin\API\Reports\Customers\DataStore::anonymize_customer accept an order instance as a parameter to ensure compatibility with the `woocommerce_privacy_remove_order_personal_data` hook. diff --git a/plugins/woocommerce/includes/class-wc-privacy-erasers.php b/plugins/woocommerce/includes/class-wc-privacy-erasers.php index 69cba905dce..3d43ff6052d 100644 --- a/plugins/woocommerce/includes/class-wc-privacy-erasers.php +++ b/plugins/woocommerce/includes/class-wc-privacy-erasers.php @@ -357,7 +357,7 @@ class WC_Privacy_Erasers { * Allow extensions to remove their own personal data for this order. * * @since 3.4.0 - * @param WC_Order $order A customer object. + * @param WC_Order $order Order instance. */ do_action( 'woocommerce_privacy_remove_order_personal_data', $order ); } diff --git a/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php index fac1fdaa961..e13ed41c47c 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php @@ -913,14 +913,18 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { * Anonymize the customer data for a single order. * * @internal - * @param int $order_id Order id. + * @param int|WC_Order $order Order instance or ID. * @return void */ - public static function anonymize_customer( $order_id ) { + public static function anonymize_customer( $order ) { global $wpdb; + if ( ! is_object( $order ) ) { + $order = wc_get_order( absint( $order ) ); + } + $customer_id = $wpdb->get_var( - $wpdb->prepare( "SELECT customer_id FROM {$wpdb->prefix}wc_order_stats WHERE order_id = %d", $order_id ) + $wpdb->prepare( "SELECT customer_id FROM {$wpdb->prefix}wc_order_stats WHERE order_id = %d", $order->get_id() ) ); if ( ! $customer_id ) { From 0ef7d95aff910b3f82b1a1e804ef2f8e4e0c8591 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 6 Sep 2024 10:39:55 +0800 Subject: [PATCH 17/57] Hide site visibility badge when "Launch Your Store" feature is disabled (#51159) * Fix: put site visibility badge behind the launch your store feature flag * Add changelog --- .../woocommerce/changelog/fix-site-visibility-badge | 4 ++++ .../Internal/ComingSoon/ComingSoonAdminBarBadge.php | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-site-visibility-badge diff --git a/plugins/woocommerce/changelog/fix-site-visibility-badge b/plugins/woocommerce/changelog/fix-site-visibility-badge new file mode 100644 index 00000000000..ea310491096 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-site-visibility-badge @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Put site visibility badge behind the launch your store feature flag diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php index 0b65041e322..082944cd3b6 100644 --- a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php @@ -4,6 +4,8 @@ declare( strict_types = 1 ); namespace Automattic\WooCommerce\Internal\ComingSoon; +use Automattic\WooCommerce\Admin\Features\Features; + /** * Adds hooks to add a badge to the WordPress admin bar showing site visibility. */ @@ -27,6 +29,11 @@ class ComingSoonAdminBarBadge { * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance. */ public function site_visibility_badge( $wp_admin_bar ) { + // Early exit if LYS feature is disabled. + if ( ! Features::is_enabled( 'launch-your-store' ) ) { + return; + } + $labels = array( 'coming-soon' => __( 'Coming soon', 'woocommerce' ), 'store-coming-soon' => __( 'Store coming soon', 'woocommerce' ), @@ -60,6 +67,11 @@ class ComingSoonAdminBarBadge { * @internal */ public function output_css() { + // Early exit if LYS feature is disabled. + if ( ! Features::is_enabled( 'launch-your-store' ) ) { + return; + } + if ( is_admin_bar_showing() ) { echo ' `, + previewBody: ( body ) => `
    ${ body } `, + + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + + docs: { + autodocs: true, + }, }; diff --git a/tools/storybook/package.json b/tools/storybook/package.json index 8e6fee1694b..a4ecb5533d9 100644 --- a/tools/storybook/package.json +++ b/tools/storybook/package.json @@ -11,10 +11,10 @@ "author": "Automattic", "license": "GPL-3.0-or-later", "scripts": { - "build-storybook": "build-storybook -c ./.storybook", + "build-storybook": "pnpm build-woocommerce && ./import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true storybook build -c ./.storybook", "build-woocommerce": "pnpm --filter=@woocommerce/plugin-woocommerce build", "preinstall": "npx only-allow pnpm", - "storybook": "pnpm build-woocommerce && ./import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true start-storybook -c ./.storybook -p 6007 --ci", + "storybook": "pnpm build-woocommerce && ./import-wp-css-storybook.sh && BABEL_ENV=storybook STORYBOOK=true storybook dev -c ./.storybook -p 6007 --ci", "storybook-rtl": "USE_RTL_STYLE=true pnpm storybook" }, "engines": { @@ -27,32 +27,30 @@ "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/preset-env": "^7.23.5", + "@babel/preset-react": "7.23.3", + "@babel/preset-typescript": "^7.23.3", "@babel/runtime": "^7.23.5", - "@storybook/addon-a11y": "6.5.17-alpha.0", - "@storybook/addon-actions": "6.5.17-alpha.0", + "@storybook/addon-a11y": "7.6.19", + "@storybook/addon-actions": "7.6.19", "@storybook/addon-console": "^1.2.3", - "@storybook/addon-controls": "6.5.17-alpha.0", - "@storybook/addon-docs": "6.5.17-alpha.0", - "@storybook/addon-knobs": "^6.4.0", - "@storybook/addon-links": "6.5.17-alpha.0", - "@storybook/addon-storysource": "6.5.17-alpha.0", - "@storybook/addon-viewport": "6.5.17-alpha.0", - "@storybook/addons": "6.5.17-alpha.0", - "@storybook/api": "6.5.17-alpha.0", - "@storybook/builder-webpack5": "6.5.17-alpha.0", - "@storybook/components": "6.5.17-alpha.0", - "@storybook/core-events": "6.5.17-alpha.0", - "@storybook/manager-webpack5": "6.5.17-alpha.0", - "@storybook/react": "6.5.17-alpha.0", - "@storybook/theming": "6.5.17-alpha.0", + "@storybook/addon-controls": "7.6.19", + "@storybook/addon-docs": "7.6.19", + "@storybook/addon-links": "7.6.19", + "@storybook/addon-storysource": "7.6.19", + "@storybook/addon-viewport": "7.6.19", + "@storybook/addons": "7.6.19", + "@storybook/api": "7.6.19", + "@storybook/components": "7.6.19", + "@storybook/core-events": "7.6.19", + "@storybook/react": "7.6.19", + "@storybook/react-webpack5": "7.6.19", + "@storybook/theming": "7.6.19", "@woocommerce/eslint-plugin": "workspace:*", "react": "18.3.1", "react-dom": "18.3.1", + "storybook": "7.6.19", "typescript": "^5.3.3", "webpack": "^5.89.0", "wireit": "0.14.3" - }, - "dependencies": { - "@babel/preset-typescript": "^7.23.3" } } diff --git a/tools/storybook/webpack.config.js b/tools/storybook/webpack.config.js index 5de3c72deb3..8d56c0ad6ea 100644 --- a/tools/storybook/webpack.config.js +++ b/tools/storybook/webpack.config.js @@ -50,6 +50,8 @@ module.exports = ( storybookConfig ) => { __dirname, './node_modules/react-dom' ); + storybookConfig.resolve.alias[ '@storybook/react-dom-shim' ] = + '@storybook/react-dom-shim/dist/react-18'; storybookConfig.resolve.modules = [ path.join( __dirname, '../../plugins/woocommerce-admin/client' ), From b40c4a95e5f8e92413abb335d1687d8b8efc230b Mon Sep 17 00:00:00 2001 From: Vlad Olaru Date: Mon, 9 Sep 2024 18:06:43 +0300 Subject: [PATCH 39/57] Attach WooPayments incentive ID to wcadmin_tasklist_click Tracks event props (#51105) * Add WooPayments incentive ID to task additional data * Attach WooPayments incentive ID to wcadmin_tasklist_click Tracks event * Add changelog * Add changelog * Replace short array syntax * docs: Update docs --- ...1103-woopayments-incentive-to-tracks-props | 4 +++ packages/js/data/src/onboarding/types.ts | 1 + .../task-lists/fills/tax/test/index.tsx | 2 +- .../setup-task-list/setup-task-list.tsx | 4 +++ ...1103-woopayments-incentive-to-tracks-props | 5 +++ plugins/woocommerce/src/Admin/API/Plugins.php | 4 +-- .../Tasks/WooCommercePayments.php | 17 +++++++++- .../src/Internal/Admin/WcPayWelcomePage.php | 33 ++++++++++++++++--- 8 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 packages/js/data/changelog/update-51103-woopayments-incentive-to-tracks-props create mode 100644 plugins/woocommerce/changelog/update-51103-woopayments-incentive-to-tracks-props diff --git a/packages/js/data/changelog/update-51103-woopayments-incentive-to-tracks-props b/packages/js/data/changelog/update-51103-woopayments-incentive-to-tracks-props new file mode 100644 index 00000000000..c0d87293d0e --- /dev/null +++ b/packages/js/data/changelog/update-51103-woopayments-incentive-to-tracks-props @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add wooPaymentsIncentiveId to the TaskType additionalData. diff --git a/packages/js/data/src/onboarding/types.ts b/packages/js/data/src/onboarding/types.ts index a39fa627e63..7e3e3058c73 100644 --- a/packages/js/data/src/onboarding/types.ts +++ b/packages/js/data/src/onboarding/types.ts @@ -35,6 +35,7 @@ export type TaskType = { stripeTaxActivated?: boolean; woocommerceTaxActivated?: boolean; woocommerceShippingActivated?: boolean; + wooPaymentsIncentiveId?: string; }; // Possibly added in DeprecatedTasks.mergeDeprecatedCallbackFunctions isDeprecated?: boolean; diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/test/index.tsx b/plugins/woocommerce-admin/client/task-lists/fills/tax/test/index.tsx index 0e744c53f33..2b1edb3e310 100644 --- a/plugins/woocommerce-admin/client/task-lists/fills/tax/test/index.tsx +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/test/index.tsx @@ -17,7 +17,7 @@ jest.mock( '@wordpress/data', () => ( { const fakeTask: { additionalData: { - [ key: string ]: boolean | string[]; + [ key: string ]: boolean | string | string[]; }; } = { additionalData: {}, diff --git a/plugins/woocommerce-admin/client/task-lists/setup-task-list/setup-task-list.tsx b/plugins/woocommerce-admin/client/task-lists/setup-task-list/setup-task-list.tsx index 9649f9c3b75..df515f70e93 100644 --- a/plugins/woocommerce-admin/client/task-lists/setup-task-list/setup-task-list.tsx +++ b/plugins/woocommerce-admin/client/task-lists/setup-task-list/setup-task-list.tsx @@ -200,6 +200,10 @@ export const SetupTaskList: React.FC< TaskListProps > = ( { recordEvent( `${ listEventPrefix }click`, { task_name: task.id, context: layoutString, + ...( task?.additionalData?.wooPaymentsIncentiveId && { + woopayments_incentive_id: + task.additionalData.wooPaymentsIncentiveId, + } ), } ); }; diff --git a/plugins/woocommerce/changelog/update-51103-woopayments-incentive-to-tracks-props b/plugins/woocommerce/changelog/update-51103-woopayments-incentive-to-tracks-props new file mode 100644 index 00000000000..3a20bc5b0f6 --- /dev/null +++ b/plugins/woocommerce/changelog/update-51103-woopayments-incentive-to-tracks-props @@ -0,0 +1,5 @@ +Significance: patch +Type: tweak +Comment: If there is a WooPayments incentive active, attach its ID to the wcadmin_tasklist_click Tracks event. + + diff --git a/plugins/woocommerce/src/Admin/API/Plugins.php b/plugins/woocommerce/src/Admin/API/Plugins.php index f8b3e43150f..6fa02a01bab 100644 --- a/plugins/woocommerce/src/Admin/API/Plugins.php +++ b/plugins/woocommerce/src/Admin/API/Plugins.php @@ -592,9 +592,9 @@ class Plugins extends \WC_REST_Data_Controller { } /** - * Returns a URL that can be used to by WCPay to verify business details. + * Returns a URL that can be used by WooPayments to verify business details. * - * @return WP_Error|array Connect URL. + * @return \WP_Error|array Connect URL. */ public function connect_wcpay() { if ( ! class_exists( 'WC_Payments' ) ) { diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index 5c6d45255f6..64f7cf1b4e7 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -6,7 +6,6 @@ use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Admin\PluginsHelper; use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions; -use Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init as WCPayPromotionInit; use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways; /** @@ -65,6 +64,22 @@ class WooCommercePayments extends Task { ); } + /** + * Additional data. + * + * @return mixed + */ + public function get_additional_data() { + /** + * Filter WooPayments onboarding task additional data. + * + * @since 9.4.0 + * + * @param ?array $additional_data The task additional data. + */ + return apply_filters( 'woocommerce_admin_woopayments_onboarding_task_additional_data', null ); + } + /** * Time. * diff --git a/plugins/woocommerce/src/Internal/Admin/WcPayWelcomePage.php b/plugins/woocommerce/src/Internal/Admin/WcPayWelcomePage.php index 0815e1b4f8a..446c336d9b1 100644 --- a/plugins/woocommerce/src/Internal/Admin/WcPayWelcomePage.php +++ b/plugins/woocommerce/src/Internal/Admin/WcPayWelcomePage.php @@ -43,10 +43,11 @@ class WcPayWelcomePage { * WCPayWelcomePage constructor. */ public function __construct() { - add_action( 'admin_menu', [ $this, 'register_payments_welcome_page' ] ); - add_filter( 'woocommerce_admin_shared_settings', [ $this, 'shared_settings' ] ); - add_filter( 'woocommerce_admin_allowed_promo_notes', [ $this, 'allowed_promo_notes' ] ); - add_filter( 'woocommerce_admin_woopayments_onboarding_task_badge', [ $this, 'onboarding_task_badge' ] ); + add_action( 'admin_menu', array( $this, 'register_payments_welcome_page' ) ); + add_filter( 'woocommerce_admin_shared_settings', array( $this, 'shared_settings' ) ); + add_filter( 'woocommerce_admin_allowed_promo_notes', array( $this, 'allowed_promo_notes' ) ); + add_filter( 'woocommerce_admin_woopayments_onboarding_task_badge', array( $this, 'onboarding_task_badge' ) ); + add_filter( 'woocommerce_admin_woopayments_onboarding_task_additional_data', array( $this, 'onboarding_task_additional_data' ) ); } /** @@ -211,6 +212,30 @@ class WcPayWelcomePage { return $this->get_incentive()['task_badge'] ?? $badge; } + /** + * Filter the onboarding task additional data to add the WooPayments incentive data to it. + * + * @param ?array $additional_data The current task additional data. + * + * @return ?array The filtered task additional data. + */ + public function onboarding_task_additional_data( ?array $additional_data ): ?array { + // Return early if the incentive must not be visible. + if ( ! $this->must_be_visible() ) { + return $additional_data; + } + + // If we have an incentive, add the incentive ID to the additional data. + if ( $this->get_incentive()['id'] ) { + if ( empty( $additional_data ) ) { + $additional_data = array(); + } + $additional_data['wooPaymentsIncentiveId'] = $this->get_incentive()['id']; + } + + return $additional_data; + } + /** * Check if the WooPayments payment gateway is active and set up or was at some point, * or there are orders processed with it, at some moment. From a8b42b3ce36c23ad967b730771ce38a5ba4df020 Mon Sep 17 00:00:00 2001 From: Matthew Reishus Date: Mon, 9 Sep 2024 10:59:58 -0500 Subject: [PATCH 40/57] Blocks\Assets\Api: Do not attempt to update script_data transient with the same data (#50962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Blocks\Assets\Api: Do not attempt to update script_data transient with the same data * Add changelog * Update plugins/woocommerce/src/Blocks/Assets/Api.php Co-authored-by: Albert Juhé Lluveras * Update plugins/woocommerce/changelog/50962-dont-update-transient-with-same-data Co-authored-by: Albert Juhé Lluveras --------- Co-authored-by: Alex Florisca Co-authored-by: Albert Juhé Lluveras --- .../50962-dont-update-transient-with-same-data | 4 ++++ plugins/woocommerce/src/Blocks/Assets/Api.php | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 plugins/woocommerce/changelog/50962-dont-update-transient-with-same-data diff --git a/plugins/woocommerce/changelog/50962-dont-update-transient-with-same-data b/plugins/woocommerce/changelog/50962-dont-update-transient-with-same-data new file mode 100644 index 00000000000..6a83f4fd019 --- /dev/null +++ b/plugins/woocommerce/changelog/50962-dont-update-transient-with-same-data @@ -0,0 +1,4 @@ +Significance: patch +Type: performance + +Don't update the script data transient for the same data \ No newline at end of file diff --git a/plugins/woocommerce/src/Blocks/Assets/Api.php b/plugins/woocommerce/src/Blocks/Assets/Api.php index 56f3a67f55d..9b191724bdb 100644 --- a/plugins/woocommerce/src/Blocks/Assets/Api.php +++ b/plugins/woocommerce/src/Blocks/Assets/Api.php @@ -41,6 +41,13 @@ class Api { */ private $script_data = null; + /** + * Tracks whether script_data was modified during the current request. + * + * @var boolean + */ + private $script_data_modified = false; + /** * Stores the hash for the script data, made up of the site url, plugin version and package path. * @@ -171,6 +178,9 @@ class Api { if ( is_null( $this->script_data ) || $this->disable_cache ) { return; } + if ( ! $this->script_data_modified ) { + return; + } set_transient( $this->script_data_transient_key, wp_json_encode( @@ -216,6 +226,7 @@ class Api { 'version' => ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src ), 'dependencies' => ! empty( $asset['dependencies'] ) ? $asset['dependencies'] : [], ); + $this->script_data_modified = true; } // Return asset details as well as the requested dependencies array. From 0645a0326d0a51fb1ffd0444163047f3b16d388f Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:29:09 +0100 Subject: [PATCH 41/57] [k6 tests] Manage K6 version (#51239) --- plugins/woocommerce/changelog/tests-manage-k6 | 4 ++ plugins/woocommerce/package.json | 22 ++++------ .../woocommerce/tests/performance/.gitignore | 3 ++ .../tests/performance/bin/install-k6.sh | 42 +++++++++++++++++++ 4 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 plugins/woocommerce/changelog/tests-manage-k6 create mode 100644 plugins/woocommerce/tests/performance/.gitignore create mode 100755 plugins/woocommerce/tests/performance/bin/install-k6.sh diff --git a/plugins/woocommerce/changelog/tests-manage-k6 b/plugins/woocommerce/changelog/tests-manage-k6 new file mode 100644 index 00000000000..f3ca2dde059 --- /dev/null +++ b/plugins/woocommerce/changelog/tests-manage-k6 @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Manage K6 version diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 28129dc3d7d..a01cd250ffc 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -30,8 +30,7 @@ "env:start:blocks": "pnpm --filter='@woocommerce/block-library' env:start && pnpm playwright install chromium --with-deps", "env:stop": "pnpm wp-env stop", "env:test": "pnpm env:dev", - "env:perf:install-k6": "curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1", - "env:perf": "pnpm env:dev && pnpm env:performance-init && pnpm env:perf:install-k6", + "env:perf": "pnpm env:dev && pnpm env:performance-init", "preinstall": "npx only-allow pnpm", "postinstall": "XDEBUG_MODE=off composer install --quiet", "lint": "pnpm --if-present '/^lint:lang:.*$/'", @@ -57,8 +56,10 @@ "test:e2e:install": "pnpm playwright install chromium", "test:e2e:blocks": "pnpm --filter='@woocommerce/block-library' test:e2e", "test:e2e:with-env": "pnpm test:e2e:install && bash ./tests/e2e-pw/run-tests-with-env.sh", - "test:perf": "./k6 run ./tests/performance/tests/gh-action-pr-requests.js", "test:env:start": "pnpm env:test", + "test:perf": "./tests/performance/bin/k6 run ./tests/performance/tests/gh-action-pr-requests.js", + "test:perf:ci-setup": "pnpm test:perf:install-k6 && pnpm env:perf", + "test:perf:install-k6": "bash ./tests/performance/bin/install-k6.sh", "test:php": "./vendor/bin/phpunit -c ./phpunit.xml", "test:php:watch": "./vendor/bin/phpunit-watcher watch", "test:metrics": "USE_WP_ENV=1 pnpm playwright test --config=tests/metrics/playwright.config.js", @@ -474,22 +475,13 @@ "command": "test:perf", "optional": true, "changes": [ - "client/admin/config/*.json", - "composer.json", - "composer.lock", - "includes/**/*.php", - "patterns/**/*.php", - "src/**/*.php", - "templates/**/*.php", - "tests/performance/**", - ".wp-env.json", - "woocommerce.php", - "uninstall.php" + "tests/performance/**" ], "testEnv": { - "start": "env:perf" + "start": "test:perf:ci-setup" }, "events": [ + "pull_request", "push" ] }, diff --git a/plugins/woocommerce/tests/performance/.gitignore b/plugins/woocommerce/tests/performance/.gitignore new file mode 100644 index 00000000000..f1907178cf7 --- /dev/null +++ b/plugins/woocommerce/tests/performance/.gitignore @@ -0,0 +1,3 @@ +k6 +k6-*.tar.gz +k6-*.zip diff --git a/plugins/woocommerce/tests/performance/bin/install-k6.sh b/plugins/woocommerce/tests/performance/bin/install-k6.sh new file mode 100755 index 00000000000..60942993e49 --- /dev/null +++ b/plugins/woocommerce/tests/performance/bin/install-k6.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -eo pipefail + +K6_VERSION="0.53.0" +DOWNLOAD_URL="https://github.com/grafana/k6/releases/download/v$K6_VERSION" +SCRIPT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" || return; pwd -P) + +download_archive() { + local archive=$1 + local download_url="$DOWNLOAD_URL/$archive" + local download_path="$SCRIPT_PATH/$archive" + + echo "Downloading from $download_url to $download_path" + curl "$download_url" -L -o "$download_path" +} + +arch=$(uname -m) + +if [[ "$arch" == "x86_64" || "$arch" == "amd64" ]]; then + arch="amd64" + elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then + arch="arm64" + else + echo "Unsupported CPU architecture: $arch. Please check K6 docs and install the right version for your system." + exit 1 +fi + +if [ "$(uname)" == "Darwin" ]; then + archive="k6-v$K6_VERSION-macos-$arch.zip" + download_archive "$archive" + unzip -j -o "$SCRIPT_PATH/$archive" -d "$SCRIPT_PATH" +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + archive="k6-v$K6_VERSION-linux-$arch.tar.gz" + download_archive "$archive" + tar --strip-components=1 -xzf "$SCRIPT_PATH/$archive" -C "$SCRIPT_PATH" +else + echo "Unsupported operating system. Please check K6 docs and install the right version for your system." + exit 1 +fi + +"$SCRIPT_PATH/k6" version From e85869f3a4f1b2f03da84f9eb5a0449bbe1a91cd Mon Sep 17 00:00:00 2001 From: Vedanshu Jain Date: Tue, 10 Sep 2024 04:50:25 +0530 Subject: [PATCH 42/57] Add phone to searchable FTS fields for order addresses. (#51065) This PR adds the phone number to the FTS index on the order address table, and adds the migration routines so that we can recreate the index for shops that already have this enabled. Additionally, it also removes support for relevancy operators to make search performant. Fixes #51213 --- .../woocommerce/changelog/enhance-fts-index | 4 ++ .../woocommerce/changelog/enhance-fts-index-2 | 4 ++ .../woocommerce/includes/class-wc-install.php | 3 ++ ...rest-system-status-tools-v2-controller.php | 15 +++++++ .../includes/wc-update-functions.php | 20 +++++++++ .../Orders/CustomOrdersTableController.php | 41 +++++++++++++++-- .../Orders/OrdersTableSearchQuery.php | 17 +++++-- .../src/Internal/Utilities/DatabaseUtil.php | 44 ++++++++++++++++++- .../Internal/Utilities/DatabaseUtilTest.php | 24 ++++++++++ 9 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 plugins/woocommerce/changelog/enhance-fts-index create mode 100644 plugins/woocommerce/changelog/enhance-fts-index-2 diff --git a/plugins/woocommerce/changelog/enhance-fts-index b/plugins/woocommerce/changelog/enhance-fts-index new file mode 100644 index 00000000000..279ab53ddaa --- /dev/null +++ b/plugins/woocommerce/changelog/enhance-fts-index @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Add phone number to FTS index to improve order searchability. diff --git a/plugins/woocommerce/changelog/enhance-fts-index-2 b/plugins/woocommerce/changelog/enhance-fts-index-2 new file mode 100644 index 00000000000..91e6392ec1d --- /dev/null +++ b/plugins/woocommerce/changelog/enhance-fts-index-2 @@ -0,0 +1,4 @@ +Significance: patch +Type: performance + +Remove relevancy ranking from FTS queries to improve performance. diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index e6d591d8d11..002844d49c4 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -266,6 +266,9 @@ class WC_Install { 'wc_update_930_add_woocommerce_coming_soon_option', 'wc_update_930_migrate_user_meta_for_launch_your_store_tour', ), + '9.4.0' => array( + 'wc_update_940_add_phone_to_order_address_fts_index', + ), ); /** diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php index 822b2a651f1..fd50fc10a88 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php @@ -8,6 +8,9 @@ * @since 3.0.0 */ +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; +use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; + defined( 'ABSPATH' ) || exit; /** @@ -217,6 +220,11 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller { __( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce' ) ), ), + 'recreate_order_address_fts_index' => array( + 'name' => __( 'Re-create Order Address FTS index', 'woocommerce' ), + 'button' => __( 'Recreate index', 'woocommerce' ), + 'desc' => __( 'This tool will recreate the full text search index for order addresses. If the index does not exist, it will try to create it.', 'woocommerce' ), + ), ); if ( method_exists( 'WC_Install', 'verify_base_tables' ) ) { $tools['verify_db_tables'] = array( @@ -598,6 +606,13 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller { } break; + case 'recreate_order_address_fts_index': + $hpos_controller = wc_get_container()->get( CustomOrdersTableController::class ); + $results = $hpos_controller->recreate_order_address_fts_index(); + $ran = $results['status']; + $message = $results['message']; + break; + default: $tools = $this->get_tools(); if ( isset( $tools[ $tool ]['callback'] ) ) { diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index 34b8efdbd26..e4f880a2a76 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -24,12 +24,14 @@ use Automattic\WooCommerce\Database\Migrations\MigrationHelper; use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs; use Automattic\WooCommerce\Internal\Admin\Notes\WooSubscriptionsNotes; use Automattic\WooCommerce\Internal\AssignDefaultCategory; +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator; use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore; use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories; use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync; +use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Automattic\WooCommerce\Utilities\StringUtil; /** @@ -2852,3 +2854,21 @@ function wc_update_930_migrate_user_meta_for_launch_your_store_tour() { ) ); } + +/** + * Recreate FTS index if it already exists, so that phone number can be added to the index. + */ +function wc_update_940_add_phone_to_order_address_fts_index(): void { + $fts_already_exists = get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes'; + if ( ! $fts_already_exists ) { + return; + } + + $hpos_controller = wc_get_container()->get( CustomOrdersTableController::class ); + $result = $hpos_controller->recreate_order_address_fts_index(); + if ( ! $result['status'] ) { + if ( class_exists( 'WC_Admin_Settings ' ) ) { + WC_Admin_Settings::add_error( $result['message'] ); + } + } +} diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index 3345f6a61e5..cd646c03269 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -345,9 +345,9 @@ class CustomOrdersTableController { // Check again to see if index was actually created. if ( $this->db_util->fts_index_on_order_address_table_exists() ) { - update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', true ); + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', false ); } else { - update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', true ); + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', false ); if ( class_exists( 'WC_Admin_Settings ' ) ) { WC_Admin_Settings::add_error( __( 'Failed to create FTS index on address table', 'woocommerce' ) ); } @@ -359,15 +359,48 @@ class CustomOrdersTableController { // Check again to see if index was actually created. if ( $this->db_util->fts_index_on_order_item_table_exists() ) { - update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'yes', true ); + update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'yes', false ); } else { - update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'no', true ); + update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'no', false ); if ( class_exists( 'WC_Admin_Settings ' ) ) { WC_Admin_Settings::add_error( __( 'Failed to create FTS index on order item table', 'woocommerce' ) ); } } } + /** + * Recreate order addresses FTS index. Useful when updating to 9.4 when phone number was added to index, or when other recreating index is needed. + * + * @since 9.4.0. + * + * @return array Array with keys status (bool) and message (string). + */ + public function recreate_order_address_fts_index(): array { + $this->db_util->drop_fts_index_order_address_table(); + if ( $this->db_util->fts_index_on_order_address_table_exists() ) { + return array( + 'status' => false, + 'message' => __( 'Failed to modify existing FTS index. Please go to WooCommerce > Status > Tools and run the "Re-create Order Address FTS index" tool.', 'woocommerce' ), + ); + } else { + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', false ); + } + + $this->db_util->create_fts_index_order_address_table(); + if ( ! $this->db_util->fts_index_on_order_address_table_exists() ) { + return array( + 'status' => false, + 'message' => __( 'Failed to create FTS index on order address table. Please go to WooCommerce > Status > Tools and run the "Re-create Order Address FTS index" tool.', 'woocommerce' ), + ); + } else { + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', false ); + return array( + 'status' => true, + 'message' => __( 'FTS index recreated.', 'woocommerce' ), + ); + } + } + /** * Handler for the setting pre-update hook. * We use it to verify that authoritative orders table switch doesn't happen while sync is pending. diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php index 73b6c9e31aa..08b3b112767 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Internal\DataStores\Orders; +use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Exception; /** @@ -238,6 +239,7 @@ class OrdersTableSearchQuery { */ private function get_where_for_products() { global $wpdb; + $db_util = wc_get_container()->get( DatabaseUtil::class ); $items_table = $this->query->get_table_name( 'items' ); $orders_table = $this->query->get_table_name( 'orders' ); $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION ) === 'yes'; @@ -251,7 +253,7 @@ $orders_table.id in ( MATCH ( search_query_items.order_item_name ) AGAINST ( %s IN BOOLEAN MODE ) ) ", - '*' . $wpdb->esc_like( $this->search_term ) . '*' + $wpdb->esc_like( $db_util->sanitise_boolean_fts_search_term( $this->search_term ) ), ); // phpcs:enable } @@ -279,18 +281,27 @@ $orders_table.id in ( $order_table = $this->query->get_table_name( 'orders' ); $address_table = $this->query->get_table_name( 'addresses' ); + $db_util = wc_get_container()->get( DatabaseUtil::class ); + $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes'; if ( $fts_enabled ) { + $matchers = "$address_table.first_name, $address_table.last_name, $address_table.company, $address_table.address_1, $address_table.address_2, $address_table.city, $address_table.state, $address_table.postcode, $address_table.country, $address_table.email"; + + // Support for phone was added in 9.4. + if ( version_compare( get_option( 'woocommerce_db_version' ), '9.4.0', '>=' ) ) { + $matchers .= ", $address_table.phone"; + } + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table and $address_table are hardcoded. return $wpdb->prepare( " $order_table.id IN ( SELECT order_id FROM $address_table WHERE - MATCH( $address_table.first_name, $address_table.last_name, $address_table.company, $address_table.address_1, $address_table.address_2, $address_table.city, $address_table.state, $address_table.postcode, $address_table.country, $address_table.email ) AGAINST ( %s IN BOOLEAN MODE ) + MATCH( $matchers ) AGAINST ( %s IN BOOLEAN MODE ) ) ", - '*' . $wpdb->esc_like( $this->search_term ) . '*' + $wpdb->esc_like( $db_util->sanitise_boolean_fts_search_term( $this->search_term ) ) ); // phpcs:enable } diff --git a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php index ab9dd8d26f5..86b7bf8140c 100644 --- a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php +++ b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php @@ -357,7 +357,49 @@ $on_duplicate_clause global $wpdb; $address_table = $wpdb->prefix . 'wc_order_addresses'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded. - $wpdb->query( "CREATE FULLTEXT INDEX order_addresses_fts ON $address_table (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email)" ); + $wpdb->query( "CREATE FULLTEXT INDEX order_addresses_fts ON $address_table (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email, phone)" ); + } + + /** + * Helper method to drop the fulltext index on order address table. + * + * @since 9.4.0 + * + * @return void + */ + public function drop_fts_index_order_address_table(): void { + global $wpdb; + $address_table = $wpdb->prefix . 'wc_order_addresses'; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded. + $wpdb->query( "ALTER TABLE $address_table DROP INDEX order_addresses_fts;" ); + } + + /** + * Sanitize FTS Search params to remove relevancy operators for performance, and add partial matches. Useful when the sorting is already happening based on some other conditions, so relevancy calculation is not needed. + * + * @since 9.4.0 + * + * @param string $param Search term. + * + * @return string Sanitized search term. + */ + public function sanitise_boolean_fts_search_term( string $param ): string { + // Remove any operator to prevent incorrect query and fatals, such as search starting with `++`. We can allow this in the future if we have proper validation for FTS search operators. + // Space is allowed to provide multiple words. + $sanitized_param = preg_replace( '/[^\p{L}\p{N}_]+/u', ' ', $param ); + if ( $sanitized_param !== $param ) { + $param = str_replace( '"', '', $param ); + return '"' . $param . '"'; + } + // Split the search phrase into words so that we can add operators when needed. + $words = explode( ' ', $param ); + $sanitized_words = array(); + foreach ( $words as $word ) { + // Add `*` as suffix to every term so that partial matches happens. + $word = $word . '*'; + $sanitized_words[] = $word; + } + return implode( ' ', $sanitized_words ); } /** diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php index 6f98bac9163..d83d37b3830 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php @@ -131,4 +131,28 @@ class DatabaseUtilTest extends \WC_Unit_Test_Case { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Hardcoded query. $this->assertEquals( 'Updated Content', $wpdb->get_var( $content_query ) ); } + + /** + * @testDox Test that sanitise_boolean_fts_search_term() works as expected. + */ + public function test_sanitise_boolean_fts_search_term(): void { + $terms_sanitized_mapping = array( + // Normal terms are suffixed with wildcard. + 'abc' => 'abc*', + 'abc def' => 'abc* def*', + // Terms containing operators are quoted. + '+abc -def' => '"+abc -def"', + '++abc-def' => '"++abc-def"', + 'abc (>def '"abc (>def '"abc def"', + 'abc*' => '"abc*"', + // Some edge cases. + '' => '*', + '"' => '""', + ); + + foreach ( $terms_sanitized_mapping as $term => $expected ) { + $this->assertEquals( $expected, $this->sut->sanitise_boolean_fts_search_term( $term ) ); + } + } } From 7dc26a8c6601ecc69f30f401028f62124e8ba116 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 10 Sep 2024 11:29:14 +0800 Subject: [PATCH 43/57] Clean up 'Profile Wizard' code (#51190) * Remove plugins/woocommerce-admin/client/profile-wizard * Clean up styles * Remove setup wizard hook reference * Add changelog * Remove e2e utils related to old setup wizard * Fix lint * Add changelog * Remove unused usage modal --- packages/js/e2e-utils/README.md | 8 +- .../changelog/cleanup-profiler-wizard | 4 + packages/js/e2e-utils/src/components.js | 146 --- packages/js/e2e-utils/src/flows/merchant.js | 58 +- packages/js/e2e-utils/src/old-flows.js | 5 - .../bin/hook-reference/data.json | 13 - .../client/layout/controller.js | 49 +- .../client/layout/footer/footer.scss | 4 - .../woocommerce-admin/client/layout/index.js | 21 - .../layout/transient-notices/style.scss | 10 - .../client/profile-wizard/header.js | 125 --- .../client/profile-wizard/index.js | 362 ------- .../business-details/data/employee-options.js | 31 - .../business-details/data/platform-options.js | 51 - .../business-details/data/product-options.js | 58 -- .../business-details/data/revenue-options.js | 100 -- .../data/selling-venue-options.js | 36 - .../selective-bundle/app-illustration.js | 140 --- .../flows/selective-bundle/index.js | 901 ------------------ .../selective-extensions-bundle/index.js | 412 -------- .../selective-extensions-bundle/style.scss | 82 -- .../selective-extensions-bundle/test/index.js | 156 --- .../flows/selective-bundle/style.scss | 32 - .../steps/business-details/index.js | 73 -- .../steps/business-details/style.scss | 10 - .../steps/business-details/test/index.js | 204 ---- .../client/profile-wizard/steps/industry.js | 350 ------- .../steps/product-types/index.js | 394 -------- .../steps/product-types/label.js | 100 -- .../steps/product-types/product-type.js | 98 -- .../steps/product-types/style.scss | 87 -- .../test/__snapshots__/index.js.snap | 393 -------- .../test/__snapshots__/product-type.js.snap | 95 -- .../steps/product-types/test/index.js | 140 --- .../steps/product-types/test/product-type.js | 58 -- .../profile-wizard/steps/skip-button.tsx | 115 --- .../steps/store-details/index.js | 522 ---------- .../steps/store-details/style.scss | 29 - .../test/__snapshots__/index.js.snap | 712 -------------- .../steps/store-details/test/index.js | 176 ---- .../profile-wizard/steps/usage-modal.js | 204 ---- .../client/profile-wizard/style.scss | 435 --------- .../profile-wizard/unsaved-changes-modal.js | 40 - .../components/WCPay/UsageModal.js | 2 +- .../components/WCPay/index.js | 1 - .../changelog/update-cleanup-profiler-wizard | 4 + 46 files changed, 38 insertions(+), 7008 deletions(-) create mode 100644 packages/js/e2e-utils/changelog/cleanup-profiler-wizard delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/header.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/data/employee-options.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/data/platform-options.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/data/product-options.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/data/revenue-options.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/data/selling-venue-options.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/app-illustration.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/selective-extensions-bundle/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/selective-extensions-bundle/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/selective-extensions-bundle/test/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/business-details/test/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/industry.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/label.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/product-type.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/test/__snapshots__/index.js.snap delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/test/__snapshots__/product-type.js.snap delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/test/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/product-types/test/product-type.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/skip-button.tsx delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/index.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/steps/usage-modal.js delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/style.scss delete mode 100644 plugins/woocommerce-admin/client/profile-wizard/unsaved-changes-modal.js create mode 100644 plugins/woocommerce/changelog/update-cleanup-profiler-wizard diff --git a/packages/js/e2e-utils/README.md b/packages/js/e2e-utils/README.md index 12c9abe07ec..93f898daf37 100644 --- a/packages/js/e2e-utils/README.md +++ b/packages/js/e2e-utils/README.md @@ -12,7 +12,8 @@ npm install @woocommerce/e2e-utils --save ## Usage Example: -~~~js + +```js import { shopper, merchant, @@ -29,7 +30,7 @@ describe( 'Cart page', () => { await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } ); } ); } ); -~~~ +``` ### Retries @@ -86,7 +87,6 @@ This package provides support for enabling retries in tests: |----------|-------------|------------| | `addDownloadableProductPermission` | `productName` | Add a downloadable permission for product in order | | `collapseAdminMenu` | `collapse` | Collapse or expand the WP admin menu | -| `dismissOnboardingWizard` | | Dismiss the onboarding wizard if present | | `goToOrder` | `orderId` | Go to view a single order | | `goToProduct` | `productId` | Go to view a single product | | `login` | | Log in as merchant | @@ -100,7 +100,6 @@ This package provides support for enabling retries in tests: | `openPermalinkSettings` | | Go to Settings -> Permalinks | | `openPlugins` | | Go to the Plugins screen | | `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 | | `openAnalyticsPage` | | Open any Analytics page | @@ -210,7 +209,6 @@ There is a general utilities object `utils` with the following functions: | `clickFilter` | `selector` | helper method that clicks on a list page filter | | `clickTab` | `tabName` | Click on a WooCommerce -> Settings tab | | `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page | -| `completeOnboardingWizard` | | completes the onboarding wizard with some default settings | | `createCoupon` | `couponAmount`, `couponType` | creates a basic coupon. Default amount is 5. Default coupon type is fixed discount. Returns the generated coupon code. | | `createGroupedProduct` | | creates a grouped product for the grouped product tests. Returns the product id. | | `createSimpleDownloadableProduct` | `name, downloadLimit, downloadName, price` | Create a simple downloadable product | diff --git a/packages/js/e2e-utils/changelog/cleanup-profiler-wizard b/packages/js/e2e-utils/changelog/cleanup-profiler-wizard new file mode 100644 index 00000000000..8a55c777336 --- /dev/null +++ b/packages/js/e2e-utils/changelog/cleanup-profiler-wizard @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Remove Profile Wizard releated utils diff --git a/packages/js/e2e-utils/src/components.js b/packages/js/e2e-utils/src/components.js index 911d8d51a54..b641c674869 100644 --- a/packages/js/e2e-utils/src/components.js +++ b/packages/js/e2e-utils/src/components.js @@ -60,151 +60,6 @@ const waitAndClickPrimary = async ( waitForNetworkIdle = true ) => { await page.waitForNavigation( { waitUntil: 'networkidle0' } ); } }; -/** - * Complete onboarding wizard. - */ -const completeOnboardingWizard = async () => { - // Store Details section - await merchant.runSetupWizard(); - - // Fill store's address - first line - await expect( page ).toFill( - '#inspector-text-control-0', - config.get( 'addresses.admin.store.addressfirstline' ) - ); - - // Fill store's address - second line - await expect( page ).toFill( - '#inspector-text-control-1', - config.get( 'addresses.admin.store.addresssecondline' ) - ); - - // Fill country and state where the store is located - await expect( page ).toFill( - '.woocommerce-select-control__control-input', - config.get( 'addresses.admin.store.countryandstate' ) - ); - - // Fill the city where the store is located - await expect( page ).toFill( - '#inspector-text-control-2', - config.get( 'addresses.admin.store.city' ) - ); - - // Fill postcode of the store - await expect( page ).toFill( - '#inspector-text-control-3', - config.get( 'addresses.admin.store.postcode' ) - ); - - // Verify that checkbox next to "I'm setting up a store for a client" is not selected - await verifyCheckboxIsUnset( '.components-checkbox-control__input' ); - - // Wait for "Continue" button to become active - await page.waitForSelector( 'button.is-primary:not(:disabled)' ); - - // Click on "Continue" button to move to the next step - await page.click( 'button.is-primary', { text: 'Continue' } ); - - // Wait for usage tracking pop-up window to appear on a new site - const usageTrackingHeader = await page.$( - '.components-modal__header-heading' - ); - if ( usageTrackingHeader ) { - await expect( page ).toMatchElement( - '.components-modal__header-heading', - { - text: 'Build a better WooCommerce', - } - ); - - // Query for "No Thanks" buttons - const continueButtons = await page.$$( - '.woocommerce-usage-modal__actions button.is-secondary' - ); - expect( continueButtons ).toHaveLength( 1 ); - - await continueButtons[ 0 ].click(); - await expect( page ).toMatchElement( - '.woocommerce-usage-modal__actions button.is-secondary.is-busy' - ); - await expect( page ).not.toMatchElement( - '.woocommerce-usage-modal__actions button.is-primary:disabled' - ); - } - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - // Industry section - - // Query for the industries checkboxes - const industryCheckboxes = await page.$$( - '.components-checkbox-control__input' - ); - expect( industryCheckboxes ).toHaveLength( 8 ); - - // Select all industries including "Other" - for ( let i = 0; i < 8; i++ ) { - await industryCheckboxes[ i ].click(); - } - - // Fill "Other" industry - await expect( page ).toFill( - '.components-text-control__input', - config.get( 'onboardingwizard.industry' ) - ); - - // Wait for "Continue" button to become active - await waitAndClickPrimary(); - - // Product types section - - // Query for the product types checkboxes - const productTypesCheckboxes = await page.$$( - '.components-checkbox-control__input' - ); - expect( productTypesCheckboxes ).toHaveLength( 7 ); - - // Select Physical and Downloadable products - for ( let i = 1; i < 2; i++ ) { - await productTypesCheckboxes[ i ].click(); - } - - // Wait for "Continue" button to become active - await waitAndClickPrimary(); - - // Business Details section - - // Temporarily add delay to reduce test flakiness - await page.waitFor( 2000 ); - - // Query for the s - const selectControls = await page.$$( '.woocommerce-select-control' ); - expect( selectControls ).toHaveLength( 2 ); - - // Fill the number of products you plan to sell - await selectControls[ 0 ].click(); - await page.waitForSelector( '.woocommerce-select-control__control' ); - await expect( page ).toClick( '.woocommerce-select-control__option', { - text: config.get( 'onboardingwizard.numberofproducts' ), - } ); - - // Fill currently selling elsewhere - await selectControls[ 1 ].click(); - await page.waitForSelector( '.woocommerce-select-control__control' ); - await expect( page ).toClick( '.woocommerce-select-control__option', { - text: config.get( 'onboardingwizard.sellingelsewhere' ), - } ); - - // Wait for "Continue" button to become active - await waitAndClickPrimary( false ); - - // Skip installing extensions - await unsetCheckbox( '.components-checkbox-control__input' ); - await verifyCheckboxIsUnset( '.components-checkbox-control__input' ); - await waitAndClickPrimary(); - - // End of onboarding wizard -}; /** * Create simple product. @@ -668,7 +523,6 @@ const deleteAllShippingZones = async () => { }; export { - completeOnboardingWizard, createSimpleProduct, createVariableProduct, createGroupedProduct, diff --git a/packages/js/e2e-utils/src/flows/merchant.js b/packages/js/e2e-utils/src/flows/merchant.js index d312ad64686..0bde1222503 100644 --- a/packages/js/e2e-utils/src/flows/merchant.js +++ b/packages/js/e2e-utils/src/flows/merchant.js @@ -31,8 +31,6 @@ const { WP_ADMIN_NEW_PRODUCT, WP_ADMIN_PERMALINK_SETTINGS, WP_ADMIN_PLUGINS, - WP_ADMIN_SETUP_WIZARD, - WP_ADMIN_WC_HOME, WP_ADMIN_WC_SETTINGS, WP_ADMIN_WC_EXTENSIONS, WP_ADMIN_WC_HELPER, @@ -42,7 +40,6 @@ const { WP_ADMIN_IMPORT_PRODUCTS, WP_ADMIN_PLUGIN_INSTALL, WP_ADMIN_WP_UPDATES, - IS_RETEST_MODE, } = require( './constants' ); const { getSlug, waitForTimeout } = require( './utils' ); @@ -83,7 +80,6 @@ const merchant = { await Promise.all( [ page.click( 'input[type=submit]' ), page.waitForNavigation( { waitUntil: 'networkidle0' } ), - merchant.dismissOnboardingWizard(), ] ); }, @@ -173,15 +169,6 @@ const merchant = { } ); }, - runSetupWizard: async () => { - const setupWizard = IS_RETEST_MODE - ? WP_ADMIN_SETUP_WIZARD - : WP_ADMIN_WC_HOME; - await page.goto( setupWizard, { - waitUntil: 'networkidle0', - } ); - }, - goToOrder: async ( orderId ) => { await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), { waitUntil: 'networkidle0', @@ -290,9 +277,10 @@ const merchant = { revokeDownloadableProductPermission: async ( productName ) => { // Revoke downloadable product permission - const permission = await expect( - page - ).toMatchElement( 'div.wc-metabox > h3', { text: productName } ); + const permission = await expect( page ).toMatchElement( + 'div.wc-metabox > h3', + { text: productName } + ); await expect( permission ).toClick( 'button.revoke_access' ); // Wait for auto save @@ -625,10 +613,10 @@ const merchant = { }, /* Uploads and activates a plugin located at the provided file path. This will also deactivate and delete the plugin if it exists. - * - * @param {string} pluginFilePath The location of the plugin zip file to upload. - * @param {string} pluginName The name of the plugin. For example, `WooCommerce`. - */ + * + * @param {string} pluginFilePath The location of the plugin zip file to upload. + * @param {string} pluginName The name of the plugin. For example, `WooCommerce`. + */ uploadAndActivatePlugin: async ( pluginFilePath, pluginName ) => { await merchant.openPlugins(); @@ -647,8 +635,7 @@ const merchant = { await page.click( 'a.upload-view-toggle' ); await expect( page ).toMatchElement( 'p.install-help', { - text: - 'If you have a plugin in a .zip format, you may install or update it by uploading it here.', + text: 'If you have a plugin in a .zip format, you may install or update it by uploading it here.', } ); const uploader = await page.$( 'input[type=file]' ); @@ -753,33 +740,6 @@ const merchant = { } }, - /** - * Dismiss the onboarding wizard if it is open. - */ - dismissOnboardingWizard: async () => { - let waitForNav = false; - const skipButton = await page.$( - '.woocommerce-profile-wizard__footer-link' - ); - if ( skipButton ) { - await skipButton.click(); - waitForNav = true; - } - - // Dismiss usage tracking pop-up window if it appears on a new site - const usageTrackingHeader = await page.$( - '.woocommerce-usage-modal button.is-secondary' - ); - if ( usageTrackingHeader ) { - await usageTrackingHeader.click(); - waitForNav = true; - } - - if ( waitForNav ) { - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - } - }, - /** * Expand or collapse the WP admin menu. * diff --git a/packages/js/e2e-utils/src/old-flows.js b/packages/js/e2e-utils/src/old-flows.js index 4afde090920..c59a7d6e74a 100644 --- a/packages/js/e2e-utils/src/old-flows.js +++ b/packages/js/e2e-utils/src/old-flows.js @@ -172,11 +172,6 @@ const StoreOwnerFlow = { StoreOwnerFlowDeprecated(); await merchant.openSettings( tab, section ); }, - - runSetupWizard: async () => { - StoreOwnerFlowDeprecated(); - await merchant.runSetupWizard(); - }, }; export { CustomerFlow, StoreOwnerFlow }; diff --git a/plugins/woocommerce-admin/bin/hook-reference/data.json b/plugins/woocommerce-admin/bin/hook-reference/data.json index 8693d4a18e4..67bdf3e86bd 100644 --- a/plugins/woocommerce-admin/bin/hook-reference/data.json +++ b/plugins/woocommerce-admin/bin/hook-reference/data.json @@ -810,19 +810,6 @@ } ] }, - { - "description": "Filter for Onboarding steps configuration.", - "sourceFile": "https://github.com/woocommerce/woocommerce-admin/blob/46c8304c425749dfc715b38e59f56198b05e7b46/client/profile-wizard/index.js#L140-L145", - "name": "woocommerce_admin_profile_wizard_steps", - "type": "filter", - "params": [ - { - "name": "steps", - "type": "Array.", - "description": "Array of steps for Onboarding Wizard." - } - ] - }, { "description": "Store Management extensions links", "sourceFile": "https://github.com/woocommerce/woocommerce-admin/blob/46c8304c425749dfc715b38e59f56198b05e7b46/client/store-management-links/index.js#L171-L176", diff --git a/plugins/woocommerce-admin/client/layout/controller.js b/plugins/woocommerce-admin/client/layout/controller.js index dd686f064c3..d1c0a8d1a3d 100644 --- a/plugins/woocommerce-admin/client/layout/controller.js +++ b/plugins/woocommerce-admin/client/layout/controller.js @@ -60,16 +60,13 @@ const MarketingOverviewMultichannel = lazy( () => const Marketplace = lazy( () => import( /* webpackChunkName: "marketplace" */ '../marketplace' ) ); -const ProfileWizard = lazy( () => - import( /* webpackChunkName: "profile-wizard" */ '../profile-wizard' ) -); const CoreProfiler = lazy( () => import( /* webpackChunkName: "core-profiler" */ '../core-profiler' ) ); const SettingsGroup = lazy( () => - import( /* webpackChunkName: "profile-wizard" */ '../settings' ) + import( /* webpackChunkName: "settings" */ '../settings' ) ); const WCPaymentsWelcomePage = lazy( () => import( @@ -256,34 +253,22 @@ export const getPages = () => { } ); if ( window.wcAdminFeatures.onboarding ) { - if ( ! window.wcAdminFeatures[ 'core-profiler' ] ) { - pages.push( { - container: ProfileWizard, - path: '/setup-wizard', - breadcrumbs: [ - ...initialBreadcrumbs, - __( 'Setup Wizard', 'woocommerce' ), - ], - capability: 'manage_woocommerce', - } ); - } else { - pages.push( { - container: CoreProfiler, - path: '/setup-wizard', - breadcrumbs: [ - ...initialBreadcrumbs, - __( 'Profiler', 'woocommerce' ), - ], - capability: 'manage_woocommerce', - layout: { - header: false, - footer: false, - showNotices: true, - showStoreAlerts: false, - showPluginArea: false, - }, - } ); - } + pages.push( { + container: CoreProfiler, + path: '/setup-wizard', + breadcrumbs: [ + ...initialBreadcrumbs, + __( 'Profiler', 'woocommerce' ), + ], + capability: 'manage_woocommerce', + layout: { + header: false, + footer: false, + showNotices: true, + showStoreAlerts: false, + showPluginArea: false, + }, + } ); } if ( window.wcAdminFeatures[ 'core-profiler' ] ) { diff --git a/plugins/woocommerce-admin/client/layout/footer/footer.scss b/plugins/woocommerce-admin/client/layout/footer/footer.scss index 6f962779f54..e1e1aa93a3c 100644 --- a/plugins/woocommerce-admin/client/layout/footer/footer.scss +++ b/plugins/woocommerce-admin/client/layout/footer/footer.scss @@ -9,10 +9,6 @@ bottom: -1px; /* to hide the border when no visible children */ z-index: 1001; /* on top of #wp-content-editor-tools */ - .woocommerce-profile-wizard__body & { - width: 100%; - } - @include breakpoint("782px-960px") { width: calc(100% - 36px); } diff --git a/plugins/woocommerce-admin/client/layout/index.js b/plugins/woocommerce-admin/client/layout/index.js index 7b7986343bd..65eac2013a4 100644 --- a/plugins/woocommerce-admin/client/layout/index.js +++ b/plugins/woocommerce-admin/client/layout/index.js @@ -58,12 +58,6 @@ const StoreAlerts = lazy( () => import( /* webpackChunkName: "store-alerts" */ './store-alerts' ) ); -const WCPayUsageModal = lazy( () => - import( - /* webpackChunkName: "wcpay-usage-modal" */ '../task-lists/fills/PaymentGatewaySuggestions/components/WCPay/UsageModal' - ) -); - export class PrimaryLayout extends Component { render() { const { @@ -163,15 +157,6 @@ function _Layout( { }, 0 ); }, [ location?.pathname ] ); - function isWCPaySettingsPage() { - const { page: queryPage, section, tab } = getQuery(); - return ( - queryPage === 'wc-settings' && - tab === 'checkout' && - section === 'woocommerce_payments' - ); - } - const { breadcrumbs, layout = { header: true, footer: true } } = page; const { header: showHeader = true, @@ -237,12 +222,6 @@ function _Layout( { ) } - - { isEmbedded && isWCPaySettingsPage() && ( - - - - ) } { showFooter &&