diff --git a/README.md b/README.md index a0dc78caa81..4ff249daa69 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ To get up and running within the WooCommerce Monorepo, you will need to make sur Once you've installed all of the prerequisites, you can run the following commands to get everything working. ```bash -# Ensure that you're using the correct version of Node -nvm use +# Ensure that correct version of Node is installed and being used +nvm install # Install the PHP and Composer dependencies for all of the plugins, packages, and tools pnpm install # Build all of the plugins, packages, and tools in the monorepo diff --git a/changelog.txt b/changelog.txt index e34b03260d4..2ba165e2bdc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,245 @@ == Changelog == += 9.3.1 2024-09-12 = + +* Tweak - Disable remote logging feature by default [#51312](https://github.com/woocommerce/woocommerce/pull/51312) + += 9.3.0 2024-09-10 = + +**WooCommerce** + +* Enhancement - Add query params masking to remote logger [#51108](https://github.com/woocommerce/woocommerce/pull/51108) +* Update - Added more paths to remote logger query param whitelist [#51108](https://github.com/woocommerce/woocommerce/pull/51108) +* Fix - Revert update to React 18 in Checkout block. [#51289](https://github.com/woocommerce/woocommerce/pull/51289) +* Fix - Add check to ensure themes API is safe [#51081](https://github.com/woocommerce/woocommerce/pull/51081) +* Fix - CYS - Remove usage of `prepare_item_for_response` function in `Images` endpoint. [#50923](https://github.com/woocommerce/woocommerce/pull/50923) +* Fix - Add ability for a screen reader to announce the current tab on a single product page. [#50373](https://github.com/woocommerce/woocommerce/pull/50373) +* Fix - Add a label to the product pagination for the woocommerce pagination [#49924](https://github.com/woocommerce/woocommerce/pull/49924) +* Fix - Add aria-current to the current link in My Account side nav [#49800](https://github.com/woocommerce/woocommerce/pull/49800) +* Fix - Add aria-label on View order button to aid in accessibility for screen readers [#49424](https://github.com/woocommerce/woocommerce/pull/49424) +* Fix - Add CSS outline for site visibility badge keyboard accessibility [#50794](https://github.com/woocommerce/woocommerce/pull/50794) +* Fix - Add scope attribute and aria-label to the product attributes table [#49768](https://github.com/woocommerce/woocommerce/pull/49768) +* Fix - Add to Cart with Options - Fix translation when used inside the Single Product block. [#50628](https://github.com/woocommerce/woocommerce/pull/50628) +* Fix - Allow verified parameter to be set by REST API request [#50525](https://github.com/woocommerce/woocommerce/pull/50525) +* Fix - Avoid PHP warnings if `add-to-cart.php` template does not pass `aria-describedby_text` [#48969](https://github.com/woocommerce/woocommerce/pull/48969) +* Fix - Cart block: Strip HTML tags and decode HTML entities in quantity change notifications. [#50541](https://github.com/woocommerce/woocommerce/pull/50541) +* Fix - Changed from using React.render to React.createRoot for marketing coupons as it has been deprecated since React 18 [#48832](https://github.com/woocommerce/woocommerce/pull/48832) +* Fix - Changed from using React.render to React.createRoot for payment methods promotion, shipping settings region zone as it has been deprecated since React 18 [#48835](https://github.com/woocommerce/woocommerce/pull/48835) +* Fix - Changed from using React.render to React.createRoot for print shipping banner as it has been deprecated since React 18 [#48831](https://github.com/woocommerce/woocommerce/pull/48831) +* Fix - Changed from using React.render to React.createRoot for product-usage-notice-modal as it has been deprecated since React 18 [#50765](https://github.com/woocommerce/woocommerce/pull/50765) +* Fix - Changed from using React.render to React.createRoot for wc addon tour as it has been deprecated since React 18 [#48833](https://github.com/woocommerce/woocommerce/pull/48833) +* Fix - Changed from using React.render to React.createRoot for WCAdmin uses as it has been deprecated since React 18 [#48785](https://github.com/woocommerce/woocommerce/pull/48785) +* Fix - Changed instances of prime marks inappropriately used when apostrophes are supposed to be used for some parts of WC Admin JS/TS/TSX files [#50776](https://github.com/woocommerce/woocommerce/pull/50776) +* Fix - Clear product unique ID (`global_unique_id`) when duplicating products. [#50629](https://github.com/woocommerce/woocommerce/pull/50629) +* Fix - Compatibility Layer: fix 'woocommerce_before_single_product_summary' hook position. [#50392](https://github.com/woocommerce/woocommerce/pull/50392) +* Fix - CYS - Improve the error when a request fails due to permissions [#50211](https://github.com/woocommerce/woocommerce/pull/50211) +* Fix - CYS - Update the "show_on_front" setting to "posts" to avoid overriding the "page" template. [#50083](https://github.com/woocommerce/woocommerce/pull/50083) +* Fix - CYS: disable zoom out on fonts/color pairs iframe [#50498](https://github.com/woocommerce/woocommerce/pull/50498) +* Fix - CYS: Fix auto scroll when a new block is added. [#50431](https://github.com/woocommerce/woocommerce/pull/50431) +* Fix - CYS: Improve opt in flow [#50529](https://github.com/woocommerce/woocommerce/pull/50529) +* Fix - Display address card for virtual products if shopper's address is known [#50127](https://github.com/woocommerce/woocommerce/pull/50127) +* Fix - Enable skipped E2E tests for attributes #50143 [#50143](https://github.com/woocommerce/woocommerce/pull/50143) +* Fix - Ensure coupon errors are visible on block checkout when invalid coupons are removed. [#50412](https://github.com/woocommerce/woocommerce/pull/50412) +* Fix - Ensure low and no stock email notification routine is triggered whenever product stock changes [#49583](https://github.com/woocommerce/woocommerce/pull/49583) +* Fix - Ensure session object is initialized before attempting to get chosen shipping methods [#50774](https://github.com/woocommerce/woocommerce/pull/50774) +* Fix - Ensure that the orders REST endpoint behaves the same as the UI when updating an order to remove a line item. [#50606](https://github.com/woocommerce/woocommerce/pull/50606) +* Fix - Featured Product: Fix variable product Selection dropdown #50633 [#50633](https://github.com/woocommerce/woocommerce/pull/50633) +* Fix - Fix "Product Meta" translations - Register the block server side. [#50625](https://github.com/woocommerce/woocommerce/pull/50625) +* Fix - Fix: ensure the global product object is always ready for compatibility layer by disabling default render routine of Product Templates inner blocks. [#49971](https://github.com/woocommerce/woocommerce/pull/49971) +* Fix - Fix activating the installed subscription when the user has multiple active licenses for the same product. [#49803](https://github.com/woocommerce/woocommerce/pull/49803) +* Fix - Fix address heading level on My Account page. [#49764](https://github.com/woocommerce/woocommerce/pull/49764) +* Fix - Fix an admin bar CSS positioning bug in WordPress.com on mobile [#50709](https://github.com/woocommerce/woocommerce/pull/50709) +* Fix - Fix cart shortcode updates when not used on the main cart page. [#50524](https://github.com/woocommerce/woocommerce/pull/50524) +* Fix - Fix core profiler checkbox vertical alignment and border color [#50151](https://github.com/woocommerce/woocommerce/pull/50151) +* Fix - Fix core profiler set up my store button and TOS are too close to each other [#50579](https://github.com/woocommerce/woocommerce/pull/50579) +* Fix - Fix e2e Google for WooCommerce strict mode violation error [#50189](https://github.com/woocommerce/woocommerce/pull/50189) +* Fix - Fixed Core Profiler's sticky footer button problem [#50727](https://github.com/woocommerce/woocommerce/pull/50727) +* Fix - Fixed placeholders in the classic cart shipping calculator to update with country selection. [#49684](https://github.com/woocommerce/woocommerce/pull/49684) +* Fix - Fixes a bug where some express payment buttons weren't being rendered correctly [#49304](https://github.com/woocommerce/woocommerce/pull/49304) +* Fix - Fix extensionCartUpdates to surface generic error messages, and include documentation for the error handling. [#49762](https://github.com/woocommerce/woocommerce/pull/49762) +* Fix - Fix focus order on checkout block page. [#49649](https://github.com/woocommerce/woocommerce/pull/49649) +* Fix - Fix navigation badge decreases when installing extension in "Grow your business task" [#50584](https://github.com/woocommerce/woocommerce/pull/50584) +* Fix - Fix page titles of the cart and checkout page when using blocks and FSE themes. [#49986](https://github.com/woocommerce/woocommerce/pull/49986) +* Fix - Fix rescheduling of actions that are blocked by other delayed actions [#50082](https://github.com/woocommerce/woocommerce/pull/50082) +* Fix - Fix the "Add payment methods" link in LYS congrat screen redirects to a blank page [#50609](https://github.com/woocommerce/woocommerce/pull/50609) +* Fix - Fix translation - Avoid registering blocks in the wrong context. [#50615](https://github.com/woocommerce/woocommerce/pull/50615) +* Fix - Fix `Product meta` console error. [#50680](https://github.com/woocommerce/woocommerce/pull/50680) +* Fix - Fix `store-title` endpoint - Pass default value to `get_option`. [#50673](https://github.com/woocommerce/woocommerce/pull/50673) +* Fix - Hide save changes button in main payments screen [#50064](https://github.com/woocommerce/woocommerce/pull/50064) +* Fix - In Remote Specs, treat empty arrays as valid cached values so individual engines can return default values. [#50521](https://github.com/woocommerce/woocommerce/pull/50521) +* Fix - Keep focus on shipping option input once selected [#49360](https://github.com/woocommerce/woocommerce/pull/49360) +* Fix - Make the matching variations alert a live region [#50132](https://github.com/woocommerce/woocommerce/pull/50132) +* Fix - Only count published products in productCount [#50503](https://github.com/woocommerce/woocommerce/pull/50503) +* Fix - Prevent fatal error if NULL is provided in array_search under Jetpack Stats [#50696](https://github.com/woocommerce/woocommerce/pull/50696) +* Fix - Prevent Store API orders being placed with empty state [#50028](https://github.com/woocommerce/woocommerce/pull/50028) +* Fix - Prevent sync-on-read from affecting results of HPOS diff CLI tool. [#49726](https://github.com/woocommerce/woocommerce/pull/49726) +* Fix - Product Collection: Fix max price query to include prices less or equal to the given max value. [#49917](https://github.com/woocommerce/woocommerce/pull/49917) +* Fix - Product Collection: fix the preview if used in Products by specific Category or Tag [#49889](https://github.com/woocommerce/woocommerce/pull/49889) +* Fix - Product Price block: prevent price amounts from breaking into multiple lines [#50660](https://github.com/woocommerce/woocommerce/pull/50660) +* Fix - Properly detect active plugins in multisite WP installations. [#50417](https://github.com/woocommerce/woocommerce/pull/50417) +* Fix - Reduce error noise in the user profile screen, by removing the requirement for custom fields to have a class attribute. [#48079](https://github.com/woocommerce/woocommerce/pull/48079) +* Fix - Remove Active Shipping Zones check for displaying shipping calculator on the Cart Page. [#49214](https://github.com/woocommerce/woocommerce/pull/49214) +* Fix - Single product block - Fix translation for title and description in edit mode. [#50599](https://github.com/woocommerce/woocommerce/pull/50599) +* Fix - Store API: Do not resume pending orders--create a new order instead [#50531](https://github.com/woocommerce/woocommerce/pull/50531) +* Fix - Transform labels in shipping zone region selector to decode html entities [#50694](https://github.com/woocommerce/woocommerce/pull/50694) +* Fix - Treat post_type=product as a shop page. [#50567](https://github.com/woocommerce/woocommerce/pull/50567) +* Fix - Update product order status colors to ensure accessible color contrasts [#49934](https://github.com/woocommerce/woocommerce/pull/49934) +* Add - Add an additional field for the email settings that sets the footer text color [#49648](https://github.com/woocommerce/woocommerce/pull/49648) +* Add - Add blueprint behind a feature flag for testing purposes. [#49763](https://github.com/woocommerce/woocommerce/pull/49763) +* Add - Add field for the email footer text color [#49648](https://github.com/woocommerce/woocommerce/pull/49648) +* Add - Add function to clear system status theme info cache [#50803](https://github.com/woocommerce/woocommerce/pull/50803) +* Add - Add methods required by extensions to control product feature usage based on subscription status. [#50218](https://github.com/woocommerce/woocommerce/pull/50218) +* Add - Add parameter to avoid attempting to create the logs directory if it doesn't exist [#49766](https://github.com/woocommerce/woocommerce/pull/49766) +* Add - Add Pattern button to no blocks view on the CYS assembler [#49981](https://github.com/woocommerce/woocommerce/pull/49981) +* Add - Add reactified main payments screen [#49972](https://github.com/woocommerce/woocommerce/pull/49972) +* Add - Add reactify-classic-payments-settings feature flag [#49966](https://github.com/woocommerce/woocommerce/pull/49966) +* Add - Add tracks for WordPress Importer/Export pages. [#50769](https://github.com/woocommerce/woocommerce/pull/50769) +* Add - Add `FilteredGetDataTrait`, `OrderAwareControllerTrait`, and `StatsDataStoreTrait` for extension developers to reuse while creating custom Analytics [#49425](https://github.com/woocommerce/woocommerce/pull/49425) +* Add - Implement server-side remote error logging [#49599](https://github.com/woocommerce/woocommerce/pull/49599) +* Add - Inform screen reader users when mini cart updates [#48295](https://github.com/woocommerce/woocommerce/pull/48295) +* Add - Integrate JS remote logging package in WooCommerce Admin [#50134](https://github.com/woocommerce/woocommerce/pull/50134) +* Add - Product Collection: emit the JS event when PC block is rendered [#50166](https://github.com/woocommerce/woocommerce/pull/50166) +* Add - Product Collection: Enable Context-Aware Previews by Adding `usesReference` to `registerProductCollection` [#49796](https://github.com/woocommerce/woocommerce/pull/49796) +* Add - Track frequency of unhandled JS errors with MC Stats [#50155](https://github.com/woocommerce/woocommerce/pull/50155) +* Add - Use MC Stats for PHP fatal error counting [#49658](https://github.com/woocommerce/woocommerce/pull/49658) +* Add - [E2E tests]: Add product description using the block editor #50232 [#50232](https://github.com/woocommerce/woocommerce/pull/50232) +* Update - Update WooCommerce Shipping Promo Banner to install the latest version of WooCommerce Shipping instead of WCS&T. [#50970](https://github.com/woocommerce/woocommerce/pull/50970) +* Update - Add abbreviations for fields GTIN, UPC, EAN, OR ISBN [#50042](https://github.com/woocommerce/woocommerce/pull/50042) +* Update - Add additional fields to new product editor e2e tests. [#50241](https://github.com/woocommerce/woocommerce/pull/50241) +* Update - Add confirmation prompt for site visibility settings when changing from live to coming soon mode [#50759](https://github.com/woocommerce/woocommerce/pull/50759) +* Update - Add pattern validation for global_unique_id [#50501](https://github.com/woocommerce/woocommerce/pull/50501) +* Update - Add remote logger as a log handler to wc logger [#50430](https://github.com/woocommerce/woocommerce/pull/50430) +* Update - Add request_uri prop to remote logging data [#50671](https://github.com/woocommerce/woocommerce/pull/50671) +* Update - Add woocommerce_coming_soon option for all sites [#50581](https://github.com/woocommerce/woocommerce/pull/50581) +* Update - Comment: Fix typos in documentation. [#50282](https://github.com/woocommerce/woocommerce/pull/50282) +* Update - CYS - Add tests for the Full Composability feature. [#49748](https://github.com/woocommerce/woocommerce/pull/49748) +* Update - CYS - Run appropriate tests depending on the WordPress version. [#50016](https://github.com/woocommerce/woocommerce/pull/50016) +* Update - CYS - Update icon and text colors in the assembler. [#50478](https://github.com/woocommerce/woocommerce/pull/50478) +* Update - CYS: Improve opt-in flow fonts. [#50086](https://github.com/woocommerce/woocommerce/pull/50086) +* Update - CYS: Improve opt-in flow patterns. [#50080](https://github.com/woocommerce/woocommerce/pull/50080) +* Update - CYS: Improve tracking survey [#50196](https://github.com/woocommerce/woocommerce/pull/50196) +* Update - CYS: Improve tracking survey [#50354](https://github.com/woocommerce/woocommerce/pull/50354) +* Update - CYS: Update the tracking URL to the external Fiverr link in sidebar of the **Add your logo** screen. [#50753](https://github.com/woocommerce/woocommerce/pull/50753) +* Update - Enable remote logging feature flag [#50351](https://github.com/woocommerce/woocommerce/pull/50351) +* Update - feat: add `aria-required` attributes to WC form fields [#48371](https://github.com/woocommerce/woocommerce/pull/48371) +* Update - Fixed log-out link behavior so that redirects work, and so that security nonces are automatically added to link in navigation menus. [#49605](https://github.com/woocommerce/woocommerce/pull/49605) +* Update - Migrate LYS user meta [#50664](https://github.com/woocommerce/woocommerce/pull/50664) +* Update - Move marketing task to things to do next task list [#50487](https://github.com/woocommerce/woocommerce/pull/50487) +* Update - Move site visibility badge to admin bar. [#50775](https://github.com/woocommerce/woocommerce/pull/50775) +* Update - Remove "Need help?" modal from onboarding [#47812](https://github.com/woocommerce/woocommerce/pull/47812) +* Update - Remove all links from the CYS sidebars [#50414](https://github.com/woocommerce/woocommerce/pull/50414) +* Update - Remove remote API call from marketing task [#50479](https://github.com/woocommerce/woocommerce/pull/50479) +* Update - Remove WooCommerce Navigation client side feature and deprecate PHP classes. [#50190](https://github.com/woocommerce/woocommerce/pull/50190) +* Update - Renamed columns inside In-App Marketplace > My subscriptions and added action to turn auto-renewal on for a subscription [#49985](https://github.com/woocommerce/woocommerce/pull/49985) +* Update - Rename woocommerce_is_store_page to woocommerce_is_extension_store_page [#50771](https://github.com/woocommerce/woocommerce/pull/50771) +* Update - Reverting the new `buttonAttributes` API. This will be included in a later release [#50763](https://github.com/woocommerce/woocommerce/pull/50763) +* Update - Revert the Zoom Out feature for the CYS experience [#50535](https://github.com/woocommerce/woocommerce/pull/50535) +* Update - Show expiring and expired notices to active and unconnected subscriptions [#50383](https://github.com/woocommerce/woocommerce/pull/50383) +* Update - Store API: Remove the need for nonces when using cart tokens. Remove deprecated X-WC-Store-API-Nonce header. [#50025](https://github.com/woocommerce/woocommerce/pull/50025) +* Update - Strip HTML tags from aria-label in wc_help_tip function [#50103](https://github.com/woocommerce/woocommerce/pull/50103) +* Update - Text adjustments on shipping zones settings page [#50136](https://github.com/woocommerce/woocommerce/pull/50136) +* Update - Update AdditionalPayments task to use default payment gateways [#50674](https://github.com/woocommerce/woocommerce/pull/50674) +* Update - Update add product task button section UI [#50580](https://github.com/woocommerce/woocommerce/pull/50580) +* Update - Update all blocks to use API Version 3. [#48720](https://github.com/woocommerce/woocommerce/pull/48720) +* Update - Update Blueprint settings layout. [#50724](https://github.com/woocommerce/woocommerce/pull/50724) +* Update - Update core profiler continue button container on extension screen [#50582](https://github.com/woocommerce/woocommerce/pull/50582) +* Update - Update Store Alert actions to have unique keys. [#50424](https://github.com/woocommerce/woocommerce/pull/50424) +* Update - Update WooCommercePayments task is_supported to use default suggestions [#50585](https://github.com/woocommerce/woocommerce/pull/50585) +* Update - Enhance CSV path and upload handling in product import [#51344](https://github.com/woocommerce/woocommerce/pull/51344) +* Dev - Execute test env setup on host instead of wp-env container [#51021](https://github.com/woocommerce/woocommerce/pull/51021) +* Dev - Added code docs with examples to the Analytics classes [#49425](https://github.com/woocommerce/woocommerce/pull/49425) +* Dev - Add lost password e2e tests [#50611](https://github.com/woocommerce/woocommerce/pull/50611) +* Dev - Add unit tests for the product_add_publish track. [#49916](https://github.com/woocommerce/woocommerce/pull/49916) +* Dev - CI: introduce PHPUnit tests sharding. [#50084](https://github.com/woocommerce/woocommerce/pull/50084) +* Dev - CI: minor speed boost of wp-env startup. [#50445](https://github.com/woocommerce/woocommerce/pull/50445) +* Dev - CI: speedup assets size verification job execution time. [#50178](https://github.com/woocommerce/woocommerce/pull/50178) +* Dev - CI: Use a single shard when re-running failed tests in CI [#50492](https://github.com/woocommerce/woocommerce/pull/50492) +* Dev - CI config: update changes list to include more paths [#50399](https://github.com/woocommerce/woocommerce/pull/50399) +* Dev - Clean up unused images [#50516](https://github.com/woocommerce/woocommerce/pull/50516) +* Dev - CYS - Document possible Intro pages [#50171](https://github.com/woocommerce/woocommerce/pull/50171) +* Dev - CYS - Move the "ai/patterns" endpoint to woocommerce admin API. [#50372](https://github.com/woocommerce/woocommerce/pull/50372) +* Dev - CYS - Move the "ai/store-info" endpoint to woocommerce admin API [#50363](https://github.com/woocommerce/woocommerce/pull/50363) +* Dev - CYS - Move the ai/business-description endpoint to woocommerce admin API [#50359](https://github.com/woocommerce/woocommerce/pull/50359) +* Dev - CYS - Move the ai/store-title endpoint to woocommerce admin API [#50352](https://github.com/woocommerce/woocommerce/pull/50352) +* Dev - CYS - Move the `ai/images` endpoint to woocommerce admin API [#50365](https://github.com/woocommerce/woocommerce/pull/50365) +* Dev - CYS - Move the `ai/product` endpoint to woocommerce admin API. [#50393](https://github.com/woocommerce/woocommerce/pull/50393) +* Dev - CYS: add E2E tests for fonts installation. [#50210](https://github.com/woocommerce/woocommerce/pull/50210) +* Dev - E2E tests: add a flaky test reporter for Core e2e tests [#50259](https://github.com/woocommerce/woocommerce/pull/50259) +* Dev - E2E tests: add an option to skip the env setup script running before test execution [#50620](https://github.com/woocommerce/woocommerce/pull/50620) +* Dev - E2E tests: add buildkite-test-collector for blocks e2e tests [#50642](https://github.com/woocommerce/woocommerce/pull/50642) +* Dev - E2E tests: add environment reporter [#49988](https://github.com/woocommerce/woocommerce/pull/49988) +* Dev - E2E tests: add hpos-disabled env and tagged tests with hpos tag [#50448](https://github.com/woocommerce/woocommerce/pull/50448) +* Dev - E2E tests: fixed broken logo picker tests [#50473](https://github.com/woocommerce/woocommerce/pull/50473) +* Dev - E2E tests: fix flakiness in page-loads customer page test [#50559](https://github.com/woocommerce/woocommerce/pull/50559) +* Dev - E2E tests: fix flakiness in product attributes test [#50485](https://github.com/woocommerce/woocommerce/pull/50485) +* Dev - E2E tests: removed Github reporter [#50256](https://github.com/woocommerce/woocommerce/pull/50256) +* Dev - E2E tests: Removed unnecessary pause in the test [#50043](https://github.com/woocommerce/woocommerce/pull/50043) +* Dev - E2E tests for verifying approve, spam and reply to product reviews. [#50060](https://github.com/woocommerce/woocommerce/pull/50060) +* Dev - Fix E2E tests SKU field id #49729 [#49729](https://github.com/woocommerce/woocommerce/pull/49729) +* Dev - Fixes a flaky product variations e2e test [#50807](https://github.com/woocommerce/woocommerce/pull/50807) +* Dev - Fix Metrics CI job [#50214](https://github.com/woocommerce/woocommerce/pull/50214) +* Dev - Fix optional param in PHPdoc for `WC_Admin_Marketplace_Promotions` to generate code-reference w/o warnings [#50732](https://github.com/woocommerce/woocommerce/pull/50732) +* Dev - Fix the Metrics job by adding a missing NVM install step [#50482](https://github.com/woocommerce/woocommerce/pull/50482) +* Dev - Make the Metrics tests use utilities provided by the updated @wordpress/e2e-test-utils-playwright package. [#50626](https://github.com/woocommerce/woocommerce/pull/50626) +* Dev - Mark ReportTable tableData prop as not required [#50816](https://github.com/woocommerce/woocommerce/pull/50816) +* Dev - Monorepo: enable new linting rules for PHP (PSR-4 naming, Strict types declaration). [#49438](https://github.com/woocommerce/woocommerce/pull/49438) +* Dev - Monorepo: tweak Webpack loaders paths filtering for better build perfromance. [#49714](https://github.com/woocommerce/woocommerce/pull/49714) +* Dev - move block theme docs to docs site folder [#50638](https://github.com/woocommerce/woocommerce/pull/50638) +* Dev - move part of checkout docs to main docs folder [#49984](https://github.com/woocommerce/woocommerce/pull/49984) +* Dev - Move `ReportError` to `@woocommerce/components` as `AnalyticsError` [#50108](https://github.com/woocommerce/woocommerce/pull/50108) +* Dev - moving product collection docs to main docs folder [#50368](https://github.com/woocommerce/woocommerce/pull/50368) +* Dev - Reduce duplicated code in Analytics controllers, unify their behavior and API. [#49425](https://github.com/woocommerce/woocommerce/pull/49425) +* Dev - Reduce the amount of duplicated code in Analytics `DataStore`s. [#49425](https://github.com/woocommerce/woocommerce/pull/49425) +* Dev - Removed defaultProps from React functional components since they will be deprecated for React 19 [#50266](https://github.com/woocommerce/woocommerce/pull/50266) +* Dev - Removed directive to disable woocommerce_coming_soon in e2e tests so that we get better test coverage [#50344](https://github.com/woocommerce/woocommerce/pull/50344) +* Dev - Render a React placeholder for offline and WooCommerce Payments settings sections [#50008](https://github.com/woocommerce/woocommerce/pull/50008) +* Dev - Replace `Automattic\WooCommerce\Admin\API\Reports\*\Query` classes with a single `GenericQuery` class. [#49425](https://github.com/woocommerce/woocommerce/pull/49425) +* Dev - Switch `render()` to `createRoot().render()` to use React 18 features. [#48843](https://github.com/woocommerce/woocommerce/pull/48843) +* Dev - Tests: moved api core tests as a suite in e2e-pw [#50024](https://github.com/woocommerce/woocommerce/pull/50024) +* Dev - Tweak the lost password e2e logic [#50666](https://github.com/woocommerce/woocommerce/pull/50666) +* Dev - Update @wordpress/e2e-test-utils-playwright core dependency to wp-6.6 [#50274](https://github.com/woocommerce/woocommerce/pull/50274) +* Dev - Updated e2e tests docs to clarify the use of environments [#50530](https://github.com/woocommerce/woocommerce/pull/50530) +* Dev - Updated the workflow prompting for testing instructions to only run once (preventing double comments) [#50034](https://github.com/woocommerce/woocommerce/pull/50034) +* Dev - Update E2E tests for linked list and variation creation with new component changes. [#50128](https://github.com/woocommerce/woocommerce/pull/50128) +* Dev - Update lys e2e tests to test with both classic and block themes [#50657](https://github.com/woocommerce/woocommerce/pull/50657) +* Dev - Update Playwright to 1.46.1 from 1.45.1 [#50772](https://github.com/woocommerce/woocommerce/pull/50772) +* Dev - Update WP version to 6.6 in Blocks wp-env config. [#49704](https://github.com/woocommerce/woocommerce/pull/49704) +* Dev - Use stricter text selector on test [#50848](https://github.com/woocommerce/woocommerce/pull/50848) +* Dev - [Filter Products by Price]: Update view when changing the min/max value #50651 [#50651](https://github.com/woocommerce/woocommerce/pull/50651) +* Tweak - Disable remote logging feature by default [#51312](https://github.com/woocommerce/woocommerce/pull/51312) +* Tweak - Add GTIN in structured data [#50087](https://github.com/woocommerce/woocommerce/pull/50087) +* Tweak - Add link to title, remove link from a description, minor copy changes to site visibility settings page [#50781](https://github.com/woocommerce/woocommerce/pull/50781) +* Tweak - Add the `woocommerce_should_clear_cart_after_payment` filter to influence whether the cart should be cleared after payment. [#44515](https://github.com/woocommerce/woocommerce/pull/44515) +* Tweak - allows the quantity selector on block cart page to render as readonly when editable is false [#49450](https://github.com/woocommerce/woocommerce/pull/49450) +* Tweak - Bump Jetpack COnnection, Jetpack Constants and a8c MC Stats [#50471](https://github.com/woocommerce/woocommerce/pull/50471) +* Tweak - Extract the checkbox list option logic into its own component [#50566](https://github.com/woocommerce/woocommerce/pull/50566) +* Tweak - Make `geolocation_ajax_get_location_hash` case-insensitive, to reduce the number of cache misses. [#45439](https://github.com/woocommerce/woocommerce/pull/45439) +* Tweak - Optimize large image files [#50517](https://github.com/woocommerce/woocommerce/pull/50517) +* Tweak - Product Collection: fix the PHP deprecated warning [#50661](https://github.com/woocommerce/woocommerce/pull/50661) +* Tweak - Reduce core profiler sticky footer height [#50788](https://github.com/woocommerce/woocommerce/pull/50788) +* Tweak - Remove colon from product data meta box checkboxes [#50619](https://github.com/woocommerce/woocommerce/pull/50619) +* Tweak - Remove the code related to the automatic Products (Beta) -> Product Collection upgrade. [#50440](https://github.com/woocommerce/woocommerce/pull/50440) +* Tweak - Set timeout to 2 seconds for helper product-usage-notice-rules endpoint request [#50821](https://github.com/woocommerce/woocommerce/pull/50821) +* Tweak - Update size of site visibility badge. [#50792](https://github.com/woocommerce/woocommerce/pull/50792) +* Tweak - Vertically center product meta elements [#50826](https://github.com/woocommerce/woocommerce/pull/50826) +* Performance - Cache order dates in options for performance. [#50066](https://github.com/woocommerce/woocommerce/pull/50066) +* Performance - Compress pattern placeholder image assets [#50405](https://github.com/woocommerce/woocommerce/pull/50405) +* Performance - Improve performance of maybe_assign_default_product_cat by only dropping cache and term recounting if changes were made in the database [#50006](https://github.com/woocommerce/woocommerce/pull/50006) +* Performance - Improve setup_tasks_remaining performance [#50655](https://github.com/woocommerce/woocommerce/pull/50655) +* Enhancement - Add a filter to override the SKU database lock. [#49755](https://github.com/woocommerce/woocommerce/pull/49755) +* Enhancement - Add email type to Checkout block email field. [#48611](https://github.com/woocommerce/woocommerce/pull/48611) +* Enhancement - Add filter `woocommerce_is_store_page` to modify whether Coming Soon mode considers a URL a store page or not. [#50174](https://github.com/woocommerce/woocommerce/pull/50174) +* Enhancement - Add username in email reset-password link [#49737](https://github.com/woocommerce/woocommerce/pull/49737) +* Enhancement - CYS: improve CTA [#50278](https://github.com/woocommerce/woocommerce/pull/50278) +* Enhancement - Ensure `wccomHelper` data is only loaded on the Extensions page where it's needed. [#49758](https://github.com/woocommerce/woocommerce/pull/49758) +* Enhancement - Fixed minor issues in the developer documentation recently added by public resources team [#50845](https://github.com/woocommerce/woocommerce/pull/50845) +* Enhancement - Hide zoomed product images for screen readers. [#50003](https://github.com/woocommerce/woocommerce/pull/50003) +* Enhancement - Improve hover style on product tabs when using the Minimal style in the Product Details block [#50605](https://github.com/woocommerce/woocommerce/pull/50605) +* Enhancement - Make screen readers announce notice messages once page loads. [#50061](https://github.com/woocommerce/woocommerce/pull/50061) +* Enhancement - Refactor: Migrate the All Products block to API version 3 [#50203](https://github.com/woocommerce/woocommerce/pull/50203) +* Enhancement - Remove opacity from the hover style of the mini cart button [#50240](https://github.com/woocommerce/woocommerce/pull/50240) +* Enhancement - Use standard link color in legal disclaimers on core profiler [#50830](https://github.com/woocommerce/woocommerce/pull/50830) + = 9.2.3 2024-08-26 = **WooCommerce** diff --git a/docs/cart-and-checkout-blocks/additional-checkout-fields.md b/docs/cart-and-checkout-blocks/additional-checkout-fields.md index 29981df9bdc..89af92968c2 100644 --- a/docs/cart-and-checkout-blocks/additional-checkout-fields.md +++ b/docs/cart-and-checkout-blocks/additional-checkout-fields.md @@ -339,11 +339,11 @@ This results in the following address form (the billing form will be the same): The rendered markup looks like this: ```html - + data-custom="custom data" value="" > ``` ### Rendering a checkbox field diff --git a/docs/cart-and-checkout-blocks/available-filters/README.md b/docs/cart-and-checkout-blocks/available-filters/README.md index 6185107f41b..80eeab35f71 100644 --- a/docs/cart-and-checkout-blocks/available-filters/README.md +++ b/docs/cart-and-checkout-blocks/available-filters/README.md @@ -81,7 +81,7 @@ const modifyCartItemClass = ( defaultValue, extensions, args ) => { const modifyCartItemPrice = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { - return ' for all items'; + return '<price/> for all items'; } return defaultValue; }; @@ -95,7 +95,7 @@ const modifyItemName = ( defaultValue, extensions, args ) => { const modifySubtotalPriceFormat = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { - return ' per item'; + return '<price/> per item'; } return defaultValue; }; diff --git a/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md b/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md index ce4134df03c..c210fa39fcf 100644 --- a/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md +++ b/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md @@ -106,17 +106,17 @@ The `cartItemPrice` filter allows to format the order summary item price. ### Parameters -- _defaultValue_ `string` (default: ``) - The default order summary item price. +- _defaultValue_ `string` (default: `<price/>`) - The default order summary item price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. -- _validation_ `boolean` - Checks if the return value contains the substring ``. +- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`. ### Returns -- `string` - The modified format of the order summary item price, which must contain the substring ``, or the original price format. +- `string` - The modified format of the order summary item price, which must contain the substring `<price/>`, or the original price format. ### Code examples @@ -132,7 +132,7 @@ const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { return defaultValue; } - return ' for all items'; + return '<price/> for all items'; }; registerCheckoutFilters( 'example-extension', { @@ -153,14 +153,14 @@ const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { } if ( args?.cartItem?.name === 'Beanie with Logo' ) { - return ' to keep you ☀️'; + return '<price/> to keep you ☀️'; } if ( args?.cartItem?.name === 'Sunglasses' ) { - return ' to keep you ❄️'; + return '<price/> to keep you ❄️'; } - return ' for all items'; + return '<price/> for all items'; }; registerCheckoutFilters( 'example-extension', { @@ -261,17 +261,17 @@ The `subtotalPriceFormat` filter allows to format the order summary item subtota ### Parameters -- _defaultValue_ `string` (default: ``) - The default order summary item subtotal price. +- _defaultValue_ `string` (default: `<price/>`) - The default order summary item subtotal price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. -- _validation_ `boolean` - Checks if the return value contains the substring ``. +- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`. ### Returns -- `string` - The modified format of the order summary item subtotal price, which must contain the substring ``, or the original price format. +- `string` - The modified format of the order summary item subtotal price, which must contain the substring `<price/>`, or the original price format. ### Code examples @@ -292,7 +292,7 @@ const modifySubtotalPriceFormat = ( return defaultValue; } - return ' per item'; + return '<price/> per item'; }; registerCheckoutFilters( 'example-extension', { @@ -318,14 +318,14 @@ const modifySubtotalPriceFormat = ( } if ( args?.cartItem?.name === 'Beanie with Logo' ) { - return ' per warm beanie'; + return '<price/> per warm beanie'; } if ( args?.cartItem?.name === 'Sunglasses' ) { - return ' per cool sunglasses'; + return '<price/> per cool sunglasses'; } - return ' per item'; + return '<price/> per item'; }; registerCheckoutFilters( 'example-extension', { diff --git a/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md b/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md index 360c7627b80..c3a72631330 100644 --- a/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md +++ b/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md @@ -71,11 +71,11 @@ The `totalValue` filter allows to format the total price in the footer of the Ca - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). -- _validation_ `boolean` - Checks if the return value contains the substring ``. +- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`. ### Returns -- `string` - The modified format of the total price, which must contain the substring ``, or the original price format. +- `string` - The modified format of the total price, which must contain the substring `<price/>`, or the original price format. ### Code example @@ -83,7 +83,7 @@ The `totalValue` filter allows to format the total price in the footer of the Ca const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyTotalsPrice = ( defaultValue, extensions, args, validation ) => { - return 'Pay now'; + return 'Pay <price/> now'; }; registerCheckoutFilters( 'my-extension', { diff --git a/docs/cart-and-checkout-blocks/available-slot-fills.md b/docs/cart-and-checkout-blocks/available-slot-fills.md index 80effcba522..efc47abcfa2 100644 --- a/docs/cart-and-checkout-blocks/available-slot-fills.md +++ b/docs/cart-and-checkout-blocks/available-slot-fills.md @@ -22,11 +22,11 @@ const { ExperimentalOrderMeta } = window.wc.blocksCheckout; const render = () => { return ( - -
+ <ExperimentalOrderMeta> + <div class="wc-block-components-totals-wrapper"> { __( 'Yearly recurring total ...', 'YOUR-TEXTDOMAIN' ) } -
-
+ </div> + </ExperimentalOrderMeta> ); }; @@ -61,9 +61,9 @@ const { ExperimentalOrderShippingPackages } = window.wc.blocksCheckout; const render = () => { return ( - -
{ __( 'Express Shipping', 'YOUR-TEXTDOMAIN' ) }
-
+ <ExperimentalOrderShippingPackages> + <div>{ __( 'Express Shipping', 'YOUR-TEXTDOMAIN' ) }</div> + </ExperimentalOrderShippingPackages> ); }; @@ -104,14 +104,14 @@ const { ExperimentalOrderLocalPickupPackages } = window.wc.blocksCheckout; const render = () => { return ( - -
+ <ExperimentalOrderLocalPickupPackages> + <div> { __( 'By using our convenient local pickup option, you can come to our store and pick up your order. We will send you and email when your order is ready for pickup.', 'YOUR-TEXTDOMAIN' ) } -
-
+ </div> + </ExperimentalOrderLocalPickupPackages> ); }; @@ -143,11 +143,11 @@ const { ExperimentalDiscountsMeta } = window.wc.blocksCheckout; const render = () => { return ( - -
+ <ExperimentalDiscountsMeta> + <div class="wc-block-components-totals-wrapper"> { __( 'You have 98683 coins to spend ...', 'YOUR-TEXTDOMAIN' ) } -
-
+ </div> + </ExperimentalDiscountsMeta> ); }; diff --git a/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md b/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md index 602bcb7deaa..d3457214654 100644 --- a/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md +++ b/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md @@ -43,8 +43,8 @@ The options you feed the configuration instance should be an object in this shap ```js const options = { name: 'my_payment_method', - content:
A React node
, - edit:
A React node
, + content: <div>A React node</div>, + edit: <div>A React node</div>, canMakePayment: () => true, paymentMethodId: 'new_payment_method', supports: { diff --git a/docs/cart-and-checkout-blocks/slot-fills.md b/docs/cart-and-checkout-blocks/slot-fills.md index d694d03bb1e..38003d4921f 100644 --- a/docs/cart-and-checkout-blocks/slot-fills.md +++ b/docs/cart-and-checkout-blocks/slot-fills.md @@ -32,14 +32,14 @@ const { registerPlugin } = wp.plugins; const { ExperimentalOrderMeta } = wc.blocksCheckout; const MyCustomComponent = ( { cart, extensions } ) => { - return
Hello WooCommerce
; + return <div className="my-component">Hello WooCommerce</div>; }; const render = () => { return ( - - - + <ExperimentalOrderMeta> + <MyCustomComponent /> + </ExperimentalOrderMeta> ); }; diff --git a/docs/code-snippets/customising-checkout-fields.md b/docs/code-snippets/customising-checkout-fields.md index 4b8aa96aee8..ab90d600be1 100644 --- a/docs/code-snippets/customising-checkout-fields.md +++ b/docs/code-snippets/customising-checkout-fields.md @@ -295,7 +295,7 @@ function custom_override_checkout_fields( $fields ) { add_action( 'woocommerce_admin_order_data_after_shipping_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 ); function my_custom_checkout_field_display_admin_order_meta($order){ - echo '

'. esc_html__( 'Phone From Checkout Form' ) . ': ' . esc_html( $order->get_meta( '_shipping_phone', true ) ) . '

'; + echo '<p><strong>'. esc_html__( 'Phone From Checkout Form' ) . ':</strong> ' . esc_html( $order->get_meta( '_shipping_phone', true ) ) . '</p>'; } ``` @@ -317,7 +317,7 @@ add_action( 'woocommerce_after_order_notes', 'my_custom_checkout_field' ); function my_custom_checkout_field( $checkout ) { - echo '

' . esc_html__( 'My Field' ) . '

'; + echo '<div id="my_custom_checkout_field"><h2>' . esc_html__( 'My Field' ) . '</h2>'; woocommerce_form_field( 'my_field_name', @@ -330,7 +330,7 @@ function my_custom_checkout_field( $checkout ) { $checkout->get_value( 'my_field_name' ) ); - echo '
'; + echo '</div>'; } ``` @@ -387,7 +387,7 @@ If you wish to display the custom field value on the admin order edition page, y add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 ); function my_custom_checkout_field_display_admin_order_meta( $order ){ - echo '

' . esc_html__( 'My Field' ) . ': ' . esc_html( $order->get_meta( 'My Field', true ) ) . '

'; + echo '<p><strong>' . esc_html__( 'My Field' ) . ':</strong> ' . esc_html( $order->get_meta( 'My Field', true ) ) . '</p>'; } ``` diff --git a/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md b/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md index 0549ead8a57..6d3caeedb2a 100644 --- a/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md +++ b/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md @@ -33,7 +33,7 @@ function woocommerce_custom_field_example() { $custom_field_value = get_post_meta( $product->get_id(), 'woo_custom_field', true ); if ( ! empty( $custom_field_value ) ) { - echo '
' . esc_html( $custom_field_value ) . '
'; + echo '<div class="custom-field">' . esc_html( $custom_field_value ) . '</div>'; } } diff --git a/docs/code-snippets/link-to-logged-data.md b/docs/code-snippets/link-to-logged-data.md index a4d31dd362b..d2fdf8da947 100644 --- a/docs/code-snippets/link-to-logged-data.md +++ b/docs/code-snippets/link-to-logged-data.md @@ -19,7 +19,7 @@ if ( defined( 'WC_LOG_DIR' ) ) { $log_url = add_query_arg( 'log_file', $log_key, $log_url ); // Add a link to the logs to the label - $label .= ' | ' . sprintf( \_\_( '%1$sView Log%2$s', 'your-textdomain-here' ), '', '' ); + $label .= ' | ' . sprintf( \_\_( '%1$sView Log%2$s', 'your-textdomain-here' ), '<a href\="' . esc_url( $log_url ) . '">', '</a\>' ); } // Add the logging option to the form fields diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 9896e658e12..45cec2a5a3f 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -100,7 +100,7 @@ "menu_title": "Slot and Fill", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/slot-fills.md", - "hash": "f83a5fbef86e5ef6b0ec1d63fdbcbf4742f54de1125e535fa0f32f5f80ec794a", + "hash": "a232ca3d53f10857170113f6dc5b37ac7ae629e852629bac015a8d3c2cd1bbc4", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/slot-fills.md", "id": "e388101586765dd9aca752d66d667d74951a1504" }, @@ -136,7 +136,7 @@ "menu_title": "Available Slots", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/available-slot-fills.md", - "hash": "770da9156eea1fdc24db0736ce4ccd44ebde4f3b0373cd875b1ae88d4d9c8a49", + "hash": "444d9892cb6552c8394ecdf81816952987b59bc79fa53f3083c3d14a89d1e961", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/available-slot-fills.md", "id": "c7ac16eee5540b06b6db928f5d03282ff177e84e" }, @@ -145,14 +145,14 @@ "menu_title": "Additional Checkout Fields", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/additional-checkout-fields.md", - "hash": "1b034ede098b933b6b00a9a27ba33e418b1c88c4883e2b9b191092e32866f7b9", + "hash": "641615864f627be4bb42574df378cea91f4a7fda9edab099558bad06b92ce62d", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/additional-checkout-fields.md", "id": "cb5dd8d59043a4e53929121b45da7b33b1661ab8" } ], "categories": [ { - "content": "\nThis document lists the filters that are currently available to extensions and offers usage information for each one of them. Information on registering filters can be found on the [Checkout - Filter Registry](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/packages/checkout/filter-registry/README.md) page.\n\n## Cart Line Items filters\n\nThe following [Cart Line Items filters](./cart-line-items.md) are available:\n\n- `cartItemClass`\n- `cartItemPrice`\n- `itemName`\n- `saleBadgePriceFormat`\n- `showRemoveItemLink`\n- `subtotalPriceFormat`\n\nThe following screenshot shows which parts the individual filters affect:\n\n![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png)\n\n## Order Summary Items filters\n\nThe following [Order Summary Items filters](./order-summary-items.md) are available:\n\n- `cartItemClass`\n- `cartItemPrice`\n- `itemName`\n- `subtotalPriceFormat`\n\nThe following screenshot shows which parts the individual filters affect:\n\n![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png)\n\n## Totals Footer Item filter\n\nThe following [Totals Footer Item filter](./totals-footer-item.md) is available:\n\n- `totalLabel`\n- `totalValue`\n\n## Checkout and place order button filters\n\nThe following [Checkout and place order button filters](./checkout-and-place-order-button.md) are available:\n\n- `proceedToCheckoutButtonLabel`\n- `proceedToCheckoutButtonLink`\n- `placeOrderButtonLabel`\n\n## Coupon filters\n\nThe following [Coupon filters](./coupons.md) are available:\n\n- `coupons`\n- `showApplyCouponNotice`\n- `showRemoveCouponNotice`\n\n## Additional Cart and Checkout inner block types filter\n\nThe following [Additional Cart and Checkout inner block types filter](./additional-cart-checkout-inner-block-types.md) is available:\n\n- `additionalCartCheckoutInnerBlockTypes`\n\n## Combined filters\n\nFilters can also be combined. The following example shows how to combine some of the available filters.\n\n```tsx\nconst { registerCheckoutFilters } = window.wc.blocksCheckout;\n\nconst isOrderSummaryContext = ( args ) => args?.context === 'summary';\n\nconst modifyCartItemClass = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn 'my-custom-class';\n\t}\n\treturn defaultValue;\n};\n\nconst modifyCartItemPrice = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn ' for all items';\n\t}\n\treturn defaultValue;\n};\n\nconst modifyItemName = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn `${ defaultValue }`;\n\t}\n\treturn defaultValue;\n};\n\nconst modifySubtotalPriceFormat = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn ' per item';\n\t}\n\treturn defaultValue;\n};\n\nregisterCheckoutFilters( 'example-extension', {\n\tcartItemClass: modifyCartItemClass,\n\tcartItemPrice: modifyCartItemPrice,\n\titemName: modifyItemName,\n\tsubtotalPriceFormat: modifySubtotalPriceFormat,\n} );\n```\n\n## Troubleshooting\n\nIf you are logged in to the store as an administrator, you should be shown an error like this if your filter is not\nworking correctly. The error will also be shown in your console.\n\n![Troubleshooting](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-30-at-10.52.53.png)\n\n\n", + "content": "\nThis document lists the filters that are currently available to extensions and offers usage information for each one of them. Information on registering filters can be found on the [Checkout - Filter Registry](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/packages/checkout/filter-registry/README.md) page.\n\n## Cart Line Items filters\n\nThe following [Cart Line Items filters](./cart-line-items.md) are available:\n\n- `cartItemClass`\n- `cartItemPrice`\n- `itemName`\n- `saleBadgePriceFormat`\n- `showRemoveItemLink`\n- `subtotalPriceFormat`\n\nThe following screenshot shows which parts the individual filters affect:\n\n![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png)\n\n## Order Summary Items filters\n\nThe following [Order Summary Items filters](./order-summary-items.md) are available:\n\n- `cartItemClass`\n- `cartItemPrice`\n- `itemName`\n- `subtotalPriceFormat`\n\nThe following screenshot shows which parts the individual filters affect:\n\n![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png)\n\n## Totals Footer Item filter\n\nThe following [Totals Footer Item filter](./totals-footer-item.md) is available:\n\n- `totalLabel`\n- `totalValue`\n\n## Checkout and place order button filters\n\nThe following [Checkout and place order button filters](./checkout-and-place-order-button.md) are available:\n\n- `proceedToCheckoutButtonLabel`\n- `proceedToCheckoutButtonLink`\n- `placeOrderButtonLabel`\n\n## Coupon filters\n\nThe following [Coupon filters](./coupons.md) are available:\n\n- `coupons`\n- `showApplyCouponNotice`\n- `showRemoveCouponNotice`\n\n## Additional Cart and Checkout inner block types filter\n\nThe following [Additional Cart and Checkout inner block types filter](./additional-cart-checkout-inner-block-types.md) is available:\n\n- `additionalCartCheckoutInnerBlockTypes`\n\n## Combined filters\n\nFilters can also be combined. The following example shows how to combine some of the available filters.\n\n```tsx\nconst { registerCheckoutFilters } = window.wc.blocksCheckout;\n\nconst isOrderSummaryContext = ( args ) => args?.context === 'summary';\n\nconst modifyCartItemClass = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn 'my-custom-class';\n\t}\n\treturn defaultValue;\n};\n\nconst modifyCartItemPrice = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn '<price/> for all items';\n\t}\n\treturn defaultValue;\n};\n\nconst modifyItemName = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn `${ defaultValue }`;\n\t}\n\treturn defaultValue;\n};\n\nconst modifySubtotalPriceFormat = ( defaultValue, extensions, args ) => {\n\tif ( isOrderSummaryContext( args ) ) {\n\t\treturn '<price/> per item';\n\t}\n\treturn defaultValue;\n};\n\nregisterCheckoutFilters( 'example-extension', {\n\tcartItemClass: modifyCartItemClass,\n\tcartItemPrice: modifyCartItemPrice,\n\titemName: modifyItemName,\n\tsubtotalPriceFormat: modifySubtotalPriceFormat,\n} );\n```\n\n## Troubleshooting\n\nIf you are logged in to the store as an administrator, you should be shown an error like this if your filter is not\nworking correctly. The error will also be shown in your console.\n\n![Troubleshooting](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-30-at-10.52.53.png)\n\n\n", "category_slug": "cart-and-checkout-available-filters", "category_title": "Available Filters", "posts": [ @@ -161,7 +161,7 @@ "menu_title": "Totals Footer Item", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md", - "hash": "3a9869d7d7beadb8117c100c3b58675e416e16386ee753f78e1a9087e768053f", + "hash": "6cf668422809b036dca7c1996ae907497a38631dd5bfb7e67d6bf3620425e411", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/available-filters/totals-footer-item.md", "id": "90a9b8df374082f1713866a58b810303adb4d3da" }, @@ -170,7 +170,7 @@ "menu_title": "Order Summary Items", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md", - "hash": "36f1bfa8d192b106d28d71334b42413d4c289a0a8d1f5b76b2f905d6fa453883", + "hash": "1796f53f3d67dd6b47fe8d7f67cbd69bddcaa6416bb5a0cc1a0fc99f42ea9d10", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/available-filters/order-summary-items.md", "id": "78eb3b135f82a3624a49979e3e93334295abd060" }, @@ -223,7 +223,7 @@ "menu_title": "Payment Method Integration", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md", - "hash": "f60acaaea4a6ac4adf637bc7069c966e01db089f9dfaa937def91165a71a4255", + "hash": "138ffbf27e79ec8b35d2c46e87e3663c203d91fc9ba3f76c43f3cbe76258e5bf", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md", "id": "c9a763b6976ecf03aeb961577c17c31f1ac7c420", "links": { @@ -363,7 +363,7 @@ "menu_title": "Add link to logged data", "tags": "code-snippets", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/link-to-logged-data.md", - "hash": "fd1c3a58da8b7eed11da841d901b4d3cc117c6753c3b3834f3de41ea266490b9", + "hash": "4e51c120a6ea7b14c0e43f11e8eb1b785e4447fbe2b997f5789f10b57c485137", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/link-to-logged-data.md", "id": "34da337f79be5ce857024f541a99d302174ca37d" }, @@ -389,7 +389,7 @@ "menu_title": "Displaying custom fields in theme", "tags": "code-snippet", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md", - "hash": "8048c2e9e5d25268d17d4f4ca7929e265eddbd4653318dd8f544856ddecd39dd", + "hash": "013acf9daaef92daf49e49315b2c0eba730b96adb8078eaab1146db4afc5270b", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md", "id": "3e3fd004afda355cf9dbb05f0967523d6d0da1ce" }, @@ -405,7 +405,7 @@ "post_title": "Customizing checkout fields using actions and filters", "tags": "code-snippet", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/customising-checkout-fields.md", - "hash": "8bbfe162402e484ae89427e1aedaed4faa57555b64b5a77ca800f701524314cb", + "hash": "ce63f640d5b91d85c3bbb80128d8a19e9c00d1c0e252abd4f958e29dcc1e60ce", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/customising-checkout-fields.md", "id": "83097d3b7414557fc80dcf9f8f1a708bbdcdd884" }, @@ -676,7 +676,7 @@ { "post_title": "Settings API", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/settings-api.md", - "hash": "ca80728c56d60bb7416bb2865678b9e04807d0e208a4df56b8efaf32e9ac465d", + "hash": "9015453d8be72871bb26a450b86e542aa698c67b93284a04cd2b18008113bf43", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/settings-api.md", "id": "ed56b97b9de350074a302373ebaaa5dcce727e8b" }, @@ -691,7 +691,7 @@ "post_title": "Integrating with coming soon mode", "tags": "how-to, coming-soon", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/integrating-coming-soon-mode.md", - "hash": "8c2087952ae79bb4c3e3977c57d9e933fcfaa418a5bc643b3827059daa5879a7", + "hash": "791cd6d3928b3aafc72a24d0283a404a90a0f021c7c36edaa445eb44978114a3", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/integrating-coming-soon-mode.md", "id": "787743efb6ef0ad509b17735eaf58b2a9a08afbc" }, @@ -700,7 +700,7 @@ "menu_title": "Creating custom settings", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/implementing-settings.md", - "hash": "604d455f9e413c23a208c174ba25611c333e02eef0bafb0d38253f8dd8e3a04c", + "hash": "5cab83a84bb7eb11090bac244754fdae1f8aef1030850d12c29c09054c50bc61", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/implementing-settings.md", "id": "58bcbd3a0cd3b3e5fe738c3bb625cf9b7747c99a" }, @@ -727,7 +727,7 @@ "menu_title": "Implement merchant onboarding", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/handling-merchant-onboarding.md", - "hash": "c73e3c5015e6cda3be9ebd2d5fbda590ac9fa599e5fb02163c971c01060970ad", + "hash": "85fc7d70f47fdb195ad2c690d3b95565169221f9e4d7afa98e88f3824ad0e267", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/handling-merchant-onboarding.md", "id": "89fe15dc232379f546852822230c334d3d940b93" }, @@ -814,7 +814,7 @@ "menu_title": "Add custom product types to Add Products onboarding list", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md", - "hash": "60e50ef5d7e2ac6d0745c31031140df1dbb3c1b8724230cab1eaedebe3814688", + "hash": "92a8e17f2cd8dc32a78f03970ded1beec2fd60cadbf14c8cefcabbf7abae59c5", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md", "id": "747321d7fd2eb5c9c3351ea38374dfc80d3ec968" }, @@ -861,7 +861,7 @@ "menu_title": "Troubleshooting Endpoints", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/getting-started/troubleshooting-endpoints.md", - "hash": "1a015d82f4d82cc2d9f13f188f03c4e6e03b98ea9d22c5a7710547e7d3c8c78f", + "hash": "448bcd827ff44e9eb10d039bfd933cd63a37df05bd694bf80f9d9f978a3afdf5", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/getting-started/troubleshooting-endpoints.md", "id": "dff57bd736ae83850bfc7e4ac994bd22141d96ee", "links": { @@ -874,7 +874,7 @@ "menu_title": "Development environment setup", "tags": "tutorial, setup", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/getting-started/development-environment.md", - "hash": "b9f56987247aee67eaa2c74d1059e1cadfd335fa81c77b76c0717648d5631c0f", + "hash": "bf5d77349ea64d1b8e19fe6b7472be35ed92406c5aafe677ce92363fb13f94d4", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/getting-started/development-environment.md", "id": "9080572a3904349c44c565ca7e1bef1212c58757" }, @@ -903,7 +903,7 @@ "menu_title": "Customizing Endpoint URLs", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/getting-started/customizing-endpoint-urls.md", - "hash": "7feda75b07a1c11d533afabc7781abb80438ce2fa2c3fb37c173e1275098e720", + "hash": "364ed14d70c49498ba5017104b9c83743322d5095c215262d4311866a76181e5", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/getting-started/customizing-endpoint-urls.md", "id": "c19e1b1da6543f8a95ee04ba120f4f171f8e6e40", "links": { @@ -956,7 +956,7 @@ "post_title": "HPOS CLI Tools", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/high-performance-order-storage/cli-tools.md", - "hash": "8cd823759ce20551d582c39f57ae79f9e0227a8cb0131146e6b7dac5e7312708", + "hash": "63e5edd55720c963de6700854515ea51946ff734b716ab61793955308b72af91", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/cli-tools.md", "id": "cdd9d9ad5777d978ba953e3478fbb61cab8fdf59" } @@ -1050,7 +1050,7 @@ "menu_title": "Registering custom collections", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-collection-block/register-product-collection.md", - "hash": "27c321bed35524d74019e015f5eed6cdca7e6c2efe0bc89ffdd2b9b5d43c47e8", + "hash": "6d32bc27924226b032e03624dbeedde3c899c2e8eb777a1fece93bed99544f03", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-collection-block/register-product-collection.md", "id": "3bf26fc7c56ae6e6a56e1171f750f5204fcfcece" }, @@ -1117,7 +1117,7 @@ { "post_title": "Extending the product form with custom fields", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md", - "hash": "f0d0273c0d65739d605448492bfbe684f0ed33f9e6e274df06f26e83cb6ba341", + "hash": "dfa00ed71af6eda1f539684657d5c880850ececea4c07bd11e89a605fab77ec7", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md", "id": "fed80efbb225df9054fadd6e1fc45c2cd03e7f99" } @@ -1329,7 +1329,7 @@ "menu_title": "Extend analytics reports", "tags": "how-to", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/reporting/extending-woocommerce-admin-reports.md", - "hash": "b694b0e857d3ca60acdef2ffaae329a93f0a0243eacc4b192562c7f507f169b3", + "hash": "56712b3583d0b0a4d96eb19153e5abcb8a386fcd083fa56481acf1be530afa25", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/reporting/extending-woocommerce-admin-reports.md", "id": "3ef20148084c97d7f62b565b92df844392ac27f7" }, @@ -1498,7 +1498,7 @@ "post_title": "Classic theme development handbook", "menu_title": "Classic theme development", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/theme-development/classic-theme-developer-handbook.md", - "hash": "1194437fbc2ec82d60c8b73a9742ec650bd90fe734758c3a2b27ed852d4d14f7", + "hash": "95ce7250479a5133bba6c68939d86e4e79708c65044d70727c73f6a88f716da7", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/theme-development/classic-theme-developer-handbook.md", "id": "c2fde53e1dc3efbded3cfe1fb4df27136a3799a4" } @@ -1775,7 +1775,7 @@ "menu_title": "Commands", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/wc-cli/wc-cli-commands.md", - "hash": "a926ff45642539e0edc6b4e3dfeba4b31c2d01082700af132a2e8d56cfa25ec5", + "hash": "17bbb18fd0ad0523a5b864f74acbec64c853ae7b42ecd7e6d9dbce1fbe2669aa", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/wc-cli/wc-cli-commands.md", "id": "73d6bc6468d23a9e93d16d574399105b143e43af" }, @@ -1804,5 +1804,5 @@ "categories": [] } ], - "hash": "8199f0d3c854474839300ed606f03f9f286ace35f65d7c47ffc6477762eaf51e" + "hash": "dfe48a2a48d383c1f4e5b5bb4258d369dd4e80ac8582462b16bbfedd879cb0bf" } \ No newline at end of file diff --git a/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md b/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md index 4d50b8e4d31..f85464aa86f 100644 --- a/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md +++ b/docs/extension-development/adding-custom-products-to-add-products-onboarding-list.md @@ -45,8 +45,8 @@ addFilter( key: 'custom-product', title: __('Custom product', 'custom-product'), content: __('Create an awesome custom product.', 'custom-product'), - before: , - after: , + before: <FolderMultipleIcon />, + after: <Icon icon={chevronRight} />, onClick: () => { } }, diff --git a/docs/extension-development/handling-merchant-onboarding.md b/docs/extension-development/handling-merchant-onboarding.md index 3dec70d6f89..5b3adaed57d 100644 --- a/docs/extension-development/handling-merchant-onboarding.md +++ b/docs/extension-development/handling-merchant-onboarding.md @@ -448,7 +448,7 @@ class ExampleNote { $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); // Set the type of layout the note uses. Supported layout types are: - // 'banner', 'plain', 'thumbnail' + // 'plain', 'thumbnail' $note->set_layout( 'plain' ); // Set the image for the note. This property renders as the src diff --git a/docs/extension-development/implementing-settings.md b/docs/extension-development/implementing-settings.md index 4b86c41b714..360fb1e09a6 100644 --- a/docs/extension-development/implementing-settings.md +++ b/docs/extension-development/implementing-settings.md @@ -227,19 +227,19 @@ public function generate_button_html( $key, $data ) { ob_start(); ?> - - - + <tr valign="top"> + <th scope="row" class="titledesc"> + <label for=""></label> get_tooltip_html( $data ); ?> - - -
- - + </th> + <td class="forminp"> + <fieldset< + <legend class="screen-reader-text"><span>" type="button" name="" id="" style="" get_custom_attribute_html( $data ); ?>></button> get_description_html( $data ); ?> -
- - + </fieldset> + </td> + </tr> ) { + if ( get_the_ID() === <page-id> ) { return true; } return $is_excluded; diff --git a/docs/extension-development/settings-api.md b/docs/extension-development/settings-api.md index 6078ceca42a..60ed624a3fe 100644 --- a/docs/extension-development/settings-api.md +++ b/docs/extension-development/settings-api.md @@ -70,10 +70,10 @@ Create a method called `admin_options` containing the following: ```php function admin_options() { ?> -

- + <h2></h2> + <table class="form-table"> generate_settings_html(); ?> -
+ </table> - - - - - - - - - - - - - - - - - +<<>?xml version="1.0" encoding="UTF-8"?> +<configuration> + <system.webServer> + <handlers accessPolicy="Read, Execute, Script" /> + <rewrite> + <rules> + <rule name="wordpress" patternSyntax="Wildcard"> + <match url="*" /> + <conditions> + <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> + <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> + </conditions> + <action type="Rewrite" url="index.php" /> + </rule> + </rules> + </rewrite> + </system.webServer> +</configuration> ``` ### Pages direct to wrong place @@ -74,6 +74,6 @@ Landing on the wrong page when clicking an endpoint URL is typically caused by i ### How to Remove "Downloads" from My Account -Sometimes the "Downloads" endpoint on the "My account" page does not need to be displayed. This can be removed by going to **WooCommerce → Settings → Advanced → Account endpoints** and clearing the Downloads endpoint field. +Sometimes the "Downloads" endpoint on the "My account" page does not need to be displayed. This can be removed by going to **WooCommerce > Settings > Advanced > Account endpoints** and clearing the Downloads endpoint field. ![Account endpoints](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-04-09-at-11.45.58-PM.png) diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md index 3cde937ffdb..989587d4153 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/development-environment.md @@ -14,7 +14,7 @@ If you would like to contribute to the WooCommerce core platform; please read ou ## Prerequisites -WooCommerce does adhere to WordPress code standards and guidelines, so it’s best to familiarize yourself with [WordPress Development](https://learn.wordpress.org/tutorial/introduction-to-wordpress/) as well as [PHP](https://www.php.net/). +WooCommerce does adhere to WordPress code standards and guidelines, so it's best to familiarize yourself with [WordPress Development](https://learn.wordpress.org/tutorial/introduction-to-wordpress/) as well as [PHP](https://www.php.net/). Knowledge and understanding of [WooCommerce Hooks and Filters](https://woocommerce.com/document/introduction-to-hooks-actions-and-filters/) will allow you to add and change code without editing core files. You can learn more about WordPress hooks and filters in the [WordPress Plugin Development Handbook](https://developer.wordpress.org/plugins/hooks/). @@ -80,22 +80,16 @@ git clone https://github.com/woocommerce/woocommerce.git cd woocommerce ``` -### Activate the required Node version +### Install and Activate the required Node version ```sh -nvm use -Found '/path/to/woocommerce/.nvmrc' with version -Now using node v12.21.0 (npm v6.14.11) +nvm install +Found '/path/to/woocommerce/.nvmrc' with version +v20.17.0 is already installed. +Now using node v20.17.0 (npm v10.8.2) ``` -Note: if you don't have the required version of Node installed, NVM will alert you so you can install it: - -```sh -Found '/path/to/woocommerce/.nvmrc' with version -N/A: version "v12 -> N/A" is not yet installed. - -You need to run "nvm install v12" to install it before using it. -``` +Note: if you don't have the required version of Node installed, NVM will install it. ### Install dependencies diff --git a/docs/getting-started/troubleshooting-endpoints.md b/docs/getting-started/troubleshooting-endpoints.md index 467d9dba1cb..11de5172810 100644 --- a/docs/getting-started/troubleshooting-endpoints.md +++ b/docs/getting-started/troubleshooting-endpoints.md @@ -18,24 +18,24 @@ For more information, learn how to [Customize Endpoints](./customizing-endpoint- On Windows servers, the **web.config** file may not be set correctly to allow for the endpoints to work correctly. In this case, clicking on endpoint links (e.g. /edit-account/ or /customer-logout/) may appear to do nothing except refresh the page. In order to resolve this, try simplifying the **web.config** file on your Windows server. Here's a sample file configuration: ```xml - - - - - - - - - - - - - - - - - - +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <system.webServer> + <handlers accessPolicy="Read, Execute, Script" /> + <rewrite> + <rules> + <rule name="wordpress" patternSyntax="Wildcard"> + <match url="*" /> + <conditions> + <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> + <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> + </conditions> + <action type="Rewrite" url="index.php" /> + </rule> + </rules> + </rewrite> + </system.webServer> +</configuration> ``` ## Pages direct to wrong place @@ -44,6 +44,6 @@ Landing on the wrong page when clicking an endpoint URL is typically caused by i ## How to Remove "Downloads" from My Account -Sometimes the "Downloads" endpoint on the "My account" page does not need to be displayed. This can be removed by going to **WooCommerce → Settings → Advanced → Account endpoints** and clearing the Downloads endpoint field. +Sometimes the "Downloads" endpoint on the "My account" page does not need to be displayed. This can be removed by going to **WooCommerce > Settings > Advanced > Account endpoints** and clearing the Downloads endpoint field. ![Account endpoints](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-04-09-at-11.45.58-PM.png) diff --git a/docs/high-performance-order-storage/cli-tools.md b/docs/high-performance-order-storage/cli-tools.md index e61035d60cd..c4b1877b9e5 100644 --- a/docs/high-performance-order-storage/cli-tools.md +++ b/docs/high-performance-order-storage/cli-tools.md @@ -229,7 +229,7 @@ The backfill command can be used to selectively migrate order data (or whole ord The exact syntax for this command is as follows: ```plaintext -wp wc hpos backfill --from= --to= [--meta_keys=] [--props=] +wp wc hpos backfill <order_id> --from=<datastore> --to=<datastore> [--meta_keys=<meta_keys>] [--props=<props>] ``` You have to specify which datastore to use as source (either `posts` or `hpos`) and which one to use as destination. The `--meta_keys` and `--props` arguments receive a comma separated list of meta keys and order properties, which can be used to move only certain data from one datastore to the other, instead of the whole order. diff --git a/docs/product-collection-block/register-product-collection.md b/docs/product-collection-block/register-product-collection.md index 677668ba71e..b754746a679 100644 --- a/docs/product-collection-block/register-product-collection.md +++ b/docs/product-collection-block/register-product-collection.md @@ -36,7 +36,7 @@ We will explain important arguments that can be passed to `__experimentalRegiste A Collection is defined by an object that can contain the following fields: -- `name` (type `string`): A unique and machine-readable collection name. We recommend using the format `/product-collection/`. Both `` and `` should consist only of alphanumeric characters and hyphens (e.g., `my-plugin/product-collection/my-collection`). +- `name` (type `string`): A unique and machine-readable collection name. We recommend using the format `<plugin-name>/product-collection/<collection-name>`. Both `<plugin-name>` and `<collection-name>` should consist only of alphanumeric characters and hyphens (e.g., `my-plugin/product-collection/my-collection`). - `title` (type `string`): The title of the collection, which will be displayed in various places including the block inserter and collection chooser. - `description` (optional, type `string`): A human-readable description of the collection. - `innerBlocks` (optional, type `Array[]`): An array of inner blocks that will be added to the collection. If not provided, the default inner blocks will be used. diff --git a/docs/product-editor-development/how-to-guides/custom-field-tutorial.md b/docs/product-editor-development/how-to-guides/custom-field-tutorial.md index 64fb3203025..ae243d3c9dc 100644 --- a/docs/product-editor-development/how-to-guides/custom-field-tutorial.md +++ b/docs/product-editor-development/how-to-guides/custom-field-tutorial.md @@ -62,7 +62,7 @@ In React: import { registerBlockType } from '@wordpress/blocks'; function Edit() { - return

Hello World (from the editor).

; + return <p>Hello World (from the editor).</p>; } registerBlockType( 'tutorial/new-product-form-field', { @@ -217,8 +217,8 @@ function Edit( { attributes } ) { const blockProps = useWooBlockProps( attributes ); return ( -
- -
+ /> + </div> ); } ``` diff --git a/docs/reporting/extending-woocommerce-admin-reports.md b/docs/reporting/extending-woocommerce-admin-reports.md index 2abe7862fe3..fe328313aee 100644 --- a/docs/reporting/extending-woocommerce-admin-reports.md +++ b/docs/reporting/extending-woocommerce-admin-reports.md @@ -32,7 +32,7 @@ npm run create-wc-extension After choosing a name, move into that folder and start webpack to watch and build files. ```sh -cd ../ +cd ../<my-plugin-name> npm install npm start ``` diff --git a/docs/theme-development/classic-theme-developer-handbook.md b/docs/theme-development/classic-theme-developer-handbook.md index dcdd64c0b6f..3f34881afe9 100644 --- a/docs/theme-development/classic-theme-developer-handbook.md +++ b/docs/theme-development/classic-theme-developer-handbook.md @@ -73,11 +73,11 @@ add_action('woocommerce_before_main_content', 'my_theme_wrapper_start', 10); add_action('woocommerce_after_main_content', 'my_theme_wrapper_end', 10); function my_theme_wrapper_start() { - echo '
'; + echo '<section id="main">'; } function my_theme_wrapper_end() { - echo '
'; + echo '</section>'; } ``` diff --git a/docs/wc-cli/wc-cli-commands.md b/docs/wc-cli/wc-cli-commands.md index 06606bdb500..aa7d3e3a646 100644 --- a/docs/wc-cli/wc-cli-commands.md +++ b/docs/wc-cli/wc-cli-commands.md @@ -117,7 +117,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc customer_download -#### wc customer_download list +#### wc customer_download list <customer_id> - `--customer_id` - Unique identifier for the resource. - `--context` - Scope under which the request is made; determines fields present in response. @@ -198,7 +198,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc order_note -#### wc order_note list +#### wc order_note list <order_id> - `--order_id` - The order ID. - `--context` - Scope under which the request is made; determines fields present in response. @@ -211,14 +211,14 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc order_note create +#### wc order_note create <order_id> - `--order_id` - The order ID. - `--note` - Order note content. (*Required*) - `--customer_note` - If true, the note will be shown to customers and they will be notified. If false, the note will be for admin reference only. - `--porcelain` - Output just the id when the operation is successful. -#### wc order_note get [id] +#### wc order_note get <order_id> [id] - `--id` - Unique identifier for the resource. - `--order_id` - The order ID. @@ -231,7 +231,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc order_note delete [id] +#### wc order_note delete <order_id> [id] - `--id` - Unique identifier for the resource. - `--order_id` - The order ID. @@ -240,7 +240,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc shop_order_refund -#### wc shop_order_refund list +#### wc shop_order_refund list <order_id> - `--order_id` - The order ID. - `--context` - Scope under which the request is made; determines fields present in response. @@ -265,7 +265,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc shop_order_refund create +#### wc shop_order_refund create <order_id> - `--order_id` - The order ID. - `--amount` - Refund amount. @@ -276,7 +276,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--api_refund` - When true, the payment gateway API is used to generate the refund. - `--porcelain` - Output just the id when the operation is successful. -#### wc shop_order_refund get [id] +#### wc shop_order_refund get <order_id> [id] - `--order_id` - The order ID. - `--id` - Unique identifier for the resource. @@ -289,7 +289,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc shop_order_refund delete [id] +#### wc shop_order_refund delete <order_id> [id] - `--order_id` - The order ID. - `--id` - Unique identifier for the resource. @@ -386,7 +386,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc product_attribute_term -#### wc product_attribute_term list +#### wc product_attribute_term list <attribute_id> - `--attribute_id` - Unique identifier for the attribute of the terms. - `--context` - Scope under which the request is made; determines fields present in response. @@ -409,7 +409,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_attribute_term create +#### wc product_attribute_term create <attribute_id> - `--attribute_id` - Unique identifier for the attribute of the terms. - `--name` - Name for the resource. (*Required*) @@ -418,7 +418,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--menu_order` - Menu order, used to custom sort the resource. - `--porcelain` - Output just the id when the operation is successful. -#### wc product_attribute_term get [id] +#### wc product_attribute_term get <attribute_id> [id] - `--id` - Unique identifier for the resource. - `--attribute_id` - Unique identifier for the attribute of the terms. @@ -431,7 +431,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_attribute_term update [id] +#### wc product_attribute_term update <attribute_id> [id] - `--id` - Unique identifier for the resource. - `--attribute_id` - Unique identifier for the attribute of the terms. @@ -441,7 +441,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--menu_order` - Menu order, used to custom sort the resource. - `--porcelain` - Output just the id when the operation is successful. -#### wc product_attribute_term delete [id] +#### wc product_attribute_term delete <attribute_id> [id] - `--id` - Unique identifier for the resource. - `--attribute_id` - Unique identifier for the attribute of the terms. @@ -565,7 +565,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc product_review -#### wc product_review list +#### wc product_review list <product_id> - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the variation. @@ -578,7 +578,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_review create +#### wc product_review create <product_id> - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the variation. @@ -590,7 +590,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--email` - Email of the reviewer. (*Required*) - `--porcelain` - Output just the id when the operation is successful. -#### wc product_review get [id] +#### wc product_review get <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the resource. @@ -603,7 +603,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_review update [id] +#### wc product_review update <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the resource. @@ -615,7 +615,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--email` - Reviewer email. - `--porcelain` - Output just the id when the operation is successful. -#### wc product_review delete [id] +#### wc product_review delete <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the resource. @@ -893,7 +893,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope ### wc product_variation -#### wc product_variation list +#### wc product_variation list <product_id> - `--product_id` - Unique identifier for the variable product. - `--context` - Scope under which the request is made; determines fields present in response. @@ -932,7 +932,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_variation create +#### wc product_variation create <product_id> - `--product_id` - Unique identifier for the variable product. - `--description` - Variation description. @@ -964,7 +964,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--meta_data` - Meta data. - `--porcelain` - Output just the id when the operation is successful. -#### wc product_variation get [id] +#### wc product_variation get <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the variation. @@ -977,7 +977,7 @@ Default: table Options: table, json, csv, ids, yaml, count, headers, body, envelope -#### wc product_variation update [id] +#### wc product_variation update <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the variation. @@ -1010,7 +1010,7 @@ Options: table, json, csv, ids, yaml, count, headers, body, envelope - `--meta_data` - Meta data. - `--porcelain` - Output just the id when the operation is successful. -#### wc product_variation delete [id] +#### wc product_variation delete <product_id> [id] - `--product_id` - Unique identifier for the variable product. - `--id` - Unique identifier for the variation. diff --git a/packages/js/data/changelog/fix-remove-help-panel-user-meta b/packages/js/data/changelog/fix-remove-help-panel-user-meta new file mode 100644 index 00000000000..9b1882ac793 --- /dev/null +++ b/packages/js/data/changelog/fix-remove-help-panel-user-meta @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Removed the leftover user meta from the help panel spotlight diff --git a/packages/js/data/src/user/types.ts b/packages/js/data/src/user/types.ts index 34328a41f18..428b8e1a5e1 100644 --- a/packages/js/data/src/user/types.ts +++ b/packages/js/data/src/user/types.ts @@ -15,7 +15,6 @@ export type UserPreferences = { dashboard_chart_type?: string; dashboard_leaderboard_rows?: string; dashboard_sections?: string; - help_panel_highlight_shown?: string; homepage_layout?: string; homepage_stats?: string; orders_report_columns?: string; diff --git a/plugins/woocommerce-admin/client/core-profiler/index.tsx b/plugins/woocommerce-admin/client/core-profiler/index.tsx index 428566a4578..8dbcd742f3f 100644 --- a/plugins/woocommerce-admin/client/core-profiler/index.tsx +++ b/plugins/woocommerce-admin/client/core-profiler/index.tsx @@ -300,15 +300,28 @@ const exitToWooHome = fromPromise( async () => { window.location.href = getNewPath( {}, '/', {} ); } ); +const getPluginNameParam = ( + pluginsSelected: CoreProfilerStateMachineContext[ 'pluginsSelected' ] +) => { + if ( pluginsSelected.includes( 'woocommerce-payments' ) ) { + return 'woocommerce-payments'; + } + return 'jetpack-ai'; +}; + const redirectToJetpackAuthPage = ( { event, + context, }: { context: CoreProfilerStateMachineContext; event: { output: { url: string } }; } ) => { const url = new URL( event.output.url ); url.searchParams.set( 'installed_ext_success', '1' ); - url.searchParams.set( 'plugin_name', 'jetpack-ai' ); + url.searchParams.set( + 'plugin_name', + getPluginNameParam( context.pluginsSelected ) + ); window.location.href = url.toString(); }; diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx index ab05865014a..60313651ee7 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx @@ -14,6 +14,11 @@ import { mergeBaseAndUserConfigs } from '@wordpress/edit-site/build-module/compo import { unlock } from '@wordpress/edit-site/build-module/lock-unlock'; import { isEqual, noop } from 'lodash'; +/** + * Internal dependencies + */ +import { trackEvent } from '~/customize-store/tracking'; + const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); // Removes the typography settings from the styles when the user is changing @@ -100,6 +105,24 @@ export const VariationContainer = ( { variation, children } ) => { ), }; } ); + + if ( variation.settings.color?.palette ) { + trackEvent( + 'customize_your_store_assembler_hub_color_palette_item_click', + { + item: variation.title, + } + ); + } + + if ( variation.settings.typography ) { + trackEvent( + 'customize_your_store_assembler_hub_typography_item_click', + { + item: variation.title, + } + ); + } }; const selectOnEnter = ( event ) => { diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-color-palette.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-color-palette.tsx index b1b10bab2e6..c851175894a 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-color-palette.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-color-palette.tsx @@ -17,6 +17,7 @@ import { unlock } from '@wordpress/edit-site/build-module/lock-unlock'; import { CustomizeStoreContext } from '../'; import { SidebarNavigationScreen } from './sidebar-navigation-screen'; import { ColorPalette, ColorPanel } from './global-styles'; +import { trackEvent } from '~/customize-store/tracking'; import { FlowType } from '~/customize-store/types'; const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); @@ -27,6 +28,14 @@ const SidebarNavigationScreenColorPaletteContent = () => { const hasCreatedOwnColors = !! ( user.settings.color && user.settings.color.palette.hasCreatedOwnColors ); + + function handlePanelBodyToggle( open?: boolean ) { + trackEvent( + 'customize_your_store_assembler_hub_color_palette_create_toggle', + { open } + ); + } + // Wrap in a BlockEditorProvider to ensure that the Iframe's dependencies are // loaded. This is necessary because the Iframe component waits until // the block editor store's `__internalIsInitialized` is true before @@ -45,6 +54,7 @@ const SidebarNavigationScreenColorPaletteContent = () => { className="woocommerce-customize-store__color-panel-container" title={ __( 'or create your own', 'woocommerce' ) } initialOpen={ hasCreatedOwnColors } + onToggle={ handlePanelBodyToggle } > diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-logo.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-logo.tsx index 0a55417170d..10f4c6a5f02 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-logo.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-logo.tsx @@ -297,16 +297,26 @@ const LogoEdit = ( { ); } + function handleMediaUploadSelect( media: { id: string; url: string } ) { + onInitialSelectLogo( media ); + trackEvent( 'customize_your_store_assembler_hub_logo_select' ); + } + if ( ! logoUrl ) { return ( void } ) => (
+ + + + +

+ { createInterpolateElement( + /* translators: %1$s terms page link, %2$s privacy page link. */ + __( + 'By creating an account you agree to our and .', + 'woocommerce' + ), + { terms: termsPageLink, privacy: privacyPageLink } + ) } +

+ + ); +}; + +export default Form; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/frontend.tsx b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/frontend.tsx new file mode 100644 index 00000000000..8af1238fb5e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/frontend.tsx @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { renderFrontend } from '@woocommerce/base-utils'; + +/** + * Internal dependencies + */ +import Block from './form'; +import { parseAttributes } from './utils'; + +const getProps = ( el: HTMLElement ) => { + return { + attributes: parseAttributes( el.dataset ), + isEditor: false, + }; +}; + +// This does not replace the entire block markup, just the form part. +renderFrontend( { + selector: '.woocommerce-order-confirmation-create-account-form', + Block, + getProps, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/index.tsx new file mode 100644 index 00000000000..d12ff49337c --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/index.tsx @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { registerBlockType } from '@wordpress/blocks'; +import { Icon, people } from '@wordpress/icons'; +import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; +import { ExternalLink } from '@wordpress/components'; +import { ADMIN_URL } from '@woocommerce/settings'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import { Save, Edit } from './edit'; + +if ( isExperimentalBlocksEnabled() ) { + registerBlockType( metadata, { + apiVersion: 3, + description: ( + <> + { metadata.description } +
+ + { __( 'Manage account settings', 'woocommerce' ) } + + + ), + icon: { + src: ( + + ), + }, + attributes: { + ...metadata.attributes, + }, + edit: Edit, + save: Save, + } ); +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/style.scss new file mode 100644 index 00000000000..8da053ef4e1 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/style.scss @@ -0,0 +1,82 @@ +.wc-block-order-confirmation-create-account { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: $gap; + padding: $gap-larger; + margin-top: $gap-larger !important; + margin-bottom: $gap-larger !important; + background: rgba(0, 0, 0, 0.04); + box-sizing: border-box; + + > div { + flex: 1; + } + + p { + margin-top: 0; + margin-bottom: 0; + } + + .woocommerce-order-confirmation-create-account-content, + .block-editor-block-list__layout { + > :first-child { + margin-top: 0 !important; + } + > :last-child { + margin-bottom: 0 !important; + } + ul { + li { + margin-bottom: $gap; + } + } + * { + color: inherit; + } + } + + form { + display: flex; + flex-direction: column; + gap: $gap; + + p, + .wc-block-components-text-input { + margin-top: 0; + margin-bottom: 0; + } + + .wc-block-components-button { + width: 100%; + padding: 1em; + } + + .wc-block-order-confirmation-create-account-terms { + @include font-size(small); + text-align: center; + + a, + span { + white-space: nowrap; + } + } + + .wc-block-components-password-strength.hidden { + display: none; + } + } + + .woocommerce-order-confirmation-create-account-success { + text-align: center; + padding: $gap-larger 0; + + > :first-child { + margin-top: 0 !important; + } + > :last-child { + margin-bottom: 0 !important; + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/utils.ts b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/utils.ts new file mode 100644 index 00000000000..51040951d07 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/utils.ts @@ -0,0 +1,6 @@ +export const parseAttributes = ( data: Record< string, unknown > ) => { + return { + customerEmail: data?.customerEmail || '', + nonceToken: data?.nonceToken || '', + }; +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/status/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/status/style.scss index 71c9a6e4feb..da618182353 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/status/style.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/status/style.scss @@ -1,3 +1,16 @@ +.wc-block-order-confirmation-status-description { + .woocommerce-verify-email { + margin-top: $gap-larger; + #verify-email { + width: 50%; + + @include breakpoint("<782px") { + width: 100%; + } + } + } +} + .wc-block-order-confirmation-status { margin-top: $gap; margin-bottom: $gap; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/summary/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/summary/style.scss index 63380eda4fa..a1d7f58cf27 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/summary/style.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/summary/style.scss @@ -36,12 +36,6 @@ } } } - .woocommerce-verify-email { - margin-top: $gap-larger; - #verify-email { - width: 50%; - } - } &.has-background { padding: $gap; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/index.tsx new file mode 100644 index 00000000000..81ebaa2368e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/index.tsx @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ +import './style.scss'; + +/** + * The reason for using this component instead of the core/disabled component is + * that the Disabled component disrupts the focus on inner blocks. For example, + * when a heading block is nested inside, the text cursor, which indicates the + * editable area, isn't visible when focused on the heading block. + * + * This component only uses CSS to control the selected behavior of inner + * blocks, which fixes the abovementioned issues. However, being a static + * component comes with a limitation: this component is meant to be placed + * directly inside the block wrapper element that holds block props. + */ +export const InitialDisabled = ( { + children, +}: { + children: React.ReactNode; +} ): JSX.Element => ( +
+
+ { children } +
+); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/style.scss new file mode 100644 index 00000000000..9c18798a936 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/initial-disabled/style.scss @@ -0,0 +1,17 @@ +.wc-block-product-filter-components-initial-disabled { + position: relative; + + .wc-block-product-filter-components-initial-disabled-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + + .is-selected > &, + .has-child-selected > & { + z-index: -1; + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/index.tsx new file mode 100644 index 00000000000..f8cc250fc10 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/index.tsx @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { Icon } from '@wordpress/components'; +import { info } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import './style.scss'; + +/** + * A custom notice component is designed specifically for new filter blocks. We + * are not reusing the existing components because we have a new design for the + * filter blocks notice. We want users to utilize the sidebar for attribute + * settings, so we are keeping the new notice minimal." + */ +export const Notice = ( { children }: { children: React.ReactNode } ) => ( +
+ +
+ { children } +
+
+); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/style.scss new file mode 100644 index 00000000000..97985ee80e9 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/components/notice/style.scss @@ -0,0 +1,17 @@ +.wc-block-product-filter-components-notice { + display: flex; + padding: $gap; + gap: $gap-smaller; + border: 1px solid; + + &__icon { + fill: $alert-red; + } + + &__content { + > * { + padding: 0; + margin: 0; + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/constants.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/constants.ts index 12220a2bf0c..5374588fd53 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/constants.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/constants.ts @@ -3,3 +3,9 @@ export const BlockOverlayAttribute = { MOBILE: 'mobile', ALWAYS: 'always', } as const; + +export const EXCLUDED_BLOCKS = [ + 'woocommerce/product-filter-attribute', + 'woocommerce/product-collection', + 'core/query', +]; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/edit.tsx index 09d6521991e..0d8c773e3cd 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/edit.tsx @@ -2,7 +2,6 @@ * External dependencies */ import { getSetting } from '@woocommerce/settings'; -import { AttributeSetting } from '@woocommerce/types'; import { InnerBlocks, InspectorControls, @@ -37,10 +36,6 @@ import './editor.scss'; import { type BlockAttributes } from './types'; import { BlockOverlayAttribute } from './constants'; -const defaultAttribute = getSetting< AttributeSetting >( - 'defaultProductFilterAttribute' -); - const TEMPLATE: InnerBlockTemplate[] = [ [ 'core/heading', @@ -50,42 +45,8 @@ const TEMPLATE: InnerBlockTemplate[] = [ content: __( 'Filters', 'woocommerce' ), }, ], - [ - 'woocommerce/product-filter', - { - filterType: 'active-filters', - heading: __( 'Active', 'woocommerce' ), - }, - ], - [ - 'woocommerce/product-filter', - { - filterType: 'price-filter', - heading: __( 'Price', 'woocommerce' ), - }, - ], - [ - 'woocommerce/product-filter', - { - filterType: 'stock-filter', - heading: __( 'Status', 'woocommerce' ), - }, - ], - [ - 'woocommerce/product-filter', - { - filterType: 'attribute-filter', - heading: defaultAttribute.attribute_label, - attributeId: parseInt( defaultAttribute.attribute_id, 10 ), - }, - ], - [ - 'woocommerce/product-filter', - { - filterType: 'rating-filter', - heading: __( 'Rating', 'woocommerce' ), - }, - ], + [ 'woocommerce/product-filter-active' ], + [ 'woocommerce/product-filter-attribute' ], [ 'core/buttons', { layout: { type: 'flex' } }, diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/frontend.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/frontend.tsx index dab9ef6c6c9..4b3085a291e 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/frontend.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/frontend.tsx @@ -1,7 +1,12 @@ /** * External dependencies */ -import { getContext as getContextFn, store } from '@woocommerce/interactivity'; +import { + getContext as getContextFn, + store, + navigate as navigateFn, +} from '@woocommerce/interactivity'; +import { getSetting } from '@woocommerce/settings'; export interface ProductFiltersContext { isDialogOpen: boolean; @@ -11,7 +16,7 @@ export interface ProductFiltersContext { const getContext = ( ns?: string ) => getContextFn< ProductFiltersContext >( ns ); -const productFilters = { +store( 'woocommerce/product-filters', { state: { isDialogOpen: () => { const context = getContext(); @@ -36,8 +41,41 @@ const productFilters = { }, }, callbacks: {}, -}; +} ); -store( 'woocommerce/product-filters', productFilters ); +const isBlockTheme = getSetting< boolean >( 'isBlockTheme' ); +const isProductArchive = getSetting< boolean >( 'isProductArchive' ); +const needsRefresh = getSetting< boolean >( + 'needsRefreshForInteractivityAPI', + false +); -export type ProductFilters = typeof productFilters; +export function navigate( href: string, options = {} ) { + /** + * We may need to reset the current page when changing filters. + * This is because the current page may not exist for this set + * of filters and will 404 when the user navigates to it. + * + * There are different pagination formats to consider, as documented here: + * https://github.com/WordPress/gutenberg/blob/317eb8f14c8e1b81bf56972cca2694be250580e3/packages/block-library/src/query-pagination-numbers/index.php#L22-L85 + */ + const url = new URL( href ); + // When pretty permalinks are enabled, the page number may be in the path name. + url.pathname = url.pathname.replace( /\/page\/[0-9]+/i, '' ); + // When plain permalinks are enabled, the page number may be in the "paged" query parameter. + url.searchParams.delete( 'paged' ); + // On posts and pages the page number will be in a query parameter that + // identifies which block we are paginating. + url.searchParams.forEach( ( _, key ) => { + if ( key.match( /^query(?:-[0-9]+)?-page$/ ) ) { + url.searchParams.delete( key ); + } + } ); + // Make sure to update the href with the changes. + href = url.href; + + if ( needsRefresh || ( ! isBlockTheme && isProductArchive ) ) { + return ( window.location.href = href ); + } + return navigateFn( href, options ); +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/block.json index 6d002248202..2754afa721a 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/block.json +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "name": "woocommerce/product-filter-active", "version": "1.0.0", - "title": "Filter Options", + "title": "Active (Experimental)", "description": "Display the currently active filters.", "category": "woocommerce", "keywords": [ @@ -11,11 +11,10 @@ "textdomain": "woocommerce", "apiVersion": 3, "ancestor": [ - "woocommerce/product-filter" + "woocommerce/product-filters" ], "supports": { "interactivity": true, - "inserter": false, "color": { "text": true, "background": false @@ -27,7 +26,7 @@ "attributes": { "displayStyle": { "type": "string", - "default": "list" + "default": "chips" } } -} \ No newline at end of file +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/frontend.ts index 0b462692f15..69cab9093e1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/frontend.ts @@ -6,7 +6,7 @@ import { store, getContext } from '@woocommerce/interactivity'; /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; type ActiveFiltersContext = { queryId: number; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/index.tsx index c1285586709..198cc46ce78 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/active-filters/index.tsx @@ -3,7 +3,7 @@ */ import { registerBlockType } from '@wordpress/blocks'; import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; -import { productFilterOptions } from '@woocommerce/icons'; +import { productFilterActive } from '@woocommerce/icons'; /** * Internal dependencies @@ -14,7 +14,7 @@ import './style.scss'; if ( isExperimentalBlocksEnabled() ) { registerBlockType( metadata, { - icon: productFilterOptions, + icon: productFilterActive, edit: Edit, } ); } diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/block.json index 2cc947376f2..fa9d859c2ab 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/block.json +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "name": "woocommerce/product-filter-attribute", "version": "1.0.0", - "title": "Filter Options", + "title": "Attribute (Experimental)", "description": "Enable customers to filter the product grid by selecting one or more attributes, such as color.", "category": "woocommerce", "keywords": [ @@ -11,11 +11,10 @@ "textdomain": "woocommerce", "apiVersion": 3, "ancestor": [ - "woocommerce/product-filter" + "woocommerce/product-filters" ], "supports": { "interactivity": true, - "inserter": false, "color": { "text": true, "background": false, @@ -78,7 +77,7 @@ }, "displayStyle": { "type": "string", - "default": "list" + "default": "woocommerce/product-filter-checkbox-list" }, "selectType": { "type": "string", @@ -100,5 +99,10 @@ "type": "boolean", "default":true } + }, + "example": { + "attributes": { + "isPreview": true + } } -} \ No newline at end of file +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-dropdown.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-dropdown.tsx deleted file mode 100644 index 933ab2e111a..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-dropdown.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** - * External dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { Icon, chevronDown } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import { PreviewDropdown } from '../../components/preview-dropdown'; - -type Props = { - label: string; -}; - -export const AttributeDropdown = ( { label }: Props ) => { - return ( -
- - -
- ); -}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-select-controls.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-select-controls.tsx deleted file mode 100644 index 8055d2a2dce..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/attribute-select-controls.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/** - * External dependencies - */ -import { sort } from 'fast-sort'; -import { __, sprintf, _n } from '@wordpress/i18n'; -import { SearchListControl } from '@woocommerce/editor-components/search-list-control'; -import { getSetting } from '@woocommerce/settings'; -import { SearchListItem } from '@woocommerce/editor-components/search-list-control/types'; -import { AttributeSetting } from '@woocommerce/types'; - -const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] ); - -type AttributeSelectControlsProps = { - isCompact: boolean; - setAttributeId: ( id: number ) => void; - attributeId: number; -}; - -export const AttributeSelectControls = ( { - isCompact, - setAttributeId, - attributeId, -}: AttributeSelectControlsProps ) => { - const messages = { - clear: __( 'Clear selected attribute', 'woocommerce' ), - list: __( 'Product Attributes', 'woocommerce' ), - noItems: __( - "Your store doesn't have any product attributes.", - 'woocommerce' - ), - search: __( 'Search for a product attribute:', 'woocommerce' ), - selected: ( n: number ) => - sprintf( - /* translators: %d is the number of attributes selected. */ - _n( - '%d attribute selected', - '%d attributes selected', - n, - 'woocommerce' - ), - n - ), - updated: __( - 'Product attribute search results updated.', - 'woocommerce' - ), - }; - - const list = sort( - ATTRIBUTES.map( ( item ) => { - return { - id: parseInt( item.attribute_id, 10 ), - name: item.attribute_label, - }; - } ) - ).asc( 'name' ); - - const onChange = ( selected: SearchListItem[] ) => { - if ( ! selected || ! selected.length ) { - return; - } - - const selectedId = selected[ 0 ].id; - const productAttribute = ATTRIBUTES.find( - ( attribute ) => attribute.attribute_id === selectedId.toString() - ); - - if ( ! productAttribute || attributeId === selectedId ) { - return; - } - - setAttributeId( selectedId as number ); - }; - - return ( - id === attributeId ) } - onChange={ onChange } - messages={ messages } - isSingle - isCompact={ isCompact } - /> - ); -}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/checkbox-list-editor.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/checkbox-list-editor.tsx deleted file mode 100644 index c06e5ae30bb..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/checkbox-list-editor.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Ideally, this component should belong to packages/interactivity-components. - * But we haven't export it as a packages so we place it here temporary. - */ -export const Preview = ( { items }: { items: string[] } ) => { - const threshold = 15; - const isLongList = items.length > threshold; - return ( -
-
    - { ( isLongList ? items.slice( 0, threshold ) : items ).map( - ( item, index ) => ( -
  • - -
  • - ) - ) } -
- { isLongList && ( - - { __( 'Show more…', 'woocommerce' ) } - - ) } -
- ); -}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/placeholder.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/placeholder.tsx deleted file mode 100644 index bee0d130c28..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/placeholder.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Icon, category, external } from '@wordpress/icons'; -import { getAdminLink } from '@woocommerce/settings'; -import { Placeholder, Button } from '@wordpress/components'; - -export const AttributesPlaceholder = ( { - children, -}: { - children: React.ReactNode; -} ) => ( - } - label={ __( 'Filter by Attribute', 'woocommerce' ) } - instructions={ __( - 'Enable customers to filter the product grid by selecting one or more attributes, such as color.', - 'woocommerce' - ) } - > - { children } - -); - -export const NoAttributesPlaceholder = () => ( - -

- { __( - "Attributes are needed for filtering your products. You haven't created any attributes yet.", - 'woocommerce' - ) } -

- - -
-); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/constants.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/constants.ts index 8c47d8c30e4..e535dfebc16 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/constants.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/constants.ts @@ -5,49 +5,71 @@ import { __ } from '@wordpress/i18n'; export const attributeOptionsPreview = [ { - id: 23, - name: __( 'Blue', 'woocommerce' ), - slug: 'blue', - attr_slug: 'blue', - description: '', - parent: 0, - count: 4, + label: __( 'Blue', 'woocommerce' ), + value: 'blue', + rawData: { + id: 23, + name: __( 'Blue', 'woocommerce' ), + slug: 'blue', + attr_slug: 'blue', + description: '', + parent: 0, + count: 4, + }, }, { - id: 29, - name: __( 'Gray', 'woocommerce' ), - slug: 'gray', - attr_slug: 'gray', - description: '', - parent: 0, - count: 3, + label: __( 'Gray', 'woocommerce' ), + value: 'gray', + selected: true, + rawData: { + id: 29, + name: __( 'Gray', 'woocommerce' ), + slug: 'gray', + attr_slug: 'gray', + description: '', + parent: 0, + count: 3, + }, }, { - id: 24, - name: __( 'Green', 'woocommerce' ), - slug: 'green', - attr_slug: 'green', - description: '', - parent: 0, - count: 3, + label: __( 'Green', 'woocommerce' ), + value: 'green', + rawData: { + id: 24, + name: __( 'Green', 'woocommerce' ), + slug: 'green', + attr_slug: 'green', + description: '', + parent: 0, + count: 3, + }, }, { - id: 25, - name: __( 'Red', 'woocommerce' ), - slug: 'red', - attr_slug: 'red', - description: '', - parent: 0, - count: 4, + label: __( 'Red', 'woocommerce' ), + value: 'red', + selected: true, + rawData: { + id: 25, + name: __( 'Red', 'woocommerce' ), + slug: 'red', + attr_slug: 'red', + description: '', + parent: 0, + count: 4, + }, }, { - id: 30, - name: __( 'Yellow', 'woocommerce' ), - slug: 'yellow', - attr_slug: 'yellow', - description: '', - parent: 0, - count: 1, + label: __( 'Yellow', 'woocommerce' ), + value: 'yellow', + rawData: { + id: 30, + name: __( 'Yellow', 'woocommerce' ), + slug: 'yellow', + attr_slug: 'yellow', + description: '', + parent: 0, + count: 1, + }, }, ]; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/edit.tsx index 9fb37b004b1..5f709d0089d 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/edit.tsx @@ -5,29 +5,34 @@ import { useCollection, useCollectionData, } from '@woocommerce/base-context/hooks'; -import { getSetting } from '@woocommerce/settings'; import { AttributeSetting, AttributeTerm, objectHasProp, } from '@woocommerce/types'; -import { useBlockProps } from '@wordpress/block-editor'; -import { Disabled, Notice, withSpokenMessages } from '@wordpress/components'; -import { useEffect, useState, useMemo } from '@wordpress/element'; +import { + useBlockProps, + useInnerBlocksProps, + BlockContextProvider, +} from '@wordpress/block-editor'; +import { withSpokenMessages } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { getSetting } from '@woocommerce/settings'; /** * Internal dependencies */ -import { AttributeDropdown } from './components/attribute-dropdown'; -import { Preview as CheckboxListPreview } from './components/checkbox-list-editor'; -import { Inspector } from './components/inspector'; -import { NoAttributesPlaceholder } from './components/placeholder'; +import { Inspector } from './inspector'; import { attributeOptionsPreview } from './constants'; import './style.scss'; import { EditProps, isAttributeCounts } from './types'; import { getAttributeFromId } from './utils'; -import './editor.scss'; +import { getAllowedBlocks } from '../../utils'; +import { EXCLUDED_BLOCKS } from '../../constants'; +import { FilterOptionItem } from '../../types'; +import { InitialDisabled } from '../../components/initial-disabled'; +import { Notice } from '../../components/notice'; const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] ); @@ -47,8 +52,10 @@ const Edit = ( props: EditProps ) => { const attributeObject = getAttributeFromId( attributeId ); const [ attributeOptions, setAttributeOptions ] = useState< - AttributeTerm[] + FilterOptionItem[] >( [] ); + const [ isOptionsLoading, setIsOptionsLoading ] = + useState< boolean >( true ); const { results: attributeTerms, isLoading: isTermsLoading } = useCollection< AttributeTerm >( { @@ -59,7 +66,7 @@ const Edit = ( props: EditProps ) => { query: { orderby: 'menu_order', hide_empty: hideEmpty }, } ); - const { results: filteredCounts, isLoading: isCountsLoading } = + const { results: filteredCounts, isLoading: isFilterCountsLoading } = useCollectionData( { queryAttribute: { taxonomy: attributeObject?.taxonomy || '', @@ -69,90 +76,137 @@ const Edit = ( props: EditProps ) => { isEditor: true, } ); - const isLoading = isTermsLoading || isCountsLoading; - useEffect( () => { + if ( isTermsLoading || isFilterCountsLoading ) return; + const termIdHasProducts = objectHasProp( filteredCounts, 'attribute_counts' ) && isAttributeCounts( filteredCounts.attribute_counts ) ? filteredCounts.attribute_counts.map( ( term ) => term.term ) : []; - if ( termIdHasProducts.length === 0 && hideEmpty ) - return setAttributeOptions( [] ); + if ( termIdHasProducts.length === 0 && hideEmpty ) { + setAttributeOptions( [] ); + } else { + setAttributeOptions( + attributeTerms + .filter( ( term ) => { + if ( hideEmpty ) + return termIdHasProducts.includes( term.id ); + return true; + } ) + .sort( ( a, b ) => { + switch ( sortOrder ) { + case 'name-asc': + return a.name > b.name ? 1 : -1; + case 'name-desc': + return a.name < b.name ? 1 : -1; + case 'count-asc': + return a.count > b.count ? 1 : -1; + case 'count-desc': + default: + return a.count < b.count ? 1 : -1; + } + } ) + .map( ( term, index ) => ( { + label: showCounts + ? `${ term.name } (${ term.count })` + : term.name, + value: term.id.toString(), + selected: index === 1, + rawData: term, + } ) ) + ); + } - setAttributeOptions( - attributeTerms - .filter( ( term ) => { - if ( hideEmpty ) - return termIdHasProducts.includes( term.id ); - return true; - } ) - .sort( ( a, b ) => { - switch ( sortOrder ) { - case 'name-asc': - return a.name > b.name ? 1 : -1; - case 'name-desc': - return a.name < b.name ? 1 : -1; - case 'count-asc': - return a.count > b.count ? 1 : -1; - case 'count-desc': - default: - return a.count < b.count ? 1 : -1; - } - } ) - ); - }, [ attributeTerms, filteredCounts, sortOrder, hideEmpty ] ); + setIsOptionsLoading( false ); + }, [ + showCounts, + attributeTerms, + filteredCounts, + sortOrder, + hideEmpty, + isTermsLoading, + isFilterCountsLoading, + ] ); - const Wrapper = ( { children }: { children: React.ReactNode } ) => ( -
- - { children } -
+ const { children, ...innerBlocksProps } = useInnerBlocksProps( + useBlockProps(), + { + allowedBlocks: getAllowedBlocks( EXCLUDED_BLOCKS ), + template: [ + [ + 'core/group', + { + layout: { + type: 'flex', + flexWrap: 'nowrap', + }, + metadata: { + name: __( 'Header', 'woocommerce' ), + }, + style: { + spacing: { + blockGap: '0', + }, + }, + }, + [ + [ + 'core/heading', + { + level: 3, + content: + attributeObject?.label || + __( 'Attribute', 'woocommerce' ), + }, + ], + [ + 'woocommerce/product-filter-clear-button', + { + lock: { + remove: true, + move: false, + }, + }, + ], + ], + ], + [ + displayStyle, + { + lock: { + remove: true, + }, + }, + ], + ], + } ); - const loadingState = useMemo( () => { - return [ ...Array( 5 ) ].map( ( x, i ) => ( -
  • -   -
  • - ) ); - }, [] ); + const isLoading = + isTermsLoading || isFilterCountsLoading || isOptionsLoading; - if ( isPreview ) { - return ( - - - { - if ( showCounts ) - return `${ term.name } (${ term.count })`; - return term.name; - } ) } - /> - - - ); - } - - // Block rendering starts. if ( Object.keys( ATTRIBUTES ).length === 0 ) return ( - - - +
    + + +

    + { __( + "Attributes are needed for filtering your products. You haven't created any attributes yet.", + 'woocommerce' + ) } +

    +
    +
    ); if ( ! attributeId || ! attributeObject ) return ( - - +
    + +

    { __( 'Please select an attribute to use this filter!', @@ -160,22 +214,14 @@ const Edit = ( props: EditProps ) => { ) }

    - +
    ); - if ( isLoading ) + if ( ! isLoading && attributeTerms.length === 0 ) return ( - -
      - { loadingState } -
    -
    - ); - - if ( attributeTerms.length === 0 ) - return ( - - +
    + +

    { __( 'There are no products with the selected attributes.', @@ -183,30 +229,28 @@ const Edit = ( props: EditProps ) => { ) }

    - +
    ); return ( - - - { displayStyle === 'dropdown' ? ( - - ) : ( - { - if ( showCounts ) - return `${ term.name } (${ term.count })`; - return term.name; - } ) } - /> - ) } - - +
    + + + + { children } + + +
    ); }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/editor.scss deleted file mode 100644 index 0580af4584f..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/editor.scss +++ /dev/null @@ -1,8 +0,0 @@ -.wp-block-woocommerce-product-filter-attribute__loading { - padding: 0; - - li { - @include placeholder(); - margin: 5px 0; - } -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/frontend.ts index 0f9eb935586..6170ececffb 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/frontend.ts @@ -8,7 +8,7 @@ import { HTMLElementEvent } from '@woocommerce/types'; /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; type AttributeFilterContext = { attributeSlug: string; @@ -106,5 +106,12 @@ store( 'woocommerce/product-filter-attribute', { navigate( getUrl( selectedTerms, attributeSlug, queryType ) ); }, + + clearFilters: () => { + const { attributeSlug, queryType } = + getContext< ActiveAttributeFilterContext >(); + + navigate( getUrl( [], attributeSlug, queryType ) ); + }, }, } ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/index.tsx index 6c1c49b3fb7..66a1c806d0b 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/index.tsx @@ -2,18 +2,21 @@ * External dependencies */ import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; -import { productFilterOptions } from '@woocommerce/icons'; +import { productFilterAttribute } from '@woocommerce/icons'; import { getSetting } from '@woocommerce/settings'; -import { registerBlockType } from '@wordpress/blocks'; import { AttributeSetting } from '@woocommerce/types'; +import { registerBlockType } from '@wordpress/blocks'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import metadata from './block.json'; import Edit from './edit'; +import Save from './save'; import './style.scss'; +const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] ); if ( isExperimentalBlocksEnabled() ) { const defaultAttribute = getSetting< AttributeSetting >( 'defaultProductFilterAttribute' @@ -21,7 +24,7 @@ if ( isExperimentalBlocksEnabled() ) { registerBlockType( metadata, { edit: Edit, - icon: productFilterOptions, + icon: productFilterAttribute, attributes: { ...metadata.attributes, attributeId: { @@ -29,5 +32,25 @@ if ( isExperimentalBlocksEnabled() ) { default: parseInt( defaultAttribute.attribute_id, 10 ), }, }, + save: Save, + variations: ATTRIBUTES.map( ( attribute, index ) => { + return { + name: `product-filter-attribute-${ attribute.attribute_name }`, + title: `${ attribute.attribute_label } (Experimental)`, + description: sprintf( + // translators: %s is the attribute label. + __( + `Enable customers to filter the product collection by selecting one or more %s attributes.`, + 'woocommerce' + ), + attribute.attribute_label + ), + attributes: { + attributeId: parseInt( attribute.attribute_id, 10 ), + }, + isActive: [ 'attributeId' ], + isDefault: index === 0, + }; + } ), } ); } diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/inspector.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx similarity index 68% rename from plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/inspector.tsx rename to plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx index 21b62ac0857..d2efdccbc07 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/components/inspector.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx @@ -5,8 +5,9 @@ import { getSetting } from '@woocommerce/settings'; import { AttributeSetting } from '@woocommerce/types'; import { InspectorControls } from '@wordpress/block-editor'; import { dispatch, useSelect } from '@wordpress/data'; -import { createInterpolateElement } from '@wordpress/element'; +import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { Block, getBlockTypes, createBlock } from '@wordpress/blocks'; import { ComboboxControl, PanelBody, @@ -23,12 +24,15 @@ import { /** * Internal dependencies */ -import { sortOrderOptions } from '../constants'; -import { BlockAttributes, EditProps } from '../types'; -import { getAttributeFromId } from '../utils'; +import { sortOrderOptions } from './constants'; +import { BlockAttributes, EditProps } from './types'; +import { getAttributeFromId } from './utils'; +import { getInnerBlockByName } from '../../utils'; const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] ); +let displayStyleOptions: Block[] = []; + export const Inspector = ( { clientId, attributes, @@ -43,47 +47,29 @@ export const Inspector = ( { hideEmpty, clearButton, } = attributes; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { productFilterWrapperBlockId, productFilterWrapperHeadingBlockId } = - useSelect( - ( select ) => { - if ( ! clientId ) - return { - productFilterWrapperBlockId: undefined, - productFilterWrapperHeadingBlockId: undefined, - }; + const { updateBlockAttributes, insertBlock, replaceBlock } = + dispatch( 'core/block-editor' ); + const filterBlock = useSelect( + ( select ) => { + return select( 'core/block-editor' ).getBlock( clientId ); + }, + [ clientId ] + ); + const [ displayStyleBlocksAttributes, setDisplayStyleBlocksAttributes ] = + useState< Record< string, unknown > >( {} ); - const { getBlockParentsByBlockName, getBlock } = - select( 'core/block-editor' ); + const filterHeadingBlock = getInnerBlockByName( + filterBlock, + 'core/heading' + ); - const parentBlocksByBlockName = getBlockParentsByBlockName( - clientId, - 'woocommerce/product-filter' - ); - - if ( parentBlocksByBlockName.length === 0 ) - return { - productFilterWrapperBlockId: undefined, - productFilterWrapperHeadingBlockId: undefined, - }; - - const parentBlockId = parentBlocksByBlockName[ 0 ]; - - const parentBlock = getBlock( parentBlockId ); - const headerGroupBlock = parentBlock?.innerBlocks.find( - ( block ) => block.name === 'core/group' - ); - const headingBlock = headerGroupBlock?.innerBlocks.find( - ( block ) => block.name === 'core/heading' - ); - - return { - productFilterWrapperBlockId: parentBlockId, - productFilterWrapperHeadingBlockId: headingBlock?.clientId, - }; - }, - [ clientId ] + if ( displayStyleOptions.length === 0 ) { + displayStyleOptions = getBlockTypes().filter( ( blockType ) => + blockType.ancestor?.includes( + 'woocommerce/product-filter-attribute' + ) ); + } return ( <> @@ -102,17 +88,9 @@ export const Inspector = ( { } ); const attributeObject = getAttributeFromId( numericId ); - if ( productFilterWrapperBlockId ) { + if ( filterHeadingBlock ) { updateBlockAttributes( - productFilterWrapperBlockId, - { - attributeId: numericId, - } - ); - } - if ( productFilterWrapperHeadingBlockId ) { - updateBlockAttributes( - productFilterWrapperHeadingBlockId, + filterHeadingBlock.clientId, { content: attributeObject?.label ?? @@ -188,17 +166,46 @@ export const Inspector = ( { value={ displayStyle } onChange={ ( value: BlockAttributes[ 'displayStyle' ] - ) => setAttributes( { displayStyle: value } ) } + ) => { + if ( ! filterBlock ) return; + const currentStyleBlock = getInnerBlockByName( + filterBlock, + displayStyle + ); + + if ( currentStyleBlock ) { + setDisplayStyleBlocksAttributes( { + ...displayStyleBlocksAttributes, + [ displayStyle ]: + currentStyleBlock.attributes, + } ); + replaceBlock( + currentStyleBlock.clientId, + createBlock( + value, + displayStyleBlocksAttributes[ value ] || + {} + ) + ); + } else { + insertBlock( + createBlock( value ), + filterBlock.innerBlocks.length, + filterBlock.clientId, + false + ); + } + setAttributes( { displayStyle: value } ); + } } style={ { width: '100%' } } > - - + { displayStyleOptions.map( ( blockType ) => ( + + ) ) } { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + return
    ; +}; + +export default Save; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/block.json new file mode 100644 index 00000000000..75be211eb62 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/block.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "name": "woocommerce/product-filter-checkbox-list", + "version": "1.0.0", + "title": "List", + "description": "Display a list of filter options.", + "category": "woocommerce", + "keywords": [ + "WooCommerce" + ], + "textdomain": "woocommerce", + "apiVersion": 3, + "ancestor": [ + "woocommerce/product-filter-attribute" + ], + "supports": { + "color": { + "enableContrastChecker": false + } + }, + "usesContext": [ + "filterData" + ], + "attributes": { + "optionElementBorder": { + "type": "string", + "default": "" + }, + "customOptionElementBorder": { + "type": "string", + "default": "" + }, + "optionElementSelected": { + "type": "string", + "default": "" + }, + "customOptionElementSelected": { + "type": "string", + "default": "" + }, + "optionElement": { + "type": "string", + "default": "" + }, + "customOptionElement": { + "type": "string", + "default": "" + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx new file mode 100644 index 00000000000..3e4ea3b73c4 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx @@ -0,0 +1,223 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; +import { __ } from '@wordpress/i18n'; +import { Icon } from '@wordpress/components'; +import { checkMark } from '@woocommerce/icons'; +import { useMemo } from '@wordpress/element'; +import { + useBlockProps, + withColors, + InspectorControls, + // @ts-expect-error - no types. + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, + // @ts-expect-error - no types. + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, +} from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import './style.scss'; +import './editor.scss'; +import { EditProps } from './types'; + +const Edit = ( props: EditProps ): JSX.Element => { + const { + clientId, + context, + attributes, + setAttributes, + optionElementBorder, + setOptionElementBorder, + optionElementSelected, + setOptionElementSelected, + optionElement, + setOptionElement, + } = props; + + const { + customOptionElementBorder, + customOptionElementSelected, + customOptionElement, + } = attributes; + const { filterData } = context; + const { isLoading, items } = filterData; + + const colorGradientSettings = useMultipleOriginColorsAndGradients(); + const blockProps = useBlockProps( { + className: clsx( 'wc-block-product-filter-checkbox-list', { + 'is-loading': isLoading, + 'has-option-element-border-color': + optionElementBorder.color || customOptionElementBorder, + 'has-option-element-selected-color': + optionElementSelected.color || customOptionElementSelected, + 'has-option-element-color': + optionElement.color || customOptionElement, + } ), + style: { + '--wc-product-filter-checkbox-list-option-element-border': + optionElementBorder.color || customOptionElementBorder, + '--wc-product-filter-checkbox-list-option-element-selected': + optionElementSelected.color || customOptionElementSelected, + '--wc-product-filter-checkbox-list-option-element': + optionElement.color || customOptionElement, + }, + } ); + + const loadingState = useMemo( () => { + return [ ...Array( 5 ) ].map( ( x, i ) => ( +
  • +   +
  • + ) ); + }, [] ); + + if ( ! items ) { + return <>; + } + + const threshold = 15; + const isLongList = items.length > threshold; + + return ( + <> +
    +
      + { isLoading && loadingState } + { ! isLoading && + ( isLongList + ? items.slice( 0, threshold ) + : items + ).map( ( item, index ) => ( +
    • + +
    • + ) ) } +
    + { ! isLoading && isLongList && ( + + { __( 'Show more…', 'woocommerce' ) } + + ) } +
    + + { colorGradientSettings.hasColorsOrGradients && ( + { + setOptionElementBorder( colorValue ); + setAttributes( { + customOptionElementBorder: colorValue, + } ); + }, + resetAllFilter: () => { + setOptionElementBorder( '' ); + setAttributes( { + customOptionElementBorder: '', + } ); + }, + }, + { + label: __( + 'Option Element (Selected)', + 'woocommerce' + ), + colorValue: + optionElementSelected.color || + customOptionElementSelected, + isShownByDefault: true, + enableAlpha: true, + onColorChange: ( colorValue: string ) => { + setOptionElementSelected( colorValue ); + setAttributes( { + customOptionElementSelected: colorValue, + } ); + }, + resetAllFilter: () => { + setOptionElementSelected( '' ); + setAttributes( { + customOptionElementSelected: '', + } ); + }, + }, + { + label: __( 'Option Element', 'woocommerce' ), + colorValue: + optionElement.color || customOptionElement, + isShownByDefault: true, + enableAlpha: true, + onColorChange: ( colorValue: string ) => { + setOptionElement( colorValue ); + setAttributes( { + customOptionElement: colorValue, + } ); + }, + resetAllFilter: () => { + setOptionElement( '' ); + setAttributes( { + customOptionElement: '', + } ); + }, + }, + ] } + panelId={ clientId } + { ...colorGradientSettings } + /> + ) } + + + ); +}; + +export default withColors( { + optionElementBorder: 'option-element-border', + optionElementSelected: 'option-element-border', + optionElement: 'option-element', +} )( Edit ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/editor.scss new file mode 100644 index 00000000000..dbca49525b7 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/editor.scss @@ -0,0 +1,10 @@ +.wc-block-product-filter-checkbox-list.is-loading { + .wc-block-product-filter-checkbox-list__list { + padding: 0; + + li { + @include placeholder(); + margin: 5px 0; + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/frontend.ts new file mode 100644 index 00000000000..f88d26cf9e2 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/frontend.ts @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { getContext, store } from '@woocommerce/interactivity'; +import { HTMLElementEvent } from '@woocommerce/types'; + +/** + * Internal dependencies + */ + +export type CheckboxListContext = { + items: { + id: string; + label: string; + value: string; + checked: boolean; + }[]; + showAll: boolean; +}; + +store( 'woocommerce/product-filter-checkbox-list', { + actions: { + showAllItems: () => { + const context = getContext< CheckboxListContext >(); + context.showAll = true; + }, + + selectCheckboxItem: ( event: HTMLElementEvent< HTMLInputElement > ) => { + const context = getContext< CheckboxListContext >(); + const value = event.target.value; + + context.items = context.items.map( ( item ) => { + if ( item.value.toString() === value ) { + return { + ...item, + checked: ! item.checked, + }; + } + return item; + } ); + }, + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/index.tsx new file mode 100644 index 00000000000..d87769fa657 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/index.tsx @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; +import { productFilterOptions } from '@woocommerce/icons'; +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import './style.scss'; + +if ( isExperimentalBlocksEnabled() ) { + registerBlockType( metadata, { + edit: Edit, + icon: productFilterOptions, + } ); +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/style.scss new file mode 100644 index 00000000000..ece2e52bc7f --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/style.scss @@ -0,0 +1,86 @@ +:where(.wc-block-product-filter-checkbox-list__list) { + list-style: none outside; + margin: 0; + padding: 0; +} + +.wc-block-product-filter-checkbox-list__item.hidden { + display: none; +} + + +:where(.wc-block-product-filter-checkbox-list__label) { + align-items: center; + display: flex; + gap: 0.625em; +} + +.wc-block-product-filter-checkbox-list__item .wc-block-product-filter-checkbox-list__label { + margin-bottom: 0; +} + +:where(.wc-block-product-filter-checkbox-list__input-wrapper) { + display: block; + position: relative; +} + +.wc-block-product-filter-checkbox-list__input-wrapper::before { + content: ""; + left: 0; + position: absolute; + top: 0; + background: currentColor; + opacity: 0.1; + width: 1em; + height: 1em; + border-radius: 2px; + + .has-option-element-color & { + display: none; + } +} + +:where(.wc-block-product-filter-checkbox-list__input) { + appearance: none; + border-radius: 2px; + border: 1px solid var(--wc-product-filter-checkbox-list-option-element-border, transparent); + color: inherit; + display: block; + font-size: inherit; + height: 1em; + margin: 0; + width: 1em; + background: var(--wc-product-filter-checkbox-list-option-element, transparent); +} + +.wc-block-product-filter-checkbox-list__input:checked + .wc-block-product-filter-checkbox-list__mark { + display: block; + pointer-events: none; +} + +.wc-block-product-filter-checkbox-list__input:focus { + outline-width: 1px; + outline-color: var(--wc-product-filter-checkbox-list-option-element-border, currentColor); +} + +:where(.wc-block-product-filter-checkbox-list__mark) { + box-sizing: border-box; + display: none; + height: 1em; + left: 0; + padding: 0.2em; + position: absolute; + top: 0; + width: 1em; + color: var(--wc-product-filter-checkbox-list-option-element-selected, currentColor); +} + +:where(.wc-block-product-filter-checkbox-list__show-more) { + cursor: pointer; + text-decoration: underline; +} + +.wc-block-product-filter-checkbox-list__show-more.hidden { + display: none; +} + diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/types.ts new file mode 100644 index 00000000000..ef10e7f6bd4 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/types.ts @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import { BlockEditProps } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { FilterBlockContext } from '../../types'; + +export type Color = { + slug: string; + class: string; + name: string; + color: string; +}; + +export type BlockAttributes = { + className: string; + optionElementBorder: string; + customOptionElementBorder: string; + optionElementSelected: string; + customOptionElementSelected: string; + optionElement: string; + customOptionElement: string; +}; + +export type EditProps = BlockEditProps< BlockAttributes > & { + context: FilterBlockContext; + optionElementBorder: Color; + setOptionElementBorder: ( value: string ) => void; + optionElementSelected: Color; + setOptionElementSelected: ( value: string ) => void; + optionElement: Color; + setOptionElement: ( value: string ) => void; +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json new file mode 100644 index 00000000000..44e26c25279 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "name": "woocommerce/product-filter-chips", + "version": "1.0.0", + "title": "Chips", + "description": "Display filter options as chips.", + "category": "woocommerce", + "keywords": [ + "WooCommerce" + ], + "textdomain": "woocommerce", + "apiVersion": 3, + "ancestor": [ + "woocommerce/product-filter-attribute" + ], + "supports": {}, + "usesContext": [ + "filterData", + "isParentSelected" + ], + "attributes": {} +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx new file mode 100644 index 00000000000..fcefe04c0ab --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx @@ -0,0 +1,15 @@ +/** + * External dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import './style.scss'; + +const Edit = () => { + return
    These are chips.
    ; +}; + +export default Edit; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/index.tsx new file mode 100644 index 00000000000..d87769fa657 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/index.tsx @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; +import { productFilterOptions } from '@woocommerce/icons'; +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import './style.scss'; + +if ( isExperimentalBlocksEnabled() ) { + registerBlockType( metadata, { + edit: Edit, + icon: productFilterOptions, + } ); +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss new file mode 100644 index 00000000000..a8af7fda118 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss @@ -0,0 +1,3 @@ +:where(.wc-block-product-filter-chips) { + // WIP +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/types.ts similarity index 59% rename from plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/types.ts rename to plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/types.ts index 726784edfd3..0a68e80edca 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/chips/types.ts @@ -6,13 +6,9 @@ import { BlockEditProps } from '@wordpress/blocks'; /** * Internal dependencies */ -import { BLOCK_NAME_MAP } from './constants'; - -export type FilterType = keyof typeof BLOCK_NAME_MAP; export type BlockAttributes = { - filterType: FilterType; - heading: string; + className: string; }; export type EditProps = BlockEditProps< BlockAttributes >; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/clear-button/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/clear-button/frontend.ts index 0bfc299b660..7fccd55836c 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/clear-button/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/clear-button/frontend.ts @@ -1,3 +1,8 @@ +/** + * Logic in this file is unused and should be moved to product-fitlers block. + * + * @see https://github.com/woocommerce/woocommerce/issues/50868 + */ /** * External dependencies */ @@ -6,7 +11,7 @@ import { store, getContext } from '@woocommerce/interactivity'; /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; const getQueryParams = ( e: Event ) => { const filterNavContainer = ( e.target as HTMLElement )?.closest( diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/price-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/price-filter/frontend.ts index 844e3c364f6..482e3e51117 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/price-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/price-filter/frontend.ts @@ -9,7 +9,7 @@ import { debounce } from '@woocommerce/base-utils'; /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; import type { PriceFilterContext, PriceFilterStore } from './types'; const getUrl = ( context: PriceFilterContext ) => { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block-variations.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block-variations.tsx deleted file mode 100644 index 7d33338bea6..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block-variations.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/** - * External dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { BlockVariation } from '@wordpress/blocks'; -import { - productFilterActive, - productFilterAttribute, - productFilterPrice, - productFilterRating, - productFilterStockStatus, -} from '@woocommerce/icons'; -import { getSetting } from '@woocommerce/settings'; -import { AttributeSetting, objectHasProp } from '@woocommerce/types'; - -const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] ); - -const variations: BlockVariation[] = [ - { - name: 'product-filter-active', - title: __( 'Active (Experimental)', 'woocommerce' ), - description: __( - 'Display the currently active filters.', - 'woocommerce' - ), - attributes: { - heading: __( 'Active filters', 'woocommerce' ), - filterType: 'active-filters', - }, - icon: productFilterActive, - isDefault: true, - }, - { - name: 'product-filter-price', - title: __( 'Price (Experimental)', 'woocommerce' ), - description: __( - 'Enable customers to filter the product collection by choosing a price range.', - 'woocommerce' - ), - attributes: { - filterType: 'price-filter', - heading: __( 'Price', 'woocommerce' ), - }, - icon: productFilterPrice, - }, - { - name: 'product-filter-stock-status', - title: __( 'Status (Experimental)', 'woocommerce' ), - description: __( - 'Enable customers to filter the product collection by stock status.', - 'woocommerce' - ), - attributes: { - filterType: 'stock-filter', - heading: __( 'Status', 'woocommerce' ), - }, - icon: productFilterStockStatus, - }, - { - name: 'product-filter-rating', - title: __( 'Rating (Experimental)', 'woocommerce' ), - description: __( - 'Enable customers to filter the product collection by rating.', - 'woocommerce' - ), - attributes: { - filterType: 'rating-filter', - heading: __( 'Rating', 'woocommerce' ), - }, - icon: productFilterRating, - }, -]; - -ATTRIBUTES.forEach( ( attribute ) => { - variations.push( { - name: `product-filter-attribute-${ attribute.attribute_name }`, - title: `${ attribute.attribute_label } (Experimental)`, - description: sprintf( - // translators: %s is the attribute label. - __( - `Enable customers to filter the product collection by selecting one or more %s attributes.`, - 'woocommerce' - ), - attribute.attribute_label - ), - attributes: { - filterType: 'attribute-filter', - heading: attribute.attribute_label, - attributeId: parseInt( attribute.attribute_id, 10 ), - }, - icon: productFilterAttribute, - // Can be `isActive: [ 'filterType', 'attributeId' ]`, but the API is available from 6.6. - isActive: ( blockAttributes, variationAttributes ) => { - return ( - blockAttributes.filterType === variationAttributes.filterType && - blockAttributes.attributeId === variationAttributes.attributeId - ); - }, - } ); -} ); - -variations.push( { - name: 'product-filter-attribute', - title: __( 'Attribute (Experimental)', 'woocommerce' ), - description: __( - 'Enable customers to filter the product collection by selecting one or more attributes, such as color.', - 'woocommerce' - ), - attributes: { - filterType: 'attribute-filter', - heading: __( 'Attribute', 'woocommerce' ), - attributeId: 0, - }, - icon: productFilterAttribute, -} ); - -/** - * Add `isActive` function to all Product Filter block variations. - * `isActive` function is used to find a variation match from a created - * Block by providing its attributes. - */ -variations.forEach( ( variation ) => { - if ( ! objectHasProp( variation, 'isActive' ) ) { - // @ts-expect-error: `isActive` is currently typed wrong in `@wordpress/blocks`. - variation.isActive = [ 'filterType' ]; - } -} ); - -export const blockVariations = variations; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block.json deleted file mode 100644 index 673300a288c..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/block.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "woocommerce/product-filter", - "version": "1.0.0", - "title": "Product Filter (Experimental)", - "description": "A block that adds product filters to the product collection.", - "category": "woocommerce", - "keywords": [ - "WooCommerce", - "Filters" - ], - "textdomain": "woocommerce", - "supports": { - "html": false, - "reusable": false, - "inserter": true - }, - "ancestor": [ - "woocommerce/product-filters" - ], - "usesContext": [ - "query", - "queryId" - ], - "attributes": { - "filterType": { - "type": "string" - }, - "heading": { - "type": "string" - }, - "isPreview": { - "type": "boolean", - "default": false - }, - "attributeId": { - "type": "number", - "default": 0 - } - }, - "example": { - "attributes": { - "isPreview": true - } - }, - "apiVersion": 3, - "$schema": "https://schemas.wp.org/trunk/block.json" -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/components/warning.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/components/warning.tsx deleted file mode 100644 index 33b16eaf5cf..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/components/warning.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/** - * External dependencies - */ -import { getSetting } from '@woocommerce/settings'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -const Warning = () => { - const isWidgetEditor = getSetting< boolean >( 'isWidgetEditor' ); - if ( isWidgetEditor ) { - return ( - - { __( - 'The widget area containing Collection Filters block needs to be placed on a product archive page for filters to function properly.', - 'woocommerce' - ) } - - ); - } - - const isSiteEditor = getSetting< boolean >( 'isSiteEditor' ); - if ( ! isSiteEditor ) { - return ( - - { __( - 'When added to a post or page, Collection Filters block needs to be nested inside a Product Collection block to function properly.', - 'woocommerce' - ) } - - ); - } - - return null; -}; - -export default Warning; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/constants.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/constants.ts deleted file mode 100644 index ba212b495f2..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const BLOCK_NAME_MAP = { - 'active-filters': 'woocommerce/product-filter-active', - 'price-filter': 'woocommerce/product-filter-price', - 'stock-filter': 'woocommerce/product-filter-stock-status', - 'rating-filter': 'woocommerce/product-filter-rating', - 'attribute-filter': 'woocommerce/product-filter-attribute', - 'clear-button': 'woocommerce/product-filter-clear-button', -}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/edit.tsx deleted file mode 100644 index 9706f037f9f..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/edit.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/** - * External dependencies - */ -import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; -import { BlockEditProps } from '@wordpress/blocks'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import Warning from './components/warning'; -import './editor.scss'; -import { getAllowedBlocks } from './utils'; -import { BLOCK_NAME_MAP } from './constants'; -import type { FilterType } from './types'; - -const Edit = ( { - attributes, - clientId, -}: BlockEditProps< { - heading: string; - filterType: FilterType; - isPreview: boolean; - attributeId: number | undefined; -} > ) => { - const blockProps = useBlockProps(); - - const isNested = useSelect( ( select ) => { - const { getBlockParentsByBlockName } = select( 'core/block-editor' ); - return !! getBlockParentsByBlockName( - clientId, - 'woocommerce/product-collection' - ).length; - } ); - - return ( - - ); -}; - -export default Edit; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/editor.scss deleted file mode 100644 index b739f84f377..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/editor.scss +++ /dev/null @@ -1,5 +0,0 @@ -.wp-block-woocommerce-collection-filters { - .components-notice { - margin: 0; - } -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/frontend.ts deleted file mode 100644 index 13a489f89c0..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/frontend.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * External dependencies - */ -import { navigate as navigateFn } from '@woocommerce/interactivity'; -import { getSetting } from '@woocommerce/settings'; - -const isBlockTheme = getSetting< boolean >( 'isBlockTheme' ); -const isProductArchive = getSetting< boolean >( 'isProductArchive' ); -const needsRefresh = getSetting< boolean >( - 'needsRefreshForInteractivityAPI', - false -); - -export function navigate( href: string, options = {} ) { - /** - * We may need to reset the current page when changing filters. - * This is because the current page may not exist for this set - * of filters and will 404 when the user navigates to it. - * - * There are different pagination formats to consider, as documented here: - * https://github.com/WordPress/gutenberg/blob/317eb8f14c8e1b81bf56972cca2694be250580e3/packages/block-library/src/query-pagination-numbers/index.php#L22-L85 - */ - const url = new URL( href ); - // When pretty permalinks are enabled, the page number may be in the path name. - url.pathname = url.pathname.replace( /\/page\/[0-9]+/i, '' ); - // When plain permalinks are enabled, the page number may be in the "paged" query parameter. - url.searchParams.delete( 'paged' ); - // On posts and pages the page number will be in a query parameter that - // identifies which block we are paginating. - url.searchParams.forEach( ( _, key ) => { - if ( key.match( /^query(?:-[0-9]+)?-page$/ ) ) { - url.searchParams.delete( key ); - } - } ); - // Make sure to update the href with the changes. - href = url.href; - - if ( needsRefresh || ( ! isBlockTheme && isProductArchive ) ) { - return ( window.location.href = href ); - } - return navigateFn( href, options ); -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/index.tsx deleted file mode 100644 index 850ac787f5d..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * External dependencies - */ -import { - BlockInstance, - createBlock, - registerBlockType, -} from '@wordpress/blocks'; -import { Icon, more } from '@wordpress/icons'; -import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings'; -/** - * Internal dependencies - */ -import metadata from './block.json'; -import edit from './edit'; -import save from './save'; -import { BLOCK_NAME_MAP } from './constants'; -import { BlockAttributes } from './types'; -import { blockVariations } from './block-variations'; - -if ( isExperimentalBlocksEnabled() ) { - registerBlockType( metadata, { - icon: { - src: ( - - ), - }, - edit, - save, - variations: blockVariations, - transforms: { - from: [ - { - type: 'block', - blocks: [ 'woocommerce/filter-wrapper' ], - transform: ( - attributes: BlockAttributes, - innerBlocks: BlockInstance[] - ) => { - const newInnerBlocks: BlockInstance[] = []; - // Loop through inner blocks to preserve the block order. - innerBlocks.forEach( ( block ) => { - if ( - block.name === - `woocommerce/${ attributes.filterType }` - ) { - newInnerBlocks.push( - createBlock( - BLOCK_NAME_MAP[ attributes.filterType ], - block.attributes - ) - ); - } - - if ( block.name === 'core/heading' ) { - newInnerBlocks.push( block ); - } - } ); - - return createBlock( - 'woocommerce/product-filter', - attributes, - newInnerBlocks - ); - }, - }, - ], - }, - } ); -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/save.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/save.tsx deleted file mode 100644 index 992874cb932..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/save.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/** - * External dependencies - */ -import { InnerBlocks } from '@wordpress/block-editor'; - -export default function save() { - return ; -} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/utils.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/utils.ts deleted file mode 100644 index 10a27961150..00000000000 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/product-filter/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * External dependencies - */ -import { getBlockTypes } from '@wordpress/blocks'; - -/** - * Returns an array of allowed block names excluding the disallowedBlocks array. - * - * @param disallowedBlocks Array of block names to disallow. - * @return Array of allowed block names. - */ -export const getAllowedBlocks = ( disallowedBlocks: string[] ) => { - const allBlocks = getBlockTypes(); - - return allBlocks - .map( ( block ) => block.name ) - .filter( ( name ) => ! disallowedBlocks.includes( name ) ); -}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/rating-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/rating-filter/frontend.ts index 3bd9f961704..8d865131b33 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/rating-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/rating-filter/frontend.ts @@ -8,7 +8,7 @@ import { DropdownContext } from '@woocommerce/interactivity-components/dropdown' /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; function getUrl( filters: Array< string | null > ) { filters = filters.filter( Boolean ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/stock-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/stock-filter/frontend.ts index 9833ff753c1..f0037014671 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/stock-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/inner-blocks/stock-filter/frontend.ts @@ -9,7 +9,7 @@ import { CheckboxListContext } from '@woocommerce/interactivity-components/check /** * Internal dependencies */ -import { navigate } from '../product-filter/frontend'; +import { navigate } from '../../frontend'; const getUrl = ( activeFilters: string ) => { const url = new URL( window.location.href ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/types.ts index f7ad8b247e9..0709a1490b8 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/types.ts @@ -7,8 +7,8 @@ export type BlockOverlayAttributeOptions = ( typeof BlockOverlayAttribute )[ keyof typeof BlockOverlayAttribute ]; export interface BlockAttributes { - productId?: string; setAttributes: ( attributes: ProductFiltersBlockAttributes ) => void; + productId?: string; overlay: BlockOverlayAttributeOptions; overlayIcon: | 'filter-icon-1' @@ -18,3 +18,23 @@ export interface BlockAttributes { overlayButtonStyle: 'label-icon' | 'label' | 'icon'; overlayIconSize?: number; } + +export type FilterOptionItem = { + label: string; + value: string; + selected?: boolean; + rawData?: Record< string, unknown >; +}; + +export type FilterBlockContext = { + filterData: { + isLoading: boolean; + items?: FilterOptionItem[]; + range?: { + min: number; + max: number; + step: number; + }; + }; + isParentSelected: boolean; +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-filters/utils.ts b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/utils.ts new file mode 100644 index 00000000000..d21e622fc3e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-filters/utils.ts @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { BlockInstance, getBlockTypes } from '@wordpress/blocks'; + +/** + * Returns an array of allowed block names excluding the disallowedBlocks array. + * + * @param disallowedBlocks Array of block names to disallow. + * @return Array of allowed block names. + */ +export const getAllowedBlocks = ( disallowedBlocks: string[] ) => { + const allBlocks = getBlockTypes(); + + return allBlocks + .map( ( block ) => block.name ) + .filter( ( name ) => ! disallowedBlocks.includes( name ) ); +}; + +export const getInnerBlockByName = ( + block: BlockInstance | null, + name: string +): BlockInstance | null => { + if ( ! block ) return null; + + if ( block.innerBlocks.length === 0 ) return null; + + for ( const innerBlock of block.innerBlocks ) { + if ( innerBlock.name === name ) return innerBlock; + const innerInnerBlock = getInnerBlockByName( innerBlock, name ); + if ( innerInnerBlock ) return innerInnerBlock; + } + + return null; +}; diff --git a/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx b/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx new file mode 100644 index 00000000000..f98bd4be1f5 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx @@ -0,0 +1,184 @@ +/** + * External dependencies + */ +import { __, _n, sprintf } from '@wordpress/i18n'; +import clsx from 'clsx'; +import type { CSSProperties } from 'react'; +import { isNumber, ProductResponseItem } from '@woocommerce/types'; + +type RatingProps = { + className: string; + reviews: number; + rating: number; + parentClassName?: string; +}; + +export const getAverageRating = ( + product: Omit< ProductResponseItem, 'average_rating' > & { + average_rating: string; + } +) => { + const rating = parseFloat( product.average_rating ); + + return Number.isFinite( rating ) && rating > 0 ? rating : 0; +}; + +export const getRatingCount = ( product: ProductResponseItem ) => { + const count = isNumber( product.review_count ) + ? product.review_count + : parseInt( product.review_count, 10 ); + + return Number.isFinite( count ) && count > 0 ? count : 0; +}; + +const getStarStyle = ( rating: number ) => ( { + width: ( rating / 5 ) * 100 + '%', +} ); + +const NoRating = ( { + className, + parentClassName, +}: { + className: string; + parentClassName: string; +} ) => { + const starStyle = getStarStyle( 0 ); + + return ( +
    +
    + +
    + { __( 'No Reviews', 'woocommerce' ) } +
    + ); +}; + +const Rating = ( props: RatingProps ): JSX.Element => { + const { className, rating, reviews, parentClassName } = props; + + const starStyle = getStarStyle( rating ); + + const ratingText = sprintf( + /* translators: %f is referring to the average rating value */ + __( 'Rated %f out of 5', 'woocommerce' ), + rating + ); + + const ratingHTML = { + __html: sprintf( + /* translators: %1$s is referring to the average rating value, %2$s is referring to the number of ratings */ + _n( + 'Rated %1$s out of 5 based on %2$s customer rating', + 'Rated %1$s out of 5 based on %2$s customer ratings', + reviews, + 'woocommerce' + ), + sprintf( '%f', rating ), + sprintf( '%d', reviews ) + ), + }; + return ( +
    + +
    + ); +}; + +const ReviewsCount = ( props: { + className: string; + reviews: number; +} ): JSX.Element => { + const { className, reviews } = props; + + const reviewsCount = sprintf( + /* translators: %s is referring to the total of reviews for a product */ + _n( + '(%s customer review)', + '(%s customer reviews)', + reviews, + 'woocommerce' + ), + reviews + ); + + return ( + + { reviewsCount } + + ); +}; + +type ProductRatingProps = { + className: string; + showReviewCount?: boolean; + showMockedReviews?: boolean; + parentClassName?: string; + rating: number; + reviews: number; + styleProps: { + className: string; + style: CSSProperties; + }; + textAlign?: string; +}; + +export const ProductRating = ( + props: ProductRatingProps +): JSX.Element | null => { + const { + className = 'wc-block-components-product-rating', + showReviewCount, + showMockedReviews, + parentClassName = '', + rating, + reviews, + styleProps, + textAlign, + } = props; + + const wrapperClassName = clsx( styleProps.className, className, { + [ `${ parentClassName }__product-rating` ]: parentClassName, + [ `has-text-align-${ textAlign }` ]: textAlign, + } ); + + const mockedRatings = showMockedReviews && ( + + ); + + const content = reviews ? ( + + ) : ( + mockedRatings + ); + + const isReviewCountVisible = reviews && showReviewCount; + + return ( +
    +
    + { content } + { isReviewCountVisible ? ( + + ) : null } +
    +
    + ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/icons/index.js b/plugins/woocommerce-blocks/assets/js/icons/index.js index 446bbadcebb..6cfa7e5f667 100644 --- a/plugins/woocommerce-blocks/assets/js/icons/index.js +++ b/plugins/woocommerce-blocks/assets/js/icons/index.js @@ -4,6 +4,7 @@ export { default as bagAlt } from './library/bag-alt'; export { default as barcode } from './library/barcode'; export { default as cart } from './library/cart'; export { default as cartOutline } from './library/cart-outline'; +export { default as checkMark } from './library/check-mark'; export { default as checkPayment } from './library/check-payment'; export { default as closeSquareShadow } from './library/close-square-shadow'; export { default as customerAccount } from './library/customer-account'; diff --git a/plugins/woocommerce-blocks/assets/js/icons/library/check-mark.tsx b/plugins/woocommerce-blocks/assets/js/icons/library/check-mark.tsx new file mode 100644 index 00000000000..cd7e6b1bffa --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/icons/library/check-mark.tsx @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { IconProps } from '@wordpress/icons/build-types/icon'; +import { Path, SVG } from '@wordpress/primitives'; + +const CheckMark = ( props: IconProps ) => ( + + + +); + +export default CheckMark; diff --git a/plugins/woocommerce-blocks/assets/js/middleware/index.js b/plugins/woocommerce-blocks/assets/js/middleware/index.js index f5d970a90d3..0d071b0b069 100644 --- a/plugins/woocommerce-blocks/assets/js/middleware/index.js +++ b/plugins/woocommerce-blocks/assets/js/middleware/index.js @@ -2,3 +2,4 @@ * Internal dependencies */ import './store-api-nonce'; +import './remove-user-locale'; diff --git a/plugins/woocommerce-blocks/assets/js/middleware/remove-user-locale.ts b/plugins/woocommerce-blocks/assets/js/middleware/remove-user-locale.ts new file mode 100644 index 00000000000..d53a7cb6666 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/middleware/remove-user-locale.ts @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { isStoreApiRequest } from './store-api-nonce'; + +/** + * Middleware to add the '_locale=site' query parameter from API requests. + * + * TODO: Remove once https://github.com/WordPress/gutenberg/issues/16805 is fixed and replace by removing userLocaleMiddleware middleware. + * + * @param {Object} options Fetch options. + * @param {Object} options.url The URL of the request. + * @param {Object} options.path The path of the request. + * @param {Function} next The next middleware or fetchHandler to call. + * @return {*} The evaluated result of the remaining middleware chain. + */ +const removeUserLocaleMiddleware = ( + options: { url?: string; path?: string }, + next: ( options: { url?: string; path?: string } ) => Promise< unknown > +): Promise< unknown > => { + if ( typeof options.url === 'string' && isStoreApiRequest( options ) ) { + options.url = addQueryArgs( options.url, { _locale: 'site' } ); + } + + if ( typeof options.path === 'string' && isStoreApiRequest( options ) ) { + options.path = addQueryArgs( options.path, { _locale: 'site' } ); + } + + return next( options ); +}; + +apiFetch.use( removeUserLocaleMiddleware ); diff --git a/plugins/woocommerce-blocks/assets/js/middleware/store-api-nonce.js b/plugins/woocommerce-blocks/assets/js/middleware/store-api-nonce.js index a5b121d8693..dc813d71653 100644 --- a/plugins/woocommerce-blocks/assets/js/middleware/store-api-nonce.js +++ b/plugins/woocommerce-blocks/assets/js/middleware/store-api-nonce.js @@ -23,7 +23,7 @@ try { * * @return {boolean} Returns true if this is a store request. */ -const isStoreApiRequest = ( options ) => { +export const isStoreApiRequest = ( options ) => { const url = options.url || options.path; if ( ! url || ! options.method || options.method === 'GET' ) { return false; diff --git a/plugins/woocommerce-blocks/bin/webpack-entries.js b/plugins/woocommerce-blocks/bin/webpack-entries.js index a3adb0662fc..aeeb511c7eb 100644 --- a/plugins/woocommerce-blocks/bin/webpack-entries.js +++ b/plugins/woocommerce-blocks/bin/webpack-entries.js @@ -88,10 +88,6 @@ const blocks = { 'product-filters': { isExperimental: true, }, - 'product-filter': { - isExperimental: true, - customDir: 'product-filters/inner-blocks/product-filter', - }, 'product-filters-overlay': { isExperimental: true, customDir: 'product-filters/inner-blocks/overlay', @@ -124,6 +120,14 @@ const blocks = { customDir: 'product-filters/inner-blocks/clear-button', isExperimental: true, }, + 'product-filter-checkbox-list': { + customDir: 'product-filters/inner-blocks/checkbox-list', + isExperimental: true, + }, + 'product-filter-chips': { + customDir: 'product-filters/inner-blocks/chips', + isExperimental: true, + }, 'order-confirmation-summary': { customDir: 'order-confirmation/summary', }, @@ -163,6 +167,10 @@ const blocks = { 'order-confirmation-additional-fields': { customDir: 'order-confirmation/additional-fields', }, + 'order-confirmation-create-account': { + customDir: 'order-confirmation/create-account', + isExperimental: true, + }, }; // Intentional separation of cart and checkout entry points to allow for better code splitting. diff --git a/plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md b/plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md index 2254ddb6d82..f35c9a00d2d 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md @@ -58,6 +58,10 @@ The majority of our feature flagging is blocks, this is a list of them: - [PHP flag](https://github.com/woocommerce/woocommerce/blob/a0f9d159e5196983d93064762fd20a510de57d55/plugins/woocommerce/src/Blocks/BlockTypesController.php#L303) - [Webpack flag](https://github.com/woocommerce/woocommerce/blob/a0f9d159e5196983d93064762fd20a510de57d55/plugins/woocommerce-blocks/bin/webpack-entries.js#L101) - [JS flag](https://github.com/woocommerce/woocommerce/blob/a0f9d159e5196983d93064762fd20a510de57d55/plugins/woocommerce-blocks/assets/js/blocks/product-filter/inner-blocks/stock-filter/index.tsx#L15) +- Delayed Account Creation (Experimental) + - [PHP flag](https://github.com/woocommerce/woocommerce/blob/9897737880dcbef9831ee41799684dab1960d94f/plugins/woocommerce/src/Blocks/BlockTypesController.php#L417) + - [Webpack flag](https://github.com/woocommerce/woocommerce/blob/9897737880dcbef9831ee41799684dab1960d94f/plugins/woocommerce-blocks/bin/webpack-entries.js#L168) + - [JS flag](https://github.com/woocommerce/woocommerce/blob/9897737880dcbef9831ee41799684dab1960d94f/plugins/woocommerce-blocks/assets/js/blocks/order-confirmation/create-account/index.tsx#L14) ## Features behind flags diff --git a/plugins/woocommerce-blocks/packages/components/validation-input-error/index.tsx b/plugins/woocommerce-blocks/packages/components/validation-input-error/index.tsx index 759468951df..7959d855b9e 100644 --- a/plugins/woocommerce-blocks/packages/components/validation-input-error/index.tsx +++ b/plugins/woocommerce-blocks/packages/components/validation-input-error/index.tsx @@ -3,6 +3,7 @@ */ import { useSelect } from '@wordpress/data'; import { VALIDATION_STORE_KEY } from '@woocommerce/block-data'; +import { Icon, warning } from '@wordpress/icons'; /** * Internal dependencies @@ -38,7 +39,10 @@ export const ValidationInputError = ( { return (
    -

    { errorMessage }

    +

    + + { errorMessage } +

    ); }; diff --git a/plugins/woocommerce-blocks/packages/components/validation-input-error/style.scss b/plugins/woocommerce-blocks/packages/components/validation-input-error/style.scss index ff5f571d745..3819f68a499 100644 --- a/plugins/woocommerce-blocks/packages/components/validation-input-error/style.scss +++ b/plugins/woocommerce-blocks/packages/components/validation-input-error/style.scss @@ -6,7 +6,17 @@ > p { margin: 0; - padding: $gap-smallest 0 0 0; + padding: $gap-smaller 0 0 0; + display: flex; + align-items: center; + gap: 2px; + } + + svg { + fill: currentColor; + width: 1.5em; + height: 1.5em; + margin-top: -1px; } } diff --git a/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_active-filters.handlebars b/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_active-filters.handlebars index f99258d7b35..1434a83d574 100644 --- a/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_active-filters.handlebars +++ b/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_active-filters.handlebars @@ -1,11 +1,12 @@ - - -

    Active filters

    - + +
    {{#> wp-block blockName='woocommerce/product-filter-active' attributes=attributes }} {{/ wp-block }} +
    + +
    diff --git a/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_attribute-filter.handlebars b/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_attribute-filter.handlebars index 1ae53f9c3d8..805634b68a7 100644 --- a/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_attribute-filter.handlebars +++ b/plugins/woocommerce-blocks/tests/e2e/content-templates/template_archive-product_attribute-filter.handlebars @@ -1,18 +1,26 @@ - - -

    Filter by Attribute

    - + +
    +{{#> wp-block blockName='woocommerce/product-filter-attribute' attributes=attributes }} +
    +
    +

    Attribute

    + + + -
    + - - +
    + -{{#> wp-block blockName='woocommerce/product-filter-attribute' attributes=attributes }} +
    {{/ wp-block }} +
    + +
    diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.spec.ts index de86eff5685..d3b78538f47 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.spec.ts @@ -482,6 +482,8 @@ test.describe( 'Shopper → Checkout Form Errors (guest user)', () => { await frontendUtils.goToCheckout(); await page.getByLabel( 'Email address' ).clear(); + // Notices on the email field will move content when the field loses focus. This can cause the click to "miss". + await page.getByRole( 'button', { name: 'Place order' } ).focus(); await page.getByRole( 'button', { name: 'Place order' } ).click(); // Verify that all required fields show the correct warning. diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts deleted file mode 100644 index b4c78fd0ab1..00000000000 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * External dependencies - */ -import { TemplateCompiler, test as base, expect } from '@woocommerce/e2e-utils'; - -const test = base.extend< { templateCompiler: TemplateCompiler } >( { - templateCompiler: async ( { requestUtils }, use ) => { - const compiler = await requestUtils.createTemplateFromFile( - 'archive-product_active-filters' - ); - await use( compiler ); - }, -} ); - -test.describe( 'Product Filter: Active Filters Block', () => { - test.describe( 'frontend', () => { - test.beforeEach( async ( { requestUtils } ) => { - await requestUtils.activatePlugin( - 'woocommerce-blocks-test-enable-experimental-features' - ); - } ); - - test( 'Without any filters selected, only a wrapper block is rendered', async ( { - page, - templateCompiler, - } ) => { - await templateCompiler.compile(); - - await page.goto( '/shop' ); - - const locator = page.locator( - '.wp-block-woocommerce-product-filter' - ); - - await expect( locator ).toHaveCount( 1 ); - - const html = await locator.innerHTML(); - expect( html.trim() ).toBe( '' ); - } ); - - test( 'With rating filters applied it shows the correct active filters', async ( { - page, - templateCompiler, - } ) => { - await templateCompiler.compile(); - - await page.goto( `${ '/shop' }?rating_filter=1,2,5` ); - - await expect( page.getByText( 'Rating:' ) ).toBeVisible(); - await expect( page.getByText( 'Rated 1 out of 5' ) ).toBeVisible(); - await expect( page.getByText( 'Rated 2 out of 5' ) ).toBeVisible(); - await expect( page.getByText( 'Rated 5 out of 5' ) ).toBeVisible(); - } ); - - test( 'With stock filters applied it shows the correct active filters', async ( { - page, - templateCompiler, - } ) => { - await templateCompiler.compile(); - - await page.goto( - `${ '/shop' }?filter_stock_status=instock,onbackorder` - ); - - await expect( page.getByText( 'Stock Status:' ) ).toBeVisible(); - await expect( page.getByText( 'In stock' ) ).toBeVisible(); - await expect( page.getByText( 'On backorder' ) ).toBeVisible(); - } ); - - test( 'With attribute filters applied it shows the correct active filters', async ( { - page, - templateCompiler, - } ) => { - await templateCompiler.compile(); - - await page.goto( - `${ '/shop' }?filter_color=blue,gray&query_type_color=or` - ); - - await expect( page.getByText( 'Color:' ) ).toBeVisible(); - await expect( page.getByText( 'Blue' ) ).toBeVisible(); - await expect( page.getByText( 'Gray' ) ).toBeVisible(); - } ); - - test( 'With price filters applied it shows the correct active filters', async ( { - page, - templateCompiler, - } ) => { - await templateCompiler.compile(); - - await page.goto( `${ '/shop' }?min_price=17&max_price=71` ); - - await expect( page.getByText( 'Price:' ) ).toBeVisible(); - await expect( - page.getByText( 'Between $17 and $71' ) - ).toBeVisible(); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts deleted file mode 100644 index d39c14ff018..00000000000 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts +++ /dev/null @@ -1,316 +0,0 @@ -/** - * External dependencies - */ -import { TemplateCompiler, test as base, expect } from '@woocommerce/e2e-utils'; - -const COLOR_ATTRIBUTE_VALUES = [ 'Blue', 'Gray', 'Green', 'Red', 'Yellow' ]; -const COLOR_ATTRIBUTES_WITH_COUNTS = [ - 'Blue (4)', - 'Gray (2)', - 'Green (3)', - 'Red (4)', - 'Yellow (1)', -]; - -const test = base.extend< { templateCompiler: TemplateCompiler } >( { - templateCompiler: async ( { requestUtils }, use ) => { - const compiler = await requestUtils.createTemplateFromFile( - 'archive-product_attribute-filter' - ); - await use( compiler ); - }, -} ); - -test.describe( 'Product Filter: Attribute Block', () => { - test.describe( 'With default display style', () => { - test.beforeEach( async ( { requestUtils, templateCompiler } ) => { - await requestUtils.activatePlugin( - 'woocommerce-blocks-test-enable-experimental-features' - ); - await templateCompiler.compile( { - attributes: { - attributeId: 1, - }, - } ); - } ); - - test( 'clear button is not shown on initial page load', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await expect( button ).toBeHidden(); - } ); - - test( 'renders a checkbox list with the available attribute filters', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const attributes = page.locator( - '.wc-block-interactivity-components-checkbox-list__label' - ); - - await expect( attributes ).toHaveCount( 5 ); - - for ( let i = 0; i < COLOR_ATTRIBUTE_VALUES.length; i++ ) { - await expect( attributes.nth( i ) ).toHaveText( - COLOR_ATTRIBUTE_VALUES[ i ] - ); - } - } ); - - test( 'filters the list of products by selecting an attribute', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const grayCheckbox = page.getByText( 'Gray' ); - await grayCheckbox.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=gray.*/ ); - - const products = page.locator( '.wc-block-product' ); - - await expect( products ).toHaveCount( 2 ); - } ); - - test( 'clear button appears after a filter is applied', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const grayCheckbox = page.getByText( 'Gray' ); - await grayCheckbox.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=gray.*/ ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await expect( button ).toBeVisible(); - } ); - - test( 'clear button hides after deselecting all filters', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const grayCheckbox = page.getByText( 'Gray' ); - await grayCheckbox.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=gray.*/ ); - - await grayCheckbox.click(); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await expect( button ).toBeHidden(); - } ); - - test( 'filters are cleared after clear button is clicked', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const grayCheckbox = page.getByText( 'Gray' ); - await grayCheckbox.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=gray.*/ ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await button.click(); - - COLOR_ATTRIBUTE_VALUES.map( async ( color ) => { - const element = page.locator( - `input[value="${ color.toLowerCase() }"]` - ); - - await expect( element ).not.toBeChecked(); - } ); - } ); - } ); - - test.describe( 'With show counts enabled', () => { - test.beforeEach( async ( { requestUtils, templateCompiler } ) => { - await requestUtils.activatePlugin( - 'woocommerce-blocks-test-enable-experimental-features' - ); - await templateCompiler.compile( { - attributes: { - attributeId: 1, - showCounts: true, - }, - } ); - } ); - - test( 'Renders checkboxes with associated product counts', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const attributes = page.locator( - '.wc-block-interactivity-components-checkbox-list__label' - ); - - await expect( attributes ).toHaveCount( 5 ); - - for ( let i = 0; i < COLOR_ATTRIBUTES_WITH_COUNTS.length; i++ ) { - await expect( attributes.nth( i ) ).toHaveText( - COLOR_ATTRIBUTES_WITH_COUNTS[ i ] - ); - } - } ); - } ); - - test.describe( "With display style 'dropdown'", () => { - test.beforeEach( async ( { requestUtils, templateCompiler } ) => { - await requestUtils.activatePlugin( - 'woocommerce-blocks-test-enable-experimental-features' - ); - await templateCompiler.compile( { - attributes: { - attributeId: 1, - displayStyle: 'dropdown', - }, - } ); - } ); - - test( 'clear button is not shown on initial page load', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await expect( button ).toBeHidden(); - } ); - - test( 'renders a dropdown list with the available attribute filters', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const dropdownLocator = page.locator( - '.wc-interactivity-dropdown' - ); - - await expect( dropdownLocator ).toBeVisible(); - await dropdownLocator.click(); - - for ( let i = 0; i < COLOR_ATTRIBUTE_VALUES.length; i++ ) { - await expect( - dropdownLocator.getByText( COLOR_ATTRIBUTE_VALUES[ i ] ) - ).toBeVisible(); - } - } ); - - test( 'Clicking a dropdown option should filter the displayed products', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const dropdownLocator = page.locator( - '.wc-interactivity-dropdown' - ); - - await expect( dropdownLocator ).toBeVisible(); - await dropdownLocator.click(); - - const yellowOption = page.getByText( 'Yellow' ); - await yellowOption.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=yellow.*/ ); - - const products = page.locator( '.wc-block-product' ); - - await expect( products ).toHaveCount( 1 ); - } ); - - test( 'clear button appears after a filter is applied', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const dropdownLocator = page.locator( - '.wc-interactivity-dropdown' - ); - - await expect( dropdownLocator ).toBeVisible(); - await dropdownLocator.click(); - - const yellowOption = page.getByText( 'Yellow' ); - await yellowOption.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=yellow.*/ ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await expect( button ).toBeVisible(); - } ); - - test( 'clear button hides after deselecting all filters', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const dropdownLocator = page.locator( - '.wc-interactivity-dropdown' - ); - - await dropdownLocator.click(); - - const yellowOption = page.getByText( 'Yellow' ); - await yellowOption.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=yellow.*/ ); - - await dropdownLocator.click(); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - const removeFilter = page.locator( - '.wc-interactivity-dropdown__badge-remove' - ); - - await removeFilter.click(); - - await expect( button ).toBeHidden(); - } ); - - test( 'filters are cleared after clear button is clicked', async ( { - page, - } ) => { - await page.goto( '/shop' ); - - const dropdownLocator = page.locator( - '.wc-interactivity-dropdown' - ); - - await dropdownLocator.click(); - - const yellowOption = page.getByText( 'Yellow' ); - await yellowOption.click(); - - // wait for navigation - await page.waitForURL( /.*filter_color=yellow.*/ ); - - const button = page.getByRole( 'button', { name: 'Clear' } ); - - await button.click(); - - const placeholder = page.getByText( 'Select Color' ); - - await expect( placeholder ).toBeVisible(); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts deleted file mode 100644 index db9f8dd5b10..00000000000 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * External dependencies - */ -import { test, expect } from '@woocommerce/e2e-utils'; - -const filterBlocks = [ - { - name: 'woocommerce/product-filter-price', - title: 'Product Filter: Price (Experimental)', - heading: 'Filter by Price', - }, - { - name: 'woocommerce/product-filter-stock-status', - title: 'Product Filter: Stock Status (Experimental)', - heading: 'Filter by Stock Status', - }, - { - name: 'woocommerce/product-filter-rating', - title: 'Product Filter: Rating (Experimental)', - heading: 'Filter by Rating', - }, - { - name: 'woocommerce/product-filter-attribute', - title: 'Product Filter: Attribute (Experimental)', - heading: 'Filter by Attribute', - }, - { - name: 'woocommerce/product-filter-active', - title: 'Product Filter: Active Filters (Experimental)', - heading: 'Active Filters', - }, -]; - -test.describe( 'Filter blocks registration', () => { - test.beforeEach( async ( { admin } ) => { - await admin.createNewPost(); - } ); - - test( 'Variations cannot be inserted through the inserter.', async ( { - page, - editor, - } ) => { - for ( const block of filterBlocks ) { - await editor.openGlobalBlockInserter(); - await page.getByPlaceholder( 'Search' ).fill( block.title ); - const filterBlock = page.getByRole( 'option', { - name: block.title, - exact: true, - } ); - - await expect( filterBlock ).toBeHidden(); - } - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters-overlay/product-filters-overlay-template-part.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters-overlay/product-filters-overlay-template-part.block_theme.spec.ts index a5e02ea94fa..97ae2631985 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters-overlay/product-filters-overlay-template-part.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters-overlay/product-filters-overlay-template-part.block_theme.spec.ts @@ -105,10 +105,6 @@ test.describe( 'Filters Overlay Template Part', () => { templatePartData.selectors.editor.blocks.activeFilters .blockLabel ) - .getByLabel( - templatePartData.selectors.editor.blocks.filterOptions - .blockLabel - ) .click(); await editor.openDocumentSettingsSidebar(); @@ -174,7 +170,9 @@ test.describe( 'Filters Overlay Template Part', () => { await expect( productFiltersDialog ).toBeHidden(); } ); - test( 'should hide Product Filters Overlay Navigation block when the Overlay mode is set to `Never`', async ( { + // Since we need to overhaul the overlay area, we can skip this test for now. + // eslint-disable-next-line playwright/no-skipped-test + test.skip( 'should hide Product Filters Overlay Navigation block when the Overlay mode is set to `Never`', async ( { editor, page, frontendUtils, @@ -199,10 +197,6 @@ test.describe( 'Filters Overlay Template Part', () => { templatePartData.selectors.editor.blocks.activeFilters .blockLabel ) - .getByLabel( - templatePartData.selectors.editor.blocks.filterOptions - .blockLabel - ) .click(); await editor.openDocumentSettingsSidebar(); @@ -219,12 +213,6 @@ test.describe( 'Filters Overlay Template Part', () => { 'OverlayNeverMobileAlways' ); await layoutSettings.getByLabel( 'Never' ).click(); - await editor.page - .getByRole( 'link', { - name: templatePartData.selectors.editor.blocks - .productFiltersOverlayNavigation.title, - } ) - .click(); await editor.saveSiteEditorEntities( { isOnlyCurrentEntityDirty: true, @@ -269,10 +257,6 @@ test.describe( 'Filters Overlay Template Part', () => { templatePartData.selectors.editor.blocks.activeFilters .blockLabel ) - .getByLabel( - templatePartData.selectors.editor.blocks.filterOptions - .blockLabel - ) .click(); await editor.openDocumentSettingsSidebar(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/active-filter-frontend.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/active-filter-frontend.block_theme.spec.ts new file mode 100644 index 00000000000..341a57ee21e --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/active-filter-frontend.block_theme.spec.ts @@ -0,0 +1,115 @@ +/** + * External dependencies + */ +import { TemplateCompiler, test as base, expect } from '@woocommerce/e2e-utils'; + +const test = base.extend< { templateCompiler: TemplateCompiler } >( { + templateCompiler: async ( { requestUtils }, use ) => { + const compiler = await requestUtils.createTemplateFromFile( + 'archive-product_active-filters' + ); + await use( compiler ); + }, +} ); + +test.describe( 'woocommerce/product-filter-active - Frontend', () => { + test.beforeEach( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-enable-experimental-features' + ); + } ); + + test( 'Without any filters selected, only a wrapper block is rendered', async ( { + page, + templateCompiler, + } ) => { + await templateCompiler.compile( { + attributes: { + displayStyle: 'list', + }, + } ); + + await page.goto( '/shop' ); + + const locator = page.locator( + '.wp-block-woocommerce-product-filter-active' + ); + + await expect( locator ).toHaveCount( 1 ); + + const html = await locator.innerHTML(); + expect( html.trim() ).toBe( '' ); + } ); + + test( 'With rating filters applied it shows the correct active filters', async ( { + page, + templateCompiler, + } ) => { + await templateCompiler.compile( { + attributes: { + displayStyle: 'list', + }, + } ); + + await page.goto( `${ '/shop' }?rating_filter=1,2,5` ); + + await expect( page.getByText( 'Rating:' ) ).toBeVisible(); + await expect( page.getByText( 'Rated 1 out of 5' ) ).toBeVisible(); + await expect( page.getByText( 'Rated 2 out of 5' ) ).toBeVisible(); + await expect( page.getByText( 'Rated 5 out of 5' ) ).toBeVisible(); + } ); + + test( 'With stock filters applied it shows the correct active filters', async ( { + page, + templateCompiler, + } ) => { + await templateCompiler.compile( { + attributes: { + displayStyle: 'list', + }, + } ); + + await page.goto( + `${ '/shop' }?filter_stock_status=instock,onbackorder` + ); + + await expect( page.getByText( 'Stock Status:' ) ).toBeVisible(); + await expect( page.getByText( 'In stock' ) ).toBeVisible(); + await expect( page.getByText( 'On backorder' ) ).toBeVisible(); + } ); + + test( 'With attribute filters applied it shows the correct active filters', async ( { + page, + templateCompiler, + } ) => { + await templateCompiler.compile( { + attributes: { + displayStyle: 'list', + }, + } ); + + await page.goto( + `${ '/shop' }?filter_color=blue,gray&query_type_color=or` + ); + + await expect( page.getByText( 'Color:' ) ).toBeVisible(); + await expect( page.getByText( 'Blue' ) ).toBeVisible(); + await expect( page.getByText( 'Gray' ) ).toBeVisible(); + } ); + + test( 'With price filters applied it shows the correct active filters', async ( { + page, + templateCompiler, + } ) => { + await templateCompiler.compile( { + attributes: { + displayStyle: 'list', + }, + } ); + + await page.goto( `${ '/shop' }?min_price=17&max_price=71` ); + + await expect( page.getByText( 'Price:' ) ).toBeVisible(); + await expect( page.getByText( 'Between $17 and $71' ) ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-editor.block_theme.spec.ts similarity index 91% rename from plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter.block_theme.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-editor.block_theme.spec.ts index 54220f04b92..77f9b13f090 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-editor.block_theme.spec.ts @@ -48,9 +48,7 @@ test.describe( `${ blockData.name }`, () => { } ) => { await pageObject.addProductFiltersBlock( { cleanContent: true } ); - const block = editor.canvas - .getByLabel( 'Block: Color (Experimental)' ) - .getByLabel( 'Block: Filter Options' ); + const block = editor.canvas.getByLabel( 'Block: Color (Experimental)' ); await expect( block ).toBeVisible(); @@ -82,9 +80,7 @@ test.describe( `${ blockData.name }`, () => { } ) => { await pageObject.addProductFiltersBlock( { cleanContent: true } ); - const block = editor.canvas - .getByLabel( 'Block: Color (Experimental)' ) - .getByLabel( 'Block: Filter Options' ); + const block = editor.canvas.getByLabel( 'Block: Color (Experimental)' ); await expect( block ).toBeVisible(); @@ -112,9 +108,7 @@ test.describe( `${ blockData.name }`, () => { } ) => { await pageObject.addProductFiltersBlock( { cleanContent: true } ); - const block = editor.canvas - .getByLabel( 'Block: Color (Experimental)' ) - .getByLabel( 'Block: Filter Options' ); + const block = editor.canvas.getByLabel( 'Block: Color (Experimental)' ); await editor.openDocumentSettingsSidebar(); await block.click(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-frontend.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-frontend.block_theme.spec.ts new file mode 100644 index 00000000000..8f79924b291 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/attribute-filter-frontend.block_theme.spec.ts @@ -0,0 +1,171 @@ +/** + * External dependencies + */ +import { TemplateCompiler, test as base, expect } from '@woocommerce/e2e-utils'; + +const COLOR_ATTRIBUTE_VALUES = [ 'Blue', 'Gray', 'Green', 'Red', 'Yellow' ]; +const COLOR_ATTRIBUTES_WITH_COUNTS = [ + 'Blue (4)', + 'Gray (2)', + 'Green (3)', + 'Red (4)', + 'Yellow (1)', +]; + +const test = base.extend< { templateCompiler: TemplateCompiler } >( { + templateCompiler: async ( { requestUtils }, use ) => { + const compiler = await requestUtils.createTemplateFromFile( + 'archive-product_attribute-filter' + ); + await use( compiler ); + }, +} ); + +test.describe( 'woocommerce/product-filter-attribute - Frontend', () => { + test.describe( 'With default display style', () => { + test.beforeEach( async ( { requestUtils, templateCompiler } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-enable-experimental-features' + ); + await templateCompiler.compile( { + attributes: { + attributeId: 1, + }, + } ); + } ); + + test( 'clear button is not shown on initial page load', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const button = page.getByRole( 'button', { name: 'Clear' } ); + + await expect( button ).toBeHidden(); + } ); + + test( 'renders a checkbox list with the available attribute filters', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const listItems = page + .getByLabel( 'Filter Options' ) + .getByRole( 'listitem' ); + + await expect( listItems ).toHaveCount( 5 ); + + for ( let i = 0; i < COLOR_ATTRIBUTE_VALUES.length; i++ ) { + await expect( listItems.nth( i ) ).toHaveText( + COLOR_ATTRIBUTE_VALUES[ i ] + ); + } + } ); + + test( 'filters the list of products by selecting an attribute', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const grayCheckbox = page.getByText( 'Gray' ); + await grayCheckbox.click(); + + // wait for navigation + await page.waitForURL( /.*filter_color=gray.*/ ); + + const products = page.locator( '.wc-block-product' ); + + await expect( products ).toHaveCount( 2 ); + } ); + + test( 'clear button appears after a filter is applied', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const grayCheckbox = page.getByText( 'Gray' ); + await grayCheckbox.click(); + + // wait for navigation + await page.waitForURL( /.*filter_color=gray.*/ ); + + const button = page.getByRole( 'button', { name: 'Clear' } ); + + await expect( button ).toBeVisible(); + } ); + + test( 'clear button hides after deselecting all filters', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const grayCheckbox = page.getByText( 'Gray' ); + await grayCheckbox.click(); + + // wait for navigation + await page.waitForURL( /.*filter_color=gray.*/ ); + + await grayCheckbox.click(); + + const button = page.getByRole( 'button', { name: 'Clear' } ); + + await expect( button ).toBeHidden(); + } ); + + test( 'filters are cleared after clear button is clicked', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const grayCheckbox = page.getByText( 'Gray' ); + await grayCheckbox.click(); + + // wait for navigation + await page.waitForURL( /.*filter_color=gray.*/ ); + + const button = page.getByRole( 'button', { name: 'Clear' } ); + + await button.click(); + + COLOR_ATTRIBUTE_VALUES.map( async ( color ) => { + const element = page.locator( + `input[value="${ color.toLowerCase() }"]` + ); + + await expect( element ).not.toBeChecked(); + } ); + } ); + } ); + + test.describe( 'With show counts enabled', () => { + test.beforeEach( async ( { requestUtils, templateCompiler } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-enable-experimental-features' + ); + await templateCompiler.compile( { + attributes: { + attributeId: 1, + showCounts: true, + }, + } ); + } ); + + test( 'Renders checkboxes with associated product counts', async ( { + page, + } ) => { + await page.goto( '/shop' ); + + const listItems = page + .getByLabel( 'Filter Options' ) + .getByRole( 'listitem' ); + + await expect( listItems ).toHaveCount( 5 ); + + for ( let i = 0; i < COLOR_ATTRIBUTES_WITH_COUNTS.length; i++ ) { + await expect( listItems.nth( i ) ).toHaveText( + COLOR_ATTRIBUTES_WITH_COUNTS[ i ] + ); + } + } ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/price-filter-frontend.block_theme.spec.ts similarity index 99% rename from plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/product-filters/price-filter-frontend.block_theme.spec.ts index 796fd4af488..191c16f53f4 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/price-filter-frontend.block_theme.spec.ts @@ -12,7 +12,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( { }, } ); -test.describe( 'Product Filter: Price Filter Block', () => { +test.describe.skip( 'Product Filter: Price Filter Block', () => { test.describe( 'frontend', () => { test.beforeEach( async ( { requestUtils, templateCompiler } ) => { await requestUtils.activatePlugin( diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters-template-part.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters-template-part.block_theme.spec.ts index d065baf73dc..3d16f0c9e89 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters-template-part.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters-template-part.block_theme.spec.ts @@ -52,13 +52,7 @@ test.describe( 'Product Filters Template Part', () => { const block = editor.canvas.getByLabel( `Block: ${ blockData.name }` ); await expect( block ).toBeVisible(); - const searchTerms = [ - 'Status (Experimental)', - 'Price (Experimental)', - 'Rating (Experimental)', - 'Attribute (Experimental)', - 'Active (Experimental)', - ]; + const searchTerms = [ 'Color (Experimental)', 'Active (Experimental)' ]; for ( const filter of searchTerms ) { await editor.selectBlocks( blockData.selectors.editor.block ); @@ -78,13 +72,7 @@ test.describe( 'Product Filters Template Part', () => { await searchResult.click(); - let _locator = `[aria-label="Block: ${ filter }"]`; - - // We need to treat the attributes filter different because - // the variation of the block label depends on the product attribute. - if ( filter === 'Attribute (Experimental)' ) { - _locator = '.wp-block-woocommerce-product-filter-attribute'; - } + const _locator = `[aria-label="Block: ${ filter }"]`; await expect( editor.canvas.locator( _locator ) ).toHaveCount( 2 ); } diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters.block_theme.spec.ts index 887dcf60e20..e6838d7c37d 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/product-filters.block_theme.spec.ts @@ -68,53 +68,17 @@ test.describe( `${ blockData.name }`, () => { ); await expect( block ).toBeVisible(); - const activeHeading = block.getByText( 'Active', { exact: true } ); - const activeFilterBlock = block - .getByLabel( 'Block: Filter Options' ) - .and( - editor.canvas.locator( - '[data-type="woocommerce/product-filter-active"]' - ) - ); - await expect( activeHeading ).toBeVisible(); + const activeFilterBlock = block.getByLabel( + 'Block: Active (Experimental)' + ); await expect( activeFilterBlock ).toBeVisible(); - const priceHeading = block.getByText( 'Price', { - exact: true, - } ); - const priceFilterBlock = block - .getByLabel( 'Block: Filter Options' ) - .and( - editor.canvas.locator( - '[data-type="woocommerce/product-filter-price"]' - ) - ); - await expect( priceHeading ).toBeVisible(); - await expect( priceFilterBlock ).toBeVisible(); - - const statusHeading = block.getByText( 'Status', { - exact: true, - } ); - const statusFilterBlock = block - .getByLabel( 'Block: Filter Options' ) - .and( - editor.canvas.locator( - '[data-type="woocommerce/product-filter-stock-status"]' - ) - ); - await expect( statusHeading ).toBeVisible(); - await expect( statusFilterBlock ).toBeVisible(); - const colorHeading = block.getByText( 'Color', { exact: true, } ); - const colorFilterBlock = block - .getByLabel( 'Block: Filter Options' ) - .and( - editor.canvas.locator( - '[data-type="woocommerce/product-filter-attribute"]' - ) - ); + const colorFilterBlock = block.getByLabel( + 'Block: Color (Experimental)' + ); const expectedColorFilterOptions = [ 'Blue', 'Green', @@ -122,27 +86,11 @@ test.describe( `${ blockData.name }`, () => { 'Red', 'Yellow', ]; - const colorFilterOptions = ( - await colorFilterBlock.allInnerTexts() - )[ 0 ].split( '\n' ); await expect( colorHeading ).toBeVisible(); await expect( colorFilterBlock ).toBeVisible(); - expect( colorFilterOptions ).toEqual( - expect.arrayContaining( expectedColorFilterOptions ) - ); - - const ratingHeading = block.getByText( 'Rating', { - exact: true, - } ); - const ratingFilterBlock = block - .getByLabel( 'Block: Filter Options' ) - .and( - editor.canvas.locator( - '[data-type="woocommerce/product-filter-rating"]' - ) - ); - await expect( ratingHeading ).toBeVisible(); - await expect( ratingFilterBlock ).toBeVisible(); + for ( const option of expectedColorFilterOptions ) { + await expect( colorFilterBlock ).toContainText( option ); + } } ); test( 'should contain the correct inner block names in the list view', async ( { @@ -181,25 +129,10 @@ test.describe( `${ blockData.name }`, () => { ); await expect( productFilterActiveBlocksListItem ).toBeVisible(); - const productFilterPriceBlockListItem = listView.getByText( - 'Price (Experimental)' - ); - await expect( productFilterPriceBlockListItem ).toBeVisible(); - - const productFilterStatusBlockListItem = listView.getByText( - 'Status (Experimental)' - ); - await expect( productFilterStatusBlockListItem ).toBeVisible(); - const productFilterAttributeBlockListItem = listView.getByText( 'Color (Experimental)' // it must select the attribute with the highest product count ); await expect( productFilterAttributeBlockListItem ).toBeVisible(); - - const productFilterRatingBlockListItem = listView.getByText( - 'Rating (Experimental)' - ); - await expect( productFilterRatingBlockListItem ).toBeVisible(); } ); test( 'should display the correct inspector style controls', async ( { @@ -370,7 +303,7 @@ test.describe( `${ blockData.name }`, () => { ).toHaveCSS( 'align-items', 'center' ); } ); - test( 'Layout > Orientation: changing option should update the preview', async ( { + test.skip( 'Layout > Orientation: changing option should update the preview', async ( { editor, pageObject, } ) => { diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/rating-filter-frontend.block_theme.spec.ts similarity index 97% rename from plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/product-filters/rating-filter-frontend.block_theme.spec.ts index 279fd391567..272aeb4966f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/rating-filter-frontend.block_theme.spec.ts @@ -12,7 +12,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( { }, } ); -test.describe( 'Product Filter: Rating Filter Block', () => { +test.describe.skip( 'Product Filter: Rating Filter Block', () => { test.describe( 'frontend', () => { test.beforeEach( async ( { requestUtils, templateCompiler } ) => { await requestUtils.activatePlugin( diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/stock-status-frontend.block_theme.spec.ts similarity index 98% rename from plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/product-filters/stock-status-frontend.block_theme.spec.ts index c465257eea2..4012ea324af 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-filters/stock-status-frontend.block_theme.spec.ts @@ -12,7 +12,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( { }, } ); -test.describe( 'Product Filter: Stock Status Block', () => { +test.describe.skip( 'Product Filter: Stock Status Block', () => { test.describe( 'With default display style', () => { test.beforeEach( async ( { requestUtils, templateCompiler } ) => { await requestUtils.activatePlugin( diff --git a/plugins/woocommerce/bin/composer/phpcs/composer.lock b/plugins/woocommerce/bin/composer/phpcs/composer.lock index a42b55c51d6..d1c2a664f30 100644 --- a/plugins/woocommerce/bin/composer/phpcs/composer.lock +++ b/plugins/woocommerce/bin/composer/phpcs/composer.lock @@ -874,5 +874,5 @@ "platform-overrides": { "php": "7.2" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/plugins/woocommerce/bin/composer/phpunit/composer.lock b/plugins/woocommerce/bin/composer/phpunit/composer.lock index f0b64b9cbe4..89456bd9d80 100644 --- a/plugins/woocommerce/bin/composer/phpunit/composer.lock +++ b/plugins/woocommerce/bin/composer/phpunit/composer.lock @@ -315,35 +315,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -352,7 +352,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -381,7 +381,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -389,7 +389,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1759,5 +1759,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/plugins/woocommerce/bin/composer/wp/composer.lock b/plugins/woocommerce/bin/composer/wp/composer.lock index aadb4231f84..64558c071e9 100644 --- a/plugins/woocommerce/bin/composer/wp/composer.lock +++ b/plugins/woocommerce/bin/composer/wp/composer.lock @@ -635,5 +635,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription b/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription new file mode 100644 index 00000000000..8e432f67f7a --- /dev/null +++ b/plugins/woocommerce/changelog/51060-fix-21455-notice-plugin-install-products-without-subscription @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add a new notice to the plugins list for products used without subscriptions reminding them to purchase. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51163-fix-21486-iam-discover-tracks b/plugins/woocommerce/changelog/51163-fix-21486-iam-discover-tracks new file mode 100644 index 00000000000..f2459934217 --- /dev/null +++ b/plugins/woocommerce/changelog/51163-fix-21486-iam-discover-tracks @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: Fix missing Tracks event from the Extensions > Discover page + diff --git a/plugins/woocommerce/changelog/51164-fix-extra-search-request-in-my-subscriptions b/plugins/woocommerce/changelog/51164-fix-extra-search-request-in-my-subscriptions new file mode 100644 index 00000000000..85c7d2e021f --- /dev/null +++ b/plugins/woocommerce/changelog/51164-fix-extra-search-request-in-my-subscriptions @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Prevent search request in the Extensions > My Subscriptions page \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51187-update-21566-in-app-marketaplace-header-sticky b/plugins/woocommerce/changelog/51187-update-21566-in-app-marketaplace-header-sticky new file mode 100644 index 00000000000..844123a8234 --- /dev/null +++ b/plugins/woocommerce/changelog/51187-update-21566-in-app-marketaplace-header-sticky @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Make In-App Marketplace header sticky to improve discoverability. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51277-bugfix-46283 b/plugins/woocommerce/changelog/51277-bugfix-46283 new file mode 100644 index 00000000000..cc81d2fcd5b --- /dev/null +++ b/plugins/woocommerce/changelog/51277-bugfix-46283 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix a styling bug where the guest order confirmation email input was too wide. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51284-e2e-external-pressable-include-api-tests b/plugins/woocommerce/changelog/51284-e2e-external-pressable-include-api-tests new file mode 100644 index 00000000000..0351b91c1d8 --- /dev/null +++ b/plugins/woocommerce/changelog/51284-e2e-external-pressable-include-api-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Include API tests into test suites for Pressable and WPCOM. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51288-update-wccom-21570-in-app-product-cards-padding b/plugins/woocommerce/changelog/51288-update-wccom-21570-in-app-product-cards-padding new file mode 100644 index 00000000000..1f91bb228e1 --- /dev/null +++ b/plugins/woocommerce/changelog/51288-update-wccom-21570-in-app-product-cards-padding @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Update product card content padding to 24px and add 0 margin bottom to product cards \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51315-update-wccom-21574-in-app-marketplace-footer b/plugins/woocommerce/changelog/51315-update-wccom-21574-in-app-marketplace-footer new file mode 100644 index 00000000000..7f77d1fe125 --- /dev/null +++ b/plugins/woocommerce/changelog/51315-update-wccom-21574-in-app-marketplace-footer @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Update footer design, add one more element to footer content and remove `woo-marketplace` copy at footer bottom. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/51332-fix-focus-style-select-checkout-block b/plugins/woocommerce/changelog/51332-fix-focus-style-select-checkout-block new file mode 100644 index 00000000000..da9cb8df7b4 --- /dev/null +++ b/plugins/woocommerce/changelog/51332-fix-focus-style-select-checkout-block @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add focus styles to the Checkout block select field. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/add-50832 b/plugins/woocommerce/changelog/add-50832 new file mode 100644 index 00000000000..e50768a52a4 --- /dev/null +++ b/plugins/woocommerce/changelog/add-50832 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Track data for font and color pairings including 'Create your own' option in CYS diff --git a/plugins/woocommerce/changelog/add-delayed-account-creation-block-50632 b/plugins/woocommerce/changelog/add-delayed-account-creation-block-50632 new file mode 100644 index 00000000000..ffd1d6f1c9b --- /dev/null +++ b/plugins/woocommerce/changelog/add-delayed-account-creation-block-50632 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Added experimental delayed order creation block. diff --git a/plugins/woocommerce/changelog/dev-42582_use_one_rating_component b/plugins/woocommerce/changelog/dev-42582_use_one_rating_component new file mode 100644 index 00000000000..e50c3ca32e2 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-42582_use_one_rating_component @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +[Enhancement] Abstract rating block #50810 diff --git a/plugins/woocommerce/changelog/dev-slow-tests b/plugins/woocommerce/changelog/dev-slow-tests new file mode 100644 index 00000000000..d60c7d44a53 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-slow-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix slow test for Reports diff --git a/plugins/woocommerce/changelog/fix-50875-disable-client-side-nav-for-filters-in-pc-block b/plugins/woocommerce/changelog/fix-50875-disable-client-side-nav-for-filters-in-pc-block new file mode 100644 index 00000000000..e219a09d7aa --- /dev/null +++ b/plugins/woocommerce/changelog/fix-50875-disable-client-side-nav-for-filters-in-pc-block @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Product Collection: Disable client-side nav if filter blocks are detected inside diff --git a/plugins/woocommerce/changelog/fix-correct-language-in-frontend b/plugins/woocommerce/changelog/fix-correct-language-in-frontend new file mode 100644 index 00000000000..00edcbc0400 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-correct-language-in-frontend @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Use the correct language in Cart/Checkout when the user language is different from the store. diff --git a/plugins/woocommerce/changelog/fix-remote-logger-undefined-wp-current-user b/plugins/woocommerce/changelog/fix-remote-logger-undefined-wp-current-user new file mode 100644 index 00000000000..be10eb2ec34 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-remote-logger-undefined-wp-current-user @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Adds a function_exists() check so that we don't add a secondary fatal in remote logging if wp_get_current_user does not exist yet diff --git a/plugins/woocommerce/changelog/fix-remove-help-panel-user-meta b/plugins/woocommerce/changelog/fix-remove-help-panel-user-meta new file mode 100644 index 00000000000..9b1882ac793 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-remove-help-panel-user-meta @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Removed the leftover user meta from the help panel spotlight diff --git a/plugins/woocommerce/changelog/fix-unsnooze-notes b/plugins/woocommerce/changelog/fix-unsnooze-notes new file mode 100644 index 00000000000..a2f5b082011 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-unsnooze-notes @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix wc_admin_unsnooze_admin_notes events are being needlessly created diff --git a/plugins/woocommerce/changelog/pr-51020 b/plugins/woocommerce/changelog/pr-51020 new file mode 100644 index 00000000000..ac4e7978690 --- /dev/null +++ b/plugins/woocommerce/changelog/pr-51020 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Fix version number in a template diff --git a/plugins/woocommerce/changelog/test-skip-failing-tests b/plugins/woocommerce/changelog/test-skip-failing-tests new file mode 100644 index 00000000000..723af34edc0 --- /dev/null +++ b/plugins/woocommerce/changelog/test-skip-failing-tests @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: It's a test skip for experimental feature, not worth explaining + + diff --git a/plugins/woocommerce/changelog/try-new-improved-filter-blocks-structure b/plugins/woocommerce/changelog/try-new-improved-filter-blocks-structure new file mode 100644 index 00000000000..61232016454 --- /dev/null +++ b/plugins/woocommerce/changelog/try-new-improved-filter-blocks-structure @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Filter blocks: new and improve filter blocks structure. + + diff --git a/plugins/woocommerce/changelog/tweak-gtin-structured-data b/plugins/woocommerce/changelog/tweak-gtin-structured-data new file mode 100644 index 00000000000..69fc8c38c2e --- /dev/null +++ b/plugins/woocommerce/changelog/tweak-gtin-structured-data @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Validate and prepare GTIN in structured data diff --git a/plugins/woocommerce/changelog/tweak-reenable-remote-logging-by-default b/plugins/woocommerce/changelog/tweak-reenable-remote-logging-by-default new file mode 100644 index 00000000000..efcc6da22eb --- /dev/null +++ b/plugins/woocommerce/changelog/tweak-reenable-remote-logging-by-default @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Reenable remote logging feature by default diff --git a/plugins/woocommerce/changelog/update-action-scheduler-3.8.2 b/plugins/woocommerce/changelog/update-action-scheduler-3.8.2 new file mode 100644 index 00000000000..8265892ca69 --- /dev/null +++ b/plugins/woocommerce/changelog/update-action-scheduler-3.8.2 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update the Action Scheduler package to version 3.8.2 diff --git a/plugins/woocommerce/changelog/update-deprecate-banner-layout b/plugins/woocommerce/changelog/update-deprecate-banner-layout new file mode 100644 index 00000000000..4af22693683 --- /dev/null +++ b/plugins/woocommerce/changelog/update-deprecate-banner-layout @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Deprecate unsupported Inbox note banner layout diff --git a/plugins/woocommerce/changelog/update-dynamically-set-plugin-name b/plugins/woocommerce/changelog/update-dynamically-set-plugin-name new file mode 100644 index 00000000000..079eb126b45 --- /dev/null +++ b/plugins/woocommerce/changelog/update-dynamically-set-plugin-name @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Dynamically set plugin_name when redirecting to Jetpack Auth from core profiler diff --git a/plugins/woocommerce/changelog/update-remove-deprecated-rin-classes b/plugins/woocommerce/changelog/update-remove-deprecated-rin-classes new file mode 100644 index 00000000000..2fc99a0873a --- /dev/null +++ b/plugins/woocommerce/changelog/update-remove-deprecated-rin-classes @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Remove deprecated RemoteInboxNotification classes diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index cdc8c4f1dc1..a42b16a87a7 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -34,7 +34,7 @@ "composer/installers": "^1.9", "maxmind-db/reader": "^1.11", "pelago/emogrifier": "^6.0", - "woocommerce/action-scheduler": "3.8.1", + "woocommerce/action-scheduler": "3.8.2", "woocommerce/blueprint": "*" }, "require-dev": { diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index 12b5fc18ba9..230b3d42e91 100644 --- a/plugins/woocommerce/composer.lock +++ b/plugins/woocommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "15d426b01c8ba919cabdd7078845595e", + "content-hash": "e068848765b4c2df4244eac75f7ea46d", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -1251,20 +1251,20 @@ }, { "name": "woocommerce/action-scheduler", - "version": "3.8.1", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", - "reference": "e331b534d7de10402d7545a0de50177b874c0779" + "reference": "2bc91d88fdbc2c07ab899cbb56b983e11e62cf69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/e331b534d7de10402d7545a0de50177b874c0779", - "reference": "e331b534d7de10402d7545a0de50177b874c0779", + "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/2bc91d88fdbc2c07ab899cbb56b983e11e62cf69", + "reference": "2bc91d88fdbc2c07ab899cbb56b983e11e62cf69", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^7.5", @@ -1288,9 +1288,9 @@ "homepage": "https://actionscheduler.org/", "support": { "issues": "https://github.com/woocommerce/action-scheduler/issues", - "source": "https://github.com/woocommerce/action-scheduler/tree/3.8.1" + "source": "https://github.com/woocommerce/action-scheduler/tree/3.8.2" }, - "time": "2024-06-20T19:53:06+00:00" + "time": "2024-09-12T23:12:58+00:00" }, { "name": "woocommerce/blueprint", @@ -4984,7 +4984,7 @@ }, { "name": "woocommerce/monorepo-plugin", - "version": "dev-tweak/bump-jetpack-connection", + "version": "dev-update/action-scheduler-3.8.2", "dist": { "type": "path", "url": "../../packages/php/monorepo-plugin", @@ -5143,5 +5143,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-importers.php b/plugins/woocommerce/includes/admin/class-wc-admin-importers.php index 6dfec075b89..3c16a0b07d9 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-importers.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-importers.php @@ -215,114 +215,12 @@ class WC_Admin_Importers { * Ajax callback for importing one batch of products from a CSV. */ public function do_ajax_product_import() { - global $wpdb; - - check_ajax_referer( 'wc-product-import', 'security' ); - - if ( ! $this->import_allowed() || ! isset( $_POST['file'] ) ) { // PHPCS: input var ok. + if ( ! $this->import_allowed() ) { wp_send_json_error( array( 'message' => __( 'Insufficient privileges to import products.', 'woocommerce' ) ) ); } include_once WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php'; - include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php'; - - $file = wc_clean( wp_unslash( $_POST['file'] ) ); // PHPCS: input var ok. - $params = array( - 'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok. - 'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok. - 'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok. - 'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok. - 'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '', - - /** - * Batch size for the product import process. - * - * @param int $size Batch size. - * - * @since 3.1.0 - */ - 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ), - 'parse' => true, - ); - - // Log failures. - if ( 0 !== $params['start_pos'] ) { - $error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) ); - } else { - $error_log = array(); - } - - $importer = WC_Product_CSV_Importer_Controller::get_importer( $file, $params ); - $results = $importer->import(); - $percent_complete = $importer->get_percent_complete(); - $error_log = array_merge( $error_log, $results['failed'], $results['skipped'] ); - - update_user_option( get_current_user_id(), 'product_import_error_log', $error_log ); - - if ( 100 === $percent_complete ) { - // @codingStandardsIgnoreStart. - $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_original_id' ) ); - $wpdb->delete( $wpdb->posts, array( - 'post_type' => 'product', - 'post_status' => 'importing', - ) ); - $wpdb->delete( $wpdb->posts, array( - 'post_type' => 'product_variation', - 'post_status' => 'importing', - ) ); - // @codingStandardsIgnoreEnd. - - // Clean up orphaned data. - $wpdb->query( - " - DELETE {$wpdb->posts}.* FROM {$wpdb->posts} - LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->posts}.post_parent - WHERE wp.ID IS NULL AND {$wpdb->posts}.post_type = 'product_variation' - " - ); - $wpdb->query( - " - DELETE {$wpdb->postmeta}.* FROM {$wpdb->postmeta} - LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->postmeta}.post_id - WHERE wp.ID IS NULL - " - ); - // @codingStandardsIgnoreStart. - $wpdb->query( " - DELETE tr.* FROM {$wpdb->term_relationships} tr - LEFT JOIN {$wpdb->posts} wp ON wp.ID = tr.object_id - LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id - WHERE wp.ID IS NULL - AND tt.taxonomy IN ( '" . implode( "','", array_map( 'esc_sql', get_object_taxonomies( 'product' ) ) ) . "' ) - " ); - // @codingStandardsIgnoreEnd. - - // Send success. - wp_send_json_success( - array( - 'position' => 'done', - 'percentage' => 100, - 'url' => add_query_arg( array( '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ), - 'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0, - 'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0, - 'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0, - 'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0, - 'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0, - ) - ); - } else { - wp_send_json_success( - array( - 'position' => $importer->get_file_position(), - 'percentage' => $percent_complete, - 'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0, - 'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0, - 'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0, - 'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0, - 'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0, - ) - ); - } + WC_Product_CSV_Importer_Controller::dispatch_ajax(); } /** diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php index 96cc160502c..15ed3ffe3bb 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php @@ -85,6 +85,7 @@ class WC_Helper_Admin { if ( WC_Helper::is_site_connected() ) { $settings['wccomHelper']['subscription_expired_notice'] = PluginsHelper::get_expired_subscription_notice( false ); $settings['wccomHelper']['subscription_expiring_notice'] = PluginsHelper::get_expiring_subscription_notice( false ); + $settings['wccomHelper']['subscription_missing_notice'] = PluginsHelper::get_missing_subscription_notice(); } return $settings; diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php index 02eeb89e0aa..086071717d4 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php @@ -40,11 +40,13 @@ class WC_Helper_Updater { } if ( WC_Helper::is_site_connected() ) { add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_expired_and_expiring_subscriptions' ), 11 ); + add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_plugins_without_subscription' ), 11 ); } } /** * Add the hook for modifying default WPCore update notices on the plugins management page. + * This is for plugins with expired or expiring subscriptions. */ public static function setup_message_for_expired_and_expiring_subscriptions() { foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) { @@ -52,6 +54,16 @@ class WC_Helper_Updater { } } + /** + * Add the hook for modifying default WPCore update notices on the plugins management page. + * This is for plugins without a subscription. + */ + public static function setup_message_for_plugins_without_subscription() { + foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) { + add_action( 'in_plugin_update_message-' . $plugin['_filename'], array( __CLASS__, 'display_notice_for_plugins_without_subscription' ), 10, 2 ); + } + } + /** * Runs in a cron thread, or in a visitor thread if triggered * by _maybe_update_plugins(), or in an auto-update thread. @@ -294,10 +306,11 @@ class WC_Helper_Updater { $renew_link = add_query_arg( array( + 'add-to-cart' => $product_id, 'utm_source' => 'pu', 'utm_campaign' => 'pu_plugin_screen_renew', ), - PluginsHelper::WOO_SUBSCRIPTION_PAGE_URL + PluginsHelper::WOO_CART_PAGE_URL ); /* translators: 1: Product regular price */ @@ -340,6 +353,52 @@ class WC_Helper_Updater { } } + /** + * Runs on in_plugin_update_message-{file-name}, show a message if plugin is without a subscription. + * Only Woo local plugins are passed to this function. + * + * @see setup_message_for_plugins_without_subscription + * @param object $plugin_data An array of plugin metadata. + * @param object $response An object of metadata about the available plugin update. + * + * @return void. + */ + public static function display_notice_for_plugins_without_subscription( $plugin_data, $response ) { + // Extract product ID from the response. + $product_id = preg_replace( '/[^0-9]/', '', $response->id ); + + if ( WC_Helper::has_product_subscription( $product_id ) ) { + return; + } + + // Prepare the expiry notice based on subscription status. + $purchase_link = add_query_arg( + array( + 'add-to-cart' => $product_id, + 'utm_source' => 'pu', + 'utm_campaign' => 'pu_plugin_screen_purchase', + ), + PluginsHelper::WOO_CART_PAGE_URL, + ); + + $notice = sprintf( + /* translators: 1: URL to My Subscriptions page */ + __( ' You don\'t have a subscription, subscribe to update.', 'woocommerce' ), + esc_url( $purchase_link ), + ); + + // Display the expiry notice. + echo wp_kses( + $notice, + array( + 'a' => array( + 'href' => array(), + 'class' => array(), + ), + ) + ); + } + /** * Get update data for all plugins. * diff --git a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php index e5fac1cecb9..cadf53a7bc5 100644 --- a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -103,6 +103,56 @@ class WC_Product_CSV_Importer_Controller { return wc_is_file_valid_csv( $file, $check_path ); } + /** + * Runs before controller actions to check that the file used during the import is valid. + * + * @since 9.3.0 + * + * @param string $path Path to test. + * + * @throws \Exception When file validation fails. + */ + protected static function check_file_path( string $path ): void { + $is_valid_file = false; + + if ( ! empty( $path ) ) { + $path = realpath( $path ); + $is_valid_file = false !== $path; + } + + // File must be readable. + $is_valid_file = $is_valid_file && is_readable( $path ); + + // Check that file is within an allowed location. + if ( $is_valid_file ) { + $in_valid_location = false; + $valid_locations = array(); + $valid_locations[] = ABSPATH; + + $upload_dir = wp_get_upload_dir(); + if ( false === $upload_dir['error'] ) { + $valid_locations[] = $upload_dir['basedir']; + } + + foreach ( $valid_locations as $valid_location ) { + if ( 0 === stripos( $path, trailingslashit( realpath( $valid_location ) ) ) ) { + $in_valid_location = true; + break; + } + } + + $is_valid_file = $in_valid_location; + } + + if ( ! $is_valid_file ) { + throw new \Exception( esc_html__( 'File path provided for import is invalid.', 'woocommerce' ) ); + } + + if ( ! self::is_file_valid_csv( $path ) ) { + throw new \Exception( esc_html__( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); + } + } + /** * Get all the valid filetypes for a CSV file. * @@ -263,17 +313,151 @@ class WC_Product_CSV_Importer_Controller { * Dispatch current step and show correct view. */ public function dispatch() { - // phpcs:ignore WordPress.Security.NonceVerification.Missing - if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) { - call_user_func( $this->steps[ $this->step ]['handler'], $this ); + $output = ''; + + try { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) { + if ( is_callable( $this->steps[ $this->step ]['handler'] ) ) { + call_user_func( $this->steps[ $this->step ]['handler'], $this ); + } + } + + ob_start(); + + if ( is_callable( $this->steps[ $this->step ]['view'] ) ) { + call_user_func( $this->steps[ $this->step ]['view'], $this ); + } + + $output = ob_get_clean(); + } catch ( \Exception $e ) { + $this->add_error( $e->getMessage() ); } + $this->output_header(); $this->output_steps(); $this->output_errors(); - call_user_func( $this->steps[ $this->step ]['view'], $this ); + echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output is HTML we've generated ourselves. $this->output_footer(); } + /** + * Processes AJAX requests related to a product CSV import. + * + * @since 9.3.0 + */ + public static function dispatch_ajax() { + global $wpdb; + + check_ajax_referer( 'wc-product-import', 'security' ); + + try { + $file = wc_clean( wp_unslash( $_POST['file'] ?? '' ) ); // PHPCS: input var ok. + self::check_file_path( $file ); + + $params = array( + 'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok. + 'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok. + 'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok. + 'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok. + 'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '', + + /** + * Batch size for the product import process. + * + * @param int $size Batch size. + * + * @since 3.1.0 + */ + 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 1 ), + 'parse' => true, + ); + + // Log failures. + if ( 0 !== $params['start_pos'] ) { + $error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) ); + } else { + $error_log = array(); + } + + include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php'; + + $importer = self::get_importer( $file, $params ); + $results = $importer->import(); + $percent_complete = $importer->get_percent_complete(); + $error_log = array_merge( $error_log, $results['failed'], $results['skipped'] ); + + update_user_option( get_current_user_id(), 'product_import_error_log', $error_log ); + + if ( 100 === $percent_complete ) { + // @codingStandardsIgnoreStart. + $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_original_id' ) ); + $wpdb->delete( $wpdb->posts, array( + 'post_type' => 'product', + 'post_status' => 'importing', + ) ); + $wpdb->delete( $wpdb->posts, array( + 'post_type' => 'product_variation', + 'post_status' => 'importing', + ) ); + // @codingStandardsIgnoreEnd. + + // Clean up orphaned data. + $wpdb->query( + " + DELETE {$wpdb->posts}.* FROM {$wpdb->posts} + LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->posts}.post_parent + WHERE wp.ID IS NULL AND {$wpdb->posts}.post_type = 'product_variation' + " + ); + $wpdb->query( + " + DELETE {$wpdb->postmeta}.* FROM {$wpdb->postmeta} + LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->postmeta}.post_id + WHERE wp.ID IS NULL + " + ); + // @codingStandardsIgnoreStart. + $wpdb->query( " + DELETE tr.* FROM {$wpdb->term_relationships} tr + LEFT JOIN {$wpdb->posts} wp ON wp.ID = tr.object_id + LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id + WHERE wp.ID IS NULL + AND tt.taxonomy IN ( '" . implode( "','", array_map( 'esc_sql', get_object_taxonomies( 'product' ) ) ) . "' ) + " ); + // @codingStandardsIgnoreEnd. + + // Send success. + wp_send_json_success( + array( + 'position' => 'done', + 'percentage' => 100, + 'url' => add_query_arg( array( '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ), + 'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0, + 'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0, + 'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0, + 'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0, + 'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0, + ) + ); + } else { + wp_send_json_success( + array( + 'position' => $importer->get_file_position(), + 'percentage' => $percent_complete, + 'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0, + 'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0, + 'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0, + 'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0, + 'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0, + ) + ); + } + } catch ( \Exception $e ) { + wp_send_json_error( array( 'message' => $e->getMessage() ) ); + } + } + /** * Output information about the uploading process. */ @@ -314,60 +498,20 @@ class WC_Product_CSV_Importer_Controller { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce already verified in WC_Product_CSV_Importer_Controller::upload_form_handler() $file_url = isset( $_POST['file_url'] ) ? wc_clean( wp_unslash( $_POST['file_url'] ) ) : ''; - if ( empty( $file_url ) ) { - if ( ! isset( $_FILES['import'] ) ) { - return new WP_Error( 'woocommerce_product_csv_importer_upload_file_empty', __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) ); + try { + if ( ! empty( $file_url ) ) { + $path = ABSPATH . $file_url; + self::check_file_path( $path ); + } else { + $csv_import_util = wc_get_container()->get( Automattic\WooCommerce\Internal\Admin\ImportExport\CSVUploadHelper::class ); + $upload = $csv_import_util->handle_csv_upload( 'product', 'import', self::get_valid_csv_filetypes() ); + $path = $upload['file']; } - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated - if ( ! self::is_file_valid_csv( wc_clean( wp_unslash( $_FILES['import']['name'] ) ), false ) ) { - return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); - } - - $overrides = array( - 'test_form' => false, - 'mimes' => self::get_valid_csv_filetypes(), - ); - $import = $_FILES['import']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash - $upload = wp_handle_upload( $import, $overrides ); - - if ( isset( $upload['error'] ) ) { - return new WP_Error( 'woocommerce_product_csv_importer_upload_error', $upload['error'] ); - } - - // Construct the object array. - $object = array( - 'post_title' => basename( $upload['file'] ), - 'post_content' => $upload['url'], - 'post_mime_type' => $upload['type'], - 'guid' => $upload['url'], - 'context' => 'import', - 'post_status' => 'private', - ); - - // Save the data. - $id = wp_insert_attachment( $object, $upload['file'] ); - - /* - * Schedule a cleanup for one day from now in case of failed - * import or missing wp_import_cleanup() call. - */ - wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) ); - - return $upload['file']; - } elseif ( - ( 0 === stripos( realpath( ABSPATH . $file_url ), ABSPATH ) ) && - file_exists( ABSPATH . $file_url ) - ) { - if ( ! self::is_file_valid_csv( ABSPATH . $file_url ) ) { - return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); - } - - return ABSPATH . $file_url; + return $path; + } catch ( \Exception $e ) { + return new \WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', $e->getMessage() ); } - // phpcs:enable - - return new WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', __( 'Please upload or provide the link to a valid CSV file.', 'woocommerce' ) ); } /** @@ -375,6 +519,8 @@ class WC_Product_CSV_Importer_Controller { */ protected function mapping_form() { check_admin_referer( 'woocommerce-csv-importer' ); + self::check_file_path( $this->file ); + $args = array( 'lines' => 1, 'delimiter' => $this->delimiter, @@ -412,18 +558,7 @@ class WC_Product_CSV_Importer_Controller { // Displaying this page triggers Ajax action to run the import with a valid nonce, // therefore this page needs to be nonce protected as well. check_admin_referer( 'woocommerce-csv-importer' ); - - if ( ! self::is_file_valid_csv( $this->file ) ) { - $this->add_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); - $this->output_errors(); - return; - } - - if ( ! is_file( $this->file ) ) { - $this->add_error( __( 'The file does not exist, please try again.', 'woocommerce' ) ); - $this->output_errors(); - return; - } + self::check_file_path( $this->file ); if ( ! empty( $_POST['map_from'] ) && ! empty( $_POST['map_to'] ) ) { $mapping_from = wc_clean( wp_unslash( $_POST['map_from'] ) ); diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 002844d49c4..ec17ff0c71c 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -268,6 +268,7 @@ class WC_Install { ), '9.4.0' => array( 'wc_update_940_add_phone_to_order_address_fts_index', + 'wc_update_940_remove_help_panel_highlight_shown', ), ); diff --git a/plugins/woocommerce/includes/class-wc-structured-data.php b/plugins/woocommerce/includes/class-wc-structured-data.php index 98acb513224..12f1861ad9e 100644 --- a/plugins/woocommerce/includes/class-wc-structured-data.php +++ b/plugins/woocommerce/includes/class-wc-structured-data.php @@ -214,9 +214,9 @@ class WC_Structured_Data { $markup['sku'] = $product->get_id(); } - // Add GTIN only if it's a valid number. - $gtin = $product->get_global_unique_id(); - if ( $gtin && is_numeric( $gtin ) ) { + // Prepare GTIN and load it if it's valid. + $gtin = $this->prepare_gtin( $product->get_global_unique_id() ); + if ( $this->is_valid_gtin( $gtin ) ) { $markup['gtin'] = $gtin; } @@ -577,4 +577,30 @@ class WC_Structured_Data { $this->set_data( apply_filters( 'woocommerce_structured_data_order', $markup, $sent_to_admin, $order ), true ); } + + /** + * Check if a GTIN is valid. + * A valid GTIN is a string containing 8,12,13 or 14 digits. + * + * @see https://schema.org/gtin + * @param string $gtin The GTIN to check. + * @return bool True if valid. False otherwise. + */ + public function is_valid_gtin( $gtin ) { + return is_string( $gtin ) && preg_match( '/^(\d{8}|\d{12,14})$/', $gtin ); + } + + /** + * Prepare a GTIN input removing everything except numbers. + * + * @param string $gtin The GTIN to prepare. + * @return string Empty string if no GTIN is provided or the string with the replacements. + */ + public function prepare_gtin( $gtin ) { + if ( ! $gtin || ! is_string( $gtin ) ) { + return ''; + } + + return preg_replace( '/[^0-9]/', '', $gtin ); + } } diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index e4f880a2a76..79c42ac89fe 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -2872,3 +2872,51 @@ function wc_update_940_add_phone_to_order_address_fts_index(): void { } } } + +/** + * Remove user meta associated with the key 'woocommerce_admin_help_panel_highlight_shown'. + * + * This key is no longer needed since the help panel spotlight tour has been removed. + * + * @return void + */ +function wc_update_940_remove_help_panel_highlight_shown() { + global $wpdb; + + $meta_key = 'woocommerce_admin_help_panel_highlight_shown'; + + $deletions = $wpdb->query( + $wpdb->prepare( + "DELETE FROM $wpdb->usermeta WHERE meta_key = %s", + $meta_key + ) + ); + + // Get the WooCommerce logger to track the results of the deletion. + $logger = wc_get_logger(); + + if ( null === $logger ) { + return; + } + + if ( false === $deletions ) { + $logger->notice( + 'During the update to 9.4.0, WooCommerce attempted to remove user meta with the key "woocommerce_admin_help_panel_highlight_shown", but was unable to do so.', + array( + 'source' => 'wc-updater', + ) + ); + } else { + $logger->info( + sprintf( + 1 === $deletions + ? 'During the update to 9.4.0, WooCommerce removed %d user meta row associated with the meta key "woocommerce_admin_help_panel_highlight_shown".' + : 'During the update to 9.4.0, WooCommerce removed %d user meta rows associated with the meta key "woocommerce_admin_help_panel_highlight_shown".', + number_format_i18n( $deletions ) + ), + array( + 'source' => 'wc-updater', + ) + ); + } +} diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index a01cd250ffc..8ec41d6cfda 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -556,7 +556,7 @@ "on-demand" ], "report": { - "resultsBlobName": "default-pressable-core-e2e", + "resultsBlobName": "default-pressable-core-e2e-and-api", "resultsPath": "tests/e2e-pw/test-results", "allure": true } @@ -572,7 +572,7 @@ "on-demand" ], "report": { - "resultsBlobName": "default-wpcom-core-e2e", + "resultsBlobName": "default-wpcom-core-e2e-and-api", "resultsPath": "tests/e2e-pw/test-results", "allure": true } @@ -681,7 +681,6 @@ "node_modules/@woocommerce/e2e-core-tests/CHANGELOG.md", "node_modules/@woocommerce/api/dist/", "node_modules/@woocommerce/admin-e2e-tests/build", - "node_modules/@woocommerce/classic-assets/build", "node_modules/@woocommerce/block-library/build", "node_modules/@woocommerce/block-library/blocks.ini", "node_modules/@woocommerce/admin-library/build", diff --git a/plugins/woocommerce/src/Admin/Notes/Note.php b/plugins/woocommerce/src/Admin/Notes/Note.php index de8cde59dc7..9058906d87b 100644 --- a/plugins/woocommerce/src/Admin/Notes/Note.php +++ b/plugins/woocommerce/src/Admin/Notes/Note.php @@ -534,7 +534,7 @@ class Note extends \WC_Data { $this->error( 'admin_note_invalid_data', __( 'The admin note date prop cannot be empty.', 'woocommerce' ) ); } - if ( is_string( $date ) ) { + if ( is_string( $date ) && ! is_numeric( $date ) ) { $date = wc_string_to_timestamp( $date ); } $this->set_date_prop( 'date_created', $date ); @@ -546,7 +546,7 @@ class Note extends \WC_Data { * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date. */ public function set_date_reminder( $date ) { - if ( is_string( $date ) ) { + if ( is_string( $date ) && ! is_numeric( $date ) ) { $date = wc_string_to_timestamp( $date ); } $this->set_date_prop( 'date_reminder', $date ); @@ -579,6 +579,11 @@ class Note extends \WC_Data { $layout = 'plain'; } $valid_layouts = array( 'banner', 'plain', 'thumbnail' ); + + if ( 'banner' === $layout ) { + wc_deprecated_argument( 'Note::set_layout', '9.4.0', 'The "banner" layout is deprecated. Please use "thumbnail" instead to display a image.' ); + } + if ( in_array( $layout, $valid_layouts, true ) ) { $this->set_prop( 'layout', $layout ); } else { diff --git a/plugins/woocommerce/src/Admin/Notes/Notes.php b/plugins/woocommerce/src/Admin/Notes/Notes.php index 138a49cafd6..8a7c6ccd2c2 100644 --- a/plugins/woocommerce/src/Admin/Notes/Notes.php +++ b/plugins/woocommerce/src/Admin/Notes/Notes.php @@ -24,6 +24,7 @@ class Notes { add_action( 'admin_init', array( __CLASS__, 'schedule_unsnooze_notes' ) ); add_action( 'admin_init', array( __CLASS__, 'possibly_delete_survey_notes' ) ); add_action( 'update_option_woocommerce_show_marketplace_suggestions', array( __CLASS__, 'possibly_delete_marketing_notes' ), 10, 2 ); + add_action( self::UNSNOOZE_HOOK, array( __CLASS__, 'unsnooze_notes' ) ); } /** @@ -406,7 +407,6 @@ class Notes { wp_set_current_user( $user_id ); self::record_tracks_event_without_cookies( $event_name, $params ); wp_set_current_user( $current_user_id ); - } /** diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php index efcf9152f9e..1900bfdf493 100644 --- a/plugins/woocommerce/src/Admin/PluginsHelper.php +++ b/plugins/woocommerce/src/Admin/PluginsHelper.php @@ -33,17 +33,24 @@ if ( ! function_exists( 'get_plugins' ) ) { class PluginsHelper { /** - * Indicates whether the expiration notice for subscriptions can be displayed. + * Subscription notices in Woo screens are shown in clear priority order, first + * expired, and if those don't exist, expiring, and finally if none of those exist, + * then missing. This keeps track of whether we can show the next set of notices. * * @var bool */ - public static $can_show_expiring_subs_notice = true; + public static $subscription_usage_notices_already_shown = false; /** * The URL for the WooCommerce subscription page. */ const WOO_SUBSCRIPTION_PAGE_URL = 'https://woocommerce.com/my-account/my-subscriptions/'; + /** + * The URL for the WooCommerce.com cart page. + */ + const WOO_CART_PAGE_URL = 'https://woocommerce.com/cart/'; + /** * The URL for the WooCommerce.com add payment method page. */ @@ -59,6 +66,11 @@ class PluginsHelper { */ const DISMISS_EXPIRING_SUBS_NOTICE = 'woo_subscription_expiring_notice_dismiss'; + /** + * Meta key for dismissing missing subscription notices + */ + const DISMISS_MISSING_SUBS_NOTICE = 'woo_subscription_missing_notice_dismiss'; + /** * Initialize hooks. */ @@ -67,10 +79,7 @@ class PluginsHelper { add_action( 'woocommerce_plugins_install_and_activate_async_callback', array( __CLASS__, 'install_and_activate_plugins_async_callback' ), 10, 2 ); add_action( 'woocommerce_plugins_activate_callback', array( __CLASS__, 'activate_plugins' ), 10, 2 ); add_action( 'admin_notices', array( __CLASS__, 'maybe_show_connect_notice_in_plugin_list' ) ); - add_action( 'admin_notices', array( __CLASS__, 'maybe_show_expired_subscriptions_notice' ), 10 ); - add_action( 'admin_notices', array( __CLASS__, 'maybe_show_expiring_subscriptions_notice' ), 11 ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_connect_notice' ) ); - add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_subscription_notice' ) ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_notices_in_plugins' ) ); } @@ -659,6 +668,7 @@ class PluginsHelper { wp_enqueue_script( 'woo-plugin-update-connect-notice' ); wp_enqueue_script( 'woo-enable-autorenew' ); wp_enqueue_script( 'woo-renew-subscription' ); + wp_enqueue_script( 'woo-purchase-subscription' ); } /** @@ -733,11 +743,15 @@ class PluginsHelper { * @return array notice data to return. Contains type, parsed_message and product_id. */ public static function get_subscriptions_notice_data( array $all_subs, array $subs_to_show, int $total, array $messages, string $type ) { + $utm_campaign = 'expired' === $type ? + 'pu_settings_screen_renew' : + ( 'missing' === $type ? 'pu_settings_screen_purchase' : 'pu_settings_screen_enable_autorenew' ); + if ( 1 < $total ) { $hyperlink_url = add_query_arg( array( 'utm_source' => 'pu', - 'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew', + 'utm_campaign' => $utm_campaign, ), self::WOO_SUBSCRIPTION_PAGE_URL @@ -750,10 +764,18 @@ class PluginsHelper { esc_attr( $total ), ); + // All product ids. + $product_ids = array_map( + function ( $sub ) { + return $sub['product_id']; + }, + $subs_to_show + ); + return array( 'type' => 'different_subscriptions', 'parsed_message' => $parsed_message, - 'product_id' => '', + 'product_ids' => $product_ids, ); } @@ -769,8 +791,9 @@ class PluginsHelper { ) ); - $message_key = $has_multiple_subs_for_product ? 'multiple_manage' : 'single_manage'; - $renew_string = __( 'Renew', 'woocommerce' ); + $message_key = $has_multiple_subs_for_product ? 'multiple_manage' : 'single_manage'; + $renew_string = __( 'Renew', 'woocommerce' ); + $subscribe_string = __( 'Subscribe', 'woocommerce' ); if ( isset( $subscription['product_regular_price'] ) ) { /* translators: 1: Product price */ $renew_string = sprintf( __( 'Renew for %1$s', 'woocommerce' ), $subscription['product_regular_price'] ); @@ -781,7 +804,7 @@ class PluginsHelper { 'product_id' => $product_id, 'type' => $type, 'utm_source' => 'pu', - 'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew', + 'utm_campaign' => $utm_campaign, ), self::WOO_SUBSCRIPTION_PAGE_URL @@ -798,7 +821,8 @@ class PluginsHelper { esc_attr( $subscription['product_name'] ), esc_attr( $expiry_date ), esc_url( $hyperlink_url ), - esc_attr( $renew_string ), + // Show subscribe for missing subscriptions, renew otherwise. + 'missing' === $type ? esc_attr( $subscribe_string ) : esc_attr( $renew_string ), ); return array( @@ -826,7 +850,7 @@ class PluginsHelper { return array(); } - if ( ! self::$can_show_expiring_subs_notice ) { + if ( self::$subscription_usage_notices_already_shown ) { return array(); } @@ -851,6 +875,9 @@ class PluginsHelper { $total_expiring_subscriptions = count( $expiring_subscriptions ); + // Don't show missing notice if there are expiring subscriptions. + self::$subscription_usage_notices_already_shown = true; + // When payment method is missing on WooCommerce.com. $helper_notices = WC_Helper::get_notices(); if ( ! empty( $helper_notices['missing_payment_method_notice'] ) ) { @@ -927,8 +954,8 @@ class PluginsHelper { return array(); } - $total_expired_subscriptions = count( $expired_subscriptions ); - self::$can_show_expiring_subs_notice = false; + $total_expired_subscriptions = count( $expired_subscriptions ); + self::$subscription_usage_notices_already_shown = true; $notice_data = self::get_subscriptions_notice_data( $subscriptions, @@ -947,17 +974,17 @@ class PluginsHelper { $button_link = add_query_arg( array( + 'add-to-cart' => $notice_data['product_ids'], 'utm_source' => 'pu', 'utm_campaign' => $allowed_link ? 'pu_settings_screen_renew' : 'pu_in_apps_screen_renew', ), - self::WOO_SUBSCRIPTION_PAGE_URL + self::WOO_CART_PAGE_URL ); if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) { $button_link = add_query_arg( array( - 'product_id' => $notice_data['product_id'], - 'type' => 'expiring', + 'add-to-cart' => $notice_data['product_id'], ), $button_link ); @@ -970,6 +997,86 @@ class PluginsHelper { ); } + /** + * Get formatted notice information for missing subscription. + * + * @return array notice information. + */ + public static function get_missing_subscription_notice() { + if ( ! WC_Helper::is_site_connected() ) { + return array(); + } + + if ( self::$subscription_usage_notices_already_shown ) { + return array(); + } + + if ( ! self::should_show_notice( self::DISMISS_MISSING_SUBS_NOTICE ) ) { + return array(); + } + + $subscriptions = WC_Helper::get_subscription_list_data(); + $missing_subscriptions = array_filter( + $subscriptions, + function ( $sub ) { + return ( ! empty( $sub['local']['installed'] ) && empty( $sub['product_key'] ) ); + }, + ); + + // Remove WUM from missing subscriptions list. + $missing_subscriptions = array_filter( + $missing_subscriptions, + function ( $sub ) { + return 'woo-update-manager' !== $sub['zip_slug']; + } + ); + + if ( ! $missing_subscriptions ) { + return array(); + } + + $total_missing_subscriptions = count( $missing_subscriptions ); + + $notice_data = self::get_subscriptions_notice_data( + $subscriptions, + $missing_subscriptions, + $total_missing_subscriptions, + array( + /* translators: 1) product name */ + 'single_manage' => __( 'You don\'t have a subscription for %1$s. Subscribe to receive updates and streamlined support.', 'woocommerce' ), + /* translators: 1) total expired subscriptions */ + 'different_subscriptions' => __( 'You don\'t have subscriptions for %1$s Woo extensions. Subscribe to receive updates and streamlined support.', 'woocommerce' ), + ), + 'missing', + ); + + $button_link = add_query_arg( + array( + 'add-to-cart' => $notice_data['product_ids'], + 'utm_source' => 'pu', + 'utm_campaign' => 'pu_in_apps_screen_purchase', + ), + self::WOO_CART_PAGE_URL + ); + + if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) { + $button_link = add_query_arg( + array( + 'add-to-cart' => $notice_data['product_id'], + ), + $button_link + ); + } + + $button_text = __( 'Subscribe', 'woocommerce' ); + + return array( + 'description' => $notice_data['parsed_message'], + 'button_text' => $button_text, + 'button_link' => $button_link, + ); + } + /** * Determine whether a specific notice should be shown to the current user. * diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/BaseLocationCountryRuleProcessor.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/BaseLocationCountryRuleProcessor.php deleted file mode 100644 index 3854f372b83..00000000000 --- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/BaseLocationCountryRuleProcessor.php +++ /dev/null @@ -1,33 +0,0 @@ -source ) ) { $note->set_source( $spec->source ); } + if ( isset( $spec->layout ) ) { + $note->set_layout( $spec->layout ); + } // Recreate actions. $note->set_actions( self::get_actions( $spec ) ); diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/StoredStateRuleProcessor.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/StoredStateRuleProcessor.php deleted file mode 100644 index bc4f2c5e9fb..00000000000 --- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/StoredStateRuleProcessor.php +++ /dev/null @@ -1,33 +0,0 @@ - "media" ), - array("industry" => "software" ) -); -``` - -Use `array_column` to extract `array("media", "software")` then choose the first element with `dot_notation`. - -```php -"transformers": [ - { - "use": "array_column", - "arguments": { - "key": "industry" - } - }, - { - "use": "dot_notation", - "arguments": { - "key": "0" - } - } -], -``` - -**Output**: "media" - - - - - -## array_flatten - -Flattens a nested array. - -#### Arguments: N/A - -#### Definition: - -```php -"transformers": [ - { - "use": "array_flatten" - } -], -``` - -#### Example: - -Given the following data - -```php -array( - array( - 'member1', - ), - array( - 'member2', - ), - array( - 'member3', - ), -); -``` - -Use `array_flatten` to extract `array("member1", "member2", "member3")` then use `array_search` to make sure it has `member2` - - -```php -"transformers": [ - { - "use": "array_flatten", - }, - { - "use": "array_search", - "arguments": { - "key": "member2" - } - } -], -``` - -**Output**: true - -## array_keys - -PHP's built-in `array_keys` to return keys from an array. For more information about how `array_keys` works, please see PHP’s [official documentation](https://www.php.net/manual/en/function.array-column.php). - -#### Arguments: N/A - -####Definition: - -```php -"transformers": [ - { - "use": "array_keys" - } -], -``` - -#### Example: - -Given the following data - -```php -array( - "name" => "tester", - "address" => "test", - "supports_version_2" => true -) -``` - -Use `array_keys` to extract `array("name", "address", "supports_version_2")` and then use `array_search` to make sure it has `supports_version_2` - -```php -"transformers": [ - { - "use": "array_keys", - }, - { - "use": "array_search", - "arguments": { - "key": "member2" - } - } -], -``` - -**Output**: true - -## array_search - -PHP's built-in `array_search` to search a value in an array. For more information about how `array_search` works, please see PHP’s [official documentation](https://www.php.net/manual/en/function.array-search.php). - -#### Arguments: - -|name|description| -|----|---------| -| value | a value to search in the given array | - -#### Definition: - -```php -"transformers": [ - { - "use": "array_search", - "arguments": { - "value": "test" - } - } -], -``` - -#### Examples - -See examples from [array_flatten](#array_flatten) and [array_keys](#array_keys) - -## array_values - -PHP's built-in array_values to return values from an array. For more information about how `array_values` works, please see PHP’s [official documentation](https://www.php.net/manual/en/function.array-values). - - -#### Arguments: N/A - -#### Definition: - -```php -"transformers": [ - { - "use": "array_values" - } -], -``` - -#### Example: - -Given the following data - -```php -array ( - "size" => "x-large" -) -``` - -Use `array_values` to extract `array("x-large")` - -```php -"transformers": [ - { - "use": "array_values", - } -], -``` - -**Output:** "x-large" - - -## dot_notation - -Uses dot notation to select a value in an array. Dot notation lets you access an array as if it is an object. - -#### Arguments: N/A - -#### Definition: - - -```php -"transformers": [ - { - "use": "dot_notation", - "arguments": { - "path": "name" - } - } -], -``` - -#### Example: - - - -Given the following data - -```php -array( - 'name' => 'john', - 'members' => ['member1', 'member2'] -); -``` - -Select `name` field. - -```php -"transformers": [ - { - "use": "dot_notation", - "arguments": { - "path": "name" - } - } -], -``` - -**Output:** "john" - -Select `member2`. You can access array items with an index. - -```php -"transformers": [ - { - "use": "dot_notation", - "arguments": { - "path": "members.1" - } - } -], -``` - -**Output:**: "member2" - -## count - -PHP's built-in count to return the number of values from a countable, such as an array. - -#### Arguments: N/A - -#### Definition: - -```php -"transformers": [ - { - "use": "count" - } -], -``` - -#### Example: - -Given the following list of usernames - -```php -array( - "username1", - "username2", - "username3" -) -``` - -Let's count # of users with `count` - -```php -"transformers": [ - { - "use": "count", - } -], -``` - -**Output:** 3 - -## prepare_url - -This prepares the site URL by removing the protocol and the last slash. - -#### Arguments: N/A - -####Definition: - -```php -"transformers": [ - { - "use": "prepare_url" - } -], -``` - -#### Example: - -Given the following data - -```php -$siteurl = "https://mysite.com/" -``` - -Removes the protocol and the last slash. - -```php -"transformers": [ - { - "use": "prepare_url", - } -], -``` - -**Output:** "mysite.com" diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/WCAdminActiveForProvider.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/WCAdminActiveForProvider.php deleted file mode 100644 index 21d53b4c0c1..00000000000 --- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/WCAdminActiveForProvider.php +++ /dev/null @@ -1,31 +0,0 @@ -register_block_type() + * @param string $key Data to get, or default to everything. + * @return array|string + */ + protected function get_block_type_script( $key = null ) { + $script = [ + 'handle' => 'wc-order-confirmation-create-account-block-frontend', + 'path' => $this->asset_api->get_block_asset_build_path( 'order-confirmation-create-account-frontend' ), + 'dependencies' => [], + ]; + return $key ? $script[ $key ] : $script; + } + + /** + * Process posted account form. + * + * @param \WC_Order $order Order object. + * @return \WP_Error|int + */ + protected function process_form_post( $order ) { + if ( ! isset( $_POST['create-account'], $_POST['email'], $_POST['password'], $_POST['_wpnonce'] ) ) { + return 0; + } + + if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'wc_create_account' ) ) { + return new \WP_Error( 'invalid_nonce', __( 'Unable to create account. Please try again.', 'woocommerce' ) ); + } + + $user_email = sanitize_email( wp_unslash( $_POST['email'] ) ); + $password = wp_unslash( $_POST['password'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + // Does order already have user? + if ( $order->get_customer_id() ) { + return new \WP_Error( 'order_already_has_user', __( 'This order is already linked to a user account.', 'woocommerce' ) ); + } + + // Check given details match the current viewed order. + if ( $order->get_billing_email() !== $user_email ) { + return new \WP_Error( 'email_mismatch', __( 'The email address provided does not match the email address on this order.', 'woocommerce' ) ); + } + + if ( empty( $password ) || strlen( $password ) < 8 ) { + return new \WP_Error( 'password_too_short', __( 'Password must be at least 8 characters.', 'woocommerce' ) ); + } + + $customer_id = wc_create_new_customer( + $user_email, + '', + $password, + [ + 'first_name' => $order->get_billing_first_name(), + 'last_name' => $order->get_billing_last_name(), + 'source' => 'delayed-account-creation', + ] + ); + + if ( is_wp_error( $customer_id ) ) { + return $customer_id; + } + + // Associate customer with the order. + $order->set_customer_id( $customer_id ); + $order->save(); + + // Associate addresses from the order with the customer. + $order_controller = new OrderController(); + $order_controller->sync_customer_data_with_order( $order ); + + // Set the customer auth cookie. + wc_set_customer_auth_cookie( $customer_id ); + + return $customer_id; + } + + /** + * This renders the content of the block within the wrapper. + * + * @param \WC_Order $order Order object. + * @param string|false $permission If the current user can view the order details or not. + * @param array $attributes Block attributes. + * @param string $content Original block content. + * @return string + */ + protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) { + if ( ! $permission ) { + return ''; + } + + // Check registration is possible for this order/customer, and if not, return early. + if ( is_user_logged_in() || email_exists( $order->get_billing_email() ) ) { + return ''; + } + + $result = $this->process_form_post( $order ); + + if ( is_wp_error( $result ) ) { + $notice = wc_print_notice( $result->get_error_message(), 'error', [], true ); + } elseif ( $result ) { + return $this->render_confirmation(); + } + + $processor = new \WP_HTML_Tag_Processor( + $content . + '' + ); + + if ( ! $processor->next_tag( array( 'class_name' => 'wp-block-woocommerce-order-confirmation-create-account' ) ) ) { + return $content; + } + + $processor->set_attribute( 'class', '' ); + $processor->set_attribute( 'style', '' ); + $processor->add_class( 'woocommerce-order-confirmation-create-account-content' ); + + if ( ! $processor->next_tag( array( 'class_name' => 'woocommerce-order-confirmation-create-account-form' ) ) ) { + return $content; + } + + $processor->set_attribute( 'data-customer-email', $order->get_billing_email() ); + $processor->set_attribute( 'data-nonce-token', wp_create_nonce( 'wc_create_account' ) ); + + if ( ! empty( $attributes['hasDarkControls'] ) ) { + $processor->add_class( 'has-dark-controls' ); + } + + return $processor->get_updated_html(); + } + + /** + * Render the block when an account has been registered. + * + * @return string + */ + protected function render_confirmation() { + $content = ''; + + return $content; + } +} diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php index 6cc50f68d3a..047be224e28 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php @@ -2,8 +2,6 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation; -use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; - /** * Status class. */ @@ -265,7 +263,7 @@ class Status extends AbstractOrderConfirmationBlock {

    ', esc_attr( 'verify-email-submit' ), esc_html__( 'Confirm email and view order', 'woocommerce' ), - wp_nonce_field( 'wc_verify_email', 'check_submission', true, false ), + wp_nonce_field( 'wc_verify_email', '_wpnonce', true, false ), esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ) ) . ''; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php index 8f4c3ecfda1..d7d7be05d95 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php @@ -499,11 +499,18 @@ class ProductCollection extends AbstractBlock { */ private function is_block_compatible( $block_name ) { // Check for explicitly unsupported blocks. - if ( - 'core/post-content' === $block_name || - 'woocommerce/mini-cart' === $block_name || - 'woocommerce/featured-product' === $block_name - ) { + $unsupported_blocks = array( + 'core/post-content', + 'woocommerce/mini-cart', + 'woocommerce/featured-product', + 'woocommerce/active-filters', + 'woocommerce/price-filter', + 'woocommerce/stock-filter', + 'woocommerce/attribute-filter', + 'woocommerce/rating-filter', + ); + + if ( in_array( $block_name, $unsupported_blocks, true ) ) { return false; } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilter.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilter.php deleted file mode 100644 index 6cfc9a1465a..00000000000 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilter.php +++ /dev/null @@ -1,157 +0,0 @@ -register_block_type() - * @param string $key Data to get, or default to everything. - * @return array|string|null - */ - protected function get_block_type_script( $key = null ) { - return null; - } - - /** - * Extra data passed through from server to client for block. - * - * @param array $attributes Any attributes that currently are available from the block. - * Note, this will be empty in the editor context when the block is - * not in the post content on editor load. - */ - protected function enqueue_data( array $attributes = [] ) { - global $pagenow; - parent::enqueue_data( $attributes ); - - $this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() ); - $this->asset_data_registry->add( 'isProductArchive', is_shop() || is_product_taxonomy() ); - $this->asset_data_registry->add( 'isSiteEditor', 'site-editor.php' === $pagenow ); - $this->asset_data_registry->add( 'isWidgetEditor', 'widgets.php' === $pagenow || 'customize.php' === $pagenow ); - } - - /** - * Check array for checked item. - * - * @param array $items Items to check. - */ - private function hasSelectedFilter( $items ) { - foreach ( $items as $key => $value ) { - if ( 'checked' === $key && true === $value ) { - return true; - } - - if ( is_array( $value ) && $this->hasSelectedFilter( $value ) ) { - return true; - } - } - - return false; - } - - /** - * Render the block. - * - * @param array $attributes Block attributes. - * @param string $content Block content. - * @param WP_Block $block Block instance. - * @return string Rendered block type output. - */ - protected function render( $attributes, $content, $block ) { - if ( is_admin() ) { - return $content; - } - - $tags = new WP_HTML_Tag_Processor( $content ); - $has_selected_filter = false; - - while ( $tags->next_tag( 'div' ) ) { - $items = $tags->get_attribute( 'data-wc-context' ) ? json_decode( $tags->get_attribute( 'data-wc-context' ), true ) : null; - - // For checked box filters. - if ( $items && array_key_exists( 'items', $items ) ) { - $has_selected_filter = $this->hasSelectedFilter( $items['items'] ); - break; - } - - // For price range filter. - if ( $items && array_key_exists( 'minPrice', $items ) ) { - if ( $items['minPrice'] > $items['minRange'] || $items['maxPrice'] < $items['maxRange'] ) { - $has_selected_filter = true; - break; - } - } - - // For dropdown filters. - if ( $items && array_key_exists( 'selectedItems', $items ) ) { - if ( count( $items['selectedItems'] ) > 0 ) { - $has_selected_filter = true; - break; - } - } - } - - $attributes_data = array( - 'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), - 'data-wc-context' => wp_json_encode( array( 'hasSelectedFilter' => $has_selected_filter ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), - 'class' => 'wc-block-product-filters', - ); - - if ( ! isset( $block->context['queryId'] ) ) { - $attributes_data['data-wc-navigation-id'] = $this->generate_navigation_id( $block ); - } - - $tags = new WP_HTML_Tag_Processor( $content ); - - while ( $tags->next_tag( 'div' ) ) { - if ( 'yes' === $tags->get_attribute( 'data-has-filter' ) ) { - return sprintf( - '', - get_block_wrapper_attributes( $attributes_data ), - $content - ); - } - } - - return sprintf( - '', - get_block_wrapper_attributes( $attributes_data ), - ); - } - - /** - * Generate a unique navigation ID for the block. - * - * @param mixed $block - Block instance. - * @return string - Unique navigation ID. - */ - private function generate_navigation_id( $block ) { - return sprintf( - 'wc-product-filter-%s', - md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) ) - ); - } -} diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterActive.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterActive.php index 679227f3a09..11469663107 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterActive.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterActive.php @@ -57,7 +57,6 @@ final class ProductFilterActive extends AbstractBlock { array( 'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), 'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), - 'data-has-filter' => empty( $active_filters ) ? 'no' : 'yes', ) ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterAttribute.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterAttribute.php index d997799c772..0a5327f9ce0 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterAttribute.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterAttribute.php @@ -1,10 +1,10 @@ get_default_product_attribute(); - $attributes['attributeId'] = $default_product_attribute->attribute_id; + protected function render( $block_attributes, $content, $block ) { + if ( empty( $block_attributes['attributeId'] ) ) { + $default_product_attribute = $this->get_default_product_attribute(); + $block_attributes['attributeId'] = $default_product_attribute->attribute_id; } // don't render if its admin, or ajax in progress. - if ( is_admin() || wp_doing_ajax() || empty( $attributes['attributeId'] ) ) { + if ( is_admin() || wp_doing_ajax() || empty( $block_attributes['attributeId'] ) ) { return ''; } - $product_attribute = wc_get_attribute( $attributes['attributeId'] ); - $attribute_counts = $this->get_attribute_counts( $block, $product_attribute->slug, $attributes['queryType'] ); + $product_attribute = wc_get_attribute( $block_attributes['attributeId'] ); + $attribute_counts = $this->get_attribute_counts( $block, $product_attribute->slug, $block_attributes['queryType'] ); if ( empty( $attribute_counts ) ) { return sprintf( @@ -181,7 +182,6 @@ final class ProductFilterAttribute extends AbstractBlock { get_block_wrapper_attributes( array( 'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), - 'data-has-filter' => 'no', ) ), ); @@ -202,118 +202,56 @@ final class ProductFilterAttribute extends AbstractBlock { ); $attribute_options = array_map( - function ( $term ) use ( $attribute_counts, $selected_terms ) { + function ( $term ) use ( $block_attributes, $attribute_counts, $selected_terms ) { $term = (array) $term; $term['count'] = $attribute_counts[ $term['term_id'] ]; $term['selected'] = in_array( $term['slug'], $selected_terms, true ); - return $term; + return array( + 'label' => $block_attributes['showCounts'] ? sprintf( '%1$s (%2$d)', $term['name'], $term['count'] ) : $term['name'], + 'value' => $term['slug'], + 'selected' => $term['selected'], + 'rawData' => $term, + ); }, $attribute_terms ); $filtered_options = array_filter( $attribute_options, - function ( $option ) { - return $option['count'] > 0; + function ( $option ) use ( $block_attributes ) { + $hide_empty = $block_attributes['hideEmpty'] ?? true; + if ( $hide_empty ) { + return $option['rawData']['count'] > 0; + } + return true; } ); - $filter_content = 'dropdown' === $attributes['displayStyle'] ? - $this->render_attribute_dropdown( $filtered_options, $attributes ) : - $this->render_attribute_checkbox_list( $filtered_options, $attributes ); + $filter_context = array( + 'on_change' => "{$this->get_full_block_name()}::actions.updateProducts", + 'items' => $filtered_options, + ); + + foreach ( $block->parsed_block['innerBlocks'] as $inner_block ) { + $content .= ( new \WP_Block( $inner_block, array( 'filterData' => $filter_context ) ) )->render(); + } $context = array( - 'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ), - 'queryType' => $attributes['queryType'], - 'selectType' => 'multiple', + 'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ), + 'queryType' => $block_attributes['queryType'], + 'selectType' => 'multiple', + 'hasSelectedFilters' => count( $selected_terms ) > 0, ); return sprintf( - '
    %2$s%3$s
    ', + '
    %2$s
    ', get_block_wrapper_attributes( array( 'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), 'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), - 'data-has-filter' => 'yes', ) ), - $content, - $filter_content - ); - } - - /** - * Render the dropdown. - * - * @param array $options Data to render the dropdown. - * @param bool $attributes Block attributes. - */ - private function render_attribute_dropdown( $options, $attributes ) { - if ( empty( $options ) ) { - return ''; - } - - $list_items = array(); - $selected_items = array(); - - $product_attribute = wc_get_attribute( $attributes['attributeId'] ); - - foreach ( $options as $option ) { - $item = array( - 'label' => $attributes['showCounts'] ? sprintf( '%1$s (%2$d)', $option['name'], $option['count'] ) : $option['name'], - 'value' => $option['slug'], - ); - - $list_items[] = $item; - - if ( $option['selected'] ) { - $selected_items[] = $item; - } - } - - return Dropdown::render( - array( - 'items' => $list_items, - 'action' => "{$this->get_full_block_name()}::actions.navigate", - 'selected_items' => $selected_items, - 'select_type' => 'multiple', - // translators: %s is a product attribute name. - 'placeholder' => sprintf( __( 'Select %s', 'woocommerce' ), $product_attribute->name ), - ) - ); - } - - /** - * Render the attribute filter checkbox list. - * - * @param mixed $options Attribute filter options to render in the checkbox list. - * @param mixed $attributes Block attributes. - * @return string - */ - private function render_attribute_checkbox_list( $options, $attributes ) { - if ( empty( $options ) ) { - return ''; - } - - $show_counts = $attributes['showCounts'] ?? false; - - $list_options = array_map( - function ( $option ) use ( $show_counts ) { - return array( - 'id' => $option['slug'] . '-' . $option['term_id'], - 'checked' => $option['selected'], - 'label' => $show_counts ? sprintf( '%1$s (%2$d)', $option['name'], $option['count'] ) : $option['name'], - 'value' => $option['slug'], - ); - }, - $options - ); - - return CheckboxList::render( - array( - 'items' => $list_options, - 'on_change' => "{$this->get_full_block_name()}::actions.updateProducts", - ) + $content ); } @@ -380,7 +318,16 @@ final class ProductFilterAttribute extends AbstractBlock { $cached = get_transient( 'wc_block_product_filter_attribute_default_attribute' ); - if ( $cached ) { + if ( + $cached && + isset( $cached->attribute_id ) && + isset( $cached->attribute_name ) && + isset( $cached->attribute_label ) && + isset( $cached->attribute_type ) && + isset( $cached->attribute_orderby ) && + isset( $cached->attribute_public ) && + '0' !== $cached->attribute_id + ) { return $cached; } @@ -428,10 +375,9 @@ final class ProductFilterAttribute extends AbstractBlock { if ( $attribute_id ) { $default_attribute = $attributes[ $attribute_id ]; + set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute, DAY_IN_SECONDS ); } - set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute ); - return $default_attribute; } @@ -447,32 +393,26 @@ final class ProductFilterAttribute extends AbstractBlock { 'inserter' => false, 'content' => strtr( ' - - -
    - -

    {{attribute_label}}

    - + +
    + +
    + +

    {{attribute_label}}

    + - - -
    - -
    - Clear -
    - -
    - - + + +
    + +
    + +
    + + +
    - - - - + ', array( '{{attribute_id}}' => intval( $default_attribute->attribute_id ), @@ -482,4 +422,18 @@ final class ProductFilterAttribute extends AbstractBlock { ) ); } + + /** + * Skip default rendering routine for inner blocks. + * + * @param array $settings Array of determined settings for registering a block type. + * @param array $metadata Metadata provided for registering a block type. + * @return array + */ + public function add_block_type_metadata_settings( $settings, $metadata ) { + if ( ! empty( $metadata['name'] ) && "woocommerce/{$this->block_name}" === $metadata['name'] ) { + $settings['skip_inner_blocks'] = true; + } + return $settings; + } } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterCheckboxList.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterCheckboxList.php new file mode 100644 index 00000000000..a4a299d9ff8 --- /dev/null +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterCheckboxList.php @@ -0,0 +1,155 @@ +context['filterData']; + $items = $context['items'] ?? array(); + $checkbox_list_context = array( 'items' => $items ); + $on_change = $context['on_change'] ?? ''; + $namespace = wp_json_encode( array( 'namespace' => 'woocommerce/product-filter-checkbox-list' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ); + + $classes = array( + 'has-option-element-border-color' => $this->get_color_attribute_value( 'optionElementBorder', $attributes ), + 'has-option-element-selected-color' => $this->get_color_attribute_value( 'optionElementSelected', $attributes ), + 'has-option-element-color' => $this->get_color_attribute_value( 'optionElement', $attributes ), + ); + $classes = array_filter( $classes ); + + $styles = array( + '--wc-product-filter-checkbox-list-option-element-border' => $this->get_color_attribute_value( 'optionElementBorder', $attributes ), + '--wc-product-filter-checkbox-list-option-element-selected' => $this->get_color_attribute_value( 'optionElementSelected', $attributes ), + '--wc-product-filter-checkbox-list-option-element' => $this->get_color_attribute_value( 'optionElement', $attributes ), + ); + $style = array_reduce( + array_keys( $styles ), + function ( $acc, $key ) use ( $styles ) { + if ( $styles[ $key ] ) { + return $acc . "{$key}: var( --wp--preset--color--{$styles[$key]} );"; + } + } + ); + + $checked_items = array_filter( + $items, + function ( $item ) { + return $item['selected']; + } + ); + $show_initially = $context['show_initially'] ?? 15; + $remaining_initial_unchecked = count( $checked_items ) > $show_initially ? count( $checked_items ) : $show_initially - count( $checked_items ); + $count = 0; + + $wrapper_attributes = array( + 'data-wc-interactive' => esc_attr( $namespace ), + 'data-wc-context' => wp_json_encode( $checkbox_list_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ), + 'class' => implode( ' ', array_keys( $classes ) ), + 'style' => esc_attr( $style ), + ); + + ob_start(); + ?> +
    > +
      + + +
    • = $remaining_initial_unchecked ) : + ?> + class="wc-block-product-filter-checkbox-list__item hidden" + data-wc-class--hidden="!context.showAll" + + + + + class="wc-block-product-filter-checkbox-list__item" + > + +
    • + +
    + $show_initially ) : ?> + + + + +
    + '!context.hasSelectedFilter', + 'data-wc-bind--hidden' => '!context.hasSelectedFilters', ) ); $p = new \WP_HTML_Tag_Processor( $content ); if ( $p->next_tag( array( 'class_name' => 'wp-block-button__link' ) ) ) { - $p->set_attribute( 'data-wc-on--click', 'actions.clear' ); + $p->set_attribute( 'data-wc-on--click', 'actions.clearFilters' ); $style = $p->get_attribute( 'style' ); $p->set_attribute( 'style', 'outline:none;' . $style ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php index 9c149ef6b3c..0137f32abf4 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php @@ -23,6 +23,23 @@ class ProductFilters extends AbstractBlock { return array( 'postId' ); } + /** + * Extra data passed through from server to client for block. + * + * @param array $attributes Any attributes that currently are available from the block. + * Note, this will be empty in the editor context when the block is + * not in the post content on editor load. + */ + protected function enqueue_data( array $attributes = array() ) { + global $pagenow; + parent::enqueue_data( $attributes ); + + $this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() ); + $this->asset_data_registry->add( 'isProductArchive', is_shop() || is_product_taxonomy() ); + $this->asset_data_registry->add( 'isSiteEditor', 'site-editor.php' === $pagenow ); + $this->asset_data_registry->add( 'isWidgetEditor', 'widgets.php' === $pagenow || 'customize.php' === $pagenow ); + } + /** * Return the dialog content. * @@ -116,12 +133,10 @@ class ProductFilters extends AbstractBlock { * @return string Rendered block type output. */ protected function render( $attributes, $content, $block ) { - $html = $content; - $p = new \WP_HTML_Tag_Processor( $html ); - - if ( $p->next_tag() ) { - $p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ); - $p->set_attribute( + $tags = new \WP_HTML_Tag_Processor( $content ); + if ( $tags->next_tag() ) { + $tags->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/' . $this->block_name ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ); + $tags->set_attribute( 'data-wc-context', wp_json_encode( array( @@ -131,13 +146,29 @@ class ProductFilters extends AbstractBlock { JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ); - $html = $p->get_updated_html(); + $tags->set_attribute( 'data-wc-navigation-id', $this->generate_navigation_id( $block ) ); + + if ( + 'always' === $attributes['overlay'] || + ( 'mobile' === $attributes['overlay'] && wp_is_mobile() ) + ) { + return $this->inject_dialog( $tags->get_updated_html(), $this->render_dialog() ); + } + + return $tags->get_updated_html(); } + } - $dialog_html = $this->render_dialog(); - - $html = $this->inject_dialog( $html, $dialog_html ); - - return $html; + /** + * Generate a unique navigation ID for the block. + * + * @param mixed $block - Block instance. + * @return string - Unique navigation ID. + */ + private function generate_navigation_id( $block ) { + return sprintf( + 'wc-product-filters-%s', + md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) ) + ); } } diff --git a/plugins/woocommerce/src/Blocks/BlockTypesController.php b/plugins/woocommerce/src/Blocks/BlockTypesController.php index f6d84ecc051..03e87ecbbad 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypesController.php +++ b/plugins/woocommerce/src/Blocks/BlockTypesController.php @@ -404,7 +404,6 @@ final class BlockTypesController { // Update plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md // when modifying this list. if ( Features::is_enabled( 'experimental-blocks' ) ) { - $block_types[] = 'ProductFilter'; $block_types[] = 'ProductFilters'; $block_types[] = 'ProductFiltersOverlay'; $block_types[] = 'ProductFiltersOverlayNavigation'; @@ -414,6 +413,9 @@ final class BlockTypesController { $block_types[] = 'ProductFilterRating'; $block_types[] = 'ProductFilterActive'; $block_types[] = 'ProductFilterClearButton'; + $block_types[] = 'ProductFilterCheckboxList'; + $block_types[] = 'ProductFilterChips'; + $block_types[] = 'OrderConfirmation\CreateAccount'; } /** diff --git a/plugins/woocommerce/src/Container.php b/plugins/woocommerce/src/Container.php index 56d3b5b1e98..17ceac62783 100644 --- a/plugins/woocommerce/src/Container.php +++ b/plugins/woocommerce/src/Container.php @@ -32,6 +32,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\BatchP use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\LayoutTemplatesServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ComingSoonServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\StatsServiceProvider; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ImportExportServiceProvider; /** * PSR11 compliant dependency injection container for WooCommerce. @@ -83,6 +84,7 @@ final class Container { EnginesServiceProvider::class, ComingSoonServiceProvider::class, StatsServiceProvider::class, + ImportExportServiceProvider::class, ); /** diff --git a/plugins/woocommerce/src/Internal/Admin/Homescreen.php b/plugins/woocommerce/src/Internal/Admin/Homescreen.php index 1a9cd90f776..dfb0cde522e 100644 --- a/plugins/woocommerce/src/Internal/Admin/Homescreen.php +++ b/plugins/woocommerce/src/Internal/Admin/Homescreen.php @@ -166,7 +166,6 @@ class Homescreen { 'homepage_layout', 'homepage_stats', 'task_list_tracked_started_tasks', - 'help_panel_highlight_shown', ) ); } diff --git a/plugins/woocommerce/src/Internal/Admin/ImportExport/CSVUploadHelper.php b/plugins/woocommerce/src/Internal/Admin/ImportExport/CSVUploadHelper.php new file mode 100644 index 00000000000..c1759bc5e93 --- /dev/null +++ b/plugins/woocommerce/src/Internal/Admin/ImportExport/CSVUploadHelper.php @@ -0,0 +1,175 @@ +get_import_subdir_name(); + if ( $create ) { + FilesystemUtil::mkdir_p_not_indexable( $upload_dir ); + } + return $upload_dir; + } + + /** + * Handles a CSV file upload. + * + * @param string $import_type Type of upload or context. + * @param string $files_index $_FILES index that contains the file to upload. + * @param array|null $allowed_mime_types List of allowed MIME types. + * @return array { + * Details for the uploaded file. + * + * @type int $id Attachment ID. + * @type string $file Full path to uploaded file. + * } + * + * @throws \Exception In case of error. + */ + public function handle_csv_upload( string $import_type, string $files_index = 'import', ?array $allowed_mime_types = null ): array { + $import_type = sanitize_key( $import_type ); + if ( ! $import_type ) { + throw new \Exception( 'Import type is invalid.' ); + } + + if ( ! $allowed_mime_types ) { + $allowed_mime_types = array( + 'csv' => 'text/csv', + 'txt' => 'text/plain', + ); + } + + $file = $_FILES[ $files_index ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing + if ( ! isset( $file['tmp_name'] ) || ! is_uploaded_file( $file['tmp_name'] ) ) { + throw new \Exception( esc_html__( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) ); + } + + if ( ! function_exists( 'wp_import_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/import.php'; + } + + // Make sure upload dir exists. + $this->get_import_dir(); + + // Add prefix. + $file['name'] = $import_type . '-' . $file['name']; + + $overrides_callback = function ( $overrides_ ) use ( $allowed_mime_types ) { + $overrides_['test_form'] = false; + $overrides_['test_type'] = true; + $overrides_['mimes'] = $allowed_mime_types; + return $overrides_; + }; + + self::add_filter( 'upload_dir', array( $this, 'override_upload_dir' ) ); + self::add_filter( 'wp_unique_filename', array( $this, 'override_unique_filename' ), 0, 2 ); + self::add_filter( 'wp_handle_upload_overrides', $overrides_callback, 999 ); + self::add_filter( 'wp_handle_upload_prefilter', array( $this, 'remove_txt_from_uploaded_file' ), 0 ); + + $orig_files_import = $_FILES['import'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing + $_FILES['import'] = $file; // wp_import_handle_upload() expects the file to be in 'import'. + + $upload = wp_import_handle_upload(); + + remove_filter( 'upload_dir', array( $this, 'override_upload_dir' ) ); + remove_filter( 'wp_unique_filename', array( $this, 'override_unique_filename' ), 0 ); + remove_filter( 'wp_handle_upload_overrides', $overrides_callback, 999 ); + remove_filter( 'wp_handle_upload_prefilter', array( $this, 'remove_txt_from_uploaded_file' ), 0 ); + + if ( $orig_files_import ) { + $_FILES['import'] = $orig_files_import; + } else { + unset( $_FILES['import'] ); + } + + if ( ! empty( $upload['error'] ) ) { + throw new \Exception( esc_html( $upload['error'] ) ); + } + + if ( ! wc_is_file_valid_csv( $upload['file'], false ) ) { + wp_delete_attachment( $file['id'], true ); + throw new \Exception( esc_html__( 'Invalid file type for a CSV import.', 'woocommerce' ) ); + } + + return $upload; + } + + /** + * Hooked onto 'upload_dir' to override the default upload directory for a CSV upload. + * + * @param array $uploads WP upload dir details. + * @return array + */ + private function override_upload_dir( $uploads ): array { + $new_subdir = '/' . $this->get_import_subdir_name(); + + $uploads['path'] = $uploads['basedir'] . $new_subdir; + $uploads['url'] = $uploads['baseurl'] . $new_subdir; + $uploads['subdir'] = $new_subdir; + + return $uploads; + } + + /** + * Adds a random string to the name of an uploaded CSV file to make it less discoverable. Hooked onto 'wp_unique_filename'. + * + * @param string $filename File name. + * @param string $ext File extension. + * @return string + */ + private function override_unique_filename( string $filename, string $ext ): string { + $length = min( 10, 255 - strlen( $filename ) - 1 ); + if ( 1 < $length ) { + $suffix = strtolower( wp_generate_password( $length, false, false ) ); + $filename = substr( $filename, 0, strlen( $filename ) - strlen( $ext ) ) . '-' . $suffix . $ext; + } + + return $filename; + } + + /** + * `wp_import_handle_upload()` appends .txt to any file name. This function is hooked onto 'wp_handle_upload_prefilter' + * to remove those extra characters. + * + * @param array $file File details in the form of a $_FILES entry. + * @return array Modified file details. + */ + private function remove_txt_from_uploaded_file( array $file ): array { + $file['name'] = substr( $file['name'], 0, -4 ); + return $file; + } +} diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ImportExportServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ImportExportServiceProvider.php new file mode 100644 index 00000000000..58984a41fbf --- /dev/null +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ImportExportServiceProvider.php @@ -0,0 +1,35 @@ +share( CSVUploadHelper::class ); + } +} diff --git a/plugins/woocommerce/src/Internal/Logging/RemoteLogger.php b/plugins/woocommerce/src/Internal/Logging/RemoteLogger.php index 3043cd110fe..a15792fb29b 100644 --- a/plugins/woocommerce/src/Internal/Logging/RemoteLogger.php +++ b/plugins/woocommerce/src/Internal/Logging/RemoteLogger.php @@ -88,7 +88,7 @@ class RemoteLogger extends \WC_Log_Handler { unset( $context['tags'] ); } - if ( class_exists( '\WC_Tracks' ) ) { + if ( class_exists( '\WC_Tracks' ) && function_exists( 'wp_get_current_user' ) ) { $user = wp_get_current_user(); $blog_details = \WC_Tracks::get_blog_details( $user->ID ); @@ -233,7 +233,7 @@ class RemoteLogger extends \WC_Log_Handler { } return true; - } catch ( \Exception $e ) { + } catch ( \Throwable $e ) { // Log the error locally if the remote logging fails. error_log( 'Remote logging failed: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return false; diff --git a/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php index 4bac73265b3..3b91fcbc721 100644 --- a/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php +++ b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php @@ -31,6 +31,36 @@ class FilesystemUtil { return $wp_filesystem; } + /** + * Recursively creates a directory (if it doesn't exist) and adds an empty index.html and a .htaccess to prevent + * directory listing. + * + * @since 9.3.0 + * + * @param string $path Directory to create. + * @throws \Exception In case of error. + */ + public static function mkdir_p_not_indexable( string $path ): void { + $wp_fs = self::get_wp_filesystem(); + + if ( $wp_fs->is_dir( $path ) ) { + return; + } + + if ( ! wp_mkdir_p( $path ) ) { + throw new \Exception( esc_html( sprintf( 'Could not create directory: %s.', wp_basename( $path ) ) ) ); + } + + $files = array( + '.htaccess' => 'deny from all', + 'index.html' => '', + ); + + foreach ( $files as $name => $content ) { + $wp_fs->put_contents( trailingslashit( $path ) . $name, $content ); + } + } + /** * Wrapper to initialize the WP filesystem with defined credentials if they are available. * diff --git a/plugins/woocommerce/templates/cart/mini-cart.php b/plugins/woocommerce/templates/cart/mini-cart.php index ecf51eb96eb..27ae9454260 100644 --- a/plugins/woocommerce/templates/cart/mini-cart.php +++ b/plugins/woocommerce/templates/cart/mini-cart.php @@ -14,7 +14,7 @@ * * @see https://woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 9.2.0 + * @version 9.3.0 */ defined( 'ABSPATH' ) || exit; diff --git a/plugins/woocommerce/templates/parts/product-filters.html b/plugins/woocommerce/templates/parts/product-filters.html index 9e699ccd71d..a4e82272829 100644 --- a/plugins/woocommerce/templates/parts/product-filters.html +++ b/plugins/woocommerce/templates/parts/product-filters.html @@ -1,79 +1,28 @@ - -
    - +
    + +

    Filters

    + -
    -

    Filters

    - + - - -

    Active

    - + - - - - - -
    -

    Price

    - - - - -
    - -
    - -
    - - - - - - - -
    -

    Status

    - - - - -
    - -
    - -
    - - - - - - - - - -
    -

    Rating

    - - - - -
    - -
    - -
    - - - - - - -
    - -
    -
    + +
    + +
    + Apply +
    + +
    + +
    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 259c06b7fc6..4de1709668c 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,7 @@ config = { '**/customize-store/**/*.spec.js', '**/merchant/**/*.spec.js', '**/shopper/**/*.spec.js', + '**/api-tests/**/*.test.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 62169c1d0dc..08977a04e71 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,11 @@ config = { { name: 'default wpcom', use: { ...devices[ 'Desktop Chrome' ] }, - testMatch: [ '**/basic.spec.js', '**/shopper/**/*.spec.js' ], + testMatch: [ + '**/basic.spec.js', + '**/shopper/**/*.spec.js', + '**/api-tests/**/*.test.js', + ], grepInvert: /@skip-on-default-wpcom/, }, ], diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/coupons/coupons.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/coupons/coupons.test.js index 7c9a8a06565..a4a7cc9abe9 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/coupons/coupons.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/coupons/coupons.test.js @@ -69,26 +69,30 @@ test.describe( 'Coupons API tests', () => { ); } ); - test( 'can permanently delete a coupon', async ( { request } ) => { - //call API to delete previously created coupon - const response = await request.delete( - `/wp-json/wc/v3/coupons/${ couponId }`, - { - data: { force: true }, - } - ); + test( + 'can permanently delete a coupon', + { tag: '@skip-on-default-wpcom' }, + async ( { request } ) => { + //call API to delete previously created coupon + const response = await request.delete( + `/wp-json/wc/v3/coupons/${ couponId }`, + { + data: { force: true }, + } + ); - //validate response - expect( response.status() ).toEqual( 200 ); + //validate response + expect( response.status() ).toEqual( 200 ); - //call API to retrieve previously deleted coupon - const getCouponResponse = await request.get( - `/wp-json/wc/v3/coupons/${ couponId }` - ); + //call API to retrieve previously deleted coupon + const getCouponResponse = await request.get( + `/wp-json/wc/v3/coupons/${ couponId }` + ); - //validate response - expect( getCouponResponse.status() ).toEqual( 404 ); - } ); + //validate response + expect( getCouponResponse.status() ).toEqual( 404 ); + } + ); } ); test.describe( 'Batch update coupons', () => { @@ -180,38 +184,42 @@ test.describe( 'Batch update coupons', () => { expect( updatedCoupons[ 1 ].amount ).toEqual( '25.00' ); } ); - test( 'can batch delete coupons', async ( { request } ) => { - // Batch delete the 2 coupons. - const couponIdsToDelete = expectedCoupons.map( ( { id } ) => id ); - const batchDeletePayload = { - delete: couponIdsToDelete, - }; + test( + 'can batch delete coupons', + { tag: '@skip-on-default-wpcom' }, + async ( { request } ) => { + // Batch delete the 2 coupons. + const couponIdsToDelete = expectedCoupons.map( ( { id } ) => id ); + const batchDeletePayload = { + delete: couponIdsToDelete, + }; - //Call API to batch delete the coupons - const batchDeleteResponse = await request.post( - 'wp-json/wc/v3/coupons/batch', - { - data: batchDeletePayload, - } - ); - const batchDeletePayloadJSON = await batchDeleteResponse.json(); - - // Verify that the response shows the 2 coupons. - const deletedCouponIds = batchDeletePayloadJSON.delete.map( - ( { id } ) => id - ); - expect( batchDeleteResponse.status() ).toEqual( 200 ); - expect( deletedCouponIds ).toEqual( couponIdsToDelete ); - - // Verify that the 2 deleted coupons cannot be retrieved. - for ( const couponId of couponIdsToDelete ) { - //Call the API to attempte to retrieve the coupons - const response = await request.get( - `wp-json/wc/v3/coupons/${ couponId }` + //Call API to batch delete the coupons + const batchDeleteResponse = await request.post( + 'wp-json/wc/v3/coupons/batch', + { + data: batchDeletePayload, + } ); - expect( response.status() ).toEqual( 404 ); + const batchDeletePayloadJSON = await batchDeleteResponse.json(); + + // Verify that the response shows the 2 coupons. + const deletedCouponIds = batchDeletePayloadJSON.delete.map( + ( { id } ) => id + ); + expect( batchDeleteResponse.status() ).toEqual( 200 ); + expect( deletedCouponIds ).toEqual( couponIdsToDelete ); + + // Verify that the 2 deleted coupons cannot be retrieved. + for ( const couponId of couponIdsToDelete ) { + //Call the API to attempte to retrieve the coupons + const response = await request.get( + `wp-json/wc/v3/coupons/${ couponId }` + ); + expect( response.status() ).toEqual( 404 ); + } } - } ); + ); } ); test.describe( 'List coupons', () => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/customers/customers-crud.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/customers/customers-crud.test.js index 17be7e0a3bc..a93fef4198a 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/customers/customers-crud.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/customers/customers-crud.test.js @@ -2,500 +2,537 @@ const { test, expect } = require( '../../../fixtures/api-tests-fixtures' ); const { admin } = require( '../../../test-data/data' ); const { customer } = require( '../../../data' ); -test.describe( 'Customers API tests: CRUD', () => { - let customerId; - let subscriberUserId; - let subscriberUserCreatedDuringTests = false; +test.describe( + 'Customers API tests: CRUD', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + () => { + let customerId; + let subscriberUserId; + let subscriberUserCreatedDuringTests = false; - test.beforeAll( async ( { request } ) => { - // Call the API to return all users and determine if a - // subscriber user has been created - const customersResponse = await request.get( - '/wp-json/wc/v3/customers', - { - params: { - role: 'all', - }, - } - ); - const customersResponseJSON = await customersResponse.json(); - - for ( const element of customersResponseJSON ) { - if ( element.role === 'subscriber' ) { - subscriberUserId = element.id; - break; - } - } - - // If a subscriber user has not been created then create one - if ( ! subscriberUserId ) { - const now = Date.now(); - const userResponse = await request.post( '/wp-json/wp/v2/users', { - data: { - username: `customer_${ now }`, - email: `customer_${ now }@woocommercecoretestsuite.com`, - first_name: 'Jane', - last_name: 'Smith', - roles: [ 'subscriber' ], - password: 'password', - name: 'Jane', - }, - } ); - const userResponseJSON = await userResponse.json(); - // set subscriber user id to newly created user - subscriberUserId = userResponseJSON.id; - subscriberUserCreatedDuringTests = true; - } - - // Verify the subscriber user has been created - const response = await request.get( - `/wp-json/wc/v3/customers/${ subscriberUserId }` - ); - const responseJSON = await response.json(); - // eslint-disable-next-line jest/no-standalone-expect - expect( response.status() ).toEqual( 200 ); - // eslint-disable-next-line jest/no-standalone-expect - expect( responseJSON.role ).toEqual( 'subscriber' ); - } ); - - test.afterAll( async ( { request } ) => { - // delete subscriber user if one was created during the execution of these tests - if ( subscriberUserCreatedDuringTests ) { - await request.delete( - `/wp-json/wc/v3/customers/${ subscriberUserId }`, + test.beforeAll( async ( { request } ) => { + // Call the API to return all users and determine if a + // subscriber user has been created + const customersResponse = await request.get( + '/wp-json/wc/v3/customers', { - data: { - force: true, + params: { + role: 'all', }, } ); - } - } ); + const customersResponseJSON = await customersResponse.json(); - test.describe( 'Retrieve after env setup', () => { - /** - * when the environment is created, - * (https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw#woocommerce-playwright-end-to-end-tests), - * we have an admin user and a subscriber user that can both be - * accessed through their ids - * admin user will have id 1 and subscriber user will have id 2 - * neither of these are returned as part of the get all customers call - * unless the role 'all' is passed as a search param - * but they can be accessed by specific id reference - */ - test( 'can retrieve admin user', async ( { request } ) => { - // call API to retrieve the previously saved customer - const response = await request.get( '/wp-json/wc/v3/customers/1' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.is_paying_customer ).toEqual( false ); - expect( responseJSON.role ).toEqual( 'administrator' ); - // this test was updated to allow for local test setup and other test sites. - expect( responseJSON.username ).toEqual( admin.username ); - } ); + for ( const element of customersResponseJSON ) { + if ( element.role === 'subscriber' ) { + subscriberUserId = element.id; + break; + } + } - test( 'can retrieve subscriber user', async ( { request } ) => { - // if environment was created with subscriber user - // call API to retrieve the customer with id 2 + // If a subscriber user has not been created then create one + if ( ! subscriberUserId ) { + const now = Date.now(); + const userResponse = await request.post( + '/wp-json/wp/v2/users', + { + data: { + username: `customer_${ now }`, + email: `customer_${ now }@woocommercecoretestsuite.com`, + first_name: 'Jane', + last_name: 'Smith', + roles: [ 'subscriber' ], + password: 'password', + name: 'Jane', + }, + } + ); + const userResponseJSON = await userResponse.json(); + // set subscriber user id to newly created user + subscriberUserId = userResponseJSON.id; + subscriberUserCreatedDuringTests = true; + } + + // Verify the subscriber user has been created const response = await request.get( `/wp-json/wc/v3/customers/${ subscriberUserId }` ); const responseJSON = await response.json(); + // eslint-disable-next-line jest/no-standalone-expect expect( response.status() ).toEqual( 200 ); - expect( responseJSON.is_paying_customer ).toEqual( false ); + // eslint-disable-next-line jest/no-standalone-expect expect( responseJSON.role ).toEqual( 'subscriber' ); } ); - test( 'retrieve user with id 0 is invalid', async ( { request } ) => { - // call API to retrieve the previously saved customer - const response = await request.get( '/wp-json/wc/v3/customers/0' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 404 ); - expect( responseJSON.code ).toEqual( - 'woocommerce_rest_invalid_id' - ); - expect( responseJSON.message ).toEqual( 'Invalid resource ID.' ); + test.afterAll( async ( { request } ) => { + // delete subscriber user if one was created during the execution of these tests + if ( subscriberUserCreatedDuringTests ) { + await request.delete( + `/wp-json/wc/v3/customers/${ subscriberUserId }`, + { + data: { + force: true, + }, + } + ); + } } ); - test( 'can retrieve customers', async ( { request } ) => { - // call API to retrieve all customers should initially return empty array - const response = await request.get( '/wp-json/wc/v3/customers' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON.length ).toEqual( 1 ); - } ); - - // however, if we pass in the search string for role 'all' then all users are returned - test( 'can retrieve all customers', async ( { request } ) => { - // call API to retrieve all customers should initially return empty array - // unless the role 'all' is passed as a search string, in which case the admin - // and subscriber users will be returned - const response = await request.get( '/wp-json/wc/v3/customers', { - params: { - role: 'all', - }, - } ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON.length ).toBeGreaterThanOrEqual( 3 ); - } ); - } ); - - test.describe( 'Create a customer', () => { - test( 'can create a customer', async ( { request } ) => { - // call API to create a customer - const response = await request.post( '/wp-json/wc/v3/customers', { - data: customer, - } ); - const responseJSON = await response.json(); - - // Save the customer ID. It will be used by the retrieve, update, and delete tests. - customerId = responseJSON.id; - - expect( response.status() ).toEqual( 201 ); - expect( typeof responseJSON.id ).toEqual( 'number' ); - // Verify that the customer role is 'customer' - expect( responseJSON.role ).toEqual( 'customer' ); - } ); - } ); - - test.describe( 'Retrieve after create', () => { - test( 'can retrieve a customer', async ( { request } ) => { - // call API to retrieve the previously saved customer - const response = await request.get( - `/wp-json/wc/v3/customers/${ customerId }` - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.id ).toEqual( customerId ); - expect( responseJSON.is_paying_customer ).toEqual( false ); - expect( responseJSON.role ).toEqual( 'customer' ); - } ); - - test( 'can retrieve all customers after create', async ( { - request, - } ) => { - // call API to retrieve all customers - const response = await request.get( '/wp-json/wc/v3/customers' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON.length ).toBeGreaterThan( 0 ); - } ); - } ); - - test.describe( 'Update a customer', () => { - test( `can update the admin user/customer`, async ( { request } ) => { + test.describe( 'Retrieve after env setup', () => { /** - * update customer names (regular, billing and shipping) to admin - * (these were initialised blank when the environment is created, - * (https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw#woocommerce-playwright-end-to-end-tests + * when the environment is created, + * (https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw#woocommerce-playwright-end-to-end-tests), + * we have an admin user and a subscriber user that can both be + * accessed through their ids + * admin user will have id 1 and subscriber user will have id 2 + * neither of these are returned as part of the get all customers call + * unless the role 'all' is passed as a search param + * but they can be accessed by specific id reference */ - const response = await request.put( `/wp-json/wc/v3/customers/1`, { - data: { - first_name: 'admin', + test( 'can retrieve admin user', async ( { request } ) => { + // call API to retrieve the previously saved customer + const response = await request.get( + '/wp-json/wc/v3/customers/1' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.is_paying_customer ).toEqual( false ); + expect( responseJSON.role ).toEqual( 'administrator' ); + // this test was updated to allow for local test setup and other test sites. + expect( responseJSON.username ).toEqual( admin.username ); + } ); + + test( 'can retrieve subscriber user', async ( { request } ) => { + // if environment was created with subscriber user + // call API to retrieve the customer with id 2 + const response = await request.get( + `/wp-json/wc/v3/customers/${ subscriberUserId }` + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.is_paying_customer ).toEqual( false ); + expect( responseJSON.role ).toEqual( 'subscriber' ); + } ); + + test( 'retrieve user with id 0 is invalid', async ( { + request, + } ) => { + // call API to retrieve the previously saved customer + const response = await request.get( + '/wp-json/wc/v3/customers/0' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 404 ); + expect( responseJSON.code ).toEqual( + 'woocommerce_rest_invalid_id' + ); + expect( responseJSON.message ).toEqual( + 'Invalid resource ID.' + ); + } ); + + test( 'can retrieve customers', async ( { request } ) => { + // call API to retrieve all customers should initially return empty array + const response = await request.get( + '/wp-json/wc/v3/customers' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON.length ).toEqual( 1 ); + } ); + + // however, if we pass in the search string for role 'all' then all users are returned + test( 'can retrieve all customers', async ( { request } ) => { + // call API to retrieve all customers should initially return empty array + // unless the role 'all' is passed as a search string, in which case the admin + // and subscriber users will be returned + const response = await request.get( + '/wp-json/wc/v3/customers', + { + params: { + role: 'all', + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON.length ).toBeGreaterThanOrEqual( 3 ); + } ); + } ); + + test.describe( 'Create a customer', () => { + test( 'can create a customer', async ( { request } ) => { + // call API to create a customer + const response = await request.post( + '/wp-json/wc/v3/customers', + { + data: customer, + } + ); + const responseJSON = await response.json(); + + // Save the customer ID. It will be used by the retrieve, update, and delete tests. + customerId = responseJSON.id; + + expect( response.status() ).toEqual( 201 ); + expect( typeof responseJSON.id ).toEqual( 'number' ); + // Verify that the customer role is 'customer' + expect( responseJSON.role ).toEqual( 'customer' ); + } ); + } ); + + test.describe( 'Retrieve after create', () => { + test( 'can retrieve a customer', async ( { request } ) => { + // call API to retrieve the previously saved customer + const response = await request.get( + `/wp-json/wc/v3/customers/${ customerId }` + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.id ).toEqual( customerId ); + expect( responseJSON.is_paying_customer ).toEqual( false ); + expect( responseJSON.role ).toEqual( 'customer' ); + } ); + + test( 'can retrieve all customers after create', async ( { + request, + } ) => { + // call API to retrieve all customers + const response = await request.get( + '/wp-json/wc/v3/customers' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON.length ).toBeGreaterThan( 0 ); + } ); + } ); + + test.describe( 'Update a customer', () => { + test( `can update the admin user/customer`, async ( { + request, + } ) => { + /** + * update customer names (regular, billing and shipping) to admin + * (these were initialised blank when the environment is created, + * (https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw#woocommerce-playwright-end-to-end-tests + */ + const response = await request.put( + `/wp-json/wc/v3/customers/1`, + { + data: { + first_name: 'admin', + billing: { + first_name: 'admin', + }, + shipping: { + first_name: 'admin', + }, + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'admin' ); + expect( responseJSON.billing.first_name ).toEqual( 'admin' ); + expect( responseJSON.shipping.first_name ).toEqual( 'admin' ); + } ); + + test( 'retrieve after update admin', async ( { request } ) => { + // call API to retrieve the admin customer we updated above + const response = await request.get( + '/wp-json/wc/v3/customers/1' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'admin' ); + expect( responseJSON.billing.first_name ).toEqual( 'admin' ); + expect( responseJSON.shipping.first_name ).toEqual( 'admin' ); + } ); + + test( `can update the subscriber user/customer`, async ( { + request, + } ) => { + // update customer names (billing and shipping) to Jane + // (these were initialised blank, only regular first_name was populated) + const response = await request.put( + `/wp-json/wc/v3/customers/${ subscriberUserId }`, + { + data: { + billing: { + first_name: 'Jane', + }, + shipping: { + first_name: 'Jane', + }, + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'Jane' ); + expect( responseJSON.billing.first_name ).toEqual( 'Jane' ); + expect( responseJSON.shipping.first_name ).toEqual( 'Jane' ); + } ); + + test( 'retrieve after update subscriber', async ( { request } ) => { + // call API to retrieve the subscriber customer we updated above + const response = await request.get( + `/wp-json/wc/v3/customers/${ subscriberUserId }` + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'Jane' ); + expect( responseJSON.billing.first_name ).toEqual( 'Jane' ); + expect( responseJSON.shipping.first_name ).toEqual( 'Jane' ); + } ); + + test( `can update a customer`, async ( { request } ) => { + // update customer names (regular, billing and shipping) from John to Jack + const response = await request.put( + `/wp-json/wc/v3/customers/${ customerId }`, + { + data: { + first_name: 'Jack', + billing: { + first_name: 'Jack', + }, + shipping: { + first_name: 'Jack', + }, + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'Jack' ); + expect( responseJSON.billing.first_name ).toEqual( 'Jack' ); + expect( responseJSON.shipping.first_name ).toEqual( 'Jack' ); + } ); + + test( 'retrieve after update customer', async ( { request } ) => { + // call API to retrieve the updated customer we created above + const response = await request.get( + `/wp-json/wc/v3/customers/${ customerId }` + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.first_name ).toEqual( 'Jack' ); + expect( responseJSON.billing.first_name ).toEqual( 'Jack' ); + expect( responseJSON.shipping.first_name ).toEqual( 'Jack' ); + } ); + } ); + + test.describe( 'Delete a customer', () => { + test( 'can permanently delete an customer', async ( { + request, + } ) => { + // Delete the customer. + const response = await request.delete( + `/wp-json/wc/v3/customers/${ customerId }`, + { + data: { + force: true, + }, + } + ); + expect( response.status() ).toEqual( 200 ); + + // Verify that the customer can no longer be retrieved. + const getDeletedCustomerResponse = await request.get( + `/wp-json/wc/v3/customers/${ customer }` + ); + expect( getDeletedCustomerResponse.status() ).toEqual( 404 ); + } ); + } ); + + test.describe( 'Batch update customers', () => { + /** + * 2 Customers to be created in one batch. + */ + const expectedCustomers = [ + { + email: 'john.doe2@example.com', + first_name: 'John', + last_name: 'Doe', + username: 'john.doe2', billing: { - first_name: 'admin', + first_name: 'John', + last_name: 'Doe', + company: '', + address_1: '969 Market', + address_2: '', + city: 'San Francisco', + state: 'CA', + postcode: '94103', + country: 'US', + email: 'john.doe2@example.com', + phone: '(555) 555-5555', }, shipping: { - first_name: 'admin', + first_name: 'John', + last_name: 'Doe', + company: '', + address_1: '969 Market', + address_2: '', + city: 'San Francisco', + state: 'CA', + postcode: '94103', + country: 'US', }, }, - } ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'admin' ); - expect( responseJSON.billing.first_name ).toEqual( 'admin' ); - expect( responseJSON.shipping.first_name ).toEqual( 'admin' ); - } ); - - test( 'retrieve after update admin', async ( { request } ) => { - // call API to retrieve the admin customer we updated above - const response = await request.get( '/wp-json/wc/v3/customers/1' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'admin' ); - expect( responseJSON.billing.first_name ).toEqual( 'admin' ); - expect( responseJSON.shipping.first_name ).toEqual( 'admin' ); - } ); - - test( `can update the subscriber user/customer`, async ( { - request, - } ) => { - // update customer names (billing and shipping) to Jane - // (these were initialised blank, only regular first_name was populated) - const response = await request.put( - `/wp-json/wc/v3/customers/${ subscriberUserId }`, { - data: { - billing: { - first_name: 'Jane', - }, - shipping: { - first_name: 'Jane', - }, - }, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'Jane' ); - expect( responseJSON.billing.first_name ).toEqual( 'Jane' ); - expect( responseJSON.shipping.first_name ).toEqual( 'Jane' ); - } ); - - test( 'retrieve after update subscriber', async ( { request } ) => { - // call API to retrieve the subscriber customer we updated above - const response = await request.get( - `/wp-json/wc/v3/customers/${ subscriberUserId }` - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'Jane' ); - expect( responseJSON.billing.first_name ).toEqual( 'Jane' ); - expect( responseJSON.shipping.first_name ).toEqual( 'Jane' ); - } ); - - test( `can update a customer`, async ( { request } ) => { - // update customer names (regular, billing and shipping) from John to Jack - const response = await request.put( - `/wp-json/wc/v3/customers/${ customerId }`, - { - data: { - first_name: 'Jack', - billing: { - first_name: 'Jack', - }, - shipping: { - first_name: 'Jack', - }, - }, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'Jack' ); - expect( responseJSON.billing.first_name ).toEqual( 'Jack' ); - expect( responseJSON.shipping.first_name ).toEqual( 'Jack' ); - } ); - - test( 'retrieve after update customer', async ( { request } ) => { - // call API to retrieve the updated customer we created above - const response = await request.get( - `/wp-json/wc/v3/customers/${ customerId }` - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.first_name ).toEqual( 'Jack' ); - expect( responseJSON.billing.first_name ).toEqual( 'Jack' ); - expect( responseJSON.shipping.first_name ).toEqual( 'Jack' ); - } ); - } ); - - test.describe( 'Delete a customer', () => { - test( 'can permanently delete an customer', async ( { request } ) => { - // Delete the customer. - const response = await request.delete( - `/wp-json/wc/v3/customers/${ customerId }`, - { - data: { - force: true, - }, - } - ); - expect( response.status() ).toEqual( 200 ); - - // Verify that the customer can no longer be retrieved. - const getDeletedCustomerResponse = await request.get( - `/wp-json/wc/v3/customers/${ customer }` - ); - expect( getDeletedCustomerResponse.status() ).toEqual( 404 ); - } ); - } ); - - test.describe( 'Batch update customers', () => { - /** - * 2 Customers to be created in one batch. - */ - const expectedCustomers = [ - { - email: 'john.doe2@example.com', - first_name: 'John', - last_name: 'Doe', - username: 'john.doe2', - billing: { - first_name: 'John', - last_name: 'Doe', - company: '', - address_1: '969 Market', - address_2: '', - city: 'San Francisco', - state: 'CA', - postcode: '94103', - country: 'US', - email: 'john.doe2@example.com', - phone: '(555) 555-5555', - }, - shipping: { - first_name: 'John', - last_name: 'Doe', - company: '', - address_1: '969 Market', - address_2: '', - city: 'San Francisco', - state: 'CA', - postcode: '94103', - country: 'US', - }, - }, - { - email: 'joao.silva2@example.com', - first_name: 'João', - last_name: 'Silva', - username: 'joao.silva2', - billing: { - first_name: 'João', - last_name: 'Silva', - company: '', - address_1: 'Av. Brasil, 432', - address_2: '', - city: 'Rio de Janeiro', - state: 'RJ', - postcode: '12345-000', - country: 'BR', email: 'joao.silva2@example.com', - phone: '(55) 5555-5555', - }, - shipping: { first_name: 'João', last_name: 'Silva', - company: '', - address_1: 'Av. Brasil, 432', - address_2: '', - city: 'Rio de Janeiro', - state: 'RJ', - postcode: '12345-000', - country: 'BR', + username: 'joao.silva2', + billing: { + first_name: 'João', + last_name: 'Silva', + company: '', + address_1: 'Av. Brasil, 432', + address_2: '', + city: 'Rio de Janeiro', + state: 'RJ', + postcode: '12345-000', + country: 'BR', + email: 'joao.silva2@example.com', + phone: '(55) 5555-5555', + }, + shipping: { + first_name: 'João', + last_name: 'Silva', + company: '', + address_1: 'Av. Brasil, 432', + address_2: '', + city: 'Rio de Janeiro', + state: 'RJ', + postcode: '12345-000', + country: 'BR', + }, }, - }, - ]; + ]; - // set payload to use batch create: action - const batchCreate2CustomersPayload = { - create: expectedCustomers, - }; - - test( 'can batch create customers', async ( { request } ) => { - // Batch create 2 new customers. - // call API to batch create customers - const response = await request.post( - 'wp-json/wc/v3/customers/batch', - { - data: batchCreate2CustomersPayload, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - - // Verify that the 2 new customers were created - const actualCustomers = responseJSON.create; - expect( actualCustomers ).toHaveLength( expectedCustomers.length ); - - for ( let i = 0; i < actualCustomers.length; i++ ) { - const { id, first_name } = actualCustomers[ i ]; - const expectedCustomerName = expectedCustomers[ i ].first_name; - - expect( id ).toBeDefined(); - expect( first_name ).toEqual( expectedCustomerName ); - - // Save the customer id - expectedCustomers[ i ].id = id; - } - } ); - - test( 'can batch update customers', async ( { request } ) => { - // set payload to use batch update: action - const batchUpdatePayload = { - update: [ - { - id: expectedCustomers[ 0 ].id, - email: 'emailupdated@example.com', - }, - { - id: expectedCustomers[ 1 ].id, - billing: { - address_1: '123 Addressupdate Street', - }, - }, - ], + // set payload to use batch create: action + const batchCreate2CustomersPayload = { + create: expectedCustomers, }; - // Call API to update the customers - const response = await request.post( - 'wp-json/wc/v3/customers/batch', - { - data: batchUpdatePayload, - } - ); - const responseJSON = await response.json(); - - // Verify the response code and the number of customers that were updated. - const updatedCustomers = responseJSON.update; - expect( response.status() ).toEqual( 200 ); - expect( updatedCustomers ).toHaveLength( 2 ); - - // Verify that the 1st customer was updated to have a new email address. - expect( updatedCustomers[ 0 ].id ).toEqual( - expectedCustomers[ 0 ].id - ); - expect( updatedCustomers[ 0 ].email ).toEqual( - 'emailupdated@example.com' - ); - - // Verify that the amount of the 2nd customer was updated to have a new billing address. - expect( updatedCustomers[ 1 ].id ).toEqual( - expectedCustomers[ 1 ].id - ); - expect( updatedCustomers[ 1 ].billing.address_1 ).toEqual( - '123 Addressupdate Street' - ); - } ); - - test( 'can batch delete customers', async ( { request } ) => { - // Batch delete the 2 customers. - const customerIdsToDelete = expectedCustomers.map( - ( { id } ) => id - ); - const batchDeletePayload = { - delete: customerIdsToDelete, - }; - - //Call API to batch delete the customers - const response = await request.post( - 'wp-json/wc/v3/customers/batch', - { - data: batchDeletePayload, - } - ); - const responseJSON = await response.json(); - - // Verify that the response shows the 2 customers. - const deletedCustomerIds = responseJSON.delete.map( - ( { id } ) => id - ); - expect( response.status() ).toEqual( 200 ); - expect( deletedCustomerIds ).toEqual( customerIdsToDelete ); - - // Verify that the 2 deleted customers cannot be retrieved. - for ( const id of customerIdsToDelete ) { - //Call the API to attempte to retrieve the customers - const r = await request.get( - `wp-json/wc/v3/customers/${ id }` + test( 'can batch create customers', async ( { request } ) => { + // Batch create 2 new customers. + // call API to batch create customers + const response = await request.post( + 'wp-json/wc/v3/customers/batch', + { + data: batchCreate2CustomersPayload, + } ); - expect( r.status() ).toEqual( 404 ); - } + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + + // Verify that the 2 new customers were created + const actualCustomers = responseJSON.create; + expect( actualCustomers ).toHaveLength( + expectedCustomers.length + ); + + for ( let i = 0; i < actualCustomers.length; i++ ) { + const { id, first_name } = actualCustomers[ i ]; + const expectedCustomerName = + expectedCustomers[ i ].first_name; + + expect( id ).toBeDefined(); + expect( first_name ).toEqual( expectedCustomerName ); + + // Save the customer id + expectedCustomers[ i ].id = id; + } + } ); + + test( 'can batch update customers', async ( { request } ) => { + // set payload to use batch update: action + const batchUpdatePayload = { + update: [ + { + id: expectedCustomers[ 0 ].id, + email: 'emailupdated@example.com', + }, + { + id: expectedCustomers[ 1 ].id, + billing: { + address_1: '123 Addressupdate Street', + }, + }, + ], + }; + + // Call API to update the customers + const response = await request.post( + 'wp-json/wc/v3/customers/batch', + { + data: batchUpdatePayload, + } + ); + const responseJSON = await response.json(); + + // Verify the response code and the number of customers that were updated. + const updatedCustomers = responseJSON.update; + expect( response.status() ).toEqual( 200 ); + expect( updatedCustomers ).toHaveLength( 2 ); + + // Verify that the 1st customer was updated to have a new email address. + expect( updatedCustomers[ 0 ].id ).toEqual( + expectedCustomers[ 0 ].id + ); + expect( updatedCustomers[ 0 ].email ).toEqual( + 'emailupdated@example.com' + ); + + // Verify that the amount of the 2nd customer was updated to have a new billing address. + expect( updatedCustomers[ 1 ].id ).toEqual( + expectedCustomers[ 1 ].id + ); + expect( updatedCustomers[ 1 ].billing.address_1 ).toEqual( + '123 Addressupdate Street' + ); + } ); + + test( 'can batch delete customers', async ( { request } ) => { + // Batch delete the 2 customers. + const customerIdsToDelete = expectedCustomers.map( + ( { id } ) => id + ); + const batchDeletePayload = { + delete: customerIdsToDelete, + }; + + //Call API to batch delete the customers + const response = await request.post( + 'wp-json/wc/v3/customers/batch', + { + data: batchDeletePayload, + } + ); + const responseJSON = await response.json(); + + // Verify that the response shows the 2 customers. + const deletedCustomerIds = responseJSON.delete.map( + ( { id } ) => id + ); + expect( response.status() ).toEqual( 200 ); + expect( deletedCustomerIds ).toEqual( customerIdsToDelete ); + + // Verify that the 2 deleted customers cannot be retrieved. + for ( const id of customerIdsToDelete ) { + //Call the API to attempte to retrieve the customers + const r = await request.get( + `wp-json/wc/v3/customers/${ id }` + ); + expect( r.status() ).toEqual( 404 ); + } + } ); } ); - } ); -} ); + } +); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/order-complex.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/order-complex.test.js index 5308ecdcfa1..90df59a1b9c 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/order-complex.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/order-complex.test.js @@ -208,48 +208,52 @@ test.describe( 'Orders API test', () => { } ); } ); - test( 'can add complex order', async ( { request } ) => { - //ensure tax calculations are enabled - await request.put( - '/wp-json/wc/v3/settings/general/woocommerce_calc_taxes', - { - data: { - value: 'yes', - }, + test( + 'can add complex order', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + async ( { request } ) => { + //ensure tax calculations are enabled + await request.put( + '/wp-json/wc/v3/settings/general/woocommerce_calc_taxes', + { + data: { + value: 'yes', + }, + } + ); + + // Create the complex order and save its ID. + const response = await request.post( '/wp-json/wc/v3/orders', { + data: order, + } ); + const responseJSON = await response.json(); + + order.id = responseJSON.id; + + expect( response.status() ).toEqual( 201 ); + + // Verify order and tax totals + expect( responseJSON.total ).toEqual( expectedOrderTotal ); + expect( responseJSON.total_tax ).toEqual( expectedTaxTotal ); + + // Verify total tax of each product line item + const expectedTaxTotalsPerLineItem = [ + [ simpleProduct, expectedSimpleProductTaxTotal ], + [ variableProduct, expectedVariableProductTaxTotal ], + [ groupedProduct, expectedSimpleProductTaxTotal ], + [ externalProduct, expectedExternalProductTaxTotal ], + ]; + for ( const [ + product, + expectedLineTaxTotal, + ] of expectedTaxTotalsPerLineItem ) { + const { total_tax: actualLineTaxTotal } = + responseJSON.line_items.find( + ( { product_id } ) => product_id === product.id + ); + + expect( actualLineTaxTotal ).toEqual( expectedLineTaxTotal ); } - ); - - // Create the complex order and save its ID. - const response = await request.post( '/wp-json/wc/v3/orders', { - data: order, - } ); - const responseJSON = await response.json(); - - order.id = responseJSON.id; - - expect( response.status() ).toEqual( 201 ); - - // Verify order and tax totals - expect( responseJSON.total ).toEqual( expectedOrderTotal ); - expect( responseJSON.total_tax ).toEqual( expectedTaxTotal ); - - // Verify total tax of each product line item - const expectedTaxTotalsPerLineItem = [ - [ simpleProduct, expectedSimpleProductTaxTotal ], - [ variableProduct, expectedVariableProductTaxTotal ], - [ groupedProduct, expectedSimpleProductTaxTotal ], - [ externalProduct, expectedExternalProductTaxTotal ], - ]; - for ( const [ - product, - expectedLineTaxTotal, - ] of expectedTaxTotalsPerLineItem ) { - const { total_tax: actualLineTaxTotal } = - responseJSON.line_items.find( - ( { product_id } ) => product_id === product.id - ); - - expect( actualLineTaxTotal ).toEqual( expectedLineTaxTotal ); } - } ); + ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/orders.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/orders.test.js index f0164b40982..b6a556ce2de 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/orders.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/orders/orders.test.js @@ -34,3232 +34,3274 @@ const updatedCustomerShipping = { phone: '123456789', }; -test.describe.serial( 'Orders API tests', () => { - let orderId, sampleData; +test.describe.serial( + 'Orders API tests', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + () => { + let orderId, sampleData; - test.beforeAll( async ( { request } ) => { - const createSampleCategories = async () => { - const clothing = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Clothing', - }, - } - ); - const clothingJSON = await clothing.json(); - - const accessories = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Accessories', - parent: clothingJSON.id, - }, - } - ); - const accessoriesJSON = await accessories.json(); - - const hoodies = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Hoodies', - parent: clothingJSON.id, - }, - } - ); - const hoodiesJSON = await hoodies.json(); - - const tshirts = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Tshirts', - parent: clothingJSON.id, - }, - } - ); - const tshirtsJSON = await tshirts.json(); - - const decor = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Decor', - }, - } - ); - const decorJSON = await decor.json(); - - const music = await request.post( - '/wp-json/wc/v3/products/categories', - { - data: { - name: 'Music', - }, - } - ); - const musicJSON = await music.json(); - - return { - clothingJSON, - accessoriesJSON, - hoodiesJSON, - tshirtsJSON, - decorJSON, - musicJSON, - }; - }; - - const createSampleAttributes = async () => { - const color = await request.post( - '/wp-json/wc/v3/products/attributes', - { - data: { - name: 'Color', - }, - } - ); - const colorJSON = await color.json(); - - const size = await request.post( - '/wp-json/wc/v3/products/attributes', - { - data: { - name: 'Size', - }, - } - ); - const sizeJSON = await size.json(); - - const colorNames = [ 'Blue', 'Gray', 'Green', 'Red', 'Yellow' ]; - - const colorNamesObjectArray = colorNames.map( ( name ) => ( { - name, - } ) ); - - const colors = await request.post( - `/wp-json/wc/v3/products/attributes/${ colorJSON.id }/terms/batch`, - { - data: { - create: colorNamesObjectArray, - }, - } - ); - - const colorsJSON = await colors.json(); - - const sizeNames = [ 'Large', 'Medium', 'Small' ]; - - const sizeNamesObjectArray = sizeNames.map( ( name ) => ( { - name, - } ) ); - - const sizes = await request.post( - `/wp-json/wc/v3/products/attributes/${ sizeJSON.id }/terms/batch`, - { - data: { - create: sizeNamesObjectArray, - }, - } - ); - const sizesJSON = await sizes.json(); - - return { - colorJSON, - colors: colorsJSON.create, - sizeJSON, - sizes: sizesJSON.create, - }; - }; - - const createSampleTags = async () => { - const cool = await request.post( '/wp-json/wc/v3/products/tags', { - data: { - name: 'Cool', - }, - } ); - const coolJSON = await cool.json(); - - return { - coolJSON, - }; - }; - - const createSampleShippingClasses = async () => { - const freight = await request.post( - '/wp-json/wc/v3/products/shipping_classes', - { - data: { - name: 'Freight', - }, - } - ); - const freightJSON = await freight.json(); - - return { - freightJSON, - }; - }; - - const createSampleTaxClasses = async () => { - //check to see if Reduced Rate tax class exists - if not, create it - let reducedRate = await request.get( - '/wp-json/wc/v3/taxes/classes/reduced-rate' - ); - let reducedRateJSON = await reducedRate.json(); - expect( Array.isArray( reducedRateJSON ) ).toBe( true ); - - //if tax class does not exist then create it - if ( reducedRateJSON.length < 1 ) { - reducedRate = await request.post( - '/wp-json/wc/v3/taxes/classes', + test.beforeAll( async ( { request } ) => { + const createSampleCategories = async () => { + const clothing = await request.post( + '/wp-json/wc/v3/products/categories', { data: { - name: 'Reduced Rate', + name: 'Clothing', }, } ); - reducedRateJSON = await reducedRate.json(); - return { reducedRateJSON }; - } + const clothingJSON = await clothing.json(); - // return an empty object as nothing new was created so nothing will - // need deleted during cleanup - return {}; - }; - - const createSampleSimpleProducts = async ( - categories, - attributes, - tags - ) => { - const description = - '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + - 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. ' + - 'Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n'; - - const simpleProducts = await request.post( - '/wp-json/wc/v3/products/batch', - { - data: { - create: [ - { - name: 'Beanie with Logo oxo', - date_created_gmt: '2021-09-01T15:50:20', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'Woo-beanie-logo', - price: '18', - regular_price: '20', - sale_price: '18', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.2', - dimensions: { - length: '6', - width: '4', - height: '1', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.accessoriesJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Red' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 62, 63, 61, 60 ], - stock_status: 'instock', - }, - { - name: 'T-Shirt with Logo oxo', - date_created_gmt: '2021-09-02T15:50:20', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'Woo-tshirt-logo', - price: '18', - regular_price: '18', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.5', - dimensions: { - length: '10', - width: '12', - height: '0.5', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.tshirtsJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Gray' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 59, 67, 66, 56 ], - stock_status: 'instock', - }, - { - name: 'Single oxo', - date_created_gmt: '2021-09-03T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple, virtual product.

    \n', - sku: 'woo-single', - price: '2', - regular_price: '3', - sale_price: '2', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: true, - downloadable: true, - downloads: [ - { - id: '2579cf07-8b08-4c25-888a-b6258dd1f035', - name: 'Single', - file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', - }, - ], - download_limit: 1, - download_expiry: 1, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '', - dimensions: { - length: '', - width: '', - height: '', - }, - shipping_required: false, - shipping_taxable: false, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.musicJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 68 ], - stock_status: 'instock', - }, - { - name: 'Album oxo', - date_created_gmt: '2021-09-04T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple, virtual product.

    \n', - sku: 'woo-album', - price: '15', - regular_price: '15', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: true, - downloadable: true, - downloads: [ - { - id: 'cc10249f-1de2-44d4-93d3-9f88ae629f76', - name: 'Single 1', - file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', - }, - { - id: 'aea8ef69-ccdc-4d83-8e21-3c395ebb9411', - name: 'Single 2', - file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg', - }, - ], - download_limit: 1, - download_expiry: 1, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '', - dimensions: { - length: '', - width: '', - height: '', - }, - shipping_required: false, - shipping_taxable: false, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.musicJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 69 ], - stock_status: 'instock', - }, - { - name: 'Polo oxo', - date_created_gmt: '2021-09-05T15:50:19', - type: 'simple', - status: 'pending', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-polo', - price: '20', - regular_price: '20', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.8', - dimensions: { - length: '6', - width: '5', - height: '1', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.tshirtsJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Blue' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 59, 56, 66, 76 ], - stock_status: 'instock', - }, - { - name: 'Long Sleeve Tee oxo', - date_created_gmt: '2021-09-06T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-long-sleeve-tee', - price: '25', - regular_price: '25', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '1', - dimensions: { - length: '7', - width: '5', - height: '1', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: 'freight', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.tshirtsJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Green' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 59, 56, 76, 67 ], - stock_status: 'instock', - }, - { - name: 'Hoodie with Zipper oxo', - date_created_gmt: '2021-09-07T15:50:19', - type: 'simple', - status: 'publish', - featured: true, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-hoodie-with-zipper', - price: '45', - regular_price: '45', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '2', - dimensions: { - length: '8', - width: '6', - height: '2', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.hoodiesJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 57, 58 ], - stock_status: 'instock', - }, - { - name: 'Hoodie with Pocket oxo', - date_created_gmt: '2021-09-08T15:50:19', - type: 'simple', - status: 'publish', - featured: true, - catalog_visibility: 'hidden', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-hoodie-with-pocket', - price: '35', - regular_price: '45', - sale_price: '35', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '3', - dimensions: { - length: '10', - width: '8', - height: '2', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.hoodiesJSON.id, - }, - ], - tags: [ - { - id: tags.coolJSON.id, - }, - ], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Gray' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 65, 57, 58 ], - stock_status: 'instock', - }, - { - name: 'Sunglasses oxo', - date_created_gmt: '2021-09-09T15:50:19', - type: 'simple', - status: 'publish', - featured: true, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-sunglasses', - price: '90', - regular_price: '90', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: 'reduced-rate', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.2', - dimensions: { - length: '4', - width: '1.4', - height: '1', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.accessoriesJSON.id, - }, - ], - tags: [ - { - id: tags.coolJSON.id, - }, - ], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 60, 62, 77, 61 ], - stock_status: 'instock', - }, - { - name: 'Cap oxo', - date_created_gmt: '2021-09-10T15:50:19', - type: 'simple', - status: 'publish', - featured: true, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-cap', - price: '16', - regular_price: '18', - sale_price: '16', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.6', - dimensions: { - length: '8', - width: '6.5', - height: '4', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.accessoriesJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Yellow' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 60, 77, 61, 63 ], - stock_status: 'instock', - }, - { - name: 'Belt oxo', - date_created_gmt: '2021-09-12T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-belt', - price: '55', - regular_price: '65', - sale_price: '55', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '1.2', - dimensions: { - length: '12', - width: '2', - height: '1.5', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.accessoriesJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 63, 77, 62, 60 ], - stock_status: 'instock', - }, - { - name: 'Beanie oxo', - date_created_gmt: '2021-09-13T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-beanie', - price: '18', - regular_price: '20', - sale_price: '18', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.2', - dimensions: { - length: '4', - width: '5', - height: '0.5', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.accessoriesJSON.id, - }, - ], - tags: [ - { - id: tags.coolJSON.id, - }, - ], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Red' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 63, 62, 61, 77 ], - stock_status: 'instock', - }, - { - name: 'T-Shirt oxo', - date_created_gmt: '2021-09-14T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-tshirt', - price: '18', - regular_price: '18', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.8', - dimensions: { - length: '8', - width: '6', - height: '1', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.tshirtsJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Gray' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 67, 76, 56, 66 ], - stock_status: 'onbackorder', - }, - { - name: 'Hoodie with Logo oxo', - date_created_gmt: '2021-09-15T15:50:19', - type: 'simple', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: - '

    This is a simple product.

    \n', - sku: 'woo-hoodie-with-logo', - price: '45', - regular_price: '45', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '2', - dimensions: { - length: '10', - width: '6', - height: '3', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.hoodiesJSON.id, - }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: false, - options: [ 'Blue' ], - }, - ], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [ 57, 65 ], - stock_status: 'instock', - }, - ], - }, - } - ); - const simpleProductsJSON = await simpleProducts.json(); - - return simpleProductsJSON.create; - }; - - const createSampleExternalProducts = async ( categories ) => { - const externalProducts = await request.post( - '/wp-json/wc/v3/products/batch', - { - data: { - create: [ - { - name: 'WordPress Pennant oxo', - date_created_gmt: '2021-09-16T15:50:20', - type: 'external', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description: - '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + - 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + - 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n', - short_description: - '

    This is an external product.

    \n', - sku: 'wp-pennant', - price: '11.05', - regular_price: '11.05', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: false, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: - 'https://mercantile.wordpress.org/product/wordpress-pennant/', - button_text: 'Buy on the WordPress swag store!', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '', - dimensions: { - length: '', - width: '', - height: '', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.decorJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: [], - menu_order: 0, - related_ids: [], - stock_status: 'instock', - }, - ], - }, - } - ); - const externalProductsJSON = await externalProducts.json(); - - return externalProductsJSON.create; - }; - - const createSampleGroupedProduct = async ( categories ) => { - const logoProducts = await request.get( '/wp-json/wc/v3/products', { - params: { - search: 'logo', - _fields: [ 'id' ], - }, - } ); - const logoProductsJSON = await logoProducts.json(); - - const groupedProducts = await request.post( - '/wp-json/wc/v3/products/batch', - { - data: { - create: [ - { - name: 'Logo Collection oxo', - date_created_gmt: '2021-09-17T15:50:20', - type: 'grouped', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description: - '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + - 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + - 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n', - short_description: - '

    This is a grouped product.

    \n', - sku: 'logo-collection', - price: '18', - regular_price: '', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: false, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '', - dimensions: { - length: '', - width: '', - height: '', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.clothingJSON.id, - }, - ], - tags: [], - attributes: [], - default_attributes: [], - variations: [], - grouped_products: logoProductsJSON.map( - ( p ) => p.id - ), - menu_order: 0, - related_ids: [], - stock_status: 'instock', - }, - ], - }, - } - ); - const groupedProductsJSON = await groupedProducts.json(); - - return groupedProductsJSON.create; - }; - - const createSampleVariableProducts = async ( - categories, - attributes - ) => { - const description = - '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + - 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + - 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n'; - - const hoodie = await request.post( '/wp-json/wc/v3/products', { - data: { - name: 'Hoodie oxo', - date_created_gmt: '2021-09-18T15:50:19', - type: 'variable', - status: 'publish', - featured: false, - catalog_visibility: 'visible', - description, - short_description: '

    This is a variable product.

    \n', - sku: 'woo-hoodie', - price: '42', - regular_price: '', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '1.5', - dimensions: { - length: '10', - width: '8', - height: '3', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.hoodiesJSON.id, + const accessories = await request.post( + '/wp-json/wc/v3/products/categories', + { + data: { + name: 'Accessories', + parent: clothingJSON.id, }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: true, - options: [ 'Blue', 'Green', 'Red' ], - }, - { - id: 0, - name: 'Logo', - position: 1, - visible: true, - variation: true, - options: [ 'Yes', 'No' ], - }, - ], - default_attributes: [], - grouped_products: [], - menu_order: 0, - stock_status: 'instock', - }, - } ); - const hoodieJSON = await hoodie.json(); + } + ); + const accessoriesJSON = await accessories.json(); - const variationDescription = - '

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. ' + - 'Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. ' + - 'Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. ' + - 'Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. ' + - 'Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. ' + - 'Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.

    \n'; - - const hoodieVariations = await request.post( - `/wp-json/wc/v3/products/${ hoodieJSON.id }/variations/batch`, - { - data: { - create: [ - { - date_created_gmt: '2021-09-19T15:50:20', - description: variationDescription, - sku: 'woo-hoodie-blue-logo', - price: '45', - regular_price: '45', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '1.5', - dimensions: { - length: '10', - width: '8', - height: '3', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Blue', - }, - { - id: 0, - name: 'Logo', - option: 'Yes', - }, - ], - menu_order: 0, - }, - { - date_created_gmt: '2021-09-20T15:50:20', - description: variationDescription, - sku: 'woo-hoodie-blue', - price: '45', - regular_price: '45', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '1.5', - dimensions: { - length: '10', - width: '8', - height: '3', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Blue', - }, - { - id: 0, - name: 'Logo', - option: 'No', - }, - ], - menu_order: 3, - }, - { - date_created_gmt: '2021-09-21T15:50:20', - description: variationDescription, - sku: 'woo-hoodie-green', - price: '45', - regular_price: '45', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '1.5', - dimensions: { - length: '10', - width: '8', - height: '3', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Green', - }, - { - id: 0, - name: 'Logo', - option: 'No', - }, - ], - menu_order: 2, - }, - { - date_created_gmt: '2021-09-22T15:50:19', - description: variationDescription, - sku: 'woo-hoodie-red', - price: '42', - regular_price: '45', - sale_price: '42', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: true, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '1.5', - dimensions: { - length: '10', - width: '8', - height: '3', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Red', - }, - { - id: 0, - name: 'Logo', - option: 'No', - }, - ], - menu_order: 1, - }, - ], - }, - } - ); - const hoodieVariationsJSON = await hoodieVariations.json(); - - const vneck = await request.post( '/wp-json/wc/v3/products', { - data: { - name: 'V-Neck T-Shirt oxo', - date_created_gmt: '2021-09-23T15:50:19', - type: 'variable', - status: 'publish', - featured: true, - catalog_visibility: 'visible', - description, - short_description: '

    This is a variable product.

    \n', - sku: 'woo-vneck-tee', - price: '15', - regular_price: '', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - purchasable: true, - total_sales: 0, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - external_url: '', - button_text: '', - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - sold_individually: false, - weight: '0.5', - dimensions: { - length: '24', - width: '1', - height: '2', - }, - shipping_required: true, - shipping_taxable: true, - shipping_class: '', - reviews_allowed: true, - average_rating: '0.00', - rating_count: 0, - upsell_ids: [], - cross_sell_ids: [], - parent_id: 0, - purchase_note: '', - categories: [ - { - id: categories.tshirtsJSON.id, + const hoodies = await request.post( + '/wp-json/wc/v3/products/categories', + { + data: { + name: 'Hoodies', + parent: clothingJSON.id, }, - ], - tags: [], - attributes: [ - { - id: attributes.colorJSON.id, - position: 0, - visible: true, - variation: true, - options: [ 'Blue', 'Green', 'Red' ], - }, - { - id: attributes.sizeJSON.id, - position: 1, - visible: true, - variation: true, - options: [ 'Large', 'Medium', 'Small' ], - }, - ], - default_attributes: [], - grouped_products: [], - menu_order: 0, - stock_status: 'instock', - }, - } ); - const vneckJSON = await vneck.json(); + } + ); + const hoodiesJSON = await hoodies.json(); - const vneckVariations = await request.post( - `/wp-json/wc/v3/products/${ vneckJSON.id }/variations/batch`, - { - data: { - create: [ - { - date_created_gmt: '2021-09-24T15:50:19', - description: variationDescription, - sku: 'woo-vneck-tee-blue', - price: '15', - regular_price: '15', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '0.5', - dimensions: { - length: '24', - width: '1', - height: '2', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Blue', - }, - ], - menu_order: 0, - }, - { - date_created_gmt: '2021-09-25T15:50:19', - description: variationDescription, - sku: 'woo-vneck-tee-green', - price: '20', - regular_price: '20', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '0.5', - dimensions: { - length: '24', - width: '1', - height: '2', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Green', - }, - ], - menu_order: 0, - }, - { - date_created_gmt: '2021-09-26T15:50:19', - description: variationDescription, - sku: 'woo-vneck-tee-red', - price: '20', - regular_price: '20', - sale_price: '', - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - on_sale: false, - status: 'publish', - purchasable: true, - virtual: false, - downloadable: false, - downloads: [], - download_limit: 0, - download_expiry: 0, - tax_status: 'taxable', - tax_class: '', - manage_stock: false, - stock_quantity: null, - stock_status: 'instock', - backorders: 'no', - backorders_allowed: false, - backordered: false, - low_stock_amount: null, - weight: '0.5', - dimensions: { - length: '24', - width: '1', - height: '2', - }, - shipping_class: '', - attributes: [ - { - id: attributes.colorJSON.id, - option: 'Red', - }, - ], - menu_order: 0, - }, - ], - }, - } - ); - const vneckVariationsJSON = await vneckVariations.json(); + const tshirts = await request.post( + '/wp-json/wc/v3/products/categories', + { + data: { + name: 'Tshirts', + parent: clothingJSON.id, + }, + } + ); + const tshirtsJSON = await tshirts.json(); - return { - hoodieJSON, - hoodieVariations: hoodieVariationsJSON.create, - vneckJSON, - vneckVariations: vneckVariationsJSON.create, + const decor = await request.post( + '/wp-json/wc/v3/products/categories', + { + data: { + name: 'Decor', + }, + } + ); + const decorJSON = await decor.json(); + + const music = await request.post( + '/wp-json/wc/v3/products/categories', + { + data: { + name: 'Music', + }, + } + ); + const musicJSON = await music.json(); + + return { + clothingJSON, + accessoriesJSON, + hoodiesJSON, + tshirtsJSON, + decorJSON, + musicJSON, + }; }; - }; - const createSampleHierarchicalProducts = async () => { - const parent = await request.post( '/wp-json/wc/v3/products', { - data: { - name: 'Parent Product oxo', - date_created_gmt: '2021-09-27T15:50:19', - }, - } ); - const parentJSON = await parent.json(); + const createSampleAttributes = async () => { + const color = await request.post( + '/wp-json/wc/v3/products/attributes', + { + data: { + name: 'Color', + }, + } + ); + const colorJSON = await color.json(); - const child = await request.post( '/wp-json/wc/v3/products', { - data: { - name: 'Child Product oxo', - parent_id: parentJSON.id, - date_created_gmt: '2021-09-28T15:50:19', - }, - } ); - const childJSON = await child.json(); + const size = await request.post( + '/wp-json/wc/v3/products/attributes', + { + data: { + name: 'Size', + }, + } + ); + const sizeJSON = await size.json(); - return { - parentJSON, - childJSON, + const colorNames = [ 'Blue', 'Gray', 'Green', 'Red', 'Yellow' ]; + + const colorNamesObjectArray = colorNames.map( ( name ) => ( { + name, + } ) ); + + const colors = await request.post( + `/wp-json/wc/v3/products/attributes/${ colorJSON.id }/terms/batch`, + { + data: { + create: colorNamesObjectArray, + }, + } + ); + + const colorsJSON = await colors.json(); + + const sizeNames = [ 'Large', 'Medium', 'Small' ]; + + const sizeNamesObjectArray = sizeNames.map( ( name ) => ( { + name, + } ) ); + + const sizes = await request.post( + `/wp-json/wc/v3/products/attributes/${ sizeJSON.id }/terms/batch`, + { + data: { + create: sizeNamesObjectArray, + }, + } + ); + const sizesJSON = await sizes.json(); + + return { + colorJSON, + colors: colorsJSON.create, + sizeJSON, + sizes: sizesJSON.create, + }; }; - }; - const createSampleProductReviews = async ( simpleProducts ) => { - const cap = simpleProducts.find( ( p ) => p.name === 'Cap oxo' ); - - const shirt = simpleProducts.find( - ( p ) => p.name === 'T-Shirt oxo' - ); - - const sunglasses = simpleProducts.find( - ( p ) => p.name === 'Sunglasses oxo' - ); - - const review1 = await request.post( - '/wp-json/wc/v3/products/reviews', - { - data: { - product_id: cap.id, - rating: 3, - review: 'Decent cap.', - reviewer: 'John Doe', - reviewer_email: 'john.doe@example.com', - }, - } - ); - const review1JSON = await review1.json(); - - // We need to update the review in order for the product's - // average_rating to be recalculated. - // See: https://github.com/woocommerce/woocommerce/issues/29906. - //await updateProductReview(review1.id); - await request.post( - `/wp-json/wc/v3/products/reviews/${ review1JSON.id }`, - { - data: {}, - } - ); - - const review2 = await request.post( - '/wp-json/wc/v3/products/reviews', - { - data: { - product_id: shirt.id, - rating: 5, - review: 'The BEST shirt ever!!', - reviewer: 'Shannon Smith', - reviewer_email: 'shannon.smith@example.com', - }, - } - ); - const review2JSON = await review2.json(); - - //await updateProductReview(review2.id); - await request.post( - `/wp-json/wc/v3/products/reviews/${ review2JSON.id }`, - { - data: {}, - } - ); - - const review3 = await request.post( - '/wp-json/wc/v3/products/reviews', - { - data: { - product_id: sunglasses.id, - rating: 1, - review: 'These are way too expensive.', - reviewer: 'Tim Frugalman', - reviewer_email: 'timmyfrufru@example.com', - }, - } - ); - const review3JSON = await review3.json(); - - await request.post( - `/wp-json/wc/v3/products/reviews/${ review3JSON.id }`, - { - data: {}, - } - ); - - return [ review1JSON.id, review2JSON.id, review3JSON.id ]; - }; - - const createSampleProductOrders = async ( simpleProducts ) => { - const single = simpleProducts.find( - ( p ) => p.name === 'Single oxo' - ); - const beanie = simpleProducts.find( - ( p ) => p.name === 'Beanie with Logo oxo' - ); - const shirt = simpleProducts.find( - ( p ) => p.name === 'T-Shirt oxo' - ); - - const order1 = await request.post( '/wp-json/wc/v3/orders', { - data: { - set_paid: true, - status: 'completed', - line_items: [ - { - product_id: single.id, - quantity: 2, + const createSampleTags = async () => { + const cool = await request.post( + '/wp-json/wc/v3/products/tags', + { + data: { + name: 'Cool', }, - { - product_id: beanie.id, - quantity: 3, + } + ); + const coolJSON = await cool.json(); + + return { + coolJSON, + }; + }; + + const createSampleShippingClasses = async () => { + const freight = await request.post( + '/wp-json/wc/v3/products/shipping_classes', + { + data: { + name: 'Freight', }, + } + ); + const freightJSON = await freight.json(); + + return { + freightJSON, + }; + }; + + const createSampleTaxClasses = async () => { + //check to see if Reduced Rate tax class exists - if not, create it + let reducedRate = await request.get( + '/wp-json/wc/v3/taxes/classes/reduced-rate' + ); + let reducedRateJSON = await reducedRate.json(); + expect( Array.isArray( reducedRateJSON ) ).toBe( true ); + + //if tax class does not exist then create it + if ( reducedRateJSON.length < 1 ) { + reducedRate = await request.post( + '/wp-json/wc/v3/taxes/classes', { - product_id: shirt.id, - quantity: 1, - }, - ], - }, - } ); - const orderJSON = await order1.json(); + data: { + name: 'Reduced Rate', + }, + } + ); + reducedRateJSON = await reducedRate.json(); + return { reducedRateJSON }; + } - return [ orderJSON ]; - }; + // return an empty object as nothing new was created so nothing will + // need deleted during cleanup + return {}; + }; - const productsTestSetupCreateSampleData = async () => { - const categories = await createSampleCategories(); - - const attributes = await createSampleAttributes(); - - const tags = await createSampleTags(); - - const shippingClasses = await createSampleShippingClasses(); - - const taxClasses = await createSampleTaxClasses(); - - const simpleProducts = await createSampleSimpleProducts( + const createSampleSimpleProducts = async ( categories, attributes, tags - ); + ) => { + const description = + '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + + 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. ' + + 'Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n'; - const externalProducts = await createSampleExternalProducts( - categories - ); + const simpleProducts = await request.post( + '/wp-json/wc/v3/products/batch', + { + data: { + create: [ + { + name: 'Beanie with Logo oxo', + date_created_gmt: '2021-09-01T15:50:20', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'Woo-beanie-logo', + price: '18', + regular_price: '20', + sale_price: '18', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.2', + dimensions: { + length: '6', + width: '4', + height: '1', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.accessoriesJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Red' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 62, 63, 61, 60 ], + stock_status: 'instock', + }, + { + name: 'T-Shirt with Logo oxo', + date_created_gmt: '2021-09-02T15:50:20', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'Woo-tshirt-logo', + price: '18', + regular_price: '18', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.5', + dimensions: { + length: '10', + width: '12', + height: '0.5', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.tshirtsJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Gray' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 59, 67, 66, 56 ], + stock_status: 'instock', + }, + { + name: 'Single oxo', + date_created_gmt: '2021-09-03T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple, virtual product.

    \n', + sku: 'woo-single', + price: '2', + regular_price: '3', + sale_price: '2', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: true, + downloadable: true, + downloads: [ + { + id: '2579cf07-8b08-4c25-888a-b6258dd1f035', + name: 'Single', + file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', + }, + ], + download_limit: 1, + download_expiry: 1, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '', + dimensions: { + length: '', + width: '', + height: '', + }, + shipping_required: false, + shipping_taxable: false, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.musicJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 68 ], + stock_status: 'instock', + }, + { + name: 'Album oxo', + date_created_gmt: '2021-09-04T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple, virtual product.

    \n', + sku: 'woo-album', + price: '15', + regular_price: '15', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: true, + downloadable: true, + downloads: [ + { + id: 'cc10249f-1de2-44d4-93d3-9f88ae629f76', + name: 'Single 1', + file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', + }, + { + id: 'aea8ef69-ccdc-4d83-8e21-3c395ebb9411', + name: 'Single 2', + file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg', + }, + ], + download_limit: 1, + download_expiry: 1, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '', + dimensions: { + length: '', + width: '', + height: '', + }, + shipping_required: false, + shipping_taxable: false, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.musicJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 69 ], + stock_status: 'instock', + }, + { + name: 'Polo oxo', + date_created_gmt: '2021-09-05T15:50:19', + type: 'simple', + status: 'pending', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-polo', + price: '20', + regular_price: '20', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.8', + dimensions: { + length: '6', + width: '5', + height: '1', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.tshirtsJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Blue' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 59, 56, 66, 76 ], + stock_status: 'instock', + }, + { + name: 'Long Sleeve Tee oxo', + date_created_gmt: '2021-09-06T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-long-sleeve-tee', + price: '25', + regular_price: '25', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '1', + dimensions: { + length: '7', + width: '5', + height: '1', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: 'freight', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.tshirtsJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Green' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 59, 56, 76, 67 ], + stock_status: 'instock', + }, + { + name: 'Hoodie with Zipper oxo', + date_created_gmt: '2021-09-07T15:50:19', + type: 'simple', + status: 'publish', + featured: true, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-hoodie-with-zipper', + price: '45', + regular_price: '45', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '2', + dimensions: { + length: '8', + width: '6', + height: '2', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.hoodiesJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 57, 58 ], + stock_status: 'instock', + }, + { + name: 'Hoodie with Pocket oxo', + date_created_gmt: '2021-09-08T15:50:19', + type: 'simple', + status: 'publish', + featured: true, + catalog_visibility: 'hidden', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-hoodie-with-pocket', + price: '35', + regular_price: '45', + sale_price: '35', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '3', + dimensions: { + length: '10', + width: '8', + height: '2', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.hoodiesJSON.id, + }, + ], + tags: [ + { + id: tags.coolJSON.id, + }, + ], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Gray' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 65, 57, 58 ], + stock_status: 'instock', + }, + { + name: 'Sunglasses oxo', + date_created_gmt: '2021-09-09T15:50:19', + type: 'simple', + status: 'publish', + featured: true, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-sunglasses', + price: '90', + regular_price: '90', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: 'reduced-rate', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.2', + dimensions: { + length: '4', + width: '1.4', + height: '1', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.accessoriesJSON.id, + }, + ], + tags: [ + { + id: tags.coolJSON.id, + }, + ], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 60, 62, 77, 61 ], + stock_status: 'instock', + }, + { + name: 'Cap oxo', + date_created_gmt: '2021-09-10T15:50:19', + type: 'simple', + status: 'publish', + featured: true, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-cap', + price: '16', + regular_price: '18', + sale_price: '16', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.6', + dimensions: { + length: '8', + width: '6.5', + height: '4', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.accessoriesJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Yellow' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 60, 77, 61, 63 ], + stock_status: 'instock', + }, + { + name: 'Belt oxo', + date_created_gmt: '2021-09-12T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-belt', + price: '55', + regular_price: '65', + sale_price: '55', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '1.2', + dimensions: { + length: '12', + width: '2', + height: '1.5', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.accessoriesJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 63, 77, 62, 60 ], + stock_status: 'instock', + }, + { + name: 'Beanie oxo', + date_created_gmt: '2021-09-13T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-beanie', + price: '18', + regular_price: '20', + sale_price: '18', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.2', + dimensions: { + length: '4', + width: '5', + height: '0.5', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.accessoriesJSON.id, + }, + ], + tags: [ + { + id: tags.coolJSON.id, + }, + ], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Red' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 63, 62, 61, 77 ], + stock_status: 'instock', + }, + { + name: 'T-Shirt oxo', + date_created_gmt: '2021-09-14T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-tshirt', + price: '18', + regular_price: '18', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.8', + dimensions: { + length: '8', + width: '6', + height: '1', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.tshirtsJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Gray' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 67, 76, 56, 66 ], + stock_status: 'onbackorder', + }, + { + name: 'Hoodie with Logo oxo', + date_created_gmt: '2021-09-15T15:50:19', + type: 'simple', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a simple product.

    \n', + sku: 'woo-hoodie-with-logo', + price: '45', + regular_price: '45', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '2', + dimensions: { + length: '10', + width: '6', + height: '3', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.hoodiesJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: false, + options: [ 'Blue' ], + }, + ], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [ 57, 65 ], + stock_status: 'instock', + }, + ], + }, + } + ); + const simpleProductsJSON = await simpleProducts.json(); - const groupedProducts = await createSampleGroupedProduct( - categories - ); + return simpleProductsJSON.create; + }; - const variableProducts = await createSampleVariableProducts( + const createSampleExternalProducts = async ( categories ) => { + const externalProducts = await request.post( + '/wp-json/wc/v3/products/batch', + { + data: { + create: [ + { + name: 'WordPress Pennant oxo', + date_created_gmt: '2021-09-16T15:50:20', + type: 'external', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description: + '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + + 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + + 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n', + short_description: + '

    This is an external product.

    \n', + sku: 'wp-pennant', + price: '11.05', + regular_price: '11.05', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: false, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: + 'https://mercantile.wordpress.org/product/wordpress-pennant/', + button_text: + 'Buy on the WordPress swag store!', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '', + dimensions: { + length: '', + width: '', + height: '', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.decorJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: [], + menu_order: 0, + related_ids: [], + stock_status: 'instock', + }, + ], + }, + } + ); + const externalProductsJSON = await externalProducts.json(); + + return externalProductsJSON.create; + }; + + const createSampleGroupedProduct = async ( categories ) => { + const logoProducts = await request.get( + '/wp-json/wc/v3/products', + { + params: { + search: 'logo', + _fields: [ 'id' ], + }, + } + ); + const logoProductsJSON = await logoProducts.json(); + + const groupedProducts = await request.post( + '/wp-json/wc/v3/products/batch', + { + data: { + create: [ + { + name: 'Logo Collection oxo', + date_created_gmt: '2021-09-17T15:50:20', + type: 'grouped', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description: + '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + + 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + + 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n', + short_description: + '

    This is a grouped product.

    \n', + sku: 'logo-collection', + price: '18', + regular_price: '', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: false, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '', + dimensions: { + length: '', + width: '', + height: '', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.clothingJSON.id, + }, + ], + tags: [], + attributes: [], + default_attributes: [], + variations: [], + grouped_products: logoProductsJSON.map( + ( p ) => p.id + ), + menu_order: 0, + related_ids: [], + stock_status: 'instock', + }, + ], + }, + } + ); + const groupedProductsJSON = await groupedProducts.json(); + + return groupedProductsJSON.create; + }; + + const createSampleVariableProducts = async ( categories, attributes - ); + ) => { + const description = + '

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. ' + + 'Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. ' + + 'Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

    \n'; - const hierarchicalProducts = - await createSampleHierarchicalProducts(); + const hoodie = await request.post( '/wp-json/wc/v3/products', { + data: { + name: 'Hoodie oxo', + date_created_gmt: '2021-09-18T15:50:19', + type: 'variable', + status: 'publish', + featured: false, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a variable product.

    \n', + sku: 'woo-hoodie', + price: '42', + regular_price: '', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '1.5', + dimensions: { + length: '10', + width: '8', + height: '3', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.hoodiesJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: true, + options: [ 'Blue', 'Green', 'Red' ], + }, + { + id: 0, + name: 'Logo', + position: 1, + visible: true, + variation: true, + options: [ 'Yes', 'No' ], + }, + ], + default_attributes: [], + grouped_products: [], + menu_order: 0, + stock_status: 'instock', + }, + } ); + const hoodieJSON = await hoodie.json(); - const reviewIds = await createSampleProductReviews( - simpleProducts - ); + const variationDescription = + '

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. ' + + 'Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. ' + + 'Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. ' + + 'Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. ' + + 'Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. ' + + 'Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.

    \n'; - const orders = await createSampleProductOrders( simpleProducts ); + const hoodieVariations = await request.post( + `/wp-json/wc/v3/products/${ hoodieJSON.id }/variations/batch`, + { + data: { + create: [ + { + date_created_gmt: '2021-09-19T15:50:20', + description: variationDescription, + sku: 'woo-hoodie-blue-logo', + price: '45', + regular_price: '45', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '1.5', + dimensions: { + length: '10', + width: '8', + height: '3', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Blue', + }, + { + id: 0, + name: 'Logo', + option: 'Yes', + }, + ], + menu_order: 0, + }, + { + date_created_gmt: '2021-09-20T15:50:20', + description: variationDescription, + sku: 'woo-hoodie-blue', + price: '45', + regular_price: '45', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '1.5', + dimensions: { + length: '10', + width: '8', + height: '3', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Blue', + }, + { + id: 0, + name: 'Logo', + option: 'No', + }, + ], + menu_order: 3, + }, + { + date_created_gmt: '2021-09-21T15:50:20', + description: variationDescription, + sku: 'woo-hoodie-green', + price: '45', + regular_price: '45', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '1.5', + dimensions: { + length: '10', + width: '8', + height: '3', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Green', + }, + { + id: 0, + name: 'Logo', + option: 'No', + }, + ], + menu_order: 2, + }, + { + date_created_gmt: '2021-09-22T15:50:19', + description: variationDescription, + sku: 'woo-hoodie-red', + price: '42', + regular_price: '45', + sale_price: '42', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: true, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '1.5', + dimensions: { + length: '10', + width: '8', + height: '3', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Red', + }, + { + id: 0, + name: 'Logo', + option: 'No', + }, + ], + menu_order: 1, + }, + ], + }, + } + ); + const hoodieVariationsJSON = await hoodieVariations.json(); - return { - categories, - attributes, - tags, - shippingClasses, - taxClasses, - simpleProducts, - externalProducts, - groupedProducts, - variableProducts, - hierarchicalProducts, - reviewIds, - orders, + const vneck = await request.post( '/wp-json/wc/v3/products', { + data: { + name: 'V-Neck T-Shirt oxo', + date_created_gmt: '2021-09-23T15:50:19', + type: 'variable', + status: 'publish', + featured: true, + catalog_visibility: 'visible', + description, + short_description: + '

    This is a variable product.

    \n', + sku: 'woo-vneck-tee', + price: '15', + regular_price: '', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + purchasable: true, + total_sales: 0, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + external_url: '', + button_text: '', + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + sold_individually: false, + weight: '0.5', + dimensions: { + length: '24', + width: '1', + height: '2', + }, + shipping_required: true, + shipping_taxable: true, + shipping_class: '', + reviews_allowed: true, + average_rating: '0.00', + rating_count: 0, + upsell_ids: [], + cross_sell_ids: [], + parent_id: 0, + purchase_note: '', + categories: [ + { + id: categories.tshirtsJSON.id, + }, + ], + tags: [], + attributes: [ + { + id: attributes.colorJSON.id, + position: 0, + visible: true, + variation: true, + options: [ 'Blue', 'Green', 'Red' ], + }, + { + id: attributes.sizeJSON.id, + position: 1, + visible: true, + variation: true, + options: [ 'Large', 'Medium', 'Small' ], + }, + ], + default_attributes: [], + grouped_products: [], + menu_order: 0, + stock_status: 'instock', + }, + } ); + const vneckJSON = await vneck.json(); + + const vneckVariations = await request.post( + `/wp-json/wc/v3/products/${ vneckJSON.id }/variations/batch`, + { + data: { + create: [ + { + date_created_gmt: '2021-09-24T15:50:19', + description: variationDescription, + sku: 'woo-vneck-tee-blue', + price: '15', + regular_price: '15', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '0.5', + dimensions: { + length: '24', + width: '1', + height: '2', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Blue', + }, + ], + menu_order: 0, + }, + { + date_created_gmt: '2021-09-25T15:50:19', + description: variationDescription, + sku: 'woo-vneck-tee-green', + price: '20', + regular_price: '20', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '0.5', + dimensions: { + length: '24', + width: '1', + height: '2', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Green', + }, + ], + menu_order: 0, + }, + { + date_created_gmt: '2021-09-26T15:50:19', + description: variationDescription, + sku: 'woo-vneck-tee-red', + price: '20', + regular_price: '20', + sale_price: '', + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + on_sale: false, + status: 'publish', + purchasable: true, + virtual: false, + downloadable: false, + downloads: [], + download_limit: 0, + download_expiry: 0, + tax_status: 'taxable', + tax_class: '', + manage_stock: false, + stock_quantity: null, + stock_status: 'instock', + backorders: 'no', + backorders_allowed: false, + backordered: false, + low_stock_amount: null, + weight: '0.5', + dimensions: { + length: '24', + width: '1', + height: '2', + }, + shipping_class: '', + attributes: [ + { + id: attributes.colorJSON.id, + option: 'Red', + }, + ], + menu_order: 0, + }, + ], + }, + } + ); + const vneckVariationsJSON = await vneckVariations.json(); + + return { + hoodieJSON, + hoodieVariations: hoodieVariationsJSON.create, + vneckJSON, + vneckVariations: vneckVariationsJSON.create, + }; }; - }; - // create Sample Data function - const createSampleData = async () => { - const testProductData = await productsTestSetupCreateSampleData(); - const orderedProducts = { - pocketHoodie: testProductData.simpleProducts.find( - ( p ) => p.name === 'Hoodie with Pocket oxo' - ), - sunglasses: testProductData.simpleProducts.find( + const createSampleHierarchicalProducts = async () => { + const parent = await request.post( '/wp-json/wc/v3/products', { + data: { + name: 'Parent Product oxo', + date_created_gmt: '2021-09-27T15:50:19', + }, + } ); + const parentJSON = await parent.json(); + + const child = await request.post( '/wp-json/wc/v3/products', { + data: { + name: 'Child Product oxo', + parent_id: parentJSON.id, + date_created_gmt: '2021-09-28T15:50:19', + }, + } ); + const childJSON = await child.json(); + + return { + parentJSON, + childJSON, + }; + }; + + const createSampleProductReviews = async ( simpleProducts ) => { + const cap = simpleProducts.find( + ( p ) => p.name === 'Cap oxo' + ); + + const shirt = simpleProducts.find( + ( p ) => p.name === 'T-Shirt oxo' + ); + + const sunglasses = simpleProducts.find( ( p ) => p.name === 'Sunglasses oxo' - ), - beanie: testProductData.simpleProducts.find( - ( p ) => p.name === 'Beanie oxo' - ), - blueVneck: - testProductData.variableProducts.vneckVariations.find( - ( p ) => p.sku === 'woo-vneck-tee-blue' + ); + + const review1 = await request.post( + '/wp-json/wc/v3/products/reviews', + { + data: { + product_id: cap.id, + rating: 3, + review: 'Decent cap.', + reviewer: 'John Doe', + reviewer_email: 'john.doe@example.com', + }, + } + ); + const review1JSON = await review1.json(); + + // We need to update the review in order for the product's + // average_rating to be recalculated. + // See: https://github.com/woocommerce/woocommerce/issues/29906. + //await updateProductReview(review1.id); + await request.post( + `/wp-json/wc/v3/products/reviews/${ review1JSON.id }`, + { + data: {}, + } + ); + + const review2 = await request.post( + '/wp-json/wc/v3/products/reviews', + { + data: { + product_id: shirt.id, + rating: 5, + review: 'The BEST shirt ever!!', + reviewer: 'Shannon Smith', + reviewer_email: 'shannon.smith@example.com', + }, + } + ); + const review2JSON = await review2.json(); + + //await updateProductReview(review2.id); + await request.post( + `/wp-json/wc/v3/products/reviews/${ review2JSON.id }`, + { + data: {}, + } + ); + + const review3 = await request.post( + '/wp-json/wc/v3/products/reviews', + { + data: { + product_id: sunglasses.id, + rating: 1, + review: 'These are way too expensive.', + reviewer: 'Tim Frugalman', + reviewer_email: 'timmyfrufru@example.com', + }, + } + ); + const review3JSON = await review3.json(); + + await request.post( + `/wp-json/wc/v3/products/reviews/${ review3JSON.id }`, + { + data: {}, + } + ); + + return [ review1JSON.id, review2JSON.id, review3JSON.id ]; + }; + + const createSampleProductOrders = async ( simpleProducts ) => { + const single = simpleProducts.find( + ( p ) => p.name === 'Single oxo' + ); + const beanie = simpleProducts.find( + ( p ) => p.name === 'Beanie with Logo oxo' + ); + const shirt = simpleProducts.find( + ( p ) => p.name === 'T-Shirt oxo' + ); + + const order1 = await request.post( '/wp-json/wc/v3/orders', { + data: { + set_paid: true, + status: 'completed', + line_items: [ + { + product_id: single.id, + quantity: 2, + }, + { + product_id: beanie.id, + quantity: 3, + }, + { + product_id: shirt.id, + quantity: 1, + }, + ], + }, + } ); + const orderJSON = await order1.json(); + + return [ orderJSON ]; + }; + + const productsTestSetupCreateSampleData = async () => { + const categories = await createSampleCategories(); + + const attributes = await createSampleAttributes(); + + const tags = await createSampleTags(); + + const shippingClasses = await createSampleShippingClasses(); + + const taxClasses = await createSampleTaxClasses(); + + const simpleProducts = await createSampleSimpleProducts( + categories, + attributes, + tags + ); + + const externalProducts = await createSampleExternalProducts( + categories + ); + + const groupedProducts = await createSampleGroupedProduct( + categories + ); + + const variableProducts = await createSampleVariableProducts( + categories, + attributes + ); + + const hierarchicalProducts = + await createSampleHierarchicalProducts(); + + const reviewIds = await createSampleProductReviews( + simpleProducts + ); + + const orders = await createSampleProductOrders( + simpleProducts + ); + + return { + categories, + attributes, + tags, + shippingClasses, + taxClasses, + simpleProducts, + externalProducts, + groupedProducts, + variableProducts, + hierarchicalProducts, + reviewIds, + orders, + }; + }; + + // create Sample Data function + const createSampleData = async () => { + const testProductData = + await productsTestSetupCreateSampleData(); + const orderedProducts = { + pocketHoodie: testProductData.simpleProducts.find( + ( p ) => p.name === 'Hoodie with Pocket oxo' ), - pennant: testProductData.externalProducts[ 0 ], - }; + sunglasses: testProductData.simpleProducts.find( + ( p ) => p.name === 'Sunglasses oxo' + ), + beanie: testProductData.simpleProducts.find( + ( p ) => p.name === 'Beanie oxo' + ), + blueVneck: + testProductData.variableProducts.vneckVariations.find( + ( p ) => p.sku === 'woo-vneck-tee-blue' + ), + pennant: testProductData.externalProducts[ 0 ], + }; - const johnAddress = { - first_name: 'John', - last_name: 'Doe', - company: 'Automattic', - country: 'US', - address_1: '60 29th Street', - address_2: '#343', - city: 'San Francisco', - state: 'CA', - postcode: '94110', - phone: '123456789', - }; - const tinaAddress = { - first_name: 'Tina', - last_name: 'Clark', - company: 'Automattic', - country: 'US', - address_1: 'Oxford Ave', - address_2: '', - city: 'Buffalo', - state: 'NY', - postcode: '14201', - phone: '123456789', - }; - const guestShippingAddress = { - first_name: 'Ano', - last_name: 'Nymous', - company: '', - country: 'US', - address_1: '0 Incognito St', - address_2: '', - city: 'Erie', - state: 'PA', - postcode: '16515', - phone: '123456789', - }; - const guestBillingAddress = { - first_name: 'Ben', - last_name: 'Efactor', - company: '', - country: 'US', - address_1: '200 W University Avenue', - address_2: '', - city: 'Gainesville', - state: 'FL', - postcode: '32601', - phone: '123456789', - email: 'ben.efactor@email.net', - }; - - const john = await request.post( '/wp-json/wc/v3/customers', { - data: { + const johnAddress = { first_name: 'John', last_name: 'Doe', - username: 'john.doe', - email: 'john.doe@example.com', - billing: { - ...johnAddress, - email: 'john.doe@example.com', - }, - shipping: johnAddress, - }, - } ); - const johnJSON = await john.json(); - - const tina = await request.post( '/wp-json/wc/v3/customers', { - data: { + company: 'Automattic', + country: 'US', + address_1: '60 29th Street', + address_2: '#343', + city: 'San Francisco', + state: 'CA', + postcode: '94110', + phone: '123456789', + }; + const tinaAddress = { first_name: 'Tina', last_name: 'Clark', - username: 'tina.clark', - email: 'tina.clark@example.com', - billing: { - ...tinaAddress, - email: 'tina.clark@example.com', - }, - shipping: tinaAddress, - }, - } ); - const tinaJSON = await tina.json(); + company: 'Automattic', + country: 'US', + address_1: 'Oxford Ave', + address_2: '', + city: 'Buffalo', + state: 'NY', + postcode: '14201', + phone: '123456789', + }; + const guestShippingAddress = { + first_name: 'Ano', + last_name: 'Nymous', + company: '', + country: 'US', + address_1: '0 Incognito St', + address_2: '', + city: 'Erie', + state: 'PA', + postcode: '16515', + phone: '123456789', + }; + const guestBillingAddress = { + first_name: 'Ben', + last_name: 'Efactor', + company: '', + country: 'US', + address_1: '200 W University Avenue', + address_2: '', + city: 'Gainesville', + state: 'FL', + postcode: '32601', + phone: '123456789', + email: 'ben.efactor@email.net', + }; - const orderBaseData = { - payment_method: 'cod', - payment_method_title: 'Cash on Delivery', - status: 'processing', - set_paid: false, - currency: 'USD', - customer_id: 0, - }; - - const orders = []; - // Have "John" order all products. - Object.values( orderedProducts ).forEach( async ( product ) => { - const order2 = await request.post( '/wp-json/wc/v3/orders', { + const john = await request.post( '/wp-json/wc/v3/customers', { data: { - ...orderBaseData, - customer_id: johnJSON.id, + first_name: 'John', + last_name: 'Doe', + username: 'john.doe', + email: 'john.doe@example.com', billing: { ...johnAddress, email: 'john.doe@example.com', }, shipping: johnAddress, - line_items: [ - { - product_id: product.id, - quantity: 1, - }, - ], }, } ); - const orderJSON = await order2.json(); + const johnJSON = await john.json(); - orders.push( orderJSON ); - } ); - - // Have "Tina" order some sunglasses and make a child order. - // This somewhat resembles a subscription renewal, but we're just testing the `parent` field. - const order2 = await request.post( '/wp-json/wc/v3/orders', { - data: { - ...orderBaseData, - status: 'completed', - set_paid: true, - customer_id: tinaJSON.id, - billing: { - ...tinaAddress, - email: 'tina.clark@example.com', - }, - shipping: tinaAddress, - line_items: [ - { - product_id: orderedProducts.sunglasses.id, - quantity: 1, - }, - ], - }, - } ); - const order2JSON = await order2.json(); - - orders.push( order2JSON ); - - // create child order by referencing a parent_id - const order3 = await request.post( '/wp-json/wc/v3/orders', { - data: { - ...orderBaseData, - parent_id: order2JSON.id, - customer_id: tinaJSON.id, - billing: { - ...tinaAddress, - email: 'tina.clark@example.com', - }, - shipping: tinaAddress, - line_items: [ - { - product_id: orderedProducts.sunglasses.id, - quantity: 1, - }, - ], - }, - } ); - const order3JSON = await order3.json(); - - orders.push( order3JSON ); - - // Guest order. - const guestOrder = await request.post( '/wp-json/wc/v3/orders', { - data: { - ...orderBaseData, - billing: guestBillingAddress, - shipping: guestShippingAddress, - line_items: [ - { - product_id: orderedProducts.pennant.id, - quantity: 2, - }, - { - product_id: orderedProducts.beanie.id, - quantity: 1, - }, - ], - }, - } ); - const guestOrderJSON = await guestOrder.json(); - - // Create an order with all possible numerical fields (taxes, fees, refunds, etc). - await request.put( - '/wp-json/wc/v3/settings/general/woocommerce_calc_taxes', - { + const tina = await request.post( '/wp-json/wc/v3/customers', { data: { - value: 'yes', + first_name: 'Tina', + last_name: 'Clark', + username: 'tina.clark', + email: 'tina.clark@example.com', + billing: { + ...tinaAddress, + email: 'tina.clark@example.com', + }, + shipping: tinaAddress, }, - } - ); + } ); + const tinaJSON = await tina.json(); - await request.post( '/wp-json/wc/v3/taxes', { - data: { - country: '*', - state: '*', - postcode: '*', - city: '*', - name: 'Tax', - rate: '5.5', - shipping: true, - }, - } ); + const orderBaseData = { + payment_method: 'cod', + payment_method_title: 'Cash on Delivery', + status: 'processing', + set_paid: false, + currency: 'USD', + customer_id: 0, + }; - const coupon = await request.post( '/wp-json/wc/v3/coupons', { - data: { - code: 'save5', - amount: '5', - }, - } ); - const couponJSON = await coupon.json(); - - const order4 = await request.post( '/wp-json/wc/v3/orders', { - data: { - ...orderBaseData, - line_items: [ + const orders = []; + // Have "John" order all products. + Object.values( orderedProducts ).forEach( async ( product ) => { + const order2 = await request.post( + '/wp-json/wc/v3/orders', { - product_id: orderedProducts.blueVneck.id, - quantity: 1, - }, - ], - coupon_lines: [ - { - code: 'save5', - }, - ], - shipping_lines: [ - { - method_id: 'flat_rate', - total: '5.00', - }, - ], - fee_lines: [ - { - total: '1.00', - name: 'Test Fee', - }, - ], - }, - } ); - const order4JSON = await order4.json(); - - await request.post( - `/wp-json/wc/v3/orders/${ order4JSON.id }/refunds`, - { - data: { - api_refund: false, // Prevent an actual refund request (fails with CoD), - line_items: [ - { - id: order4JSON.line_items[ 0 ].id, - quantity: 1, - refund_total: order4JSON.line_items[ 0 ].total, - refund_tax: [ + data: { + ...orderBaseData, + customer_id: johnJSON.id, + billing: { + ...johnAddress, + email: 'john.doe@example.com', + }, + shipping: johnAddress, + line_items: [ { - id: order4JSON.line_items[ 0 ] - .taxes[ 0 ].id, - refund_total: - order4JSON.line_items[ 0 ] - .total_tax, + product_id: product.id, + quantity: 1, }, ], }, + } + ); + const orderJSON = await order2.json(); + + orders.push( orderJSON ); + } ); + + // Have "Tina" order some sunglasses and make a child order. + // This somewhat resembles a subscription renewal, but we're just testing the `parent` field. + const order2 = await request.post( '/wp-json/wc/v3/orders', { + data: { + ...orderBaseData, + status: 'completed', + set_paid: true, + customer_id: tinaJSON.id, + billing: { + ...tinaAddress, + email: 'tina.clark@example.com', + }, + shipping: tinaAddress, + line_items: [ + { + product_id: orderedProducts.sunglasses.id, + quantity: 1, + }, ], }, - } - ); - orders.push( order4JSON ); + } ); + const order2JSON = await order2.json(); - return { - customers: { - johnJSON, - tinaJSON, - }, - orders, - precisionOrder: order4JSON, - hierarchicalOrders: { - parent: order2JSON, - child: order3JSON, - }, - guestOrderJSON, - testProductData, - couponJSON, + orders.push( order2JSON ); + + // create child order by referencing a parent_id + const order3 = await request.post( '/wp-json/wc/v3/orders', { + data: { + ...orderBaseData, + parent_id: order2JSON.id, + customer_id: tinaJSON.id, + billing: { + ...tinaAddress, + email: 'tina.clark@example.com', + }, + shipping: tinaAddress, + line_items: [ + { + product_id: orderedProducts.sunglasses.id, + quantity: 1, + }, + ], + }, + } ); + const order3JSON = await order3.json(); + + orders.push( order3JSON ); + + // Guest order. + const guestOrder = await request.post( + '/wp-json/wc/v3/orders', + { + data: { + ...orderBaseData, + billing: guestBillingAddress, + shipping: guestShippingAddress, + line_items: [ + { + product_id: orderedProducts.pennant.id, + quantity: 2, + }, + { + product_id: orderedProducts.beanie.id, + quantity: 1, + }, + ], + }, + } + ); + const guestOrderJSON = await guestOrder.json(); + + // Create an order with all possible numerical fields (taxes, fees, refunds, etc). + await request.put( + '/wp-json/wc/v3/settings/general/woocommerce_calc_taxes', + { + data: { + value: 'yes', + }, + } + ); + + await request.post( '/wp-json/wc/v3/taxes', { + data: { + country: '*', + state: '*', + postcode: '*', + city: '*', + name: 'Tax', + rate: '5.5', + shipping: true, + }, + } ); + + const coupon = await request.post( '/wp-json/wc/v3/coupons', { + data: { + code: 'save5', + amount: '5', + }, + } ); + const couponJSON = await coupon.json(); + + const order4 = await request.post( '/wp-json/wc/v3/orders', { + data: { + ...orderBaseData, + line_items: [ + { + product_id: orderedProducts.blueVneck.id, + quantity: 1, + }, + ], + coupon_lines: [ + { + code: 'save5', + }, + ], + shipping_lines: [ + { + method_id: 'flat_rate', + total: '5.00', + }, + ], + fee_lines: [ + { + total: '1.00', + name: 'Test Fee', + }, + ], + }, + } ); + const order4JSON = await order4.json(); + + await request.post( + `/wp-json/wc/v3/orders/${ order4JSON.id }/refunds`, + { + data: { + api_refund: false, // Prevent an actual refund request (fails with CoD), + line_items: [ + { + id: order4JSON.line_items[ 0 ].id, + quantity: 1, + refund_total: + order4JSON.line_items[ 0 ].total, + refund_tax: [ + { + id: order4JSON.line_items[ 0 ] + .taxes[ 0 ].id, + refund_total: + order4JSON.line_items[ 0 ] + .total_tax, + }, + ], + }, + ], + }, + } + ); + orders.push( order4JSON ); + + return { + customers: { + johnJSON, + tinaJSON, + }, + orders, + precisionOrder: order4JSON, + hierarchicalOrders: { + parent: order2JSON, + child: order3JSON, + }, + guestOrderJSON, + testProductData, + couponJSON, + }; }; - }; - sampleData = await createSampleData(); - }, 100000 ); + sampleData = await createSampleData(); + }, 100000 ); - test.afterAll( async ( { request } ) => { - const productsTestSetupDeleteSampleData = async ( _sampleData ) => { - const { - categories, - attributes, - tags, - shippingClasses, - taxClasses, - simpleProducts, - externalProducts, - groupedProducts, - variableProducts, - hierarchicalProducts, - orders, - } = _sampleData; + test.afterAll( async ( { request } ) => { + const productsTestSetupDeleteSampleData = async ( _sampleData ) => { + const { + categories, + attributes, + tags, + shippingClasses, + taxClasses, + simpleProducts, + externalProducts, + groupedProducts, + variableProducts, + hierarchicalProducts, + orders, + } = _sampleData; - const productIds = [] - .concat( simpleProducts.map( ( p ) => p.id ) ) - .concat( externalProducts.map( ( p ) => p.id ) ) - .concat( groupedProducts.map( ( p ) => p.id ) ) - .concat( [ - variableProducts.hoodieJSON.id, - variableProducts.vneckJSON.id, - ] ) - .concat( [ - hierarchicalProducts.parentJSON.id, - hierarchicalProducts.childJSON.id, - ] ); + const productIds = [] + .concat( simpleProducts.map( ( p ) => p.id ) ) + .concat( externalProducts.map( ( p ) => p.id ) ) + .concat( groupedProducts.map( ( p ) => p.id ) ) + .concat( [ + variableProducts.hoodieJSON.id, + variableProducts.vneckJSON.id, + ] ) + .concat( [ + hierarchicalProducts.parentJSON.id, + hierarchicalProducts.childJSON.id, + ] ); - for ( const _order of orders ) { - await request.delete( `/wp-json/wc/v3/orders/${ _order.id }`, { - data: { - force: true, - }, - } ); - } - - for ( const productId of productIds ) { - await request.delete( - `/wp-json/wc/v3/products/${ productId }`, - { - data: { - force: true, - }, - } - ); - } - - await request.delete( - `/wp-json/wc/v3/products/attributes/${ attributes.colorJSON.id }`, - { - data: { - force: true, - }, + for ( const _order of orders ) { + await request.delete( + `/wp-json/wc/v3/orders/${ _order.id }`, + { + data: { + force: true, + }, + } + ); } - ); - await request.delete( - `/wp-json/wc/v3/products/attributes/${ attributes.sizeJSON.id }`, - { - data: { - force: true, - }, + for ( const productId of productIds ) { + await request.delete( + `/wp-json/wc/v3/products/${ productId }`, + { + data: { + force: true, + }, + } + ); } - ); - for ( const category of Object.values( categories ) ) { await request.delete( - `/wp-json/wc/v3/products/categories/${ category.id }`, + `/wp-json/wc/v3/products/attributes/${ attributes.colorJSON.id }`, { data: { force: true, }, } ); - } - for ( const tag of Object.values( tags ) ) { await request.delete( - `/wp-json/wc/v3/products/tags/${ tag.id }`, + `/wp-json/wc/v3/products/attributes/${ attributes.sizeJSON.id }`, { data: { force: true, }, } ); - } - for ( const shippingClass of Object.values( shippingClasses ) ) { - await request.delete( - `/wp-json/wc/v3/products/shipping_classes/${ shippingClass.id }`, - { - data: { - force: true, - }, - } + for ( const category of Object.values( categories ) ) { + await request.delete( + `/wp-json/wc/v3/products/categories/${ category.id }`, + { + data: { + force: true, + }, + } + ); + } + + for ( const tag of Object.values( tags ) ) { + await request.delete( + `/wp-json/wc/v3/products/tags/${ tag.id }`, + { + data: { + force: true, + }, + } + ); + } + + for ( const shippingClass of Object.values( + shippingClasses + ) ) { + await request.delete( + `/wp-json/wc/v3/products/shipping_classes/${ shippingClass.id }`, + { + data: { + force: true, + }, + } + ); + } + + for ( const taxClass of Object.values( taxClasses ) ) { + await request.delete( + `/wp-json/wc/v3/taxes/classes/${ taxClass.slug }`, + { + data: { + force: true, + }, + } + ); + } + }; + + const deleteSampleData = async ( _sampleData ) => { + await productsTestSetupDeleteSampleData( + _sampleData.testProductData ); - } - for ( const taxClass of Object.values( taxClasses ) ) { - await request.delete( - `/wp-json/wc/v3/taxes/classes/${ taxClass.slug }`, - { - data: { - force: true, - }, - } - ); - } - }; + for ( const _order of _sampleData.orders.concat( [ + _sampleData.guestOrderJSON, + ] ) ) { + await request.delete( + `/wp-json/wc/v3/orders/${ _order.id }`, + { + data: { + force: true, + }, + } + ); + } - const deleteSampleData = async ( _sampleData ) => { - await productsTestSetupDeleteSampleData( - _sampleData.testProductData - ); + for ( const customer of Object.values( + _sampleData.customers + ) ) { + await request.delete( + `/wp-json/wc/v3/customers/${ customer.id }`, + { + data: { + force: true, + }, + } + ); + } + }; - for ( const _order of _sampleData.orders.concat( [ - _sampleData.guestOrderJSON, - ] ) ) { - await request.delete( `/wp-json/wc/v3/orders/${ _order.id }`, { - data: { - force: true, - }, - } ); - } + await deleteSampleData( sampleData ); + }, 10000 ); - for ( const customer of Object.values( _sampleData.customers ) ) { - await request.delete( - `/wp-json/wc/v3/customers/${ customer.id }`, - { - data: { - force: true, - }, - } - ); - } - }; - - await deleteSampleData( sampleData ); - }, 10000 ); - - test( 'can create an order', async ( { request } ) => { - const response = await request.post( '/wp-json/wc/v3/orders', { - data: order, - } ); - const responseJSON = await response.json(); - - expect( response.status() ).toEqual( 201 ); - expect( responseJSON.id ).toBeDefined(); - orderId = responseJSON.id; - - // Validate the data type and verify the order is in a pending state - expect( typeof responseJSON.status ).toBe( 'string' ); - expect( responseJSON.status ).toEqual( 'pending' ); - } ); - - test( 'can retrieve an order', async ( { request } ) => { - const response = await request.get( - `/wp-json/wc/v3/orders/${ orderId }` - ); - const responseJSON = await response.json(); - - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.id ).toEqual( orderId ); - } ); - - test( 'can add shipping and billing contacts to an order', async ( { - request, - } ) => { - // Update the billing and shipping fields on the order - order.billing = updatedCustomerBilling; - order.shipping = updatedCustomerShipping; - - const response = await request.put( - `/wp-json/wc/v3/orders/${ orderId }`, - { + test( 'can create an order', async ( { request } ) => { + const response = await request.post( '/wp-json/wc/v3/orders', { data: order, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - - expect( responseJSON.billing ).toEqual( updatedCustomerBilling ); - expect( responseJSON.shipping ).toEqual( updatedCustomerShipping ); - } ); - - test( 'can permanently delete an order', async ( { request } ) => { - const response = await request.delete( - `/wp-json/wc/v3/orders/${ orderId }`, - { - data: { - force: true, - }, - } - ); - expect( response.status() ).toEqual( 200 ); - - const getOrderResponse = await request.get( - `/wp-json/wc/v3/orders/${ orderId }` - ); - expect( getOrderResponse.status() ).toEqual( 404 ); - } ); - - test.describe( 'List all orders', () => { - const ORDERS_COUNT = 10; - - test( 'pagination', async ( { request } ) => { - const pageSize = 4; - const page1 = await request.get( '/wp-json/wc/v3/orders', { - params: { - per_page: pageSize, - search: 'oxo', - }, } ); - const page1JSON = await page1.json(); + const responseJSON = await response.json(); - const page2 = await request.get( '/wp-json/wc/v3/orders', { - params: { - per_page: pageSize, - page: 2, - search: 'oxo', - }, - } ); - const page2JSON = await page2.json(); + expect( response.status() ).toEqual( 201 ); + expect( responseJSON.id ).toBeDefined(); + orderId = responseJSON.id; - expect( page1.status() ).toEqual( 200 ); - expect( page2.status() ).toEqual( 200 ); - - // Verify total page count. - expect( page1.headers()[ 'x-wp-total' ] ).toEqual( - ORDERS_COUNT.toString() - ); - expect( page1.headers()[ 'x-wp-totalpages' ] ).toEqual( '3' ); - - // Verify we get pageSize'd arrays. - expect( Array.isArray( page1JSON ) ).toBe( true ); - expect( Array.isArray( page2JSON ) ).toBe( true ); - expect( page1JSON ).toHaveLength( pageSize ); - expect( page2JSON ).toHaveLength( pageSize ); - - // Ensure all of the order IDs are unique (no page overlap). - const allOrderIds = page1JSON - .concat( page2JSON ) - .reduce( ( acc, { id } ) => { - acc[ id ] = 1; - return acc; - }, {} ); - expect( Object.keys( allOrderIds ) ).toHaveLength( pageSize * 2 ); - - // Verify that offset takes precedent over page number. - const page2Offset = await request.get( 'wp-json/wc/v3/orders', { - params: { - per_page: pageSize, - page: 2, - offset: pageSize + 1, - search: 'oxo', - }, - } ); - const page2OffsetJSON = await page2Offset.json(); - - // The offset pushes the result set 1 order past the start of page 2. - expect( page2OffsetJSON ).toEqual( - expect.not.arrayContaining( [ - expect.objectContaining( { - id: page2JSON[ 0 ].id, - } ), - ] ) - ); - expect( page2OffsetJSON[ 0 ].id ).toEqual( page2JSON[ 1 ].id ); - - // Verify the last page only has 1 order as we expect. - const lastPage = await request.get( 'wp-json/wc/v3/orders', { - params: { - per_page: pageSize, - page: 3, - search: 'oxo', - }, - } ); - const lastPageJSON = await lastPage.json(); - - expect( Array.isArray( lastPageJSON ) ).toBe( true ); - expect( lastPageJSON ).toHaveLength( 2 ); - - // Verify a page outside the total page count is empty. - const page6 = await request.get( 'wp-json/wc/v3/orders', { - params: { - page: 6, - search: 'oxo', - }, - } ); - const page6JSON = await page6.json(); - - expect( Array.isArray( page6JSON ) ).toBe( true ); - expect( page6JSON ).toHaveLength( 0 ); + // Validate the data type and verify the order is in a pending state + expect( typeof responseJSON.status ).toBe( 'string' ); + expect( responseJSON.status ).toEqual( 'pending' ); } ); - test( 'inclusion / exclusion', async ( { request } ) => { - const allOrders = await request.get( 'wp-json/wc/v3/orders', { - params: { - per_page: 10, - search: 'oxo', - }, - } ); - const allOrdersJSON = await allOrders.json(); - - expect( allOrders.status() ).toEqual( 200 ); - const allOrdersIds = allOrdersJSON.map( ( _order ) => _order.id ); - expect( allOrdersIds ).toHaveLength( ORDERS_COUNT ); - - const ordersToFilter = [ - allOrdersIds[ 0 ], - allOrdersIds[ 2 ], - allOrdersIds[ 4 ], - allOrdersIds[ 7 ], - ]; - - const included = await request.get( 'wp-json/wc/v3/orders', { - params: { - per_page: 20, - include: ordersToFilter.join( ',' ), - }, - } ); - const includedJSON = await included.json(); - - expect( included.status() ).toEqual( 200 ); - expect( includedJSON ).toHaveLength( ordersToFilter.length ); - expect( includedJSON ).toEqual( - expect.arrayContaining( - ordersToFilter.map( ( id ) => - expect.objectContaining( { - id, - } ) - ) - ) + test( 'can retrieve an order', async ( { request } ) => { + const response = await request.get( + `/wp-json/wc/v3/orders/${ orderId }` ); + const responseJSON = await response.json(); - const excluded = await request.get( 'wp-json/wc/v3/orders', { - params: { - per_page: 20, - exclude: ordersToFilter.join( ',' ), - }, - } ); - const excludedJSON = await excluded.json(); - - expect( excluded.status() ).toEqual( 200 ); - expect( excludedJSON.length ).toBeGreaterThanOrEqual( - Number( ORDERS_COUNT - ordersToFilter.length ) - ); - expect( excludedJSON ).toEqual( - expect.not.arrayContaining( - ordersToFilter.map( ( id ) => - expect.objectContaining( { - id, - } ) - ) - ) - ); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.id ).toEqual( orderId ); } ); - test( 'parent', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - parent: sampleData.hierarchicalOrders.parent.id, - }, - } ); - const result1JSON = await result1.json(); + test( 'can add shipping and billing contacts to an order', async ( { + request, + } ) => { + // Update the billing and shipping fields on the order + order.billing = updatedCustomerBilling; + order.shipping = updatedCustomerShipping; - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].id ).toBe( - sampleData.hierarchicalOrders.child.id + const response = await request.put( + `/wp-json/wc/v3/orders/${ orderId }`, + { + data: order, + } ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - parent_exclude: sampleData.hierarchicalOrders.parent.id, - }, - } ); - const result2JSON = await result2.json(); - - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( [ - expect.objectContaining( { - id: sampleData.hierarchicalOrders.child.id, - } ), - ] ) - ); + expect( responseJSON.billing ).toEqual( updatedCustomerBilling ); + expect( responseJSON.shipping ).toEqual( updatedCustomerShipping ); } ); - test( 'status', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - status: 'completed', - search: 'oxo', - }, - } ); - const result1JSON = await result1.json(); - - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 2 ); - expect( result1JSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - status: 'completed', - customer_id: 0, - line_items: expect.arrayContaining( [ - expect.objectContaining( { - name: 'Single oxo', - quantity: 2, - } ), - expect.objectContaining( { - name: 'Beanie with Logo oxo', - quantity: 3, - } ), - expect.objectContaining( { - name: 'T-Shirt oxo', - quantity: 1, - } ), - ] ), - } ), - expect.objectContaining( { - status: 'completed', - customer_id: sampleData.customers.tinaJSON.id, - line_items: expect.arrayContaining( [ - expect.objectContaining( { - name: 'Sunglasses oxo', - quantity: 1, - } ), - ] ), - } ), - ] ) + test( 'can permanently delete an order', async ( { request } ) => { + const response = await request.delete( + `/wp-json/wc/v3/orders/${ orderId }`, + { + data: { + force: true, + }, + } ); + expect( response.status() ).toEqual( 200 ); - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - status: 'processing', - search: 'oxo', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 8 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( - result1JSON.map( ( { id } ) => - expect.objectContaining( { - id, - } ) - ) - ) + const getOrderResponse = await request.get( + `/wp-json/wc/v3/orders/${ orderId }` ); + expect( getOrderResponse.status() ).toEqual( 404 ); } ); - test( 'customer', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - customer: sampleData.customers.johnJSON.id, - }, - } ); - const result1JSON = await result1.json(); + test.describe( 'List all orders', () => { + const ORDERS_COUNT = 10; - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 5 ); - result1JSON.forEach( ( _order ) => - expect( _order ).toEqual( - expect.objectContaining( { - customer_id: sampleData.customers.johnJSON.id, - } ) - ) - ); + test( 'pagination', async ( { request } ) => { + const pageSize = 4; + const page1 = await request.get( '/wp-json/wc/v3/orders', { + params: { + per_page: pageSize, + search: 'oxo', + }, + } ); + const page1JSON = await page1.json(); - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - customer: 0, - search: 'oxo', - }, - } ); - const result2JSON = await result2.json(); + const page2 = await request.get( '/wp-json/wc/v3/orders', { + params: { + per_page: pageSize, + page: 2, + search: 'oxo', + }, + } ); + const page2JSON = await page2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 3 ); - result2JSON.forEach( ( _order ) => - expect( _order ).toEqual( - expect.objectContaining( { - customer_id: 0, - } ) - ) - ); - } ); + expect( page1.status() ).toEqual( 200 ); + expect( page2.status() ).toEqual( 200 ); - test( 'product', async ( { request } ) => { - const beanie = sampleData.testProductData.simpleProducts.find( - ( p ) => p.name === 'Beanie oxo' - ); - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - product: beanie.id, - }, - } ); - const result1JSON = await result1.json(); - - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 2 ); - result1JSON.forEach( ( _order ) => - expect( _order ).toEqual( - expect.objectContaining( { - line_items: expect.arrayContaining( [ - expect.objectContaining( { - name: 'Beanie oxo', - } ), - ] ), - } ) - ) - ); - } ); - - // NOTE: This does not verify the `taxes` array nested in line items. - // While the precision parameter doesn't affect those values, after some - // discussion it seems `dp` may not be supported in v4 of the API. - test( 'dp (precision)', async ( { request } ) => { - const expectPrecisionToMatch = ( value, dp ) => { - expect( value ).toEqual( - Number.parseFloat( value ).toFixed( dp ) + // Verify total page count. + expect( page1.headers()[ 'x-wp-total' ] ).toEqual( + ORDERS_COUNT.toString() ); - }; + expect( page1.headers()[ 'x-wp-totalpages' ] ).toEqual( '3' ); - const verifyOrderPrecision = ( _order, dp ) => { - expectPrecisionToMatch( _order.discount_total, dp ); - expectPrecisionToMatch( _order.discount_tax, dp ); - expectPrecisionToMatch( _order.shipping_total, dp ); - expectPrecisionToMatch( _order.shipping_tax, dp ); - expectPrecisionToMatch( _order.cart_tax, dp ); - expectPrecisionToMatch( _order.total, dp ); - expectPrecisionToMatch( _order.total_tax, dp ); + // Verify we get pageSize'd arrays. + expect( Array.isArray( page1JSON ) ).toBe( true ); + expect( Array.isArray( page2JSON ) ).toBe( true ); + expect( page1JSON ).toHaveLength( pageSize ); + expect( page2JSON ).toHaveLength( pageSize ); - _order.line_items.forEach( ( lineItem ) => { - expectPrecisionToMatch( lineItem.total, dp ); - expectPrecisionToMatch( lineItem.total_tax, dp ); - } ); + // Ensure all of the order IDs are unique (no page overlap). + const allOrderIds = page1JSON + .concat( page2JSON ) + .reduce( ( acc, { id } ) => { + acc[ id ] = 1; + return acc; + }, {} ); + expect( Object.keys( allOrderIds ) ).toHaveLength( + pageSize * 2 + ); - _order.tax_lines.forEach( ( taxLine ) => { - expectPrecisionToMatch( taxLine.tax_total, dp ); - expectPrecisionToMatch( taxLine.shipping_tax_total, dp ); - } ); - - _order.shipping_lines.forEach( ( shippingLine ) => { - expectPrecisionToMatch( shippingLine.total, dp ); - expectPrecisionToMatch( shippingLine.total_tax, dp ); - } ); - - _order.fee_lines.forEach( ( feeLine ) => { - expectPrecisionToMatch( feeLine.total, dp ); - expectPrecisionToMatch( feeLine.total_tax, dp ); - } ); - - _order.refunds.forEach( ( refund ) => { - expectPrecisionToMatch( refund.total, dp ); - } ); - }; - - const result1 = await request.get( - `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }`, - { + // Verify that offset takes precedent over page number. + const page2Offset = await request.get( 'wp-json/wc/v3/orders', { params: { - dp: 1, + per_page: pageSize, + page: 2, + offset: pageSize + 1, + search: 'oxo', }, - } - ); - const result1JSON = await result1.json(); + } ); + const page2OffsetJSON = await page2Offset.json(); - expect( result1.status() ).toEqual( 200 ); - verifyOrderPrecision( result1JSON, 1 ); + // The offset pushes the result set 1 order past the start of page 2. + expect( page2OffsetJSON ).toEqual( + expect.not.arrayContaining( [ + expect.objectContaining( { + id: page2JSON[ 0 ].id, + } ), + ] ) + ); + expect( page2OffsetJSON[ 0 ].id ).toEqual( page2JSON[ 1 ].id ); - const result2 = await request.get( - `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }`, - { + // Verify the last page only has 1 order as we expect. + const lastPage = await request.get( 'wp-json/wc/v3/orders', { params: { - dp: 3, + per_page: pageSize, + page: 3, + search: 'oxo', }, - } - ); - const result2JSON = await result2.json(); + } ); + const lastPageJSON = await lastPage.json(); - expect( result2.status() ).toEqual( 200 ); - verifyOrderPrecision( result2JSON, 3 ); + expect( Array.isArray( lastPageJSON ) ).toBe( true ); + expect( lastPageJSON ).toHaveLength( 2 ); - const result3 = await request.get( - `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }` - ); - const result3JSON = await result3.json(); + // Verify a page outside the total page count is empty. + const page6 = await request.get( 'wp-json/wc/v3/orders', { + params: { + page: 6, + search: 'oxo', + }, + } ); + const page6JSON = await page6.json(); - expect( result3.status() ).toEqual( 200 ); - verifyOrderPrecision( result3JSON, 2 ); // The default value for 'dp' is 2. - } ); - - test( 'search', async ( { request } ) => { - // By default, 'search' looks in: - // - _billing_address_index - // - _shipping_address_index - // - _billing_last_name - // - _billing_email - // - order_item_name - - // Test billing email. - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - search: 'example.com', - }, + expect( Array.isArray( page6JSON ) ).toBe( true ); + expect( page6JSON ).toHaveLength( 0 ); } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON.length ).toBeGreaterThanOrEqual( 1 ); - result1JSON.forEach( ( _order ) => - expect( _order.billing.email ).toContain( 'example.com' ) - ); + test( 'inclusion / exclusion', async ( { request } ) => { + const allOrders = await request.get( 'wp-json/wc/v3/orders', { + params: { + per_page: 10, + search: 'oxo', + }, + } ); + const allOrdersJSON = await allOrders.json(); - // Test billing address. - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - search: 'gainesville', - }, - } ); - const result2JSON = await result2.json(); + expect( allOrders.status() ).toEqual( 200 ); + const allOrdersIds = allOrdersJSON.map( + ( _order ) => _order.id + ); + expect( allOrdersIds ).toHaveLength( ORDERS_COUNT ); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 1 ); - expect( result2JSON[ 0 ].id ).toEqual( - sampleData.guestOrderJSON.id - ); + const ordersToFilter = [ + allOrdersIds[ 0 ], + allOrdersIds[ 2 ], + allOrdersIds[ 4 ], + allOrdersIds[ 7 ], + ]; - // Test shipping address. - const result3 = await request.get( 'wp-json/wc/v3/orders', { - params: { - search: 'Incognito', - }, - } ); - const result3JSON = await result3.json(); + const included = await request.get( 'wp-json/wc/v3/orders', { + params: { + per_page: 20, + include: ordersToFilter.join( ',' ), + }, + } ); + const includedJSON = await included.json(); - expect( result3.status() ).toEqual( 200 ); - expect( result3JSON ).toHaveLength( 1 ); - expect( result3JSON[ 0 ].id ).toEqual( - sampleData.guestOrderJSON.id - ); - - // Test billing last name. - const result4 = await request.get( 'wp-json/wc/v3/orders', { - params: { - search: 'Doe', - }, - } ); - const result4JSON = await result4.json(); - - expect( result4.status() ).toEqual( 200 ); - expect( result4JSON.length ).toBeGreaterThanOrEqual( 1 ); - result4JSON.forEach( ( _order ) => - expect( _order.billing.last_name ).toEqual( 'Doe' ) - ); - - // Test order item name. - const result5 = await request.get( 'wp-json/wc/v3/orders', { - params: { - search: 'Pennant oxo', - }, - } ); - const result5JSON = await result5.json(); - - expect( result5.status() ).toEqual( 200 ); - expect( result5JSON ).toHaveLength( 2 ); - result5JSON.forEach( ( _order ) => - expect( _order ).toEqual( - expect.objectContaining( { - line_items: expect.arrayContaining( [ + expect( included.status() ).toEqual( 200 ); + expect( includedJSON ).toHaveLength( ordersToFilter.length ); + expect( includedJSON ).toEqual( + expect.arrayContaining( + ordersToFilter.map( ( id ) => expect.objectContaining( { - name: 'WordPress Pennant oxo', - } ), - ] ), - } ) - ) - ); - } ); - } ); + id, + } ) + ) + ) + ); - test.describe( 'orderby', () => { - // The orders endpoint `orderby` parameter uses WP_Query, so our tests won't - // include slug and title, since they are programmatically generated. - test( 'default', async ( { request } ) => { - // Default = date desc. - const result = await request.get( 'wp-json/wc/v3/orders' ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); + const excluded = await request.get( 'wp-json/wc/v3/orders', { + params: { + per_page: 20, + exclude: ordersToFilter.join( ',' ), + }, + } ); + const excludedJSON = await excluded.json(); - // Verify all dates are in descending order. - let lastDate = Date.now(); - resultJSON.forEach( ( { date_created } ) => { - const created = Date.parse( date_created + '.000Z' ); - expect( lastDate ).toBeGreaterThanOrEqual( created ); - lastDate = created; + expect( excluded.status() ).toEqual( 200 ); + expect( excludedJSON.length ).toBeGreaterThanOrEqual( + Number( ORDERS_COUNT - ordersToFilter.length ) + ); + expect( excludedJSON ).toEqual( + expect.not.arrayContaining( + ordersToFilter.map( ( id ) => + expect.objectContaining( { + id, + } ) + ) + ) + ); + } ); + + test( 'parent', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + parent: sampleData.hierarchicalOrders.parent.id, + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].id ).toBe( + sampleData.hierarchicalOrders.child.id + ); + + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + parent_exclude: sampleData.hierarchicalOrders.parent.id, + }, + } ); + const result2JSON = await result2.json(); + + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( [ + expect.objectContaining( { + id: sampleData.hierarchicalOrders.child.id, + } ), + ] ) + ); + } ); + + test( 'status', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + status: 'completed', + search: 'oxo', + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 2 ); + expect( result1JSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + status: 'completed', + customer_id: 0, + line_items: expect.arrayContaining( [ + expect.objectContaining( { + name: 'Single oxo', + quantity: 2, + } ), + expect.objectContaining( { + name: 'Beanie with Logo oxo', + quantity: 3, + } ), + expect.objectContaining( { + name: 'T-Shirt oxo', + quantity: 1, + } ), + ] ), + } ), + expect.objectContaining( { + status: 'completed', + customer_id: sampleData.customers.tinaJSON.id, + line_items: expect.arrayContaining( [ + expect.objectContaining( { + name: 'Sunglasses oxo', + quantity: 1, + } ), + ] ), + } ), + ] ) + ); + + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + status: 'processing', + search: 'oxo', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 8 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( + result1JSON.map( ( { id } ) => + expect.objectContaining( { + id, + } ) + ) + ) + ); + } ); + + test( 'customer', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + customer: sampleData.customers.johnJSON.id, + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 5 ); + result1JSON.forEach( ( _order ) => + expect( _order ).toEqual( + expect.objectContaining( { + customer_id: sampleData.customers.johnJSON.id, + } ) + ) + ); + + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + customer: 0, + search: 'oxo', + }, + } ); + const result2JSON = await result2.json(); + + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 3 ); + result2JSON.forEach( ( _order ) => + expect( _order ).toEqual( + expect.objectContaining( { + customer_id: 0, + } ) + ) + ); + } ); + + test( 'product', async ( { request } ) => { + const beanie = sampleData.testProductData.simpleProducts.find( + ( p ) => p.name === 'Beanie oxo' + ); + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + product: beanie.id, + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 2 ); + result1JSON.forEach( ( _order ) => + expect( _order ).toEqual( + expect.objectContaining( { + line_items: expect.arrayContaining( [ + expect.objectContaining( { + name: 'Beanie oxo', + } ), + ] ), + } ) + ) + ); + } ); + + // NOTE: This does not verify the `taxes` array nested in line items. + // While the precision parameter doesn't affect those values, after some + // discussion it seems `dp` may not be supported in v4 of the API. + test( 'dp (precision)', async ( { request } ) => { + const expectPrecisionToMatch = ( value, dp ) => { + expect( value ).toEqual( + Number.parseFloat( value ).toFixed( dp ) + ); + }; + + const verifyOrderPrecision = ( _order, dp ) => { + expectPrecisionToMatch( _order.discount_total, dp ); + expectPrecisionToMatch( _order.discount_tax, dp ); + expectPrecisionToMatch( _order.shipping_total, dp ); + expectPrecisionToMatch( _order.shipping_tax, dp ); + expectPrecisionToMatch( _order.cart_tax, dp ); + expectPrecisionToMatch( _order.total, dp ); + expectPrecisionToMatch( _order.total_tax, dp ); + + _order.line_items.forEach( ( lineItem ) => { + expectPrecisionToMatch( lineItem.total, dp ); + expectPrecisionToMatch( lineItem.total_tax, dp ); + } ); + + _order.tax_lines.forEach( ( taxLine ) => { + expectPrecisionToMatch( taxLine.tax_total, dp ); + expectPrecisionToMatch( + taxLine.shipping_tax_total, + dp + ); + } ); + + _order.shipping_lines.forEach( ( shippingLine ) => { + expectPrecisionToMatch( shippingLine.total, dp ); + expectPrecisionToMatch( shippingLine.total_tax, dp ); + } ); + + _order.fee_lines.forEach( ( feeLine ) => { + expectPrecisionToMatch( feeLine.total, dp ); + expectPrecisionToMatch( feeLine.total_tax, dp ); + } ); + + _order.refunds.forEach( ( refund ) => { + expectPrecisionToMatch( refund.total, dp ); + } ); + }; + + const result1 = await request.get( + `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }`, + { + params: { + dp: 1, + }, + } + ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + verifyOrderPrecision( result1JSON, 1 ); + + const result2 = await request.get( + `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }`, + { + params: { + dp: 3, + }, + } + ); + const result2JSON = await result2.json(); + + expect( result2.status() ).toEqual( 200 ); + verifyOrderPrecision( result2JSON, 3 ); + + const result3 = await request.get( + `wp-json/wc/v3/orders/${ sampleData.precisionOrder.id }` + ); + const result3JSON = await result3.json(); + + expect( result3.status() ).toEqual( 200 ); + verifyOrderPrecision( result3JSON, 2 ); // The default value for 'dp' is 2. + } ); + + test( 'search', async ( { request } ) => { + // By default, 'search' looks in: + // - _billing_address_index + // - _shipping_address_index + // - _billing_last_name + // - _billing_email + // - order_item_name + + // Test billing email. + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + search: 'example.com', + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON.length ).toBeGreaterThanOrEqual( 1 ); + result1JSON.forEach( ( _order ) => + expect( _order.billing.email ).toContain( 'example.com' ) + ); + + // Test billing address. + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + search: 'gainesville', + }, + } ); + const result2JSON = await result2.json(); + + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 1 ); + expect( result2JSON[ 0 ].id ).toEqual( + sampleData.guestOrderJSON.id + ); + + // Test shipping address. + const result3 = await request.get( 'wp-json/wc/v3/orders', { + params: { + search: 'Incognito', + }, + } ); + const result3JSON = await result3.json(); + + expect( result3.status() ).toEqual( 200 ); + expect( result3JSON ).toHaveLength( 1 ); + expect( result3JSON[ 0 ].id ).toEqual( + sampleData.guestOrderJSON.id + ); + + // Test billing last name. + const result4 = await request.get( 'wp-json/wc/v3/orders', { + params: { + search: 'Doe', + }, + } ); + const result4JSON = await result4.json(); + + expect( result4.status() ).toEqual( 200 ); + expect( result4JSON.length ).toBeGreaterThanOrEqual( 1 ); + result4JSON.forEach( ( _order ) => + expect( _order.billing.last_name ).toEqual( 'Doe' ) + ); + + // Test order item name. + const result5 = await request.get( 'wp-json/wc/v3/orders', { + params: { + search: 'Pennant oxo', + }, + } ); + const result5JSON = await result5.json(); + + expect( result5.status() ).toEqual( 200 ); + expect( result5JSON ).toHaveLength( 2 ); + result5JSON.forEach( ( _order ) => + expect( _order ).toEqual( + expect.objectContaining( { + line_items: expect.arrayContaining( [ + expect.objectContaining( { + name: 'WordPress Pennant oxo', + } ), + ] ), + } ) + ) + ); } ); } ); - test( 'date', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/orders', { - params: { - order: 'asc', - orderby: 'date', - }, + test.describe( 'orderby', () => { + // The orders endpoint `orderby` parameter uses WP_Query, so our tests won't + // include slug and title, since they are programmatically generated. + test( 'default', async ( { request } ) => { + // Default = date desc. + const result = await request.get( 'wp-json/wc/v3/orders' ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); + + // Verify all dates are in descending order. + let lastDate = Date.now(); + resultJSON.forEach( ( { date_created } ) => { + const created = Date.parse( date_created + '.000Z' ); + expect( lastDate ).toBeGreaterThanOrEqual( created ); + lastDate = created; + } ); } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); + test( 'date', async ( { request } ) => { + const result = await request.get( 'wp-json/wc/v3/orders', { + params: { + order: 'asc', + orderby: 'date', + }, + } ); + const resultJSON = await result.json(); - // Verify all dates are in ascending order. - let lastDate = 0; - resultJSON.forEach( ( { date_created } ) => { - const created = Date.parse( date_created + '.000Z' ); - expect( created ).toBeGreaterThanOrEqual( lastDate ); - lastDate = created; + expect( result.status() ).toEqual( 200 ); + + // Verify all dates are in ascending order. + let lastDate = 0; + resultJSON.forEach( ( { date_created } ) => { + const created = Date.parse( date_created + '.000Z' ); + expect( created ).toBeGreaterThanOrEqual( lastDate ); + lastDate = created; + } ); + } ); + + test( 'id', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + order: 'asc', + orderby: 'id', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + + // Verify all results are in ascending order. + let lastId = 0; + result1JSON.forEach( ( { id } ) => { + expect( id ).toBeGreaterThan( lastId ); + lastId = id; + } ); + + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + order: 'desc', + orderby: 'id', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + lastId = Number.MAX_SAFE_INTEGER; + result2JSON.forEach( ( { id } ) => { + expect( lastId ).toBeGreaterThan( id ); + lastId = id; + } ); + } ); + + test( 'include', async ( { request } ) => { + const includeIds = [ + sampleData.precisionOrder.id, + sampleData.hierarchicalOrders.parent.id, + sampleData.guestOrderJSON.id, + ]; + + const result1 = await request.get( 'wp-json/wc/v3/orders', { + params: { + order: 'asc', + orderby: 'include', + include: includeIds.join( ',' ), + }, + } ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( includeIds.length ); + + // Verify all results are in proper order. + result1JSON.forEach( ( { id }, idx ) => { + expect( id ).toBe( includeIds[ idx ] ); + } ); + + const result2 = await request.get( 'wp-json/wc/v3/orders', { + params: { + order: 'desc', + orderby: 'include', + include: includeIds.join( ',' ), + }, + } ); + const result2JSON = await result2.json(); + + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( includeIds.length ); + + // Verify all results are in proper order. + result2JSON.forEach( ( { id }, idx ) => { + expect( id ).toBe( includeIds[ idx ] ); + } ); } ); } ); - - test( 'id', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - order: 'asc', - orderby: 'id', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - - // Verify all results are in ascending order. - let lastId = 0; - result1JSON.forEach( ( { id } ) => { - expect( id ).toBeGreaterThan( lastId ); - lastId = id; - } ); - - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - order: 'desc', - orderby: 'id', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - - // Verify all results are in descending order. - lastId = Number.MAX_SAFE_INTEGER; - result2JSON.forEach( ( { id } ) => { - expect( lastId ).toBeGreaterThan( id ); - lastId = id; - } ); - } ); - - test( 'include', async ( { request } ) => { - const includeIds = [ - sampleData.precisionOrder.id, - sampleData.hierarchicalOrders.parent.id, - sampleData.guestOrderJSON.id, - ]; - - const result1 = await request.get( 'wp-json/wc/v3/orders', { - params: { - order: 'asc', - orderby: 'include', - include: includeIds.join( ',' ), - }, - } ); - const result1JSON = await result1.json(); - - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( includeIds.length ); - - // Verify all results are in proper order. - result1JSON.forEach( ( { id }, idx ) => { - expect( id ).toBe( includeIds[ idx ] ); - } ); - - const result2 = await request.get( 'wp-json/wc/v3/orders', { - params: { - order: 'desc', - orderby: 'include', - include: includeIds.join( ',' ), - }, - } ); - const result2JSON = await result2.json(); - - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( includeIds.length ); - - // Verify all results are in proper order. - result2JSON.forEach( ( { id }, idx ) => { - expect( id ).toBe( includeIds[ idx ] ); - } ); - } ); - } ); -} ); + } +); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/payment-gateways/payment-gateways-crud.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/payment-gateways/payment-gateways-crud.test.js index c4d021c65f5..a0cd439f3e4 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/payment-gateways/payment-gateways-crud.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/payment-gateways/payment-gateways-crud.test.js @@ -1,170 +1,176 @@ const { test, expect } = require( '../../../fixtures/api-tests-fixtures' ); test.describe( 'Payment Gateways API tests', () => { - test( 'can view all payment gateways', async ( { request } ) => { - // call API to retrieve the payment gateways - const response = await request.get( '/wp-json/wc/v3/payment_gateways' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); + test( + 'can view all payment gateways', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + async ( { request } ) => { + // call API to retrieve the payment gateways + const response = await request.get( + '/wp-json/wc/v3/payment_gateways' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); - const localPickupKey = - // eslint-disable-next-line playwright/no-conditional-in-test - process.env.BASE_URL && - ! process.env.BASE_URL.includes( 'localhost' ) - ? 'pickup_location' - : 'local_pickup'; - console.log( 'localPickupKey=', localPickupKey ); + const localPickupKey = + // eslint-disable-next-line playwright/no-conditional-in-test + process.env.BASE_URL && + ! process.env.BASE_URL.includes( 'localhost' ) + ? 'pickup_location' + : 'local_pickup'; + console.log( 'localPickupKey=', localPickupKey ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'bacs', - title: 'Direct bank transfer', - description: - 'Make your payment directly into our bank account. Please use your Order ID as the payment reference. Your order will not be shipped until the funds have cleared in our account.', - order: '', - enabled: false, - method_title: 'Direct bank transfer', - method_description: - 'Take payments in person via BACS. More commonly known as direct bank/wire transfer.', - method_supports: [ 'products' ], - settings: { - title: { - id: 'title', - label: 'Title', - description: - 'This controls the title which the user sees during checkout.', - type: 'safe_text', - value: 'Direct bank transfer', - default: 'Direct bank transfer', - tip: 'This controls the title which the user sees during checkout.', - placeholder: '', + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'bacs', + title: 'Direct bank transfer', + description: + 'Make your payment directly into our bank account. Please use your Order ID as the payment reference. Your order will not be shipped until the funds have cleared in our account.', + order: '', + enabled: false, + method_title: 'Direct bank transfer', + method_description: + 'Take payments in person via BACS. More commonly known as direct bank/wire transfer.', + method_supports: [ 'products' ], + settings: { + title: { + id: 'title', + label: 'Title', + description: + 'This controls the title which the user sees during checkout.', + type: 'safe_text', + value: 'Direct bank transfer', + default: 'Direct bank transfer', + tip: 'This controls the title which the user sees during checkout.', + placeholder: '', + }, + instructions: { + id: 'instructions', + label: 'Instructions', + description: + 'Instructions that will be added to the thank you page and emails.', + type: 'textarea', + value: '', + default: '', + tip: 'Instructions that will be added to the thank you page and emails.', + placeholder: '', + }, }, - instructions: { - id: 'instructions', - label: 'Instructions', - description: - 'Instructions that will be added to the thank you page and emails.', - type: 'textarea', - value: '', - default: '', - tip: 'Instructions that will be added to the thank you page and emails.', - placeholder: '', - }, - }, - } ), + } ), - expect.objectContaining( { - id: 'cheque', - title: 'Check payments', - description: - 'Please send a check to Store Name, Store Street, Store Town, Store State / County, Store Postcode.', - order: '', - enabled: false, - method_title: 'Check payments', - method_description: - 'Take payments in person via checks. This offline gateway can also be useful to test purchases.', - method_supports: [ 'products' ], - settings: { - title: { - id: 'title', - label: 'Title', - description: - 'This controls the title which the user sees during checkout.', - type: 'safe_text', - value: 'Check payments', - default: 'Check payments', - tip: 'This controls the title which the user sees during checkout.', - placeholder: '', + expect.objectContaining( { + id: 'cheque', + title: 'Check payments', + description: + 'Please send a check to Store Name, Store Street, Store Town, Store State / County, Store Postcode.', + order: '', + enabled: false, + method_title: 'Check payments', + method_description: + 'Take payments in person via checks. This offline gateway can also be useful to test purchases.', + method_supports: [ 'products' ], + settings: { + title: { + id: 'title', + label: 'Title', + description: + 'This controls the title which the user sees during checkout.', + type: 'safe_text', + value: 'Check payments', + default: 'Check payments', + tip: 'This controls the title which the user sees during checkout.', + placeholder: '', + }, + instructions: { + id: 'instructions', + label: 'Instructions', + description: + 'Instructions that will be added to the thank you page and emails.', + type: 'textarea', + value: '', + default: '', + tip: 'Instructions that will be added to the thank you page and emails.', + placeholder: '', + }, }, - instructions: { - id: 'instructions', - label: 'Instructions', - description: - 'Instructions that will be added to the thank you page and emails.', - type: 'textarea', - value: '', - default: '', - tip: 'Instructions that will be added to the thank you page and emails.', - placeholder: '', - }, - }, - } ), + } ), - expect.objectContaining( { - id: 'cod', - title: 'Cash on delivery', - description: 'Pay with cash upon delivery.', - order: '', - enabled: false, - method_title: 'Cash on delivery', - method_description: - 'Have your customers pay with cash (or by other means) upon delivery.', - method_supports: [ 'products' ], - settings: { - title: { - id: 'title', - label: 'Title', - description: - 'Payment method description that the customer will see on your checkout.', - type: 'safe_text', - value: 'Cash on delivery', - default: 'Cash on delivery', - tip: 'Payment method description that the customer will see on your checkout.', - placeholder: '', - }, - instructions: { - id: 'instructions', - label: 'Instructions', - description: - 'Instructions that will be added to the thank you page.', - type: 'textarea', - value: 'Pay with cash upon delivery.', - default: 'Pay with cash upon delivery.', - tip: 'Instructions that will be added to the thank you page.', - placeholder: '', - }, - enable_for_methods: { - id: 'enable_for_methods', - label: 'Enable for shipping methods', - description: - 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', - type: 'multiselect', - value: '', - default: '', - tip: 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', - placeholder: '', - options: expect.objectContaining( { - 'Flat rate': { - flat_rate: - 'Any "Flat rate" method', - }, - 'Free shipping': { - free_shipping: - 'Any "Free shipping" method', - }, - 'Local pickup': expect.objectContaining( { - [ localPickupKey ]: - 'Any "Local pickup" method', + expect.objectContaining( { + id: 'cod', + title: 'Cash on delivery', + description: 'Pay with cash upon delivery.', + order: '', + enabled: false, + method_title: 'Cash on delivery', + method_description: + 'Have your customers pay with cash (or by other means) upon delivery.', + method_supports: [ 'products' ], + settings: { + title: { + id: 'title', + label: 'Title', + description: + 'Payment method description that the customer will see on your checkout.', + type: 'safe_text', + value: 'Cash on delivery', + default: 'Cash on delivery', + tip: 'Payment method description that the customer will see on your checkout.', + placeholder: '', + }, + instructions: { + id: 'instructions', + label: 'Instructions', + description: + 'Instructions that will be added to the thank you page.', + type: 'textarea', + value: 'Pay with cash upon delivery.', + default: 'Pay with cash upon delivery.', + tip: 'Instructions that will be added to the thank you page.', + placeholder: '', + }, + enable_for_methods: { + id: 'enable_for_methods', + label: 'Enable for shipping methods', + description: + 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', + type: 'multiselect', + value: '', + default: '', + tip: 'If COD is only available for certain methods, set it up here. Leave blank to enable for all methods.', + placeholder: '', + options: expect.objectContaining( { + 'Flat rate': { + flat_rate: + 'Any "Flat rate" method', + }, + 'Free shipping': { + free_shipping: + 'Any "Free shipping" method', + }, + 'Local pickup': expect.objectContaining( { + [ localPickupKey ]: + 'Any "Local pickup" method', + } ), } ), - } ), + }, + enable_for_virtual: { + id: 'enable_for_virtual', + label: 'Accept COD if the order is virtual', + description: '', + type: 'checkbox', + value: 'yes', + default: 'yes', + tip: '', + placeholder: '', + }, }, - enable_for_virtual: { - id: 'enable_for_virtual', - label: 'Accept COD if the order is virtual', - description: '', - type: 'checkbox', - value: 'yes', - default: 'yes', - tip: '', - placeholder: '', - }, - }, - } ), - ] ) - ); - } ); + } ), + ] ) + ); + } + ); test( 'can view a payment gateway', async ( { request } ) => { // call API to retrieve a single payment gateway diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/product-list.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/product-list.test.js index 03f51112b37..d9faf15633c 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/product-list.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/product-list.test.js @@ -2230,1077 +2230,1170 @@ test.describe( 'Products API tests: List All Products', () => { await deleteSampleData( sampleData ); }, 10000 ); - test.describe( 'List all products', () => { - test( 'defaults', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - search: 'xxx', - }, + test.describe( + 'List all products', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + () => { + test( 'defaults', async ( { request } ) => { + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + search: 'xxx', + }, + } ); + + expect( result.status() ).toEqual( 200 ); + expect( result.headers()[ 'x-wp-total' ] ).toEqual( + PRODUCTS_COUNT.toString() + ); + expect( result.headers()[ 'x-wp-totalpages' ] ).toEqual( '2' ); } ); - expect( result.status() ).toEqual( 200 ); - expect( result.headers()[ 'x-wp-total' ] ).toEqual( - PRODUCTS_COUNT.toString() - ); - expect( result.headers()[ 'x-wp-totalpages' ] ).toEqual( '2' ); - } ); + test( 'pagination', async ( { request } ) => { + const pageSize = 6; + const page1 = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: pageSize, + search: 'xxx', + }, + } ); + const page1JSON = await page1.json(); + const page2 = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: pageSize, + page: 2, + search: 'xxx', + }, + } ); + const page2JSON = await page2.json(); + expect( page1.status() ).toEqual( 200 ); + expect( page2.status() ).toEqual( 200 ); - test( 'pagination', async ( { request } ) => { - const pageSize = 6; - const page1 = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: pageSize, - search: 'xxx', - }, - } ); - const page1JSON = await page1.json(); - const page2 = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: pageSize, - page: 2, - search: 'xxx', - }, - } ); - const page2JSON = await page2.json(); - expect( page1.status() ).toEqual( 200 ); - expect( page2.status() ).toEqual( 200 ); + // Verify total page count. + expect( page1.headers()[ 'x-wp-total' ] ).toEqual( + PRODUCTS_COUNT.toString() + ); + expect( page1.headers()[ 'x-wp-totalpages' ] ).toEqual( '4' ); - // Verify total page count. - expect( page1.headers()[ 'x-wp-total' ] ).toEqual( - PRODUCTS_COUNT.toString() - ); - expect( page1.headers()[ 'x-wp-totalpages' ] ).toEqual( '4' ); + // Verify we get pageSize'd arrays. + expect( Array.isArray( page1JSON ) ).toBe( true ); + expect( Array.isArray( page2JSON ) ).toBe( true ); + expect( page1JSON ).toHaveLength( pageSize ); + expect( page2JSON ).toHaveLength( pageSize ); - // Verify we get pageSize'd arrays. - expect( Array.isArray( page1JSON ) ).toBe( true ); - expect( Array.isArray( page2JSON ) ).toBe( true ); - expect( page1JSON ).toHaveLength( pageSize ); - expect( page2JSON ).toHaveLength( pageSize ); + // Ensure all of the product IDs are unique (no page overlap). + const allProductIds = page1JSON + .concat( page2JSON ) + .reduce( ( acc, product ) => { + acc[ product.id ] = 1; + return acc; + }, {} ); + expect( Object.keys( allProductIds ) ).toHaveLength( + pageSize * 2 + ); - // Ensure all of the product IDs are unique (no page overlap). - const allProductIds = page1JSON - .concat( page2JSON ) - .reduce( ( acc, product ) => { - acc[ product.id ] = 1; - return acc; - }, {} ); - expect( Object.keys( allProductIds ) ).toHaveLength( pageSize * 2 ); - - // Verify that offset takes precedent over page number. - const page2Offset = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: pageSize, - page: 2, - offset: pageSize + 1, - search: 'xxx', - }, - } ); - const page2OffsetJSON = await page2Offset.json(); - // The offset pushes the result set 1 product past the start of page 2. - expect( page2OffsetJSON ).toEqual( - expect.not.arrayContaining( [ - expect.objectContaining( { - id: page2JSON[ 0 ].id, - } ), - ] ) - ); - expect( page2OffsetJSON[ 0 ].id ).toEqual( page2JSON[ 1 ].id ); - - // Verify the last page only has 2 products as we expect. - const lastPage = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: pageSize, - page: 4, - search: 'xxx', - }, - } ); - const lastPageJSON = await lastPage.json(); - expect( Array.isArray( lastPageJSON ) ).toBe( true ); - expect( lastPageJSON ).toHaveLength( 2 ); - - // Verify a page outside the total page count is empty. - const page6 = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: pageSize, - page: 6, - search: 'xxx', - }, - } ); - const page6JSON = await page6.json(); - expect( Array.isArray( page6JSON ) ).toBe( true ); - expect( page6JSON ).toHaveLength( 0 ); - } ); - - test( 'search', async ( { request } ) => { - // Match in the short description. - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - search: 'external', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON.length ).toBeGreaterThanOrEqual( 1 ); - expect( result1JSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - name: 'WordPress Pennant xxx', - } ), - ] ) - ); - - // Match in the product name. - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - search: 'pocket xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 1 ); - expect( result2JSON[ 0 ].name ).toBe( 'Hoodie with Pocket xxx' ); - } ); - - test( 'inclusion / exclusion', async ( { request } ) => { - const allProducts = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: 20, - search: 'xxx', - }, - } ); - const allProductsJSON = await allProducts.json(); - expect( allProducts.status() ).toEqual( 200 ); - const allProductIds = allProductsJSON.map( - ( product ) => product.id - ); - expect( allProductIds ).toHaveLength( PRODUCTS_COUNT ); - - const productsToFilter = [ - allProductIds[ 2 ], - allProductIds[ 4 ], - allProductIds[ 7 ], - allProductIds[ 13 ], - ]; - - const included = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: 20, - include: productsToFilter.join( ',' ), - }, - } ); - const includedJSON = await included.json(); - expect( included.status() ).toEqual( 200 ); - expect( includedJSON ).toHaveLength( productsToFilter.length ); - expect( includedJSON ).toEqual( - expect.arrayContaining( - productsToFilter.map( ( id ) => + // Verify that offset takes precedent over page number. + const page2Offset = await request.get( + 'wp-json/wc/v3/products', + { + params: { + per_page: pageSize, + page: 2, + offset: pageSize + 1, + search: 'xxx', + }, + } + ); + const page2OffsetJSON = await page2Offset.json(); + // The offset pushes the result set 1 product past the start of page 2. + expect( page2OffsetJSON ).toEqual( + expect.not.arrayContaining( [ expect.objectContaining( { - id, - } ) - ) - ) - ); + id: page2JSON[ 0 ].id, + } ), + ] ) + ); + expect( page2OffsetJSON[ 0 ].id ).toEqual( page2JSON[ 1 ].id ); - const excluded = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: 20, - exclude: productsToFilter.join( ',' ), - }, + // Verify the last page only has 2 products as we expect. + const lastPage = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: pageSize, + page: 4, + search: 'xxx', + }, + } ); + const lastPageJSON = await lastPage.json(); + expect( Array.isArray( lastPageJSON ) ).toBe( true ); + expect( lastPageJSON ).toHaveLength( 2 ); + + // Verify a page outside the total page count is empty. + const page6 = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: pageSize, + page: 6, + search: 'xxx', + }, + } ); + const page6JSON = await page6.json(); + expect( Array.isArray( page6JSON ) ).toBe( true ); + expect( page6JSON ).toHaveLength( 0 ); } ); - const excludedJSON = await excluded.json(); - expect( excluded.status() ).toEqual( 200 ); - expect( excludedJSON.length ).toBeGreaterThanOrEqual( - Number( PRODUCTS_COUNT - productsToFilter.length ) - ); - expect( excludedJSON ).toEqual( - expect.not.arrayContaining( - productsToFilter.map( ( id ) => + + test( 'search', async ( { request } ) => { + // Match in the short description. + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + search: 'external', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON.length ).toBeGreaterThanOrEqual( 1 ); + expect( result1JSON ).toEqual( + expect.arrayContaining( [ expect.objectContaining( { - id, - } ) + name: 'WordPress Pennant xxx', + } ), + ] ) + ); + + // Match in the product name. + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + search: 'pocket xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 1 ); + expect( result2JSON[ 0 ].name ).toBe( + 'Hoodie with Pocket xxx' + ); + } ); + + test( 'inclusion / exclusion', async ( { request } ) => { + const allProducts = await request.get( + 'wp-json/wc/v3/products', + { + params: { + per_page: 20, + search: 'xxx', + }, + } + ); + const allProductsJSON = await allProducts.json(); + expect( allProducts.status() ).toEqual( 200 ); + const allProductIds = allProductsJSON.map( + ( product ) => product.id + ); + expect( allProductIds ).toHaveLength( PRODUCTS_COUNT ); + + const productsToFilter = [ + allProductIds[ 2 ], + allProductIds[ 4 ], + allProductIds[ 7 ], + allProductIds[ 13 ], + ]; + + const included = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: 20, + include: productsToFilter.join( ',' ), + }, + } ); + const includedJSON = await included.json(); + expect( included.status() ).toEqual( 200 ); + expect( includedJSON ).toHaveLength( productsToFilter.length ); + expect( includedJSON ).toEqual( + expect.arrayContaining( + productsToFilter.map( ( id ) => + expect.objectContaining( { + id, + } ) + ) ) - ) + ); + + const excluded = await request.get( 'wp-json/wc/v3/products', { + params: { + per_page: 20, + exclude: productsToFilter.join( ',' ), + }, + } ); + const excludedJSON = await excluded.json(); + expect( excluded.status() ).toEqual( 200 ); + expect( excludedJSON.length ).toBeGreaterThanOrEqual( + Number( PRODUCTS_COUNT - productsToFilter.length ) + ); + expect( excludedJSON ).toEqual( + expect.not.arrayContaining( + productsToFilter.map( ( id ) => + expect.objectContaining( { + id, + } ) + ) + ) + ); + } ); + + test( 'slug', async ( { request } ) => { + // Match by slug. + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + slug: 't-shirt-with-logo-xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].slug ).toBe( 't-shirt-with-logo-xxx' ); + + // No matches + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + slug: 'no-product-with-this-slug', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 0 ); + } ); + + test( 'sku', async ( { request } ) => { + // Match by SKU. + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + sku: 'woo-sunglasses-product', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].sku ).toBe( 'woo-sunglasses-product' ); + + // No matches + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + sku: 'no-product-with-this-sku', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 0 ); + } ); + + test( 'type', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + type: 'simple', + search: 'xxx', + }, + } ); + expect( result1.status() ).toEqual( 200 ); + expect( result1.headers()[ 'x-wp-total' ] ).toEqual( '16' ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + type: 'external', + search: 'xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 1 ); + expect( result2JSON[ 0 ].name ).toBe( 'WordPress Pennant xxx' ); + + const result3 = await request.get( 'wp-json/wc/v3/products', { + params: { + type: 'variable', + search: 'xxx', + }, + } ); + const result3JSON = await result3.json(); + expect( result3.status() ).toEqual( 200 ); + expect( result3JSON ).toHaveLength( 2 ); + + const result4 = await request.get( 'wp-json/wc/v3/products', { + params: { + type: 'grouped', + search: 'xxx', + }, + } ); + const result4JSON = await result4.json(); + expect( result4.status() ).toEqual( 200 ); + expect( result4JSON ).toHaveLength( 1 ); + expect( result4JSON[ 0 ].name ).toBe( 'Logo Collection xxx' ); + } ); + + test( 'featured', async ( { request } ) => { + const featured = [ + expect.objectContaining( { + name: 'Hoodie with Zipper xxx', + } ), + expect.objectContaining( { + name: 'Hoodie with Pocket xxx', + } ), + expect.objectContaining( { + name: 'Sunglasses xxx', + } ), + expect.objectContaining( { + name: 'Cap xxx', + } ), + expect.objectContaining( { + name: 'V-Neck T-Shirt xxx', + } ), + ]; + + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + featured: true, + search: 'xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( featured.length ); + expect( result1JSON ).toEqual( + expect.arrayContaining( featured ) + ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + featured: false, + search: 'xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( featured ) + ); + } ); + + test( + 'categories', + { tag: '@skip-on-default-wpcom' }, + async ( { request } ) => { + const accessory = [ + expect.objectContaining( { + name: 'Beanie xxx', + } ), + ]; + const hoodies = [ + expect.objectContaining( { + name: 'Hoodie with Zipper xxx', + } ), + expect.objectContaining( { + name: 'Hoodie with Pocket xxx', + } ), + expect.objectContaining( { + name: 'Hoodie with Logo xxx', + } ), + expect.objectContaining( { + name: 'Hoodie xxx', + } ), + ]; + + // Verify that subcategories are included. + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + per_page: 20, + category: sampleData.categories.clothingJSON.id, + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toEqual( + expect.arrayContaining( accessory ) + ); + expect( result1JSON ).toEqual( + expect.arrayContaining( hoodies ) + ); + + // Verify sibling categories are not. + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + category: sampleData.categories.hoodiesJSON.id, + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( accessory ) + ); + expect( result2JSON ).toEqual( + expect.arrayContaining( hoodies ) + ); + } ); - } ); - test( 'slug', async ( { request } ) => { - // Match by slug. - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - slug: 't-shirt-with-logo-xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].slug ).toBe( 't-shirt-with-logo-xxx' ); - - // No matches - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - slug: 'no-product-with-this-slug', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 0 ); - } ); - - test( 'sku', async ( { request } ) => { - // Match by SKU. - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - sku: 'woo-sunglasses-product', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].sku ).toBe( 'woo-sunglasses-product' ); - - // No matches - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - sku: 'no-product-with-this-sku', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 0 ); - } ); - - test( 'type', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - type: 'simple', - search: 'xxx', - }, - } ); - expect( result1.status() ).toEqual( 200 ); - expect( result1.headers()[ 'x-wp-total' ] ).toEqual( '16' ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - type: 'external', - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 1 ); - expect( result2JSON[ 0 ].name ).toBe( 'WordPress Pennant xxx' ); - - const result3 = await request.get( 'wp-json/wc/v3/products', { - params: { - type: 'variable', - search: 'xxx', - }, - } ); - const result3JSON = await result3.json(); - expect( result3.status() ).toEqual( 200 ); - expect( result3JSON ).toHaveLength( 2 ); - - const result4 = await request.get( 'wp-json/wc/v3/products', { - params: { - type: 'grouped', - search: 'xxx', - }, - } ); - const result4JSON = await result4.json(); - expect( result4.status() ).toEqual( 200 ); - expect( result4JSON ).toHaveLength( 1 ); - expect( result4JSON[ 0 ].name ).toBe( 'Logo Collection xxx' ); - } ); - - test( 'featured', async ( { request } ) => { - const featured = [ - expect.objectContaining( { - name: 'Hoodie with Zipper xxx', - } ), - expect.objectContaining( { - name: 'Hoodie with Pocket xxx', - } ), - expect.objectContaining( { - name: 'Sunglasses xxx', - } ), - expect.objectContaining( { - name: 'Cap xxx', - } ), - expect.objectContaining( { - name: 'V-Neck T-Shirt xxx', - } ), - ]; - - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - featured: true, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( featured.length ); - expect( result1JSON ).toEqual( expect.arrayContaining( featured ) ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - featured: false, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( featured ) - ); - } ); - - test( 'categories', async ( { request } ) => { - const accessory = [ - expect.objectContaining( { - name: 'Beanie xxx', - } ), - ]; - const hoodies = [ - expect.objectContaining( { - name: 'Hoodie with Zipper xxx', - } ), - expect.objectContaining( { - name: 'Hoodie with Pocket xxx', - } ), - expect.objectContaining( { - name: 'Hoodie with Logo xxx', - } ), - expect.objectContaining( { - name: 'Hoodie xxx', - } ), - ]; - - // Verify that subcategories are included. - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - per_page: 20, - category: sampleData.categories.clothingJSON.id, - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toEqual( - expect.arrayContaining( accessory ) - ); - expect( result1JSON ).toEqual( expect.arrayContaining( hoodies ) ); - - // Verify sibling categories are not. - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - category: sampleData.categories.hoodiesJSON.id, - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( accessory ) - ); - expect( result2JSON ).toEqual( expect.arrayContaining( hoodies ) ); - } ); - - test( 'on sale', async ( { request } ) => { - const onSale = [ - expect.objectContaining( { - name: 'Beanie with Logo xxx', - } ), - expect.objectContaining( { - name: 'Hoodie with Pocket xxx', - } ), - expect.objectContaining( { - name: 'Single xxx', - } ), - expect.objectContaining( { - name: 'Cap xxx', - } ), - expect.objectContaining( { - name: 'Belt xxx', - } ), - expect.objectContaining( { - name: 'Beanie xxx', - } ), - expect.objectContaining( { - name: 'Hoodie xxx', - } ), - ]; - - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - on_sale: true, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( onSale.length ); - expect( result1JSON ).toEqual( expect.arrayContaining( onSale ) ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - on_sale: false, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( onSale ) - ); - } ); - - test( 'price', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - min_price: 21, - max_price: 28, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].name ).toBe( 'Long Sleeve Tee xxx' ); - expect( result1JSON[ 0 ].price ).toBe( '25' ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - max_price: 5, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 1 ); - expect( result2JSON[ 0 ].name ).toBe( 'Single xxx' ); - expect( result2JSON[ 0 ].price ).toBe( '2' ); - - const result3 = await request.get( 'wp-json/wc/v3/products', { - params: { - min_price: 5, - order: 'asc', - orderby: 'price', - search: 'xxx', - }, - } ); - const result3JSON = await result3.json(); - expect( result3.status() ).toEqual( 200 ); - expect( result3JSON ).toEqual( - expect.not.arrayContaining( [ + test( 'on sale', async ( { request } ) => { + const onSale = [ + expect.objectContaining( { + name: 'Beanie with Logo xxx', + } ), + expect.objectContaining( { + name: 'Hoodie with Pocket xxx', + } ), expect.objectContaining( { name: 'Single xxx', } ), - ] ) - ); - } ); + expect.objectContaining( { + name: 'Cap xxx', + } ), + expect.objectContaining( { + name: 'Belt xxx', + } ), + expect.objectContaining( { + name: 'Beanie xxx', + } ), + expect.objectContaining( { + name: 'Hoodie xxx', + } ), + ]; - test( 'before / after', async ( { request } ) => { - const before = [ - expect.objectContaining( { - name: 'Album xxx', - } ), - expect.objectContaining( { - name: 'Single xxx', - } ), - expect.objectContaining( { - name: 'T-Shirt with Logo xxx', - } ), - expect.objectContaining( { - name: 'Beanie with Logo xxx', - } ), - ]; - const after = [ - expect.objectContaining( { - name: 'Hoodie xxx', - } ), - expect.objectContaining( { - name: 'V-Neck T-Shirt xxx', - } ), - expect.objectContaining( { - name: 'Parent Product xxx', - } ), - expect.objectContaining( { - name: 'Child Product xxx', - } ), - ]; + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + on_sale: true, + search: 'xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( onSale.length ); + expect( result1JSON ).toEqual( + expect.arrayContaining( onSale ) + ); - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - before: '2021-09-05T15:50:19', - search: 'xxx', - }, + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + on_sale: false, + search: 'xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( onSale ) + ); } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( before.length ); - expect( result1JSON ).toEqual( expect.arrayContaining( before ) ); - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - after: '2021-09-18T15:50:18', - search: 'xxx', - }, + test( 'price', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + min_price: 21, + max_price: 28, + search: 'xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].name ).toBe( 'Long Sleeve Tee xxx' ); + expect( result1JSON[ 0 ].price ).toBe( '25' ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + max_price: 5, + search: 'xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 1 ); + expect( result2JSON[ 0 ].name ).toBe( 'Single xxx' ); + expect( result2JSON[ 0 ].price ).toBe( '2' ); + + const result3 = await request.get( 'wp-json/wc/v3/products', { + params: { + min_price: 5, + order: 'asc', + orderby: 'price', + search: 'xxx', + }, + } ); + const result3JSON = await result3.json(); + expect( result3.status() ).toEqual( 200 ); + expect( result3JSON ).toEqual( + expect.not.arrayContaining( [ + expect.objectContaining( { + name: 'Single xxx', + } ), + ] ) + ); } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( before ) - ); - expect( result2JSON ).toHaveLength( after.length ); - expect( result2JSON ).toEqual( expect.arrayContaining( after ) ); - } ); - test( 'attributes', async ( { request } ) => { - const red = sampleData.attributes.colors.find( - ( term ) => term.name === 'Red' - ); - - const redProducts = [ - expect.objectContaining( { - name: 'V-Neck T-Shirt xxx', - } ), - expect.objectContaining( { - name: 'Hoodie xxx', - } ), - expect.objectContaining( { - name: 'Beanie xxx', - } ), - expect.objectContaining( { - name: 'Beanie with Logo xxx', - } ), - ]; - - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - attribute: 'pa_colorxxx', - attribute_term: red.id, - }, - } ); - const resultJSON = await result.json(); - - expect( result.status() ).toEqual( 200 ); - expect( resultJSON ).toHaveLength( redProducts.length ); - expect( resultJSON ).toEqual( - expect.arrayContaining( redProducts ) - ); - } ); - - test( 'status', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - status: 'pending', - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].name ).toBe( 'Polo xxx' ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - status: 'draft', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( 0 ); - } ); - - test( 'shipping class', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - shipping_class: sampleData.shippingClasses.freightJSON.id, - }, - } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); - expect( resultJSON ).toHaveLength( 1 ); - expect( resultJSON[ 0 ].name ).toBe( 'Long Sleeve Tee xxx' ); - } ); - - test( 'tax class', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - tax_class: 'reduced-rate', - search: 'xxx', - }, - } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); - expect( resultJSON ).toHaveLength( 1 ); - expect( resultJSON[ 0 ].name ).toBe( 'Sunglasses xxx' ); - } ); - - test( 'stock status', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - stock_status: 'onbackorder', - search: 'xxx', - }, - } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); - expect( resultJSON ).toHaveLength( 1 ); - expect( resultJSON[ 0 ].name ).toBe( 'T-Shirt xxx' ); - } ); - - test( 'tags', async ( { request } ) => { - const coolProducts = [ - expect.objectContaining( { - name: 'Sunglasses xxx', - } ), - expect.objectContaining( { - name: 'Hoodie with Pocket xxx', - } ), - expect.objectContaining( { - name: 'Beanie xxx', - } ), - ]; - - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - tag: sampleData.tags.coolJSON.id, - }, - } ); - const resultJSON = await result.json(); - - expect( result.status() ).toEqual( 200 ); - expect( resultJSON ).toHaveLength( coolProducts.length ); - expect( resultJSON ).toEqual( - expect.arrayContaining( coolProducts ) - ); - } ); - - test( 'parent', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - parent: sampleData.hierarchicalProducts.parentJSON.id, - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( 1 ); - expect( result1JSON[ 0 ].name ).toBe( 'Child Product xxx' ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - parent_exclude: - sampleData.hierarchicalProducts.parentJSON.id, - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toEqual( - expect.not.arrayContaining( [ + test( 'before / after', async ( { request } ) => { + const before = [ + expect.objectContaining( { + name: 'Album xxx', + } ), + expect.objectContaining( { + name: 'Single xxx', + } ), + expect.objectContaining( { + name: 'T-Shirt with Logo xxx', + } ), + expect.objectContaining( { + name: 'Beanie with Logo xxx', + } ), + ]; + const after = [ + expect.objectContaining( { + name: 'Hoodie xxx', + } ), + expect.objectContaining( { + name: 'V-Neck T-Shirt xxx', + } ), + expect.objectContaining( { + name: 'Parent Product xxx', + } ), expect.objectContaining( { name: 'Child Product xxx', } ), - ] ) - ); - } ); - - test.describe( 'orderby', () => { - const productNamesAsc = [ - 'Album xxx', - 'Beanie with Logo xxx', - 'Beanie xxx', - 'Belt xxx', - 'Cap xxx', - 'Child Product xxx', - 'Hoodie with Logo xxx', - 'Hoodie with Pocket xxx', - 'Hoodie with Zipper xxx', - 'Hoodie xxx', - 'Logo Collection xxx', - 'Long Sleeve Tee xxx', - 'Parent Product xxx', - 'Polo xxx', - 'Single xxx', - 'Sunglasses xxx', - 'T-Shirt with Logo xxx', - 'T-Shirt xxx', - 'V-Neck T-Shirt xxx', - 'WordPress Pennant xxx', - ]; - const productNamesDesc = [ ...productNamesAsc ].reverse(); - const productNamesByRatingAsc = [ - 'Sunglasses xxx', - 'Cap xxx', - 'T-Shirt xxx', - ]; - const productNamesByRatingDesc = [ - ...productNamesByRatingAsc, - ].reverse(); - const productNamesByPopularityDesc = [ - 'Beanie with Logo xxx', - 'Single xxx', - 'T-Shirt xxx', - ]; - const productNamesByPopularityAsc = [ - ...productNamesByPopularityDesc, - ].reverse(); - - test( 'default', async ( { request } ) => { - // Default = date desc. - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - search: 'xxx', - }, - } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); - - // Verify all dates are in descending order. - let lastDate = Date.now(); - resultJSON.forEach( ( { date_created_gmt } ) => { - const created = Date.parse( date_created_gmt + '.000Z' ); - expect( lastDate ).toBeGreaterThan( created ); - lastDate = created; - } ); - } ); - - test( 'date', async ( { request } ) => { - const result = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'date', - search: 'xxx', - }, - } ); - const resultJSON = await result.json(); - expect( result.status() ).toEqual( 200 ); - - // Verify all dates are in ascending order. - let lastDate = 0; - resultJSON.forEach( ( { date_created_gmt } ) => { - const created = Date.parse( date_created_gmt + '.000Z' ); - expect( created ).toBeGreaterThan( lastDate ); - lastDate = created; - } ); - } ); - - test( 'id', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'id', - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - - // Verify all results are in ascending order. - let lastId = 0; - result1JSON.forEach( ( { id } ) => { - expect( id ).toBeGreaterThan( lastId ); - lastId = id; - } ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'id', - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - - // Verify all results are in descending order. - lastId = Number.MAX_SAFE_INTEGER; - result2JSON.forEach( ( { id } ) => { - expect( lastId ).toBeGreaterThan( id ); - lastId = id; - } ); - } ); - - test( 'title', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'title', - per_page: productNamesAsc.length, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - - // Verify all results are in ascending order. - result1JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesAsc[ idx ] ); - } ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'title', - per_page: productNamesDesc.length, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - - // Verify all results are in descending order. - result2JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesDesc[ idx ] ); - } ); - } ); - - test( 'slug orderby', async ( { request } ) => { - const productNamesBySlugAsc = [ - 'Polo xxx', // The Polo isn't published so it has an empty slug. - ...productNamesAsc.filter( ( p ) => p !== 'Polo xxx' ), ]; - const productNamesBySlugDesc = [ - ...productNamesBySlugAsc, + + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + before: '2021-09-05T15:50:19', + search: 'xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( before.length ); + expect( result1JSON ).toEqual( + expect.arrayContaining( before ) + ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + after: '2021-09-18T15:50:18', + search: 'xxx', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( before ) + ); + expect( result2JSON ).toHaveLength( after.length ); + expect( result2JSON ).toEqual( + expect.arrayContaining( after ) + ); + } ); + + test( 'attributes', async ( { request } ) => { + const red = sampleData.attributes.colors.find( + ( term ) => term.name === 'Red' + ); + + const redProducts = [ + expect.objectContaining( { + name: 'V-Neck T-Shirt xxx', + } ), + expect.objectContaining( { + name: 'Hoodie xxx', + } ), + expect.objectContaining( { + name: 'Beanie xxx', + } ), + expect.objectContaining( { + name: 'Beanie with Logo xxx', + } ), + ]; + + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + attribute: 'pa_colorxxx', + attribute_term: red.id, + }, + } ); + const resultJSON = await result.json(); + + expect( result.status() ).toEqual( 200 ); + expect( resultJSON ).toHaveLength( redProducts.length ); + expect( resultJSON ).toEqual( + expect.arrayContaining( redProducts ) + ); + } ); + + test( 'status', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + status: 'pending', + search: 'xxx', + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].name ).toBe( 'Polo xxx' ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + status: 'draft', + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( 0 ); + } ); + + test( 'shipping class', async ( { request } ) => { + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + shipping_class: + sampleData.shippingClasses.freightJSON.id, + }, + } ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); + expect( resultJSON ).toHaveLength( 1 ); + expect( resultJSON[ 0 ].name ).toBe( 'Long Sleeve Tee xxx' ); + } ); + + test( 'tax class', async ( { request } ) => { + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + tax_class: 'reduced-rate', + search: 'xxx', + }, + } ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); + expect( resultJSON ).toHaveLength( 1 ); + expect( resultJSON[ 0 ].name ).toBe( 'Sunglasses xxx' ); + } ); + + test( 'stock status', async ( { request } ) => { + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + stock_status: 'onbackorder', + search: 'xxx', + }, + } ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); + expect( resultJSON ).toHaveLength( 1 ); + expect( resultJSON[ 0 ].name ).toBe( 'T-Shirt xxx' ); + } ); + + test( 'tags', async ( { request } ) => { + const coolProducts = [ + expect.objectContaining( { + name: 'Sunglasses xxx', + } ), + expect.objectContaining( { + name: 'Hoodie with Pocket xxx', + } ), + expect.objectContaining( { + name: 'Beanie xxx', + } ), + ]; + + const result = await request.get( 'wp-json/wc/v3/products', { + params: { + tag: sampleData.tags.coolJSON.id, + }, + } ); + const resultJSON = await result.json(); + + expect( result.status() ).toEqual( 200 ); + expect( resultJSON ).toHaveLength( coolProducts.length ); + expect( resultJSON ).toEqual( + expect.arrayContaining( coolProducts ) + ); + } ); + + test( 'parent', async ( { request } ) => { + const result1 = await request.get( 'wp-json/wc/v3/products', { + params: { + parent: sampleData.hierarchicalProducts.parentJSON.id, + }, + } ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( 1 ); + expect( result1JSON[ 0 ].name ).toBe( 'Child Product xxx' ); + + const result2 = await request.get( 'wp-json/wc/v3/products', { + params: { + parent_exclude: + sampleData.hierarchicalProducts.parentJSON.id, + }, + } ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toEqual( + expect.not.arrayContaining( [ + expect.objectContaining( { + name: 'Child Product xxx', + } ), + ] ) + ); + } ); + + test.describe( 'orderby', () => { + const productNamesAsc = [ + 'Album xxx', + 'Beanie with Logo xxx', + 'Beanie xxx', + 'Belt xxx', + 'Cap xxx', + 'Child Product xxx', + 'Hoodie with Logo xxx', + 'Hoodie with Pocket xxx', + 'Hoodie with Zipper xxx', + 'Hoodie xxx', + 'Logo Collection xxx', + 'Long Sleeve Tee xxx', + 'Parent Product xxx', + 'Polo xxx', + 'Single xxx', + 'Sunglasses xxx', + 'T-Shirt with Logo xxx', + 'T-Shirt xxx', + 'V-Neck T-Shirt xxx', + 'WordPress Pennant xxx', + ]; + const productNamesDesc = [ ...productNamesAsc ].reverse(); + const productNamesByRatingAsc = [ + 'Sunglasses xxx', + 'Cap xxx', + 'T-Shirt xxx', + ]; + const productNamesByRatingDesc = [ + ...productNamesByRatingAsc, + ].reverse(); + const productNamesByPopularityDesc = [ + 'Beanie with Logo xxx', + 'Single xxx', + 'T-Shirt xxx', + ]; + const productNamesByPopularityAsc = [ + ...productNamesByPopularityDesc, ].reverse(); - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'slug', - per_page: productNamesBySlugAsc.length, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); + test( 'default', async ( { request } ) => { + // Default = date desc. + const result = await request.get( + 'wp-json/wc/v3/products', + { + params: { + search: 'xxx', + }, + } + ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); - // Verify all results are in ascending order. - result1JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesBySlugAsc[ idx ] ); + // Verify all dates are in descending order. + let lastDate = Date.now(); + resultJSON.forEach( ( { date_created_gmt } ) => { + const created = Date.parse( + date_created_gmt + '.000Z' + ); + expect( lastDate ).toBeGreaterThan( created ); + lastDate = created; + } ); } ); - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'slug', - per_page: productNamesBySlugDesc.length, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); + test( 'date', async ( { request } ) => { + const result = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'date', + search: 'xxx', + }, + } + ); + const resultJSON = await result.json(); + expect( result.status() ).toEqual( 200 ); - // Verify all results are in descending order. - result2JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesBySlugDesc[ idx ] ); + // Verify all dates are in ascending order. + let lastDate = 0; + resultJSON.forEach( ( { date_created_gmt } ) => { + const created = Date.parse( + date_created_gmt + '.000Z' + ); + expect( created ).toBeGreaterThan( lastDate ); + lastDate = created; + } ); + } ); + + test( 'id', async ( { request } ) => { + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'id', + search: 'xxx', + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + + // Verify all results are in ascending order. + let lastId = 0; + result1JSON.forEach( ( { id } ) => { + expect( id ).toBeGreaterThan( lastId ); + lastId = id; + } ); + + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'id', + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + lastId = Number.MAX_SAFE_INTEGER; + result2JSON.forEach( ( { id } ) => { + expect( lastId ).toBeGreaterThan( id ); + lastId = id; + } ); + } ); + + test( 'title', async ( { request } ) => { + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'title', + per_page: productNamesAsc.length, + search: 'xxx', + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + + // Verify all results are in ascending order. + result1JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesAsc[ idx ] ); + } ); + + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'title', + per_page: productNamesDesc.length, + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + result2JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesDesc[ idx ] ); + } ); + } ); + + test( 'slug orderby', async ( { request } ) => { + const productNamesBySlugAsc = [ + 'Polo xxx', // The Polo isn't published so it has an empty slug. + ...productNamesAsc.filter( ( p ) => p !== 'Polo xxx' ), + ]; + const productNamesBySlugDesc = [ + ...productNamesBySlugAsc, + ].reverse(); + + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'slug', + per_page: productNamesBySlugAsc.length, + search: 'xxx', + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + + // Verify all results are in ascending order. + result1JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesBySlugAsc[ idx ] ); + } ); + + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'slug', + per_page: productNamesBySlugDesc.length, + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + result2JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesBySlugDesc[ idx ] ); + } ); + } ); + + test( 'price orderby', async ( { request } ) => { + const productNamesMinPriceAsc = [ + 'Parent Product xxx', + 'Child Product xxx', + 'Single xxx', + 'WordPress Pennant xxx', + 'Album xxx', + 'V-Neck T-Shirt xxx', + 'Cap xxx', + 'Beanie with Logo xxx', + 'T-Shirt with Logo xxx', + 'Beanie xxx', + 'T-Shirt xxx', + 'Logo Collection xxx', + 'Polo xxx', + 'Long Sleeve Tee xxx', + 'Hoodie with Pocket xxx', + 'Hoodie xxx', + 'Hoodie with Zipper xxx', + 'Hoodie with Logo xxx', + 'Belt xxx', + 'Sunglasses xxx', + ]; + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'price', + per_page: productNamesMinPriceAsc.length, + search: 'xxx', + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( + productNamesMinPriceAsc.length + ); + + // Verify all results are in ascending order. + // The query uses the min price calculated in the product meta lookup table, + // so we can't just check the price property of the response. + result1JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesMinPriceAsc[ idx ] ); + } ); + + const productNamesMaxPriceDesc = [ + 'Sunglasses xxx', + 'Belt xxx', + 'Hoodie xxx', + 'Logo Collection xxx', + 'Hoodie with Logo xxx', + 'Hoodie with Zipper xxx', + 'Hoodie with Pocket xxx', + 'Long Sleeve Tee xxx', + 'V-Neck T-Shirt xxx', + 'Polo xxx', + 'T-Shirt xxx', + 'Beanie xxx', + 'T-Shirt with Logo xxx', + 'Beanie with Logo xxx', + 'Cap xxx', + 'Album xxx', + 'WordPress Pennant xxx', + 'Single xxx', + 'Child Product xxx', + 'Parent Product xxx', + ]; + + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'price', + per_page: productNamesMaxPriceDesc.length, + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( + productNamesMaxPriceDesc.length + ); + + // Verify all results are in descending order. + // The query uses the max price calculated in the product meta lookup table, + // so we can't just check the price property of the response. + result2JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesMaxPriceDesc[ idx ] ); + } ); + } ); + + test( 'include', async ( { request } ) => { + const includeIds = [ + sampleData.groupedProducts[ 0 ].id, + sampleData.simpleProducts[ 3 ].id, + sampleData.hierarchicalProducts.parentJSON.id, + ]; + + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'include', + include: includeIds.join( ',' ), + }, + } + ); + const result1JSON = await result1.json(); + + expect( result1.status() ).toEqual( 200 ); + expect( result1JSON ).toHaveLength( includeIds.length ); + + // Verify all results are in proper order. + result1JSON.forEach( ( { id }, idx ) => { + expect( id ).toBe( includeIds[ idx ] ); + } ); + + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'include', + include: includeIds.join( ',' ), + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + expect( result2JSON ).toHaveLength( includeIds.length ); + + // Verify all results are in proper order. + result2JSON.forEach( ( { id }, idx ) => { + expect( id ).toBe( includeIds[ idx ] ); + } ); + } ); + + test( 'rating (desc)', async ( { request } ) => { + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'rating', + per_page: productNamesByRatingDesc.length, + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + result2JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesByRatingDesc[ idx ] ); + } ); + } ); + + // This case will remain skipped until ratings can be sorted ascending. + // See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099. + test.skip( 'rating (asc)', async ( { request } ) => { + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'rating', + per_page: productNamesByRatingAsc.length, + search: 'xxx', + }, + } + ); + expect( result1.status() ).toEqual( 200 ); + const result1JSON = await result1.json(); + + // Verify all results are in ascending order. + result1JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( productNamesByRatingAsc[ idx ] ); + } ); + } ); + + // This case will remain skipped until popularity can be sorted ascending. + // See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099. + test.skip( 'popularity (asc)', async ( { request } ) => { + const result1 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'asc', + orderby: 'popularity', + per_page: productNamesByPopularityAsc.length, + search: 'xxx', + }, + } + ); + const result1JSON = await result1.json(); + expect( result1.status() ).toEqual( 200 ); + + // Verify all results are in ascending order. + result1JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( + productNamesByPopularityAsc[ idx ] + ); + } ); + } ); + + test( 'popularity (desc)', async ( { request } ) => { + const result2 = await request.get( + 'wp-json/wc/v3/products', + { + params: { + order: 'desc', + orderby: 'popularity', + per_page: productNamesByPopularityDesc.length, + search: 'xxx', + }, + } + ); + const result2JSON = await result2.json(); + expect( result2.status() ).toEqual( 200 ); + + // Verify all results are in descending order. + result2JSON.forEach( ( { name }, idx ) => { + expect( name ).toBe( + productNamesByPopularityDesc[ idx ] + ); + } ); } ); } ); - - test( 'price orderby', async ( { request } ) => { - const productNamesMinPriceAsc = [ - 'Parent Product xxx', - 'Child Product xxx', - 'Single xxx', - 'WordPress Pennant xxx', - 'Album xxx', - 'V-Neck T-Shirt xxx', - 'Cap xxx', - 'Beanie with Logo xxx', - 'T-Shirt with Logo xxx', - 'Beanie xxx', - 'T-Shirt xxx', - 'Logo Collection xxx', - 'Polo xxx', - 'Long Sleeve Tee xxx', - 'Hoodie with Pocket xxx', - 'Hoodie xxx', - 'Hoodie with Zipper xxx', - 'Hoodie with Logo xxx', - 'Belt xxx', - 'Sunglasses xxx', - ]; - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'price', - per_page: productNamesMinPriceAsc.length, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( - productNamesMinPriceAsc.length - ); - - // Verify all results are in ascending order. - // The query uses the min price calculated in the product meta lookup table, - // so we can't just check the price property of the response. - result1JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesMinPriceAsc[ idx ] ); - } ); - - const productNamesMaxPriceDesc = [ - 'Sunglasses xxx', - 'Belt xxx', - 'Hoodie xxx', - 'Logo Collection xxx', - 'Hoodie with Logo xxx', - 'Hoodie with Zipper xxx', - 'Hoodie with Pocket xxx', - 'Long Sleeve Tee xxx', - 'V-Neck T-Shirt xxx', - 'Polo xxx', - 'T-Shirt xxx', - 'Beanie xxx', - 'T-Shirt with Logo xxx', - 'Beanie with Logo xxx', - 'Cap xxx', - 'Album xxx', - 'WordPress Pennant xxx', - 'Single xxx', - 'Child Product xxx', - 'Parent Product xxx', - ]; - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'price', - per_page: productNamesMaxPriceDesc.length, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( - productNamesMaxPriceDesc.length - ); - - // Verify all results are in descending order. - // The query uses the max price calculated in the product meta lookup table, - // so we can't just check the price property of the response. - result2JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesMaxPriceDesc[ idx ] ); - } ); - } ); - - test( 'include', async ( { request } ) => { - const includeIds = [ - sampleData.groupedProducts[ 0 ].id, - sampleData.simpleProducts[ 3 ].id, - sampleData.hierarchicalProducts.parentJSON.id, - ]; - - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'include', - include: includeIds.join( ',' ), - }, - } ); - const result1JSON = await result1.json(); - - expect( result1.status() ).toEqual( 200 ); - expect( result1JSON ).toHaveLength( includeIds.length ); - - // Verify all results are in proper order. - result1JSON.forEach( ( { id }, idx ) => { - expect( id ).toBe( includeIds[ idx ] ); - } ); - - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'include', - include: includeIds.join( ',' ), - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - expect( result2JSON ).toHaveLength( includeIds.length ); - - // Verify all results are in proper order. - result2JSON.forEach( ( { id }, idx ) => { - expect( id ).toBe( includeIds[ idx ] ); - } ); - } ); - - test( 'rating (desc)', async ( { request } ) => { - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'rating', - per_page: productNamesByRatingDesc.length, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - - // Verify all results are in descending order. - result2JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesByRatingDesc[ idx ] ); - } ); - } ); - - // This case will remain skipped until ratings can be sorted ascending. - // See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099. - test.skip( 'rating (asc)', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'rating', - per_page: productNamesByRatingAsc.length, - search: 'xxx', - }, - } ); - expect( result1.status() ).toEqual( 200 ); - const result1JSON = await result1.json(); - - // Verify all results are in ascending order. - result1JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesByRatingAsc[ idx ] ); - } ); - } ); - - // This case will remain skipped until popularity can be sorted ascending. - // See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099. - test.skip( 'popularity (asc)', async ( { request } ) => { - const result1 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'asc', - orderby: 'popularity', - per_page: productNamesByPopularityAsc.length, - search: 'xxx', - }, - } ); - const result1JSON = await result1.json(); - expect( result1.status() ).toEqual( 200 ); - - // Verify all results are in ascending order. - result1JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesByPopularityAsc[ idx ] ); - } ); - } ); - - test( 'popularity (desc)', async ( { request } ) => { - const result2 = await request.get( 'wp-json/wc/v3/products', { - params: { - order: 'desc', - orderby: 'popularity', - per_page: productNamesByPopularityDesc.length, - search: 'xxx', - }, - } ); - const result2JSON = await result2.json(); - expect( result2.status() ).toEqual( 200 ); - - // Verify all results are in descending order. - result2JSON.forEach( ( { name }, idx ) => { - expect( name ).toBe( productNamesByPopularityDesc[ idx ] ); - } ); - } ); - } ); - } ); + } + ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/products-crud.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/products-crud.test.js index 64bf5072cfd..cbb39c3aed7 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/products-crud.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/products/products-crud.test.js @@ -589,278 +589,301 @@ test.describe( 'Products API tests: CRUD', () => { } ); } ); - test.describe( 'Product review tests: CRUD', () => { - let productReviewId; - let reviewsTestProduct; + test.describe( + 'Product review tests: CRUD', + { tag: '@skip-on-default-wpcom' }, + () => { + let productReviewId; + let reviewsTestProduct; - test.beforeAll( async ( { simpleTestProduct } ) => { - reviewsTestProduct = simpleTestProduct; - } ); - - test( 'can add a product review', async ( { request } ) => { - const response = await request.post( - 'wp-json/wc/v3/products/reviews', - { - data: { - product_id: reviewsTestProduct.id, - review: 'Nice simple product!', - reviewer: 'John Doe', - reviewer_email: 'john.doe@example.com', - rating: 5, - }, - } - ); - const responseJSON = await response.json(); - productReviewId = responseJSON.id; - - expect( response.status() ).toEqual( 201 ); - expect( typeof productReviewId ).toEqual( 'number' ); - expect( responseJSON.id ).toEqual( productReviewId ); - expect( responseJSON.product_name ).toEqual( 'A Simple Product' ); - expect( responseJSON.status ).toEqual( 'approved' ); - expect( responseJSON.reviewer ).toEqual( 'John Doe' ); - expect( responseJSON.reviewer_email ).toEqual( - 'john.doe@example.com' - ); - expect( responseJSON.review ).toEqual( 'Nice simple product!' ); - expect( responseJSON.rating ).toEqual( 5 ); - expect( responseJSON.verified ).toEqual( false ); - } ); - - test( 'cannot add a product review with invalid product_id', async ( { - request, - } ) => { - const response = await request.post( - 'wp-json/wc/v3/products/reviews', - { - data: { - product_id: 999, - review: 'A non existent product!', - reviewer: 'John Do Not', - reviewer_email: 'john.do.not@example.com', - rating: 5, - }, - } - ); - const responseJSON = await response.json(); - - expect( response.status() ).toEqual( 404 ); - expect( responseJSON.code ).toEqual( - 'woocommerce_rest_product_invalid_id' - ); - expect( responseJSON.message ).toEqual( 'Invalid product ID.' ); - } ); - - test( 'cannot add a duplicate product review', async ( { - request, - } ) => { - const response = await request.post( - 'wp-json/wc/v3/products/reviews', - { - data: { - product_id: reviewsTestProduct.id, - review: 'Nice simple product!', - reviewer: 'John Doe', - reviewer_email: 'john.doe@example.com', - rating: 5, - }, - } - ); - const responseJSON = await response.json(); - - expect( response.status() ).toEqual( 409 ); - expect( responseJSON.code ).toEqual( - 'woocommerce_rest_comment_duplicate' - ); - expect( responseJSON.message ).toEqual( - 'Duplicate comment detected; it looks as though you’ve already said that!' - ); - } ); - - test( 'can retrieve a product review', async ( { request } ) => { - const response = await request.get( - `wp-json/wc/v3/products/reviews/${ productReviewId }` - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.id ).toEqual( productReviewId ); - expect( responseJSON.product_id ).toEqual( reviewsTestProduct.id ); - expect( responseJSON.product_name ).toEqual( 'A Simple Product' ); - expect( responseJSON.status ).toEqual( 'approved' ); - expect( responseJSON.reviewer ).toEqual( 'John Doe' ); - expect( responseJSON.reviewer_email ).toEqual( - 'john.doe@example.com' - ); - expect( responseJSON.review ).toEqual( - '

    Nice simple product!

    \n' - ); - expect( responseJSON.rating ).toEqual( 5 ); - expect( responseJSON.verified ).toEqual( false ); - } ); - - test( 'can retrieve all product reviews', async ( { request } ) => { - // call API to retrieve all product reviews - const response = await request.get( - '/wp-json/wc/v3/products/reviews' - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON.length ).toBeGreaterThan( 0 ); - } ); - - test( 'can update a product review', async ( { request } ) => { - // call API to retrieve all product reviews - const response = await request.put( - `wp-json/wc/v3/products/reviews/${ productReviewId }`, - { - data: { - rating: 1, - }, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.id ).toEqual( productReviewId ); - expect( responseJSON.product_id ).toEqual( reviewsTestProduct.id ); - expect( responseJSON.product_name ).toEqual( 'A Simple Product' ); - expect( responseJSON.status ).toEqual( 'approved' ); - expect( responseJSON.reviewer ).toEqual( 'John Doe' ); - expect( responseJSON.reviewer_email ).toEqual( - 'john.doe@example.com' - ); - expect( responseJSON.review ).toEqual( 'Nice simple product!' ); - expect( responseJSON.rating ).toEqual( 1 ); - expect( responseJSON.verified ).toEqual( false ); - } ); - - test( 'can permanently delete a product review', async ( { - request, - } ) => { - // Delete the product review. - const response = await request.delete( - `wp-json/wc/v3/products/reviews/${ productReviewId }`, - { - data: { - force: true, - }, - } - ); - expect( response.status() ).toEqual( 200 ); - - // Verify that the product review can no longer be retrieved. - const getDeletedProductReviewResponse = await request.get( - `wp-json/wc/v3/products/reviews/${ productReviewId }` - ); - expect( getDeletedProductReviewResponse.status() ).toEqual( 404 ); - } ); - - test( 'can batch update product reviews', async ( { request } ) => { - // Batch create product reviews. - const response = await request.post( - `wp-json/wc/v3/products/reviews/batch`, - { - data: { - create: [ - { - product_id: reviewsTestProduct.id, - review: 'Nice product!', - reviewer: 'John Doe', - reviewer_email: 'john.doe@example.com', - rating: 4, - }, - { - product_id: reviewsTestProduct.id, - review: 'I love this thing!', - reviewer: 'Jane Doe', - reviewer_email: 'Jane.doe@example.com', - rating: 5, - }, - ], - }, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.create[ 0 ].product_id ).toEqual( - reviewsTestProduct.id - ); - expect( responseJSON.create[ 0 ].review ).toEqual( - 'Nice product!' - ); - expect( responseJSON.create[ 0 ].reviewer ).toEqual( 'John Doe' ); - expect( responseJSON.create[ 0 ].reviewer_email ).toEqual( - 'john.doe@example.com' - ); - expect( responseJSON.create[ 0 ].rating ).toEqual( 4 ); - - expect( responseJSON.create[ 1 ].product_id ).toEqual( - reviewsTestProduct.id - ); - expect( responseJSON.create[ 1 ].review ).toEqual( - 'I love this thing!' - ); - expect( responseJSON.create[ 1 ].reviewer ).toEqual( 'Jane Doe' ); - expect( responseJSON.create[ 1 ].reviewer_email ).toEqual( - 'Jane.doe@example.com' - ); - expect( responseJSON.create[ 1 ].rating ).toEqual( 5 ); - const review1Id = responseJSON.create[ 0 ].id; - const review2Id = responseJSON.create[ 1 ].id; - - // Batch create a new review, update a review and delete another. - const responseBatchUpdate = await request.post( - `wp-json/wc/v3/products/reviews/batch`, - { - data: { - create: [ - { - product_id: reviewsTestProduct.id, - review: 'Ok product.', - reviewer: 'Jack Doe', - reviewer_email: 'jack.doe@example.com', - rating: 3, - }, - ], - update: [ - { - id: review1Id, - review: 'On reflection, I hate this thing!', - rating: 1, - }, - ], - delete: [ review2Id ], - }, - } - ); - const responseBatchUpdateJSON = await responseBatchUpdate.json(); - const review3Id = responseBatchUpdateJSON.create[ 0 ].id; - expect( response.status() ).toEqual( 200 ); - - const responseUpdatedReview = await request.get( - `wp-json/wc/v3/products/reviews/${ review1Id }` - ); - const responseUpdatedReviewJSON = - await responseUpdatedReview.json(); - expect( responseUpdatedReviewJSON.review ).toEqual( - '

    On reflection, I hate this thing!

    \n' - ); - expect( responseUpdatedReviewJSON.rating ).toEqual( 1 ); - - // Verify that the deleted review can no longer be retrieved. - const getDeletedProductReviewResponse = await request.get( - `wp-json/wc/v3/products/reviews/${ review2Id }` - ); - expect( getDeletedProductReviewResponse.status() ).toEqual( 404 ); - - // Batch delete the created tags - await request.post( `wp-json/wc/v3/products/reviews/batch`, { - data: { - delete: [ review1Id, review3Id ], - }, + test.beforeAll( async ( { simpleTestProduct } ) => { + reviewsTestProduct = simpleTestProduct; } ); - } ); - } ); + + test( 'can add a product review', async ( { request } ) => { + const response = await request.post( + 'wp-json/wc/v3/products/reviews', + { + data: { + product_id: reviewsTestProduct.id, + review: 'Nice simple product!', + reviewer: 'John Doe', + reviewer_email: 'john.doe@example.com', + rating: 5, + }, + } + ); + const responseJSON = await response.json(); + productReviewId = responseJSON.id; + + expect( response.status() ).toEqual( 201 ); + expect( typeof productReviewId ).toEqual( 'number' ); + expect( responseJSON.id ).toEqual( productReviewId ); + expect( responseJSON.product_name ).toEqual( + 'A Simple Product' + ); + expect( responseJSON.status ).toEqual( 'approved' ); + expect( responseJSON.reviewer ).toEqual( 'John Doe' ); + expect( responseJSON.reviewer_email ).toEqual( + 'john.doe@example.com' + ); + expect( responseJSON.review ).toEqual( 'Nice simple product!' ); + expect( responseJSON.rating ).toEqual( 5 ); + expect( responseJSON.verified ).toEqual( false ); + } ); + + test( 'cannot add a product review with invalid product_id', async ( { + request, + } ) => { + const response = await request.post( + 'wp-json/wc/v3/products/reviews', + { + data: { + product_id: 999, + review: 'A non existent product!', + reviewer: 'John Do Not', + reviewer_email: 'john.do.not@example.com', + rating: 5, + }, + } + ); + const responseJSON = await response.json(); + + expect( response.status() ).toEqual( 404 ); + expect( responseJSON.code ).toEqual( + 'woocommerce_rest_product_invalid_id' + ); + expect( responseJSON.message ).toEqual( 'Invalid product ID.' ); + } ); + + test( 'cannot add a duplicate product review', async ( { + request, + } ) => { + const response = await request.post( + 'wp-json/wc/v3/products/reviews', + { + data: { + product_id: reviewsTestProduct.id, + review: 'Nice simple product!', + reviewer: 'John Doe', + reviewer_email: 'john.doe@example.com', + rating: 5, + }, + } + ); + const responseJSON = await response.json(); + + expect( response.status() ).toEqual( 409 ); + expect( responseJSON.code ).toEqual( + 'woocommerce_rest_comment_duplicate' + ); + expect( responseJSON.message ).toEqual( + 'Duplicate comment detected; it looks as though you’ve already said that!' + ); + } ); + + test( 'can retrieve a product review', async ( { request } ) => { + const response = await request.get( + `wp-json/wc/v3/products/reviews/${ productReviewId }` + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.id ).toEqual( productReviewId ); + expect( responseJSON.product_id ).toEqual( + reviewsTestProduct.id + ); + expect( responseJSON.product_name ).toEqual( + 'A Simple Product' + ); + expect( responseJSON.status ).toEqual( 'approved' ); + expect( responseJSON.reviewer ).toEqual( 'John Doe' ); + expect( responseJSON.reviewer_email ).toEqual( + 'john.doe@example.com' + ); + expect( responseJSON.review ).toEqual( + '

    Nice simple product!

    \n' + ); + expect( responseJSON.rating ).toEqual( 5 ); + expect( responseJSON.verified ).toEqual( false ); + } ); + + test( 'can retrieve all product reviews', async ( { request } ) => { + // call API to retrieve all product reviews + const response = await request.get( + '/wp-json/wc/v3/products/reviews' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON.length ).toBeGreaterThan( 0 ); + } ); + + test( 'can update a product review', async ( { request } ) => { + // call API to retrieve all product reviews + const response = await request.put( + `wp-json/wc/v3/products/reviews/${ productReviewId }`, + { + data: { + rating: 1, + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.id ).toEqual( productReviewId ); + expect( responseJSON.product_id ).toEqual( + reviewsTestProduct.id + ); + expect( responseJSON.product_name ).toEqual( + 'A Simple Product' + ); + expect( responseJSON.status ).toEqual( 'approved' ); + expect( responseJSON.reviewer ).toEqual( 'John Doe' ); + expect( responseJSON.reviewer_email ).toEqual( + 'john.doe@example.com' + ); + expect( responseJSON.review ).toEqual( 'Nice simple product!' ); + expect( responseJSON.rating ).toEqual( 1 ); + expect( responseJSON.verified ).toEqual( false ); + } ); + + test( 'can permanently delete a product review', async ( { + request, + } ) => { + // Delete the product review. + const response = await request.delete( + `wp-json/wc/v3/products/reviews/${ productReviewId }`, + { + data: { + force: true, + }, + } + ); + expect( response.status() ).toEqual( 200 ); + + // Verify that the product review can no longer be retrieved. + const getDeletedProductReviewResponse = await request.get( + `wp-json/wc/v3/products/reviews/${ productReviewId }` + ); + expect( getDeletedProductReviewResponse.status() ).toEqual( + 404 + ); + } ); + + test( 'can batch update product reviews', async ( { request } ) => { + // Batch create product reviews. + const response = await request.post( + `wp-json/wc/v3/products/reviews/batch`, + { + data: { + create: [ + { + product_id: reviewsTestProduct.id, + review: 'Nice product!', + reviewer: 'John Doe', + reviewer_email: 'john.doe@example.com', + rating: 4, + }, + { + product_id: reviewsTestProduct.id, + review: 'I love this thing!', + reviewer: 'Jane Doe', + reviewer_email: 'Jane.doe@example.com', + rating: 5, + }, + ], + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.create[ 0 ].product_id ).toEqual( + reviewsTestProduct.id + ); + expect( responseJSON.create[ 0 ].review ).toEqual( + 'Nice product!' + ); + expect( responseJSON.create[ 0 ].reviewer ).toEqual( + 'John Doe' + ); + expect( responseJSON.create[ 0 ].reviewer_email ).toEqual( + 'john.doe@example.com' + ); + expect( responseJSON.create[ 0 ].rating ).toEqual( 4 ); + + expect( responseJSON.create[ 1 ].product_id ).toEqual( + reviewsTestProduct.id + ); + expect( responseJSON.create[ 1 ].review ).toEqual( + 'I love this thing!' + ); + expect( responseJSON.create[ 1 ].reviewer ).toEqual( + 'Jane Doe' + ); + expect( responseJSON.create[ 1 ].reviewer_email ).toEqual( + 'Jane.doe@example.com' + ); + expect( responseJSON.create[ 1 ].rating ).toEqual( 5 ); + const review1Id = responseJSON.create[ 0 ].id; + const review2Id = responseJSON.create[ 1 ].id; + + // Batch create a new review, update a review and delete another. + const responseBatchUpdate = await request.post( + `wp-json/wc/v3/products/reviews/batch`, + { + data: { + create: [ + { + product_id: reviewsTestProduct.id, + review: 'Ok product.', + reviewer: 'Jack Doe', + reviewer_email: 'jack.doe@example.com', + rating: 3, + }, + ], + update: [ + { + id: review1Id, + review: 'On reflection, I hate this thing!', + rating: 1, + }, + ], + delete: [ review2Id ], + }, + } + ); + const responseBatchUpdateJSON = + await responseBatchUpdate.json(); + const review3Id = responseBatchUpdateJSON.create[ 0 ].id; + expect( response.status() ).toEqual( 200 ); + + const responseUpdatedReview = await request.get( + `wp-json/wc/v3/products/reviews/${ review1Id }` + ); + const responseUpdatedReviewJSON = + await responseUpdatedReview.json(); + expect( responseUpdatedReviewJSON.review ).toEqual( + '

    On reflection, I hate this thing!

    \n' + ); + expect( responseUpdatedReviewJSON.rating ).toEqual( 1 ); + + // Verify that the deleted review can no longer be retrieved. + const getDeletedProductReviewResponse = await request.get( + `wp-json/wc/v3/products/reviews/${ review2Id }` + ); + expect( getDeletedProductReviewResponse.status() ).toEqual( + 404 + ); + + // Batch delete the created tags + await request.post( `wp-json/wc/v3/products/reviews/batch`, { + data: { + delete: [ review1Id, review3Id ], + }, + } ); + } ); + } + ); test.describe( 'Product shipping classes tests: CRUD', () => { let productShippingClassId; @@ -958,83 +981,91 @@ test.describe( 'Products API tests: CRUD', () => { ); } ); - test( 'can batch update product shipping classes', async ( { - request, - } ) => { - // Batch create product shipping classes. - const response = await request.post( - `wp-json/wc/v3/products/shipping_classes/batch`, - { - data: { - create: [ - { - name: 'Small Items', - }, - { - name: 'Large Items', - }, - ], - }, - } - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( responseJSON.create[ 0 ].name ).toEqual( 'Small Items' ); - expect( responseJSON.create[ 1 ].name ).toEqual( 'Large Items' ); - const shippingClass1Id = responseJSON.create[ 0 ].id; - const shippingClass2Id = responseJSON.create[ 1 ].id; + test( + 'can batch update product shipping classes', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + async ( { request } ) => { + // Batch create product shipping classes. + const response = await request.post( + `wp-json/wc/v3/products/shipping_classes/batch`, + { + data: { + create: [ + { + name: 'Small Items', + }, + { + name: 'Large Items', + }, + ], + }, + } + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( responseJSON.create[ 0 ].name ).toEqual( + 'Small Items' + ); + expect( responseJSON.create[ 1 ].name ).toEqual( + 'Large Items' + ); + const shippingClass1Id = responseJSON.create[ 0 ].id; + const shippingClass2Id = responseJSON.create[ 1 ].id; - // Batch create a new shipping class, update a shipping class and delete another. - const responseBatchUpdate = await request.post( - `wp-json/wc/v3/products/shipping_classes/batch`, - { - data: { - create: [ - { - name: 'Express', - }, - ], - update: [ - { - id: shippingClass1Id, - description: 'Priority shipping.', - }, - ], - delete: [ shippingClass2Id ], - }, - } - ); - const responseBatchUpdateJSON = await responseBatchUpdate.json(); - const shippingClass3Id = responseBatchUpdateJSON.create[ 0 ].id; - expect( response.status() ).toEqual( 200 ); + // Batch create a new shipping class, update a shipping class and delete another. + const responseBatchUpdate = await request.post( + `wp-json/wc/v3/products/shipping_classes/batch`, + { + data: { + create: [ + { + name: 'Express', + }, + ], + update: [ + { + id: shippingClass1Id, + description: 'Priority shipping.', + }, + ], + delete: [ shippingClass2Id ], + }, + } + ); + const responseBatchUpdateJSON = + await responseBatchUpdate.json(); + const shippingClass3Id = responseBatchUpdateJSON.create[ 0 ].id; + expect( response.status() ).toEqual( 200 ); - const responseUpdatedShippingClass = await request.get( - `wp-json/wc/v3/products/shipping_classes/${ shippingClass1Id }` - ); - const responseUpdatedShippingClassJSON = - await responseUpdatedShippingClass.json(); - expect( responseUpdatedShippingClassJSON.description ).toEqual( - 'Priority shipping.' - ); + const responseUpdatedShippingClass = await request.get( + `wp-json/wc/v3/products/shipping_classes/${ shippingClass1Id }` + ); + const responseUpdatedShippingClassJSON = + await responseUpdatedShippingClass.json(); + expect( responseUpdatedShippingClassJSON.description ).toEqual( + 'Priority shipping.' + ); - // Verify that the product tag can no longer be retrieved. - const getDeletedProductShippingClassResponse = await request.get( - `wp-json/wc/v3/products/shipping_classes/${ shippingClass2Id }` - ); - expect( getDeletedProductShippingClassResponse.status() ).toEqual( - 404 - ); + // Verify that the product tag can no longer be retrieved. + const getDeletedProductShippingClassResponse = + await request.get( + `wp-json/wc/v3/products/shipping_classes/${ shippingClass2Id }` + ); + expect( + getDeletedProductShippingClassResponse.status() + ).toEqual( 404 ); - // Batch delete the created tags - await request.post( - `wp-json/wc/v3/products/shipping_classes/batch`, - { - data: { - delete: [ shippingClass1Id, shippingClass3Id ], - }, - } - ); - } ); + // Batch delete the created tags + await request.post( + `wp-json/wc/v3/products/shipping_classes/batch`, + { + data: { + delete: [ shippingClass1Id, shippingClass3Id ], + }, + } + ); + } + ); } ); test.describe( 'Product tags tests: CRUD', () => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/settings/settings-crud.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/settings/settings-crud.test.js index 376d606c745..e7caee7e0b4 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/settings/settings-crud.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/settings/settings-crud.test.js @@ -280,7 +280,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => { type: 'text', default: '', tip: 'The street address for your business location.', - value: '', + value: expect.any( String ), } ), ] ) ); @@ -310,7 +310,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => { type: 'text', default: '', tip: 'The city in which your business is located.', - value: '', + value: expect.any( String ), } ), ] ) ); @@ -341,7 +341,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => { type: 'text', default: '', tip: 'The postal code, if any, in which your business is located.', - value: '', + value: expect.any( String ), } ), ] ) ); @@ -1019,157 +1019,164 @@ test.describe.serial( 'Settings API tests: CRUD', () => { } ); test.describe( 'List all Tax settings options', () => { - test( 'can retrieve all tax settings', async ( { request } ) => { - // call API to retrieve all settings options - const response = await request.get( '/wp-json/wc/v3/settings/tax' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON.length ).toBeGreaterThan( 0 ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_prices_include_tax', - label: 'Prices entered with tax', - description: '', - type: 'radio', - default: 'no', - options: { - yes: 'Yes, I will enter prices inclusive of tax', - no: 'No, I will enter prices exclusive of tax', - }, - tip: 'This option is important as it will affect how you input prices. Changing it will not update existing products.', - value: 'no', - } ), - ] ) - ); + test( + 'can retrieve all tax settings', + { tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] }, + async ( { request } ) => { + // call API to retrieve all settings options + const response = await request.get( + '/wp-json/wc/v3/settings/tax' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON.length ).toBeGreaterThan( 0 ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_prices_include_tax', + label: 'Prices entered with tax', + description: '', + type: 'radio', + default: 'no', + options: { + yes: 'Yes, I will enter prices inclusive of tax', + no: 'No, I will enter prices exclusive of tax', + }, + tip: 'This option is important as it will affect how you input prices. Changing it will not update existing products.', + value: 'no', + } ), + ] ) + ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_based_on', - label: 'Calculate tax based on', - description: '', - type: 'select', - default: 'shipping', - options: { - shipping: 'Customer shipping address', - billing: 'Customer billing address', - base: 'Shop base address', - }, - tip: 'This option determines which address is used to calculate tax.', - value: 'shipping', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_shipping_tax_class', - label: 'Shipping tax class', - description: - 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', - type: 'select', - default: 'inherit', - options: { - inherit: 'Shipping tax class based on cart items', - '': 'Standard', - 'reduced-rate': 'Reduced rate', - 'zero-rate': 'Zero rate', - }, - tip: 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', - value: 'inherit', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_round_at_subtotal', - label: 'Rounding', - description: - 'Round tax at subtotal level, instead of rounding per line', - type: 'checkbox', - default: 'no', - value: 'no', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_classes', - label: 'Additional tax classes', - description: '', - type: 'textarea', - default: '', - tip: 'List additional tax classes you need below (1 per line, e.g. Reduced Rates). These are in addition to "Standard rate" which exists by default.', - value: '', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_display_shop', - label: 'Display prices in the shop', - description: '', - type: 'select', - default: 'excl', - options: { - incl: 'Including tax', - excl: 'Excluding tax', - }, - value: 'excl', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_display_cart', - label: 'Display prices during cart and checkout', - description: '', - type: 'select', - default: 'excl', - options: { - incl: 'Including tax', - excl: 'Excluding tax', - }, - value: 'excl', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_price_display_suffix', - label: 'Price display suffix', - description: '', - type: 'text', - default: '', - tip: 'Define text to show after your product prices. This could be, for example, "inc. Vat" to explain your pricing. You can also have prices substituted here using one of the following: {price_including_tax}, {price_excluding_tax}.', - value: '', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_tax_total_display', - label: 'Display tax totals', - description: '', - type: 'select', - default: 'itemized', - options: { - single: 'As a single total', - itemized: 'Itemized', - }, - value: 'itemized', - } ), - ] ) - ); - } ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_based_on', + label: 'Calculate tax based on', + description: '', + type: 'select', + default: 'shipping', + options: { + shipping: 'Customer shipping address', + billing: 'Customer billing address', + base: 'Shop base address', + }, + tip: 'This option determines which address is used to calculate tax.', + value: 'shipping', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_shipping_tax_class', + label: 'Shipping tax class', + description: + 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', + type: 'select', + default: 'inherit', + options: { + inherit: + 'Shipping tax class based on cart items', + '': 'Standard', + 'reduced-rate': 'Reduced rate', + 'zero-rate': 'Zero rate', + }, + tip: 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', + value: 'inherit', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_round_at_subtotal', + label: 'Rounding', + description: + 'Round tax at subtotal level, instead of rounding per line', + type: 'checkbox', + default: 'no', + value: 'no', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_classes', + label: 'Additional tax classes', + description: '', + type: 'textarea', + default: '', + tip: 'List additional tax classes you need below (1 per line, e.g. Reduced Rates). These are in addition to "Standard rate" which exists by default.', + value: '', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_display_shop', + label: 'Display prices in the shop', + description: '', + type: 'select', + default: 'excl', + options: { + incl: 'Including tax', + excl: 'Excluding tax', + }, + value: 'excl', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_display_cart', + label: 'Display prices during cart and checkout', + description: '', + type: 'select', + default: 'excl', + options: { + incl: 'Including tax', + excl: 'Excluding tax', + }, + value: 'excl', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_price_display_suffix', + label: 'Price display suffix', + description: '', + type: 'text', + default: '', + tip: 'Define text to show after your product prices. This could be, for example, "inc. Vat" to explain your pricing. You can also have prices substituted here using one of the following: {price_including_tax}, {price_excluding_tax}.', + value: '', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_tax_total_display', + label: 'Display tax totals', + description: '', + type: 'select', + default: 'itemized', + options: { + single: 'As a single total', + itemized: 'Itemized', + }, + value: 'itemized', + } ), + ] ) + ); + } + ); } ); test.describe( 'List all Shipping settings options', () => { @@ -1613,286 +1620,294 @@ test.describe.serial( 'Settings API tests: CRUD', () => { } ); } ); - test.describe( 'List all Advanced settings options', () => { - test( 'can retrieve all advanced settings', async ( { request } ) => { - // call API to retrieve all settings options - const response = await request.get( - '/wp-json/wc/v3/settings/advanced' - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); + test.describe( + 'List all Advanced settings options', + { tag: '@skip-on-default-wpcom' }, + () => { + test( 'can retrieve all advanced settings', async ( { + request, + } ) => { + // call API to retrieve all settings options + const response = await request.get( + '/wp-json/wc/v3/settings/advanced' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + + // not present in external host + // eslint-disable-next-line playwright/no-conditional-in-test + if ( ! shouldSkip ) { + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_cart_page_id', + label: 'Cart page', + description: + 'Page where shoppers review their shopping cart', + type: 'select', + default: '', + tip: 'Page where shoppers review their shopping cart', + value: expect.any( String ), + options: expect.any( Object ), + } ), + ] ) + ); + } + + // not present in external host + // eslint-disable-next-line playwright/no-conditional-in-test + if ( ! shouldSkip ) { + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_checkout_page_id', + label: 'Checkout page', + description: + 'Page where shoppers go to finalize their purchase', + type: 'select', + default: expect.any( Number ), + tip: 'Page where shoppers go to finalize their purchase', + value: expect.any( String ), + options: expect.any( Object ), + } ), + ] ) + ); + } - // not present in external host - // eslint-disable-next-line playwright/no-conditional-in-test - if ( ! shouldSkip ) { expect( responseJSON ).toEqual( expect.arrayContaining( [ expect.objectContaining( { - id: 'woocommerce_cart_page_id', - label: 'Cart page', + id: 'woocommerce_myaccount_page_id', + label: 'My account page', description: - 'Page where shoppers review their shopping cart', + 'Page contents: [woocommerce_my_account]', type: 'select', default: '', - tip: 'Page where shoppers review their shopping cart', + tip: 'Page contents: [woocommerce_my_account]', value: expect.any( String ), options: expect.any( Object ), } ), ] ) ); - } - - // not present in external host - // eslint-disable-next-line playwright/no-conditional-in-test - if ( ! shouldSkip ) { expect( responseJSON ).toEqual( expect.arrayContaining( [ expect.objectContaining( { - id: 'woocommerce_checkout_page_id', - label: 'Checkout page', + id: 'woocommerce_checkout_pay_endpoint', + label: 'Pay', description: - 'Page where shoppers go to finalize their purchase', - type: 'select', - default: expect.any( Number ), - tip: 'Page where shoppers go to finalize their purchase', - value: expect.any( String ), - options: expect.any( Object ), + 'Endpoint for the "Checkout → Pay" page.', + type: 'text', + default: 'order-pay', + tip: 'Endpoint for the "Checkout → Pay" page.', + value: 'order-pay', } ), ] ) ); - } - - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_page_id', - label: 'My account page', - description: 'Page contents: [woocommerce_my_account]', - type: 'select', - default: '', - tip: 'Page contents: [woocommerce_my_account]', - value: expect.any( String ), - options: expect.any( Object ), - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_checkout_pay_endpoint', - label: 'Pay', - description: - 'Endpoint for the "Checkout → Pay" page.', - type: 'text', - default: 'order-pay', - tip: 'Endpoint for the "Checkout → Pay" page.', - value: 'order-pay', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_checkout_order_received_endpoint', - label: 'Order received', - description: - 'Endpoint for the "Checkout → Order received" page.', - type: 'text', - default: 'order-received', - tip: 'Endpoint for the "Checkout → Order received" page.', - value: 'order-received', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_add_payment_method_endpoint', - label: 'Add payment method', - description: - 'Endpoint for the "Checkout → Add payment method" page.', - type: 'text', - default: 'add-payment-method', - tip: 'Endpoint for the "Checkout → Add payment method" page.', - value: 'add-payment-method', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_delete_payment_method_endpoint', - label: 'Delete payment method', - description: - 'Endpoint for the delete payment method page.', - type: 'text', - default: 'delete-payment-method', - tip: 'Endpoint for the delete payment method page.', - value: 'delete-payment-method', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_orders_endpoint', - label: 'Orders', - description: - 'Endpoint for the "My account → Orders" page.', - type: 'text', - default: 'orders', - tip: 'Endpoint for the "My account → Orders" page.', - value: 'orders', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_view_order_endpoint', - label: 'View order', - description: - 'Endpoint for the "My account → View order" page.', - type: 'text', - default: 'view-order', - tip: 'Endpoint for the "My account → View order" page.', - value: 'view-order', - } ), - ] ) - ); - - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_downloads_endpoint', - label: 'Downloads', - description: - 'Endpoint for the "My account → Downloads" page.', - type: 'text', - default: 'downloads', - tip: 'Endpoint for the "My account → Downloads" page.', - value: 'downloads', - } ), - ] ) - ); - - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_edit_account_endpoint', - label: 'Edit account', - description: - 'Endpoint for the "My account → Edit account" page.', - type: 'text', - default: 'edit-account', - tip: 'Endpoint for the "My account → Edit account" page.', - value: 'edit-account', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_edit_address_endpoint', - label: 'Addresses', - description: - 'Endpoint for the "My account → Addresses" page.', - type: 'text', - default: 'edit-address', - tip: 'Endpoint for the "My account → Addresses" page.', - value: 'edit-address', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_payment_methods_endpoint', - label: 'Payment methods', - description: - 'Endpoint for the "My account → Payment methods" page.', - type: 'text', - default: 'payment-methods', - tip: 'Endpoint for the "My account → Payment methods" page.', - value: 'payment-methods', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_myaccount_lost_password_endpoint', - label: 'Lost password', - description: - 'Endpoint for the "My account → Lost password" page.', - type: 'text', - default: 'lost-password', - tip: 'Endpoint for the "My account → Lost password" page.', - value: 'lost-password', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_logout_endpoint', - label: 'Logout', - description: - 'Endpoint for the triggering logout. You can add this to your menus via a custom link: yoursite.com/?customer-logout=true', - type: 'text', - default: 'customer-logout', - tip: 'Endpoint for the triggering logout. You can add this to your menus via a custom link: yoursite.com/?customer-logout=true', - value: 'customer-logout', - } ), - ] ) - ); - // eslint-disable-next-line playwright/no-conditional-in-test - if ( ! shouldSkip ) { expect( responseJSON ).toEqual( expect.arrayContaining( [ expect.objectContaining( { - id: 'woocommerce_allow_tracking', - label: 'Enable tracking', + id: 'woocommerce_checkout_order_received_endpoint', + label: 'Order received', description: - 'Allow usage of WooCommerce to be tracked', + 'Endpoint for the "Checkout → Order received" page.', + type: 'text', + default: 'order-received', + tip: 'Endpoint for the "Checkout → Order received" page.', + value: 'order-received', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_add_payment_method_endpoint', + label: 'Add payment method', + description: + 'Endpoint for the "Checkout → Add payment method" page.', + type: 'text', + default: 'add-payment-method', + tip: 'Endpoint for the "Checkout → Add payment method" page.', + value: 'add-payment-method', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_delete_payment_method_endpoint', + label: 'Delete payment method', + description: + 'Endpoint for the delete payment method page.', + type: 'text', + default: 'delete-payment-method', + tip: 'Endpoint for the delete payment method page.', + value: 'delete-payment-method', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_orders_endpoint', + label: 'Orders', + description: + 'Endpoint for the "My account → Orders" page.', + type: 'text', + default: 'orders', + tip: 'Endpoint for the "My account → Orders" page.', + value: 'orders', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_view_order_endpoint', + label: 'View order', + description: + 'Endpoint for the "My account → View order" page.', + type: 'text', + default: 'view-order', + tip: 'Endpoint for the "My account → View order" page.', + value: 'view-order', + } ), + ] ) + ); + + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_downloads_endpoint', + label: 'Downloads', + description: + 'Endpoint for the "My account → Downloads" page.', + type: 'text', + default: 'downloads', + tip: 'Endpoint for the "My account → Downloads" page.', + value: 'downloads', + } ), + ] ) + ); + + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_edit_account_endpoint', + label: 'Edit account', + description: + 'Endpoint for the "My account → Edit account" page.', + type: 'text', + default: 'edit-account', + tip: 'Endpoint for the "My account → Edit account" page.', + value: 'edit-account', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_edit_address_endpoint', + label: 'Addresses', + description: + 'Endpoint for the "My account → Addresses" page.', + type: 'text', + default: 'edit-address', + tip: 'Endpoint for the "My account → Addresses" page.', + value: 'edit-address', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_payment_methods_endpoint', + label: 'Payment methods', + description: + 'Endpoint for the "My account → Payment methods" page.', + type: 'text', + default: 'payment-methods', + tip: 'Endpoint for the "My account → Payment methods" page.', + value: 'payment-methods', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_myaccount_lost_password_endpoint', + label: 'Lost password', + description: + 'Endpoint for the "My account → Lost password" page.', + type: 'text', + default: 'lost-password', + tip: 'Endpoint for the "My account → Lost password" page.', + value: 'lost-password', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_logout_endpoint', + label: 'Logout', + description: + 'Endpoint for the triggering logout. You can add this to your menus via a custom link: yoursite.com/?customer-logout=true', + type: 'text', + default: 'customer-logout', + tip: 'Endpoint for the triggering logout. You can add this to your menus via a custom link: yoursite.com/?customer-logout=true', + value: 'customer-logout', + } ), + ] ) + ); + // eslint-disable-next-line playwright/no-conditional-in-test + if ( ! shouldSkip ) { + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_allow_tracking', + label: 'Enable tracking', + description: + 'Allow usage of WooCommerce to be tracked', + type: 'checkbox', + default: 'no', + tip: 'To opt out, leave this box unticked. Your store remains untracked, and no data will be collected. Read about what usage data is tracked at: WooCommerce.com Usage Tracking Documentation.', + value: 'no', + } ), + ] ) + ); + } else { + // Test is failing on external hosts + } + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_show_marketplace_suggestions', + label: 'Show Suggestions', + description: + 'Display suggestions within WooCommerce', type: 'checkbox', - default: 'no', - tip: 'To opt out, leave this box unticked. Your store remains untracked, and no data will be collected. Read about what usage data is tracked at: WooCommerce.com Usage Tracking Documentation.', - value: 'no', + default: 'yes', + tip: 'Leave this box unchecked if you do not want to pull suggested extensions from WooCommerce.com. You will see a static list of extensions instead.', + value: 'yes', } ), ] ) ); - } else { - // Test is failing on external hosts - } - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_show_marketplace_suggestions', - label: 'Show Suggestions', - description: 'Display suggestions within WooCommerce', - type: 'checkbox', - default: 'yes', - tip: 'Leave this box unchecked if you do not want to pull suggested extensions from WooCommerce.com. You will see a static list of extensions instead.', - value: 'yes', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'woocommerce_analytics_enabled', - label: 'Analytics', - description: 'Enable WooCommerce Analytics', - type: 'checkbox', - default: 'yes', - value: 'yes', - } ), - ] ) - ); - } ); - } ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'woocommerce_analytics_enabled', + label: 'Analytics', + description: 'Enable WooCommerce Analytics', + type: 'checkbox', + default: 'yes', + value: 'yes', + } ), + ] ) + ); + } ); + } + ); test.describe( 'List all Email New Order settings', () => { test( 'can retrieve all email new order settings', async ( { @@ -1930,7 +1945,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => { tip: expect.stringContaining( 'Enter recipients (comma separated) for this email. Defaults to' ), - value: '', + value: expect.any( String ), } ), ] ) ); @@ -1998,107 +2013,110 @@ test.describe.serial( 'Settings API tests: CRUD', () => { } ); test.describe( 'List all Email Failed Order settings', () => { - test( 'can retrieve all email failed order settings', async ( { - request, - } ) => { - // call API to retrieve all settings options - const response = await request.get( - '/wp-json/wc/v3/settings/email_failed_order' - ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); - expect( Array.isArray( responseJSON ) ).toBe( true ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'enabled', - label: 'Enable/Disable', - description: '', - type: 'checkbox', - default: 'yes', - value: 'yes', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'recipient', - label: 'Recipient(s)', - description: expect.stringContaining( - 'Enter recipients (comma separated) for this email. Defaults to' - ), - type: 'text', - default: '', - tip: expect.stringContaining( - 'Enter recipients (comma separated) for this email. Defaults to' - ), - value: '', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'subject', - label: 'Subject', - description: - 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - type: 'text', - default: '', - tip: 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - value: '', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'heading', - label: 'Email heading', - description: - 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - type: 'text', - default: '', - tip: 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - value: '', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'additional_content', - label: 'Additional content', - description: - 'Text to appear below the main email content. Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - type: 'textarea', - default: - 'Hopefully they’ll be back. Read more about troubleshooting failed payments.', - tip: 'Text to appear below the main email content. Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', - value: 'Hopefully they’ll be back. Read more about troubleshooting failed payments.', - } ), - ] ) - ); - expect( responseJSON ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - id: 'email_type', - label: 'Email type', - description: 'Choose which format of email to send.', - type: 'select', - default: 'html', - options: { - plain: 'Plain text', - html: 'HTML', - multipart: 'Multipart', - }, - tip: 'Choose which format of email to send.', - value: 'html', - } ), - ] ) - ); - } ); + test( + 'can retrieve all email failed order settings', + { tag: '@skip-on-default-pressable' }, + async ( { request } ) => { + // call API to retrieve all settings options + const response = await request.get( + '/wp-json/wc/v3/settings/email_failed_order' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + expect( Array.isArray( responseJSON ) ).toBe( true ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'enabled', + label: 'Enable/Disable', + description: '', + type: 'checkbox', + default: 'yes', + value: 'yes', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'recipient', + label: 'Recipient(s)', + description: expect.stringContaining( + 'Enter recipients (comma separated) for this email. Defaults to' + ), + type: 'text', + default: '', + tip: expect.stringContaining( + 'Enter recipients (comma separated) for this email. Defaults to' + ), + value: '', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'subject', + label: 'Subject', + description: + 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + type: 'text', + default: '', + tip: 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + value: '', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'heading', + label: 'Email heading', + description: + 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + type: 'text', + default: '', + tip: 'Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + value: '', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'additional_content', + label: 'Additional content', + description: + 'Text to appear below the main email content. Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + type: 'textarea', + default: + 'Hopefully they’ll be back. Read more about troubleshooting failed payments.', + tip: 'Text to appear below the main email content. Available placeholders: {site_title}</code>, <code>{site_address}</code>, <code>{site_url}</code>, <code>{order_date}</code>, <code>{order_number}', + value: 'Hopefully they’ll be back. Read more about troubleshooting failed payments.', + } ), + ] ) + ); + expect( responseJSON ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + id: 'email_type', + label: 'Email type', + description: + 'Choose which format of email to send.', + type: 'select', + default: 'html', + options: { + plain: 'Plain text', + html: 'HTML', + multipart: 'Multipart', + }, + tip: 'Choose which format of email to send.', + value: 'html', + } ), + ] ) + ); + } + ); } ); test.describe( 'List all Email Customer On Hold Order settings', () => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/system-status/system-status-crud.test.js b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/system-status/system-status-crud.test.js index 65d56a07196..005c48a99cd 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/api-tests/system-status/system-status-crud.test.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/api-tests/system-status/system-status-crud.test.js @@ -3,582 +3,590 @@ const { BASE_URL } = process.env; const shouldSkip = BASE_URL !== undefined && ! BASE_URL.includes( 'localhost' ); test.describe( 'System Status API tests', () => { - test( 'can view all system status items', async ( { request } ) => { - // call API to view all system status items - const response = await request.get( '/wp-json/wc/v3/system_status' ); - const responseJSON = await response.json(); - expect( response.status() ).toEqual( 200 ); + test( + 'can view all system status items', + { tag: '@skip-on-default-wpcom' }, + async ( { request } ) => { + // call API to view all system status items + const response = await request.get( + '/wp-json/wc/v3/system_status' + ); + const responseJSON = await response.json(); + expect( response.status() ).toEqual( 200 ); + + // local environment differs from external hosts. Local listed first. + // eslint-disable-next-line playwright/no-conditional-in-test + if ( ! shouldSkip ) { + expect( responseJSON ).toEqual( + expect.objectContaining( { + environment: expect.objectContaining( { + home_url: expect.any( String ), + site_url: expect.any( String ), + version: expect.any( String ), + log_directory: expect.any( String ), + log_directory_writable: expect.any( Boolean ), + wp_version: expect.any( String ), + wp_multisite: expect.any( Boolean ), + wp_memory_limit: expect.any( Number ), + wp_debug_mode: expect.any( Boolean ), + wp_cron: expect.any( Boolean ), + language: expect.any( String ), + external_object_cache: null, + server_info: expect.any( String ), + php_version: expect.any( String ), + php_post_max_size: expect.any( Number ), + php_max_execution_time: expect.any( Number ), + php_max_input_vars: expect.any( Number ), + curl_version: expect.any( String ), + suhosin_installed: expect.any( Boolean ), + max_upload_size: expect.any( Number ), + mysql_version: expect.any( String ), + mysql_version_string: expect.any( String ), + default_timezone: expect.any( String ), + fsockopen_or_curl_enabled: expect.any( Boolean ), + soapclient_enabled: expect.any( Boolean ), + domdocument_enabled: expect.any( Boolean ), + gzip_enabled: expect.any( Boolean ), + mbstring_enabled: expect.any( Boolean ), + remote_post_successful: expect.any( Boolean ), + remote_post_response: expect.any( String ), + remote_get_successful: expect.any( Boolean ), + remote_get_response: expect.any( String ), + } ), + } ) + ); + } else { + expect( responseJSON ).toEqual( + expect.objectContaining( { + environment: expect.objectContaining( { + home_url: expect.any( String ), + site_url: expect.any( String ), + version: expect.any( String ), + log_directory: expect.any( String ), + log_directory_writable: expect.any( Boolean ), + wp_version: expect.any( String ), + wp_multisite: expect.any( Boolean ), + wp_memory_limit: expect.any( Number ), + wp_debug_mode: expect.any( Boolean ), + wp_cron: expect.any( Boolean ), + language: expect.any( String ), + external_object_cache: expect.any( Boolean ), + server_info: expect.any( String ), + php_version: expect.any( String ), + php_post_max_size: expect.any( Number ), + php_max_execution_time: expect.any( Number ), + php_max_input_vars: expect.any( Number ), + curl_version: expect.any( String ), + suhosin_installed: expect.any( Boolean ), + max_upload_size: expect.any( Number ), + mysql_version: expect.any( String ), + mysql_version_string: expect.any( String ), + default_timezone: expect.any( String ), + fsockopen_or_curl_enabled: expect.any( Boolean ), + soapclient_enabled: expect.any( Boolean ), + domdocument_enabled: expect.any( Boolean ), + gzip_enabled: expect.any( Boolean ), + mbstring_enabled: expect.any( Boolean ), + remote_post_successful: expect.any( Boolean ), + remote_post_response: expect.any( Number ), + remote_get_successful: expect.any( Boolean ), + remote_get_response: expect.any( Number ), + } ), + } ) + ); + } - // local environment differs from external hosts. Local listed first. - // eslint-disable-next-line playwright/no-conditional-in-test - if ( ! shouldSkip ) { expect( responseJSON ).toEqual( expect.objectContaining( { - environment: expect.objectContaining( { - home_url: expect.any( String ), - site_url: expect.any( String ), - version: expect.any( String ), - log_directory: expect.any( String ), - log_directory_writable: expect.any( Boolean ), - wp_version: expect.any( String ), - wp_multisite: expect.any( Boolean ), - wp_memory_limit: expect.any( Number ), - wp_debug_mode: expect.any( Boolean ), - wp_cron: expect.any( Boolean ), - language: expect.any( String ), - external_object_cache: null, - server_info: expect.any( String ), - php_version: expect.any( String ), - php_post_max_size: expect.any( Number ), - php_max_execution_time: expect.any( Number ), - php_max_input_vars: expect.any( Number ), - curl_version: expect.any( String ), - suhosin_installed: expect.any( Boolean ), - max_upload_size: expect.any( Number ), - mysql_version: expect.any( String ), - mysql_version_string: expect.any( String ), - default_timezone: expect.any( String ), - fsockopen_or_curl_enabled: expect.any( Boolean ), - soapclient_enabled: expect.any( Boolean ), - domdocument_enabled: expect.any( Boolean ), - gzip_enabled: expect.any( Boolean ), - mbstring_enabled: expect.any( Boolean ), - remote_post_successful: expect.any( Boolean ), - remote_post_response: expect.any( String ), - remote_get_successful: expect.any( Boolean ), - remote_get_response: expect.any( String ), - } ), - } ) - ); - } else { - expect( responseJSON ).toEqual( - expect.objectContaining( { - environment: expect.objectContaining( { - home_url: expect.any( String ), - site_url: expect.any( String ), - version: expect.any( String ), - log_directory: expect.any( String ), - log_directory_writable: expect.any( Boolean ), - wp_version: expect.any( String ), - wp_multisite: expect.any( Boolean ), - wp_memory_limit: expect.any( Number ), - wp_debug_mode: expect.any( Boolean ), - wp_cron: expect.any( Boolean ), - language: expect.any( String ), - external_object_cache: expect.any( Boolean ), - server_info: expect.any( String ), - php_version: expect.any( String ), - php_post_max_size: expect.any( Number ), - php_max_execution_time: expect.any( Number ), - php_max_input_vars: expect.any( Number ), - curl_version: expect.any( String ), - suhosin_installed: expect.any( Boolean ), - max_upload_size: expect.any( Number ), - mysql_version: expect.any( String ), - mysql_version_string: expect.any( String ), - default_timezone: expect.any( String ), - fsockopen_or_curl_enabled: expect.any( Boolean ), - soapclient_enabled: expect.any( Boolean ), - domdocument_enabled: expect.any( Boolean ), - gzip_enabled: expect.any( Boolean ), - mbstring_enabled: expect.any( Boolean ), - remote_post_successful: expect.any( Boolean ), - remote_post_response: expect.any( Number ), - remote_get_successful: expect.any( Boolean ), - remote_get_response: expect.any( Number ), - } ), - } ) - ); - } - - expect( responseJSON ).toEqual( - expect.objectContaining( { - database: expect.objectContaining( { - wc_database_version: expect.any( String ), - database_prefix: expect.any( String ), - maxmind_geoip_database: expect.any( String ), - database_tables: expect.objectContaining( { - woocommerce: expect.objectContaining( { - wp_woocommerce_sessions: expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_api_keys: expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_attribute_taxonomies: - expect.objectContaining( { + database: expect.objectContaining( { + wc_database_version: expect.any( String ), + database_prefix: expect.any( String ), + maxmind_geoip_database: expect.any( String ), + database_tables: expect.objectContaining( { + woocommerce: expect.objectContaining( { + wp_woocommerce_sessions: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_api_keys: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_attribute_taxonomies: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_downloadable_product_permissions: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_order_items: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_order_itemmeta: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_tax_rates: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_tax_rate_locations: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_shipping_zones: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_shipping_zone_locations: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_shipping_zone_methods: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_payment_tokens: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_payment_tokenmeta: + expect.objectContaining( { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + } ), + wp_woocommerce_log: expect.objectContaining( { data: expect.any( String ), index: expect.any( String ), engine: expect.any( String ), } ), - wp_woocommerce_downloadable_product_permissions: - expect.objectContaining( { + } ), + other: expect.objectContaining( { + wp_actionscheduler_actions: { data: expect.any( String ), index: expect.any( String ), engine: expect.any( String ), - } ), - wp_woocommerce_order_items: expect.objectContaining( + }, + wp_actionscheduler_claims: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_actionscheduler_groups: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_actionscheduler_logs: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_commentmeta: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_comments: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_links: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_options: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_postmeta: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_posts: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_termmeta: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_terms: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_term_relationships: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_term_taxonomy: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_usermeta: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_users: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_admin_notes: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_admin_note_actions: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_category_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_customer_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_download_log: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_order_coupon_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_order_product_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_order_stats: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_order_tax_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_product_attributes_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_product_download_directories: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_product_meta_lookup: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_rate_limits: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_reserved_stock: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_tax_rate_classes: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + wp_wc_webhooks: { + data: expect.any( String ), + index: expect.any( String ), + engine: expect.any( String ), + }, + } ), + } ), + database_size: { + data: expect.any( Number ), + index: expect.any( Number ), + }, + } ), + } ) + ); + + expect( responseJSON ).toEqual( + expect.objectContaining( { + active_plugins: expect.arrayContaining( [ + { + plugin: expect.any( String ), + name: expect.any( String ), + version: expect.any( String ), + version_latest: expect.any( String ), + url: expect.any( String ), + author_name: expect.any( String ), + author_url: expect.any( String ), + network_activated: expect.any( Boolean ), + }, + { + plugin: expect.any( String ), + name: expect.any( String ), + version: expect.any( String ), + version_latest: expect.any( String ), + url: expect.any( String ), + author_name: expect.any( String ), + author_url: expect.any( String ), + network_activated: expect.any( Boolean ), + }, + { + plugin: expect.any( String ), + name: expect.any( String ), + version: expect.any( String ), + version_latest: expect.any( String ), + url: expect.any( String ), + author_name: expect.any( String ), + author_url: expect.any( String ), + network_activated: expect.any( Boolean ), + }, + { + plugin: expect.any( String ), + name: expect.any( String ), + version: expect.any( String ), + version_latest: expect.any( String ), + url: expect.any( String ), + author_name: expect.any( String ), + author_url: expect.any( String ), + network_activated: expect.any( Boolean ), + }, + ] ), + } ) + ); + + // local environment differs from external hosts. Local listed first. + // eslint-disable-next-line playwright/no-conditional-in-test + if ( ! shouldSkip ) { + expect( responseJSON ).toEqual( + expect.objectContaining( { + dropins_mu_plugins: expect.objectContaining( { + dropins: expect.arrayContaining( [] ), + mu_plugins: expect.arrayContaining( [] ), + } ), + } ) + ); + } else { + expect( responseJSON ).toEqual( + expect.objectContaining( { + dropins_mu_plugins: expect.objectContaining( { + dropins: expect.arrayContaining( [ { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } - ), - wp_woocommerce_order_itemmeta: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_tax_rates: expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_tax_rate_locations: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_shipping_zones: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_shipping_zone_locations: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_shipping_zone_methods: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_payment_tokens: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_payment_tokenmeta: - expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), - wp_woocommerce_log: expect.objectContaining( { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - } ), + name: expect.any( String ), + plugin: expect.any( String ), + }, + { + name: expect.any( String ), + plugin: expect.any( String ), + }, + ] ), + mu_plugins: [], } ), - other: expect.objectContaining( { - wp_actionscheduler_actions: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_actionscheduler_claims: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_actionscheduler_groups: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_actionscheduler_logs: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_commentmeta: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_comments: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_links: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_options: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_postmeta: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_posts: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_termmeta: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_terms: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_term_relationships: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_term_taxonomy: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_usermeta: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_users: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_admin_notes: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_admin_note_actions: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_category_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_customer_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_download_log: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_order_coupon_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_order_product_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_order_stats: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_order_tax_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_product_attributes_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_product_download_directories: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_product_meta_lookup: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_rate_limits: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_reserved_stock: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_tax_rate_classes: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - wp_wc_webhooks: { - data: expect.any( String ), - index: expect.any( String ), - engine: expect.any( String ), - }, - } ), - } ), - database_size: { - data: expect.any( Number ), - index: expect.any( Number ), - }, - } ), - } ) - ); - - expect( responseJSON ).toEqual( - expect.objectContaining( { - active_plugins: expect.arrayContaining( [ - { - plugin: expect.any( String ), - name: expect.any( String ), - version: expect.any( String ), - version_latest: expect.any( String ), - url: expect.any( String ), - author_name: expect.any( String ), - author_url: expect.any( String ), - network_activated: expect.any( Boolean ), - }, - { - plugin: expect.any( String ), - name: expect.any( String ), - version: expect.any( String ), - version_latest: expect.any( String ), - url: expect.any( String ), - author_name: expect.any( String ), - author_url: expect.any( String ), - network_activated: expect.any( Boolean ), - }, - { - plugin: expect.any( String ), - name: expect.any( String ), - version: expect.any( String ), - version_latest: expect.any( String ), - url: expect.any( String ), - author_name: expect.any( String ), - author_url: expect.any( String ), - network_activated: expect.any( Boolean ), - }, - { - plugin: expect.any( String ), - name: expect.any( String ), - version: expect.any( String ), - version_latest: expect.any( String ), - url: expect.any( String ), - author_name: expect.any( String ), - author_url: expect.any( String ), - network_activated: expect.any( Boolean ), - }, - ] ), - } ) - ); - - // local environment differs from external hosts. Local listed first. - // eslint-disable-next-line playwright/no-conditional-in-test - if ( ! shouldSkip ) { + } ) + ); + } expect( responseJSON ).toEqual( expect.objectContaining( { - dropins_mu_plugins: expect.objectContaining( { - dropins: expect.arrayContaining( [] ), - mu_plugins: expect.arrayContaining( [] ), + theme: expect.objectContaining( { + name: expect.any( String ), + version: expect.any( String ), + version_latest: expect.any( String ), + author_url: expect.any( String ), + is_child_theme: expect.any( Boolean ), + has_woocommerce_support: expect.any( Boolean ), + has_woocommerce_file: expect.any( Boolean ), + has_outdated_templates: expect.any( Boolean ), + overrides: expect.any( Array ), + parent_name: expect.any( String ), + parent_version: expect.any( String ), + parent_version_latest: expect.any( String ), + parent_author_url: expect.any( String ), } ), } ) ); - } else { expect( responseJSON ).toEqual( expect.objectContaining( { - dropins_mu_plugins: expect.objectContaining( { - dropins: expect.arrayContaining( [ - { - name: expect.any( String ), - plugin: expect.any( String ), - }, - { - name: expect.any( String ), - plugin: expect.any( String ), - }, - ] ), - mu_plugins: [], + settings: expect.objectContaining( { + api_enabled: expect.any( Boolean ), + force_ssl: expect.any( Boolean ), + currency: expect.any( String ), + currency_symbol: expect.any( String ), + currency_position: expect.any( String ), + thousand_separator: expect.any( String ), + decimal_separator: expect.any( String ), + number_of_decimals: expect.any( Number ), + geolocation_enabled: expect.any( Boolean ), + taxonomies: { + external: expect.any( String ), + grouped: expect.any( String ), + simple: expect.any( String ), + variable: expect.any( String ), + }, + product_visibility_terms: { + 'exclude-from-catalog': expect.any( String ), + 'exclude-from-search': expect.any( String ), + featured: expect.any( String ), + outofstock: expect.any( String ), + 'rated-1': expect.any( String ), + 'rated-2': expect.any( String ), + 'rated-3': expect.any( String ), + 'rated-4': expect.any( String ), + 'rated-5': expect.any( String ), + }, + woocommerce_com_connected: expect.any( String ), } ), } ) ); + expect( responseJSON ).toEqual( + expect.objectContaining( { + security: expect.objectContaining( { + secure_connection: expect.any( Boolean ), + hide_errors: expect.any( Boolean ), + } ), + } ) + ); + expect( responseJSON ).toEqual( + expect.objectContaining( { + pages: expect.arrayContaining( [ + { + page_name: expect.any( String ), + page_id: expect.any( String ), + page_set: expect.any( Boolean ), + page_exists: expect.any( Boolean ), + page_visible: expect.any( Boolean ), + shortcode: expect.any( String ), + block: expect.any( String ), + shortcode_required: expect.any( Boolean ), + shortcode_present: expect.any( Boolean ), + block_present: expect.any( Boolean ), + block_required: expect.any( Boolean ), + }, + { + page_name: expect.any( String ), + page_id: expect.any( String ), + page_set: expect.any( Boolean ), + page_exists: expect.any( Boolean ), + page_visible: expect.any( Boolean ), + shortcode: expect.any( String ), + block: expect.any( String ), + shortcode_required: expect.any( Boolean ), + shortcode_present: expect.any( Boolean ), + block_present: expect.any( Boolean ), + block_required: expect.any( Boolean ), + }, + { + page_name: expect.any( String ), + page_id: expect.any( String ), + page_set: expect.any( Boolean ), + page_exists: expect.any( Boolean ), + page_visible: expect.any( Boolean ), + shortcode: expect.any( String ), + block: expect.any( String ), + shortcode_required: expect.any( Boolean ), + shortcode_present: expect.any( Boolean ), + block_present: expect.any( Boolean ), + block_required: expect.any( Boolean ), + }, + { + page_name: expect.any( String ), + page_id: expect.any( String ), + page_set: expect.any( Boolean ), + page_exists: expect.any( Boolean ), + page_visible: expect.any( Boolean ), + shortcode: expect.any( String ), + block: expect.any( String ), + shortcode_required: expect.any( Boolean ), + shortcode_present: expect.any( Boolean ), + block_present: expect.any( Boolean ), + block_required: expect.any( Boolean ), + }, + { + page_name: expect.any( String ), + page_id: expect.any( String ), + page_set: expect.any( Boolean ), + page_exists: expect.any( Boolean ), + page_visible: expect.any( Boolean ), + shortcode: expect.any( String ), + block: expect.any( String ), + shortcode_required: expect.any( Boolean ), + shortcode_present: expect.any( Boolean ), + block_present: expect.any( Boolean ), + block_required: expect.any( Boolean ), + }, + ] ), + } ) + ); + expect( responseJSON ).toEqual( + expect.objectContaining( { + post_type_counts: expect.arrayContaining( [ + { + type: expect.any( String ), + count: expect.any( String ), + }, + { + type: expect.any( String ), + count: expect.any( String ), + }, + { + type: expect.any( String ), + count: expect.any( String ), + }, + ] ), + } ) + ); } - expect( responseJSON ).toEqual( - expect.objectContaining( { - theme: expect.objectContaining( { - name: expect.any( String ), - version: expect.any( String ), - version_latest: expect.any( String ), - author_url: expect.any( String ), - is_child_theme: expect.any( Boolean ), - has_woocommerce_support: expect.any( Boolean ), - has_woocommerce_file: expect.any( Boolean ), - has_outdated_templates: expect.any( Boolean ), - overrides: expect.any( Array ), - parent_name: expect.any( String ), - parent_version: expect.any( String ), - parent_version_latest: expect.any( String ), - parent_author_url: expect.any( String ), - } ), - } ) - ); - expect( responseJSON ).toEqual( - expect.objectContaining( { - settings: expect.objectContaining( { - api_enabled: expect.any( Boolean ), - force_ssl: expect.any( Boolean ), - currency: expect.any( String ), - currency_symbol: expect.any( String ), - currency_position: expect.any( String ), - thousand_separator: expect.any( String ), - decimal_separator: expect.any( String ), - number_of_decimals: expect.any( Number ), - geolocation_enabled: expect.any( Boolean ), - taxonomies: { - external: expect.any( String ), - grouped: expect.any( String ), - simple: expect.any( String ), - variable: expect.any( String ), - }, - product_visibility_terms: { - 'exclude-from-catalog': expect.any( String ), - 'exclude-from-search': expect.any( String ), - featured: expect.any( String ), - outofstock: expect.any( String ), - 'rated-1': expect.any( String ), - 'rated-2': expect.any( String ), - 'rated-3': expect.any( String ), - 'rated-4': expect.any( String ), - 'rated-5': expect.any( String ), - }, - woocommerce_com_connected: expect.any( String ), - } ), - } ) - ); - expect( responseJSON ).toEqual( - expect.objectContaining( { - security: expect.objectContaining( { - secure_connection: expect.any( Boolean ), - hide_errors: expect.any( Boolean ), - } ), - } ) - ); - expect( responseJSON ).toEqual( - expect.objectContaining( { - pages: expect.arrayContaining( [ - { - page_name: expect.any( String ), - page_id: expect.any( String ), - page_set: expect.any( Boolean ), - page_exists: expect.any( Boolean ), - page_visible: expect.any( Boolean ), - shortcode: expect.any( String ), - block: expect.any( String ), - shortcode_required: expect.any( Boolean ), - shortcode_present: expect.any( Boolean ), - block_present: expect.any( Boolean ), - block_required: expect.any( Boolean ), - }, - { - page_name: expect.any( String ), - page_id: expect.any( String ), - page_set: expect.any( Boolean ), - page_exists: expect.any( Boolean ), - page_visible: expect.any( Boolean ), - shortcode: expect.any( String ), - block: expect.any( String ), - shortcode_required: expect.any( Boolean ), - shortcode_present: expect.any( Boolean ), - block_present: expect.any( Boolean ), - block_required: expect.any( Boolean ), - }, - { - page_name: expect.any( String ), - page_id: expect.any( String ), - page_set: expect.any( Boolean ), - page_exists: expect.any( Boolean ), - page_visible: expect.any( Boolean ), - shortcode: expect.any( String ), - block: expect.any( String ), - shortcode_required: expect.any( Boolean ), - shortcode_present: expect.any( Boolean ), - block_present: expect.any( Boolean ), - block_required: expect.any( Boolean ), - }, - { - page_name: expect.any( String ), - page_id: expect.any( String ), - page_set: expect.any( Boolean ), - page_exists: expect.any( Boolean ), - page_visible: expect.any( Boolean ), - shortcode: expect.any( String ), - block: expect.any( String ), - shortcode_required: expect.any( Boolean ), - shortcode_present: expect.any( Boolean ), - block_present: expect.any( Boolean ), - block_required: expect.any( Boolean ), - }, - { - page_name: expect.any( String ), - page_id: expect.any( String ), - page_set: expect.any( Boolean ), - page_exists: expect.any( Boolean ), - page_visible: expect.any( Boolean ), - shortcode: expect.any( String ), - block: expect.any( String ), - shortcode_required: expect.any( Boolean ), - shortcode_present: expect.any( Boolean ), - block_present: expect.any( Boolean ), - block_required: expect.any( Boolean ), - }, - ] ), - } ) - ); - expect( responseJSON ).toEqual( - expect.objectContaining( { - post_type_counts: expect.arrayContaining( [ - { - type: expect.any( String ), - count: expect.any( String ), - }, - { - type: expect.any( String ), - count: expect.any( String ), - }, - { - type: expect.any( String ), - count: expect.any( String ), - }, - ] ), - } ) - ); - } ); + ); test( 'can view all system status tools', async ( { request } ) => { // call API to view system status tools diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-import-csv.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-import-csv.spec.js index 103d50fd8f6..507568812a0 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-import-csv.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-import-csv.spec.js @@ -89,8 +89,7 @@ const productCategories = [ ]; const productAttributes = [ 'Color', 'Size' ]; -const errorMessage = - 'Invalid file type. The importer supports CSV and TXT file formats.'; +const errorMessage = 'File is empty. Please upload something more substantial.'; test.describe.serial( 'Import Products from a CSV file', diff --git a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-admin-notes.php b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-admin-notes.php index 66f12f2a352..247d3c5fa84 100644 --- a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-admin-notes.php +++ b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-admin-notes.php @@ -60,7 +60,7 @@ class WC_Helper_Admin_Notes { $note_2->set_source( 'PHPUNIT_TEST' ); $note_2->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED ); $note_2->set_is_snoozable( true ); - $note_2->set_layout( 'banner' ); + $note_2->set_layout( 'thumbnail' ); $note_2->set_image( 'https://an-image.jpg' ); // This note has no actions. $note_2->save(); @@ -100,7 +100,6 @@ class WC_Helper_Admin_Notes { '?s=PHPUNIT_TEST_NOTE_4_ACTION_2_URL' ); $note_4->save(); - } /** diff --git a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-queue.php b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-queue.php index a2f4e0acfb8..50f3c8766a4 100644 --- a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-queue.php +++ b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-queue.php @@ -13,36 +13,42 @@ class WC_Helper_Queue { /** * Get all pending queued actions. - * + * @param string|null $group Optionally. Filter the actions by group. * @return array Pending jobs. */ - public static function get_all_pending() { - $jobs = WC()->queue()->search( - array( - 'per_page' => -1, - 'status' => 'pending', - 'claimed' => false, - ) + public static function get_all_pending( $group = null ) { + $args = array( + 'per_page' => -1, + 'status' => 'pending', + 'claimed' => false, ); - return $jobs; + if ( $group ) { + $args['group'] = $group; + } + + return WC()->queue()->search( $args ); } + + /** * Run all pending queued actions. - * + * @param string|null $group Optionally. Filter the actions by group. * @return void */ - public static function run_all_pending() { + public static function run_all_pending( $group = null ) { $queue_runner = new ActionScheduler_QueueRunner(); - - while ( $jobs = self::get_all_pending() ) { + $jobs = self::get_all_pending( $group ); + while ( $jobs ) { foreach ( $jobs as $job_id => $job ) { $queue_runner->process_action( $job_id ); } + $jobs = self::get_all_pending( $group ); } } + /** * Cancel all pending actions. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/products-lowinstock.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/products-lowinstock.php index b85177b3221..489a4d66fc9 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/products-lowinstock.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/products-lowinstock.php @@ -51,7 +51,7 @@ class WC_Admin_Tests_API_ProductsLowInStock extends WC_REST_Unit_Test_Case { $order->save(); // Sync analytics data (used for last order date). - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', '/wc-analytics/products/low-in-stock' ); $request->set_param( 'low_in_stock', true ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-categories.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-categories.php index f5f37b9404d..7b704fc7b4a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-categories.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-categories.php @@ -66,7 +66,7 @@ class WC_Admin_Tests_API_Reports_Categories extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' ); @@ -114,7 +114,7 @@ class WC_Admin_Tests_API_Reports_Categories extends WC_REST_Unit_Test_Case { $product->set_category_ids( array( $second_category_id ) ); $product->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' ); @@ -170,7 +170,7 @@ class WC_Admin_Tests_API_Reports_Categories extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Populate all of the data. $product = new WC_Product_Simple(); @@ -186,7 +186,7 @@ class WC_Admin_Tests_API_Reports_Categories extends WC_REST_Unit_Test_Case { $order->set_total( 400 ); // $100 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' ); $params = array( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons-stats.php index 9ebdcb6db25..86de2a966a4 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons-stats.php @@ -87,7 +87,7 @@ class WC_Admin_Tests_API_Reports_Coupons_Stats extends WC_REST_Unit_Test_Case { $order_2c->set_date_created( $time ); $order_2c->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons.php index ee8d5271552..b085e3ee690 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-coupons.php @@ -82,7 +82,7 @@ class WC_Admin_Tests_API_Reports_Coupons extends WC_REST_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $coupon_reports = $response->get_data(); @@ -132,7 +132,7 @@ class WC_Admin_Tests_API_Reports_Coupons extends WC_REST_Unit_Test_Case { $order_1c->calculate_totals(); $order_1c->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers-stats.php index 6b2c479bf93..0cc1ccacdc7 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers-stats.php @@ -127,7 +127,7 @@ class WC_Admin_Tests_API_Reports_Customers_Stats extends WC_REST_Unit_Test_Case $order->set_total( 9.12 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $response = $this->server->dispatch( $request ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers.php index 24a3af46ec5..166bf344918 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-customers.php @@ -149,7 +149,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( 'per_page' => 10 ) ); @@ -164,7 +164,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { // Creating a customer should show up regardless of orders. $customer = WC_Helper_Customer::create_customer( 'customer', 'password', 'customer@example.com' ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( @@ -221,7 +221,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( @@ -317,7 +317,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->save(); // Ensure order customer data is synced to lookup table. - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $query_params = array( 'force_cache_refresh' => true, @@ -400,7 +400,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $customer->set_billing_city( '' ); $customer->set_first_name( 'customer_andrei_1' ); $customer->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request->set_query_params( array( @@ -433,7 +433,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { // Test filter_empty param by state and postcode non empty. $customer = WC_Helper_Customer::create_customer( 'customer_2', 'password', 'customer_2@example.com' ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request->set_query_params( array( @@ -498,7 +498,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $response = $this->server->dispatch( $request ); @@ -527,7 +527,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $response = $this->server->dispatch( $request ); @@ -555,7 +555,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $response = $this->server->dispatch( $request ); @@ -584,7 +584,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { // Creating a customer should show up regardless of orders. $customer = WC_Helper_Customer::create_customer( 'deleteme', 'password', 'deleteme@example.com' ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( @@ -602,7 +602,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { // Delete the user associated with the customer. wp_delete_user( $customer->get_id() ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Verify they are gone. $request = new WP_REST_Request( 'GET', $this->endpoint ); @@ -630,7 +630,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // update order info. $order->set_billing_city( 'Random' ); @@ -638,11 +638,11 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_billing_postcode( '54321' ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $result = CustomersDataStore::sync_order_customer( $order->get_id() ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $this->assertNotEquals( -1, $result ); @@ -671,7 +671,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order2->set_total( 100 ); $order2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $customer_id = CustomersDataStore::get_existing_customer_id_from_order( $order ); $customer2_id = CustomersDataStore::get_existing_customer_id_from_order( $order2 ); @@ -682,11 +682,11 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_billing_postcode( '54321' ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $result = CustomersDataStore::sync_order_customer( $order->get_id() ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Didn't update anything. $this->assertTrue( -1 === $result ); @@ -721,7 +721,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order3->set_billing_email( 'different@example.org' ); $order3->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $customer_id = CustomersDataStore::get_existing_customer_id_from_order( $order ); $customer2_id = CustomersDataStore::get_existing_customer_id_from_order( $order2 ); @@ -734,11 +734,11 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order3->set_billing_postcode( '54321' ); $order3->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $result = CustomersDataStore::sync_order_customer( $order3->get_id() ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Didn't update anything. $this->assertNotEquals( -1, $result ); @@ -780,7 +780,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order3->set_total( 100 ); $order3->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $customer_id = CustomersDataStore::get_existing_customer_id_from_order( $order ); $customer2_id = CustomersDataStore::get_existing_customer_id_from_order( $order2 ); @@ -794,7 +794,7 @@ class WC_Admin_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $order->set_date_created( time() + 60 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $latest_order = CustomersDataStore::get_last_order( $customer_id ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads-stats.php index 0e098e40b72..8a58da14e7b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads-stats.php @@ -152,7 +152,7 @@ class WC_Admin_Tests_API_Reports_Downloads_Stats extends WC_REST_Unit_Test_Case $order->save(); $order_1 = $order->get_id(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $download = new WC_Customer_Download(); $download->set_user_id( 1 ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads.php index 3750eaba48c..98b10737888 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-downloads.php @@ -169,7 +169,7 @@ class WC_Admin_Tests_API_Reports_Downloads extends WC_REST_Unit_Test_Case { $object->set_timestamp( gmdate( 'Y-m-d H:00:00', $time - ( 2 * DAY_IN_SECONDS ) ) ); $id = $object->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); return array( 'time' => $time, diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-export.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-export.php index 48494f8fcf0..faae1f452eb 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-export.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-export.php @@ -119,7 +119,7 @@ class WC_Admin_Tests_API_Reports_Export extends WC_REST_Unit_Test_Case { $order->calculate_totals(); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Initiate an export of the taxes report. $response = $this->server->dispatch( new WP_REST_Request( 'POST', '/wc-analytics/reports/taxes/export' ) ); @@ -143,7 +143,7 @@ class WC_Admin_Tests_API_Reports_Export extends WC_REST_Unit_Test_Case { $this->assertStringMatchesFormat( '%s/wc-analytics/reports/taxes/export/%d/status', $status['_links']['self'][0]['href'] ); // Run the pending export jobs. - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Check that the status shows 100% and includes a download url. $response = $this->server->dispatch( new WP_REST_Request( 'GET', $status_route ) ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php index b40e02390ea..e9cd6edb762 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php @@ -119,7 +119,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 'success', $report['status'] ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', '/wc-analytics/reports/customers' ); $response = $this->server->dispatch( $request ); @@ -161,7 +161,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 'success', $report['status'] ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', '/wc-analytics/reports/customers' ); $response = $this->server->dispatch( $request ); @@ -247,7 +247,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { } // Check that stats exist before deleting. - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', '/wc-analytics/reports/orders' ); $request->set_query_params( array( 'per_page' => 25 ) ); @@ -273,7 +273,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 'success', $report['status'] ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Check that stats have been deleted. $request = new WP_REST_Request( 'GET', '/wc-analytics/reports/orders' ); @@ -375,7 +375,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { $this->assertEquals( 0, $report['orders']['imported'] ); $this->assertEquals( 4, $report['orders']['total'] ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Test import status after processing. $request = new WP_REST_Request( 'GET', $this->endpoint . '/status' ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders-stats.php index 18c8b856222..08b2a41cf74 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders-stats.php @@ -154,7 +154,7 @@ class WC_Admin_Tests_API_Reports_Orders_Stats extends WC_REST_Unit_Test_Case { $global_attribute = new WC_Product_Attribute(); $global_attribute->set_id( $size_attr_id ); $global_attribute->set_name( 'pa_size' ); - $global_attribute->set_options( array( $large_term->term_id ) ); // Set to small. + $global_attribute->set_options( array( $large_term->term_id ) ); // Set to large. $global_attribute->set_position( 1 ); $global_attribute->set_visible( true ); $global_attribute->set_variation( false ); @@ -187,7 +187,8 @@ class WC_Admin_Tests_API_Reports_Orders_Stats extends WC_REST_Unit_Test_Case { $order->save(); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); + WC_Helper_Queue::run_all_pending( 'woocommerce-db-updates' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( 'per_page' => 15 ) ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php index 38aeb49eed6..2a695b5d9ff 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php @@ -69,7 +69,7 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $expected_customer_id = CustomersDataStore::get_customer_id_by_user_id( 1 ); @@ -126,7 +126,7 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); @@ -238,7 +238,8 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $order->save(); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); + WC_Helper_Queue::run_all_pending( 'woocommerce-db-updates' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( 'per_page' => 15 ) ); @@ -369,7 +370,7 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $order->save(); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( array( 'per_page' => 15 ) ); @@ -438,7 +439,7 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $order_to_be_included->set_status( 'completed' ); $order_to_be_included->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Test product exclusion. $request = new WP_REST_Request( 'GET', $this->endpoint ); @@ -515,7 +516,7 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $second_order->set_status( 'on-hold' ); $second_order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Get the created orders from REST API. $request = new WP_REST_Request( 'GET', $this->endpoint ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-performance-indicators.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-performance-indicators.php index 8cbab9032a1..058a40370a6 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-performance-indicators.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-performance-indicators.php @@ -90,7 +90,7 @@ class WC_Admin_Tests_API_Reports_Performance_Indicators extends WC_REST_Unit_Tes $object->set_user_ip_address( '1.2.3.4' ); $object->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $time = time(); $request = new WP_REST_Request( 'GET', $this->endpoint ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products-stats.php index f203a65b84e..49862190354 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products-stats.php @@ -71,7 +71,7 @@ class WC_Admin_Tests_API_Reports_Products_Stats extends WC_REST_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products.php index 9e6aade8a16..b332a5047ad 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-products.php @@ -67,7 +67,7 @@ class WC_Admin_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); @@ -109,7 +109,7 @@ class WC_Admin_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes-stats.php index 1920b20b029..9ea322b25eb 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes-stats.php @@ -99,7 +99,7 @@ class WC_Admin_Tests_API_Reports_Taxes_Stats extends WC_REST_Unit_Test_Case { $wc_refund = wc_create_refund( $refund ); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php index 3f896cc5b78..cfadb1787a8 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php @@ -115,7 +115,7 @@ class WC_Admin_Tests_API_Reports_Taxes extends WC_REST_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); @@ -416,6 +416,6 @@ class WC_Admin_Tests_API_Reports_Taxes extends WC_REST_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-variations.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-variations.php index d8906331c79..2c4a974ad1b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-variations.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-variations.php @@ -69,7 +69,7 @@ class WC_Admin_Tests_API_Reports_Variations extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) ); $reports = $response->get_data(); @@ -102,7 +102,7 @@ class WC_Admin_Tests_API_Reports_Variations extends WC_REST_Unit_Test_Case { $order->set_total( 15 ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( @@ -146,7 +146,7 @@ class WC_Admin_Tests_API_Reports_Variations extends WC_REST_Unit_Test_Case { $order->set_total( 100 ); // $25 x 4. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $request = new WP_REST_Request( 'GET', $this->endpoint ); $request->set_query_params( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-note.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-note.php index 700fb702d0e..d4f7d0f8455 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-note.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/notes/class-wc-tests-notes-note.php @@ -53,4 +53,31 @@ class WC_Admin_Tests_Notes_Note extends WC_Unit_Test_Case { $this->assertEquals( $date_created_from_first_save, $date_created_from_second_save ); } + /** + * Tests setting date_reminder with various input types. + * + * @dataProvider date_reminder_provider + * @param mixed $input Input date value. + * @param int $expected_timestamp Expected timestamp. + */ + public function test_set_date_reminder_with_various_inputs( $input, $expected_timestamp ) { + $note = new Note(); + $note->set_date_reminder( $input ); + $date_reminder = $note->get_date_reminder(); + $this->assertEquals( $expected_timestamp, $date_reminder ); + } + + /** + * Data provider for test_set_date_reminder_with_various_inputs. + * + * @return array + */ + public function date_reminder_provider() { + return array( + 'timestamp' => array( 1609459200, '2021-01-01T00:00:00+00:00' ), + 'timestamp string' => array( '1609459200', '2021-01-01T00:00:00+00:00' ), + 'date string' => array( '2021-01-01', '2021-01-01T00:00:00+00:00' ), + 'WC_DateTime object' => array( new WC_DateTime( '2021-01-01' ), '2021-01-01T00:00:00+00:00' ), + ); + } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-country-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-country-rule-processor.php similarity index 94% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-country-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-country-rule-processor.php index b49df23970d..031da61b9f2 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-country-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-country-rule-processor.php @@ -2,16 +2,18 @@ /** * Base Location country rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\BaseLocationCountryRuleProcessor; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; /** - * class WC_Admin_Tests_RemoteInboxNotifications_BaseLocationCountryRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_BaseLocationCountryRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_BaseLocationCountryRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_BaseLocationCountryRuleProcessor extends WC_Unit_Test_Case { /** * Get the publish_before rule. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-state-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-state-rule-processor.php similarity index 84% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-state-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-state-rule-processor.php index 9135ab0b05d..d69972954c0 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/base-location-state-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/base-location-state-rule-processor.php @@ -2,16 +2,18 @@ /** * Base Location state rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\BaseLocationStateRuleProcessor; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; /** - * class WC_Admin_Tests_RemoteInboxNotifications_BaseLocationStateRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_BaseLocationStateRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_BaseLocationStateRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_BaseLocationStateRuleProcessor extends WC_Unit_Test_Case { /** * Get the base_location_state rule. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/comparison-operation.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/comparison-operation.php similarity index 90% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/comparison-operation.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/comparison-operation.php index 85fa28f8e9f..7ea8bd7dd8b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/comparison-operation.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/comparison-operation.php @@ -5,12 +5,14 @@ * @package WooCommerce\Admin\Tests\RemoteInboxNotification */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\ComparisonOperation; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Comparison_Operation + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_Comparison_Operation */ -class WC_Admin_Tests_RemoteInboxNotifications_Comparison_Operation extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_Comparison_Operation extends WC_Unit_Test_Case { /** * @var ComparisonOperation $operation */ @@ -36,7 +38,6 @@ class WC_Admin_Tests_RemoteInboxNotifications_Comparison_Operation extends WC_Un $this->assertFalse( $this->operation->compare( 11, array( 1, 10 ), 'range' ) ); $this->assertFalse( $this->operation->compare( 11, array( 1, 10, 2 ), 'range' ) ); $this->assertFalse( $this->operation->compare( 11, 'string', 'range' ) ); - } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/evaluate-and-get-status.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/evaluate-and-get-status.php similarity index 95% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/evaluate-and-get-status.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/evaluate-and-get-status.php index 7de3b0c6251..7ec67d2f995 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/evaluate-and-get-status.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/evaluate-and-get-status.php @@ -2,15 +2,17 @@ /** * Evaluate and get status tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\EvaluateAndGetStatus; /** - * class WC_Admin_Tests_RemoteInboxNotifications_EvaluateAndGetStatus + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_EvaluateAndGetStatus */ -class WC_Admin_Tests_RemoteInboxNotifications_EvaluateAndGetStatus extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_EvaluateAndGetStatus extends WC_Unit_Test_Case { /** * Build up a spec given the supplied parameters. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/failing-rule-evaluator.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/failing-rule-evaluator.php similarity index 58% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/failing-rule-evaluator.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/failing-rule-evaluator.php index 8d856895e5b..b4c79cffb31 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/failing-rule-evaluator.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/failing-rule-evaluator.php @@ -2,9 +2,11 @@ /** * FailingRuleEvaluator * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + /** * class FailingRuleEvaluator */ @@ -16,7 +18,7 @@ class FailingRuleEvaluator { * * @return bool The evaluated result. */ - public function evaluate( $rules ) { + public function evaluate( $rules ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return false; } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/get-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/get-rule-processor.php similarity index 70% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/get-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/get-rule-processor.php index b5eee559d84..fb2721229eb 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/get-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/get-rule-processor.php @@ -2,15 +2,17 @@ /** * Get rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\GetRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_GetRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_GetRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_GetRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_GetRuleProcessor extends WC_Unit_Test_Case { /** * Tests that an unknown rule processor returns a FailRuleProcessor * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/is-woo-express-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php similarity index 91% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/is-woo-express-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php index 5db49ff717f..ba2bca4b7cf 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/is-woo-express-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php @@ -2,15 +2,17 @@ /** * Is WooExpress rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\IsWooExpressRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_IsWooExpressRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_IsWooExpressRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_IsWooExpressRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_IsWooExpressRuleProcessor extends WC_Unit_Test_Case { /** * Set Up Before Class. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-date-time-provider.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-date-time-provider.php similarity index 87% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-date-time-provider.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-date-time-provider.php index e250192bb23..cc8c32321c2 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-date-time-provider.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-date-time-provider.php @@ -2,9 +2,11 @@ /** * Mock DateTime Provider. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface; /** @@ -29,4 +31,3 @@ class MockDateTimeProvider implements DateTimeProviderInterface { return $this->now; } } - diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-get-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-get-rule-processor.php similarity index 91% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-get-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-get-rule-processor.php index 5de623920a5..805e6aabca2 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-get-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-get-rule-processor.php @@ -2,9 +2,11 @@ /** * MockGetRuleProcessor. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\PublishAfterTimeRuleProcessor; use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\FailRuleProcessor; @@ -29,4 +31,3 @@ class MockGetRuleProcessor { return new FailRuleProcessor(); } } - diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-plugins-provider.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-plugins-provider.php similarity index 96% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-plugins-provider.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-plugins-provider.php index 72e4bc22b62..f9a14565af9 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-plugins-provider.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-plugins-provider.php @@ -2,9 +2,11 @@ /** * Mock plugins Provider. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProviderInterface; /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-product-query.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-product-query.php similarity index 85% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-product-query.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-product-query.php index 681309d4045..8b3c0381e77 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-product-query.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-product-query.php @@ -2,9 +2,11 @@ /** * Mock product query. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + /** * Mock product query. */ @@ -29,4 +31,3 @@ class MockProductQuery { ); } } - diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-wc-admin-active-for-provider.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-wc-admin-active-for-provider.php similarity index 80% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-wc-admin-active-for-provider.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-wc-admin-active-for-provider.php index 37932001686..3f65b3f1030 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/mock-wc-admin-active-for-provider.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/mock-wc-admin-active-for-provider.php @@ -2,9 +2,11 @@ /** * Mock WCAdminActiveForProvider * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + /** * Mock WCAdminActiveForProvider */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/not-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/not-rule-processor.php similarity index 88% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/not-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/not-rule-processor.php index 9088cec3242..f2628792362 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/not-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/not-rule-processor.php @@ -2,16 +2,18 @@ /** * Not rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\NotRuleProcessor; use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\RuleEvaluator; /** - * class WC_Admin_Tests_RemoteInboxNotifications_NotRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_NotRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_NotRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_NotRuleProcessor extends WC_Unit_Test_Case { /** * An empty operand evaluates to false, so negating that should * evaluate to true. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/onboarding-profile-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/onboarding-profile-rule-processor.php similarity index 87% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/onboarding-profile-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/onboarding-profile-rule-processor.php index 097670b0115..9f59c1ab06f 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/onboarding-profile-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/onboarding-profile-rule-processor.php @@ -2,16 +2,18 @@ /** * Onboarding profile rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\OnboardingProfileRuleProcessor; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; /** - * class WC_Admin_Tests_RemoteInboxNotifications_OnboardingProfileRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OnboardingProfileRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_OnboardingProfileRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OnboardingProfileRuleProcessor extends WC_Unit_Test_Case { /** * Get the publish_before rule. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/option-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/option-rule-processor.php similarity index 93% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/option-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/option-rule-processor.php index 6e062f0ec7a..20f5dccad84 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/option-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/option-rule-processor.php @@ -2,15 +2,17 @@ /** * Option rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\OptionRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_OptionRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OptionRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_OptionRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OptionRuleProcessor extends WC_Unit_Test_Case { /** * No default option resolves to false. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/or-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/or-rule-processor.php similarity index 93% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/or-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/or-rule-processor.php index 6c93dfd1fb8..90c95605e1a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/or-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/or-rule-processor.php @@ -2,16 +2,18 @@ /** * Or rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\OrRuleProcessor; use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\RuleEvaluator; /** - * class WC_Admin_Tests_RemoteInboxNotifications_OrRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OrRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_OrRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_OrRuleProcessor extends WC_Unit_Test_Case { /** * Both operands evaluating to false and ORed together evaluates to false. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/passing-rule-evaluator.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/passing-rule-evaluator.php similarity index 58% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/passing-rule-evaluator.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/passing-rule-evaluator.php index 9a504e784cd..f0d730f1ced 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/passing-rule-evaluator.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/passing-rule-evaluator.php @@ -2,9 +2,11 @@ /** * PassingRuleEvaluator * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + /** * class PassingRuleEvaluator */ @@ -16,7 +18,7 @@ class PassingRuleEvaluator { * * @return bool The evaluated result. */ - public function evaluate( $rules ) { + public function evaluate( $rules ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return true; } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugin-version-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugin-version-rule-processor.php similarity index 94% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugin-version-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugin-version-rule-processor.php index a38fdbb9a63..cd2d9c5811e 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugin-version-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugin-version-rule-processor.php @@ -2,15 +2,17 @@ /** * Plugin version rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\PluginVersionRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_PluginVersionRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PluginVersionRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_PluginVersionRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PluginVersionRuleProcessor extends WC_Unit_Test_Case { /** * Test that the processor does not pass if the plugin is not activated. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugins-activated-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugins-activated-rule-processor.php similarity index 93% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugins-activated-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugins-activated-rule-processor.php index 41f478cedce..6250102b9bc 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/plugins-activated-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/plugins-activated-rule-processor.php @@ -2,16 +2,18 @@ /** * Plugins activated rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\PluginsActivatedRuleProcessor; use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProviderInterface; /** - * class WC_Admin_Tests_RemoteInboxNotifications_PluginsActivatedRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PluginsActivatedRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_PluginsActivatedRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PluginsActivatedRuleProcessor extends WC_Unit_Test_Case { /** * Tests that the processor does not pass a plugins_activated rule with * no plugins to verify. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/product-count-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/product-count-rule-processor.php similarity index 81% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/product-count-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/product-count-rule-processor.php index a3ca1a8cb74..2e4b0e1891f 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/product-count-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/product-count-rule-processor.php @@ -2,15 +2,17 @@ /** * Product count rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\ProductCountRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_ProductCountRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_ProductCountRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_ProductCountRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_ProductCountRuleProcessor extends WC_Unit_Test_Case { /** * Get a product_count rule that passes when the product count is > 5. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-after-time-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-after-time-rule-processor.php similarity index 90% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-after-time-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-after-time-rule-processor.php index 9aec0acfcfd..4057a9f1daf 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-after-time-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-after-time-rule-processor.php @@ -2,16 +2,18 @@ /** * Publish after time rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\PublishAfterTimeRuleProcessor; use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface; /** - * class WC_Admin_Tests_RemoteInboxNotifications_PublishAfterTimeRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PublishAfterTimeRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_PublishAfterTimeRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PublishAfterTimeRuleProcessor extends WC_Unit_Test_Case { /** * Get the publish_after rule. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-before-time-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-before-time-rule-processor.php similarity index 90% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-before-time-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-before-time-rule-processor.php index 0234df59c87..60e23fbf5ec 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/publish-before-time-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/publish-before-time-rule-processor.php @@ -2,16 +2,18 @@ /** * Publish before time rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\PublishBeforeTimeRuleProcessor; use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface; /** - * class WC_Admin_Tests_RemoteInboxNotifications_PublishBeforeTimeRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PublishBeforeTimeRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_PublishBeforeTimeRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_PublishBeforeTimeRuleProcessor extends WC_Unit_Test_Case { /** * Get the publish_before rule. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/stored-state-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/stored-state-rule-processor.php similarity index 97% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/stored-state-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/stored-state-rule-processor.php index 47567a6627d..ed1f616e67c 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/stored-state-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/stored-state-rule-processor.php @@ -2,15 +2,17 @@ /** * Stored state rule processor tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\StoredStateRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_StoredStateRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_StoredStateRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_StoredStateRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_StoredStateRuleProcessor extends WC_Unit_Test_Case { /** * Empty $stored_state evaluates to false. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/total-payments-volume-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/total-payments-volume-processor.php similarity index 93% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/total-payments-volume-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/total-payments-volume-processor.php index d177dd8f975..7d11b482a78 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/total-payments-volume-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/total-payments-volume-processor.php @@ -5,13 +5,15 @@ * @package WooCommerce\Admin\Tests\RemoteInboxNotification */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\TotalPaymentsVolumeProcessor; use Automattic\WooCommerce\Admin\API\Reports\Revenue\Query as RevenueQuery; /** - * class WC_Admin_Tests_RemoteInboxNotifications_TotalPaymentsVolumeProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_TotalPaymentsVolumeProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_TotalPaymentsVolumeProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_TotalPaymentsVolumeProcessor extends WC_Unit_Test_Case { /** * Greater than 1000 total payments volume evaluates to false. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/transformer-service.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformer-service.php similarity index 95% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/transformer-service.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformer-service.php index 224e7ba86e4..c5e17aee945 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/transformer-service.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformer-service.php @@ -2,16 +2,18 @@ /** * TransformerService tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArrayKeys; use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\TransformerService; /** - * class WC_Admin_Tests_RemoteInboxNotifications_TransformerService + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_TransformerService */ -class WC_Admin_Tests_RemoteInboxNotifications_TransformerService extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_TransformerService extends WC_Unit_Test_Case { /** * Test it creates a transformer with snake case 'use' value */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-column.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-column.php similarity index 82% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-column.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-column.php index e5303725b37..bb1ed192b74 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-column.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-column.php @@ -2,23 +2,25 @@ /** * ArrayColumn tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArrayColumn; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayColumn + * class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayColumn */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayColumn extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayColumn extends WC_Unit_Test_Case { /** * Test validate method returns false when 'key' argument is missing */ public function test_validate_returns_false_when_key_argument_is_missing() { $array_column = new ArrayColumn(); $result = $array_column->validate( (object) array() ); - $this->assertFalse( false, $result ); + $this->assertFalse( $result ); } /** @@ -31,14 +33,14 @@ class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayColumn extends W 'key' => true, ) ); - $this->assertFalse( false, $result ); + $this->assertFalse( $result ); $result = $array_column->validate( (object) array( 'key' => array(), ) ); - $this->assertFalse( false, $result ); + $this->assertFalse( $result ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-flatten.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-flatten.php similarity index 92% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-flatten.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-flatten.php index b02ba40fff9..5e5d76c2834 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-flatten.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-flatten.php @@ -2,9 +2,11 @@ /** * ArrayKeys tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArrayFlatten; /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-keys.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-keys.php similarity index 77% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-keys.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-keys.php index b8e7d036192..f9e7b5df6c3 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-keys.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-keys.php @@ -2,15 +2,17 @@ /** * ArrayKeys tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArrayKeys; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayKeys + * class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayKeys */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayKeys extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayKeys extends WC_Unit_Test_Case { /** * Test it returns default value when value is not an array */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-search.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-search.php similarity index 84% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-search.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-search.php index 46167d4b93e..17172146021 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-search.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-search.php @@ -2,22 +2,24 @@ /** * ArraySearch tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArraySearch; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArraySearch + * class WC_Admin_Tests_RemoteSpecs_Transformers_ArraySearch */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArraySearch extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_ArraySearch extends WC_Unit_Test_Case { /** * Test validate method returns false when 'value' argument is missing */ public function test_validate_returns_false_when_value_argument_is_missing() { $array_column = new ArraySearch(); $result = $array_column->validate( (object) array() ); - $this->assertFalse( false, $result ); + $this->assertFalse( $result ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-values.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-values.php similarity index 77% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-values.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-values.php index efef87142be..18d96425cf4 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/array-values.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/array-values.php @@ -2,15 +2,17 @@ /** * ArrayValues tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\ArrayValues; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayValues + * class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayValues */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayValues extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayValues extends WC_Unit_Test_Case { /** * Test it returns default value when value is not an array */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/count.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/count.php similarity index 76% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/count.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/count.php index 703a5804909..38f0f65ccb5 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/count.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/count.php @@ -2,15 +2,17 @@ /** * ArrayValues tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\Count; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayValues + * class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayValues */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_ArrayCount extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_ArrayCount extends WC_Unit_Test_Case { /** * Test it returns default value when value is not an array. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/dot-notation.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/dot-notation.php similarity index 88% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/dot-notation.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/dot-notation.php index 1e74188467a..32f76474014 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/dot-notation.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/dot-notation.php @@ -2,16 +2,18 @@ /** * DotNotation tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\DotNotation; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_DotNotation + * class WC_Admin_Tests_RemoteSpecs_Transformers_DotNotation */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_DotNotation extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_DotNotation extends WC_Unit_Test_Case { /** * Test validate method returns false when 'path' argument is missing @@ -19,7 +21,7 @@ class WC_Admin_Tests_RemoteInboxNotifications_Transformers_DotNotation extends W public function test_validate_returns_false_when_path_argument_is_missing() { $array_column = new DotNotation(); $result = $array_column->validate( (object) array() ); - $this->assertFalse( false, $result ); + $this->assertFalse( $result ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/prepare-url.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/prepare-url.php similarity index 84% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/prepare-url.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/prepare-url.php index a6f9d42478a..0e8c2145358 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/Transformers/prepare-url.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/transformers/prepare-url.php @@ -2,15 +2,17 @@ /** * PrepareUrl tests. * - * @package WooCommerce\Admin\Tests\RemoteInboxNotifications + * @package WooCommerce\Admin\Tests\RemoteSpecs */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\Transformers\PrepareUrl; /** - * class WC_Admin_Tests_RemoteInboxNotifications_Transformers_PrepareUrl + * class WC_Admin_Tests_RemoteSpecs_Transformers_PrepareUrl */ -class WC_Admin_Tests_RemoteInboxNotifications_Transformers_PrepareUrl extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_Transformers_PrepareUrl extends WC_Unit_Test_Case { /** * Test it returns default value when url is not string. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/wcadmin-active-for-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/wcadmin-active-for-rule-processor.php similarity index 93% rename from plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/wcadmin-active-for-rule-processor.php rename to plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/wcadmin-active-for-rule-processor.php index dca95841a92..8cd0349f66f 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-inbox-notifications/wcadmin-active-for-rule-processor.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/wcadmin-active-for-rule-processor.php @@ -5,12 +5,14 @@ * @package WooCommerce\Admin\Tests\RemoteInboxNotification */ +declare( strict_types = 1 ); + use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\WCAdminActiveForRuleProcessor; /** - * class WC_Admin_Tests_RemoteInboxNotifications_WCAdminActiveForRuleProcessor + * class WC_Admin_Tests_RemoteSpecs_RuleProcessors_WCAdminActiveForRuleProcessor */ -class WC_Admin_Tests_RemoteInboxNotifications_WCAdminActiveForRuleProcessor extends WC_Unit_Test_Case { +class WC_Admin_Tests_RemoteSpecs_RuleProcessors_WCAdminActiveForRuleProcessor extends WC_Unit_Test_Case { /** * Greater than 7 days evaluates to true * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons-stats.php index 04e1c98698d..4abbd68e1cf 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons-stats.php @@ -57,7 +57,7 @@ class WC_Admin_Tests_Reports_Coupons_Stats extends WC_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new CouponsStatsDataStore(); $start_time = gmdate( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -133,7 +133,7 @@ class WC_Admin_Tests_Reports_Coupons_Stats extends WC_Unit_Test_Case { // Delete the coupon. $coupon_1->delete( true ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $start_time = gmdate( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = gmdate( 'Y-m-d 23:59:59', $order->get_date_created()->getOffsetTimestamp() ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons.php index 867de990e28..f7d96278eeb 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-coupons.php @@ -59,7 +59,7 @@ class WC_Admin_Tests_Reports_Coupons extends WC_Unit_Test_Case { $order_2c->calculate_totals(); $order_2c->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new CouponsDataStore(); $start_time = gmdate( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -375,7 +375,7 @@ class WC_Admin_Tests_Reports_Coupons extends WC_Unit_Test_Case { // Delete the coupons. $coupon_2->delete( true ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new CouponsDataStore(); $start_time = gmdate( 'Y-m-d 00:00:00', $order->get_date_created()->getOffsetTimestamp() ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-customers.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-customers.php index c25c56b1376..bb7283220fe 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-customers.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-customers.php @@ -29,7 +29,7 @@ class WC_Admin_Tests_Reports_Customer extends WC_Unit_Test_Case { $product->set_regular_price( 25 ); $product->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $customer_id = DataStore::get_customer_id_by_user_id( $customer->get_id() ); // This is the customer ID from lookup table. @@ -39,7 +39,7 @@ class WC_Admin_Tests_Reports_Customer extends WC_Unit_Test_Case { $order->save(); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Customer should have 3 orders. $this->assertSame( 3, DataStore::get_order_count( $customer_id ) ); @@ -75,7 +75,7 @@ class WC_Admin_Tests_Reports_Customer extends WC_Unit_Test_Case { $product2->set_regular_price( 2 ); $product2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Create the first order. $order1 = WC_Helper_Order::create_order( $customer->get_id(), $product1 ); @@ -85,7 +85,7 @@ class WC_Admin_Tests_Reports_Customer extends WC_Unit_Test_Case { $order2 = WC_Helper_Order::create_order( $customer->get_id(), $product2 ); $order2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $customer_id = DataStore::get_customer_id_by_user_id( $customer->get_id() ); // This is the customer ID from lookup table. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php index f19ee7b5dc0..09a1628daad 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php @@ -63,7 +63,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -208,7 +208,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order->save(); } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -377,7 +377,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -666,7 +666,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $orders[] = $order; } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -917,7 +917,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { } } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -3865,7 +3865,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order->apply_coupon( $coupon ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Check if lookup tables are populated. foreach ( $tables as $table ) { @@ -3996,7 +3996,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_2->calculate_totals(); $order_2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -4544,7 +4544,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $orders[] = $order; } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersStatsDataStore(); @@ -5328,7 +5328,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $orders[] = $order; } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); global $wpdb; $res = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}wc_order_stats" ); @@ -6094,7 +6094,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_0->set_total( 100 ); $order_0->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $start_time = gmdate( 'Y-m-d H:00:00', $order_0->get_date_created()->getOffsetTimestamp() ); $end_time = gmdate( 'Y-m-d H:59:59', $order_0->get_date_created()->getOffsetTimestamp() ); @@ -6114,7 +6114,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_1->set_total( 100 ); $order_1->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Time frame includes both orders -> customer is a new customer. $start_time = gmdate( 'Y-m-d H:00:00', $order_0->get_date_created()->getOffsetTimestamp() ); @@ -6153,7 +6153,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_2->set_total( 100 ); $order_2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Time frame includes second and third order -> there is one returning customer. $start_time = gmdate( 'Y-m-d H:i:s', $order_0_time + 1 ); @@ -6204,7 +6204,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_0->set_total( 100 ); $order_0->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $start_time = gmdate( 'Y-m-d H:00:00', $order_0->get_date_created()->getOffsetTimestamp() ); $end_time = gmdate( 'Y-m-d H:59:59', $order_0->get_date_created()->getOffsetTimestamp() ); @@ -6224,7 +6224,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_1->set_total( 100 ); $order_1->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Time frame includes both orders -> customer is a new customer. $start_time = gmdate( 'Y-m-d H:00:00', $order_0->get_date_created()->getOffsetTimestamp() ); @@ -6263,7 +6263,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case { $order_2->set_total( 100 ); $order_2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // Time frame includes second and third order -> there is one returning customer. $start_time = gmdate( 'Y-m-d H:i:s', $order_0_time + 1 ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php index 078da5f087b..40b50f9a248 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php @@ -64,7 +64,7 @@ class WC_Admin_Tests_Reports_Orders extends WC_Unit_Test_Case { $order->set_status( 'completed' ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -143,7 +143,7 @@ class WC_Admin_Tests_Reports_Orders extends WC_Unit_Test_Case { ) ); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -255,7 +255,7 @@ class WC_Admin_Tests_Reports_Orders extends WC_Unit_Test_Case { $order2->set_status( 'completed' ); $order2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new OrdersDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -310,7 +310,7 @@ class WC_Admin_Tests_Reports_Orders extends WC_Unit_Test_Case { $order_2->set_status( 'completed' ); $order_2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); $end_time = gmdate( 'Y-m-d H:59:59', $order->get_date_created()->getOffsetTimestamp() ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-products.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-products.php index e088c2f47e4..94e74ab59a3 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-products.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-products.php @@ -42,7 +42,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new ProductsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -119,7 +119,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { $order_2->set_date_created( $date_created_2 ); $order_2->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new ProductsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -220,7 +220,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new ProductsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -301,7 +301,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new ProductsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -383,7 +383,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { break; } - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new ProductsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -441,7 +441,7 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $term = wp_insert_term( 'Unused Category', 'product_cat' ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-revenue-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-revenue-stats.php index 30a9b0832de..7722921486f 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-revenue-stats.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-revenue-stats.php @@ -43,7 +43,7 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case { $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); // /reports/revenue/stats is mapped to Orders_Data_Store. $data_store = new OrdersStatsDataStore(); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-variations.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-variations.php index 66e55d262d1..435600978ca 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-variations.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-variations.php @@ -42,7 +42,7 @@ class WC_Admin_Tests_Reports_Variations extends WC_Unit_Test_Case { $order->set_status( 'completed' ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new VariationsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -111,7 +111,7 @@ class WC_Admin_Tests_Reports_Variations extends WC_Unit_Test_Case { $order->set_status( 'completed' ); $order->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new VariationsDataStore(); $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); @@ -228,7 +228,7 @@ class WC_Admin_Tests_Reports_Variations extends WC_Unit_Test_Case { $order_3->set_status( 'completed' ); $order_3->save(); - WC_Helper_Queue::run_all_pending(); + WC_Helper_Queue::run_all_pending( 'wc-admin-data' ); $data_store = new VariationsDataStore(); diff --git a/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php new file mode 100644 index 00000000000..652d1199e92 --- /dev/null +++ b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php @@ -0,0 +1,83 @@ +structured_data = new WC_Structured_Data(); + parent::setUp(); + } + + /** + * Test is_valid_gtin function + * + * @return void + */ + public function test_is_valid_gtin(): void { + + $valid_gtins = array( + '12345678', + '123456789012', + '1234567890123', + '12345678901234', + ); + + $invalid_gtins = array( + '', + null, + false, + 12345678, + 123.4e-5, + +1234567, + 'abcdefgh', + '-9999999', + '12-45-66', + '123', + '123456789012345', + '123456789', + '1234567890', + '12 34 56 78', + '12 34 56', + '+12345678', + '123.4e-5', + ); + + foreach ( $valid_gtins as $valid_gtin ) { + $this->assertTrue( $this->structured_data->is_valid_gtin( $valid_gtin ) ); + } + + foreach ( $invalid_gtins as $invalid_gtin ) { + $this->assertFalse( $this->structured_data->is_valid_gtin( $invalid_gtin ) ); + } + } + + /** + * Test prepare_gtin function + * + * @return void + */ + public function test_prepare_gtin(): void { + $this->assertEquals( $this->structured_data->prepare_gtin( '123-456-78' ), '12345678' ); + $this->assertEquals( $this->structured_data->prepare_gtin( '-123-456-78' ), '12345678' ); + $this->assertEquals( $this->structured_data->prepare_gtin( 'GTIN: 123-456-78' ), '12345678' ); + $this->assertEquals( $this->structured_data->prepare_gtin( '123 456 78' ), '12345678' ); + $this->assertEquals( $this->structured_data->prepare_gtin( null ), '' ); + $this->assertEquals( $this->structured_data->prepare_gtin( 'GTIN' ), '' ); + $this->assertEquals( $this->structured_data->prepare_gtin( 123 ), '' ); + $this->assertEquals( $this->structured_data->prepare_gtin( array( '123-456-78', '123-456-78' ) ), '' ); + $this->assertEquals( $this->structured_data->prepare_gtin( '+12345678' ), '12345678' ); + $this->assertEquals( $this->structured_data->prepare_gtin( '123.4e-5' ), '12345' ); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f31827f5ef..2a972c8f247 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4658,6 +4658,12 @@ importers: tools/compare-perf: dependencies: + '@tsconfig/node16': + specifier: ^1.0.4 + version: 1.0.4 + '@types/node': + specifier: ^16.18.68 + version: 16.18.68 '@wordpress/env': specifier: ^10.1.0 version: 10.5.0 @@ -4673,6 +4679,12 @@ importers: simple-git: specifier: ^3.21.0 version: 3.21.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.3.100)(@types/node@16.18.68)(typescript@5.3.3) + tslib: + specifier: ^2.6.2 + version: 2.6.3 tools/monorepo-merge: dependencies: diff --git a/tools/compare-perf/index.js b/tools/compare-perf/index.js index d14680f9bf0..beca567b34c 100755 --- a/tools/compare-perf/index.js +++ b/tools/compare-perf/index.js @@ -15,12 +15,17 @@ const catchException = ( command ) => { }; }; -const ciOption = [ '-c, --ci', 'Run in CI (non interactive)' ]; - program .command( 'compare-performance [branches...]' ) .alias( 'perf' ) - .option( ...ciOption ) + .option( + '-c, --ci', + 'Run in CI (non interactive)' + ) + .option( + '--skip-benchmarking', + 'Skips benchmarking and gets straight to reporting phase (tests results already available)' + ) .option( '--rounds ', 'Run each test suite this many times for each branch; results are summarized, default = 1' diff --git a/tools/compare-perf/package.json b/tools/compare-perf/package.json index 430d64146e5..424decbe36d 100644 --- a/tools/compare-perf/package.json +++ b/tools/compare-perf/package.json @@ -7,15 +7,19 @@ "license": "GPLv2", "repository": "woocommerce/woocommerce", "scripts": { - "compare": "node index.js", + "compare": "node -r ts-node/register index.js", "log": "node log-to-codevitals.js" }, "dependencies": { + "@types/node": "^16.18.68", + "@tsconfig/node16": "^1.0.4", "@wordpress/env": "^10.1.0", "commander": "9.5.0", "chalk": "^4.1.2", "inquirer": "^7.1.0", - "simple-git": "^3.21.0" + "simple-git": "^3.21.0", + "ts-node": "^10.9.2", + "tslib": "^2.6.2" }, "engines": { "node": "^20.11.1", diff --git a/tools/compare-perf/performance.js b/tools/compare-perf/performance.js index 47315f382ef..527ad2a914e 100644 --- a/tools/compare-perf/performance.js +++ b/tools/compare-perf/performance.js @@ -13,71 +13,27 @@ const formats = { }; const { runShellScript, - readJSONFile, askForConfirmation, getFilesFromDir, + logAtIndent, + sanitizeBranchName, } = require( './utils' ); const config = require( './config' ); +const { processPerformanceReports } = require( './process-reports.ts' ); const ARTIFACTS_PATH = process.env.WP_ARTIFACTS_PATH || path.join( process.cwd(), 'artifacts' ); -const RESULTS_FILE_SUFFIX = '.performance-results.json'; /** * @typedef WPPerformanceCommandOptions * - * @property {boolean=} ci Run on CI. - * @property {number=} rounds Run each test suite this many times for each branch. - * @property {string=} testsBranch The branch whose performance test files will be used for testing. - * @property {string=} wpVersion The WordPress version to be used as the base install for testing. + * @property {boolean=} ci Run on CI. + * @property {number=} rounds Run each test suite this many times for each branch. + * @property {string=} testsBranch The branch whose performance test files will be used for testing. + * @property {boolean=} skipBenchmarking Skip benchmarking and get to report processing (reports supplied from outside). + * @property {string=} wpVersion The WordPress version to be used as the base install for testing. */ -/** - * A logging helper for printing steps and their substeps. - * - * @param {number} indent Value to indent the log. - * @param {any} msg Message to log. - * @param {...any} args Rest of the arguments to pass to console.log. - */ -function logAtIndent( indent, msg, ...args ) { - const prefix = indent === 0 ? '▶ ' : '> '; - const newline = indent === 0 ? '\n' : ''; - return console.log( - newline + ' '.repeat( indent ) + prefix + msg, - ...args - ); -} - -/** - * Sanitizes branch name to be used in a path or a filename. - * - * @param {string} branch - * - * @return {string} Sanitized branch name. - */ -function sanitizeBranchName( branch ) { - return branch.replace( /[^a-zA-Z0-9-]/g, '-' ); -} - -/** - * Computes the median number from an array numbers. - * - * @param {number[]} array - * - * @return {number|undefined} Median value or undefined if array empty. - */ -function median( array ) { - if ( ! array || ! array.length ) return undefined; - - const numbers = [ ...array ].sort( ( a, b ) => a - b ); - const middleIndex = Math.floor( numbers.length / 2 ); - - if ( numbers.length % 2 === 0 ) { - return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2; - } - return numbers[ middleIndex ]; -} - /** * Runs the performance tests on the current branch. * @@ -106,6 +62,7 @@ async function runTestSuite( testSuite, testRunnerDir, runKey ) { */ async function runPerformanceTests( branches, options ) { const runningInCI = !! process.env.CI || !! options.ci; + const skipBenchmarking = !! options.skipBenchmarking; const TEST_ROUNDS = options.rounds || 1; // The default value doesn't work because commander provides an array. @@ -129,6 +86,20 @@ async function runPerformanceTests( branches, options ) { await askForConfirmation( 'Ready to go? ' ); } + if ( skipBenchmarking ) { + // When benchmarking is skipped, it's expected that artifacts folder contains reports for the branches. + // If so, we'll process reports and pick test suites as per current state of codebase. + const testSuites = getFilesFromDir( + path.resolve( __dirname, '../..' ) + config.testsPath + ).map( ( file ) => { + logAtIndent( 1, 'Found:', formats.success( file ) ); + return path.basename( file, '.spec.js' ); + } ); + + await processPerformanceReports( testSuites, branches ); + return; + } + logAtIndent( 0, 'Setting up' ); /** @@ -140,7 +111,6 @@ async function runPerformanceTests( branches, options ) { } const baseDir = path.join( os.tmpdir(), 'wp-performance-tests' ); - if ( fs.existsSync( baseDir ) ) { logAtIndent( 1, 'Removing existing files' ); fs.rmSync( baseDir, { recursive: true } ); @@ -350,82 +320,7 @@ async function runPerformanceTests( branches, options ) { } } - logAtIndent( 0, 'Calculating results' ); - - const resultFiles = getFilesFromDir( ARTIFACTS_PATH ).filter( ( file ) => - file.endsWith( RESULTS_FILE_SUFFIX ) - ); - /** @type {Record>>} */ - const results = {}; - - // Calculate medians from all rounds. - for ( const testSuite of testSuites ) { - logAtIndent( 1, 'Test suite:', formats.success( testSuite ) ); - - results[ testSuite ] = {}; - for ( const branch of branches ) { - const sanitizedBranchName = sanitizeBranchName( branch ); - const resultsRounds = resultFiles - .filter( ( file ) => - file.includes( - `/${ testSuite }_${ sanitizedBranchName }_round-` - ) - ) - .map( ( file ) => { - logAtIndent( 2, 'Reading from:', formats.success( file ) ); - return readJSONFile( file ); - } ); - - const metrics = Object.keys( resultsRounds[ 0 ] ); - results[ testSuite ][ branch ] = {}; - - for ( const metric of metrics ) { - const values = resultsRounds - .map( ( round ) => round[ metric ] ) - .filter( ( value ) => typeof value === 'number' ); - - const value = median( values ); - if ( value !== undefined ) { - results[ testSuite ][ branch ][ metric ] = value; - } - } - } - const calculatedResultsPath = path.join( - ARTIFACTS_PATH, - testSuite + RESULTS_FILE_SUFFIX - ); - - logAtIndent( - 2, - 'Saving curated results to:', - formats.success( calculatedResultsPath ) - ); - fs.writeFileSync( - calculatedResultsPath, - JSON.stringify( results[ testSuite ], null, 2 ) - ); - } - - logAtIndent( 0, 'Printing results' ); - - for ( const testSuite of testSuites ) { - logAtIndent( 0, formats.success( testSuite ) ); - - // Invert the results so we can display them in a table. - /** @type {Record>} */ - const invertedResult = {}; - for ( const [ branch, metrics ] of Object.entries( - results[ testSuite ] - ) ) { - for ( const [ metric, value ] of Object.entries( metrics ) ) { - invertedResult[ metric ] = invertedResult[ metric ] || {}; - invertedResult[ metric ][ branch ] = `${ value } ms`; - } - } - - // Print the results. - console.table( invertedResult ); - } + await processPerformanceReports( testSuites, branches ); } module.exports = { diff --git a/tools/compare-perf/process-reports.ts b/tools/compare-perf/process-reports.ts new file mode 100644 index 00000000000..ec2684161e9 --- /dev/null +++ b/tools/compare-perf/process-reports.ts @@ -0,0 +1,119 @@ +/** + * External dependencies + */ +const bold = require( 'chalk' ); +const fs = require( 'fs' ); +const path = require( 'path' ); + +/** + * Internal dependencies + */ +const { + getFilesFromDir, + readJSONFile, + logAtIndent, + sanitizeBranchName, + median +} = require( './utils' ) ; + +const formats = { + success: bold.green, +}; + +const ARTIFACTS_PATH = + process.env.WP_ARTIFACTS_PATH || path.join( process.cwd(), 'artifacts' ); +const RESULTS_FILE_SUFFIX = '.performance-results.json'; + +/** + * Calculates and prints results from the generated reports. + * + * @param {string[]} testSuites Test suites we are aiming. + * @param {string[]} branches Branches we are aiming. + */ +async function processPerformanceReports( + testSuites: string[], + branches: string[] +): Promise< void > { + logAtIndent( 0, 'Calculating results' ); + + const resultFiles = getFilesFromDir( ARTIFACTS_PATH ).filter( + ( file: string ) => file.endsWith( RESULTS_FILE_SUFFIX ) + ); + const results: Record< + string, + Record< string, Record< string, number > > + > = {}; + + // Calculate medians from all rounds. + for ( const testSuite of testSuites ) { + logAtIndent( 1, 'Test suite:', formats.success( testSuite ) ); + + results[ testSuite ] = {}; + for ( const branch of branches ) { + const sanitizedBranchName = sanitizeBranchName( branch ); + const resultsRounds: any[] = resultFiles + .filter( ( file: string ) => + file.includes( + `/${ testSuite }_${ sanitizedBranchName }_round-` + ) + ) + .map( ( file: string ) => { + logAtIndent( 2, 'Reading from:', formats.success( file ) ); + return readJSONFile( file ); + } ); + + const metrics = Object.keys( resultsRounds[ 0 ] ); + results[ testSuite ][ branch ] = {}; + + for ( const metric of metrics ) { + const values = resultsRounds + .map( ( round ) => round[ metric ] ) + .filter( ( value ) => typeof value === 'number' ); + + const value = median( values ); + if ( value !== undefined ) { + results[ testSuite ][ branch ][ metric ] = value; + } + } + } + const calculatedResultsPath = path.join( + ARTIFACTS_PATH, + testSuite + RESULTS_FILE_SUFFIX + ); + + logAtIndent( + 2, + 'Saving curated results to:', + formats.success( calculatedResultsPath ) + ); + fs.writeFileSync( + calculatedResultsPath, + JSON.stringify( results[ testSuite ], null, 2 ) + ); + } + + logAtIndent( 0, 'Printing results' ); + + for ( const testSuite of testSuites ) { + logAtIndent( 0, formats.success( testSuite ) ); + + // Invert the results, so we can display them in a table. + const invertedResult: Record< string, Record< string, string > > = {}; + for ( const [ branch, metrics ] of Object.entries( + results[ testSuite ] + ) ) { + for ( const [ metric, value ] of Object.entries( metrics ) ) { + invertedResult[ metric ] = invertedResult[ metric ] || {}; + invertedResult[ metric ][ branch ] = `${ value } ms`; + } + } + + // Print the results. + // eslint-disable-next-line no-console + console.table( invertedResult ); + } +} + +module.exports = { + processPerformanceReports, +}; diff --git a/tools/compare-perf/tsconfig.json b/tools/compare-perf/tsconfig.json new file mode 100644 index 00000000000..d491017bfb9 --- /dev/null +++ b/tools/compare-perf/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "./node_modules/@tsconfig/node16/tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "typeRoots": [ + "./typings", + "./node_modules/@types" + ] + }, + "ts-node": { + "transpileOnly": true, + "files": true, + }, +} diff --git a/tools/compare-perf/utils.js b/tools/compare-perf/utils.js index 2cca3b1a781..4a47cb41ded 100644 --- a/tools/compare-perf/utils.js +++ b/tools/compare-perf/utils.js @@ -97,9 +97,58 @@ function getFilesFromDir( dir ) { .map( ( dirent ) => path.join( dir, dirent.name ) ); } +/** + * A logging helper for printing steps and their substeps. + * + * @param {number} indent Value to indent the log. + * @param {any} msg Message to log. + * @param {...any} args Rest of the arguments to pass to console.log. + */ +function logAtIndent( indent, msg, ...args ) { + const prefix = indent === 0 ? '▶ ' : '> '; + const newline = indent === 0 ? '\n' : ''; + return console.log( + newline + ' '.repeat( indent ) + prefix + msg, + ...args + ); +} + +/** + * Sanitizes branch name to be used in a path or a filename. + * + * @param {string} branch + * + * @return {string} Sanitized branch name. + */ +function sanitizeBranchName( branch ) { + return branch.replace( /[^a-zA-Z0-9-]/g, '-' ); +} + +/** + * Computes the median number from an array numbers. + * + * @param {number[]} array + * + * @return {number|undefined} Median value or undefined if array empty. + */ +function median( array ) { + if ( ! array || ! array.length ) return undefined; + + const numbers = [ ...array ].sort( ( a, b ) => a - b ); + const middleIndex = Math.floor( numbers.length / 2 ); + + if ( numbers.length % 2 === 0 ) { + return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2; + } + return numbers[ middleIndex ]; +} + module.exports = { askForConfirmation, readJSONFile, runShellScript, getFilesFromDir, + logAtIndent, + sanitizeBranchName, + median };