From cb2048004cf1f2ac92b063b7f8283554567963a0 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Fri, 13 Jan 2023 16:02:28 +0100 Subject: [PATCH 01/53] Add the Cart & Checkout Blocks as a feature --- .../woocommerce/src/Internal/Features/FeaturesController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index bf2622df5c2..8a014af58a7 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -105,6 +105,11 @@ class FeaturesController { 'description' => __( 'Enable the high performance order storage feature.', 'woocommerce' ), 'is_experimental' => true, ), + 'cart_checkout_blocks' => array( + 'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ), + 'description' => __( 'Optimize for faster checkout', 'woocommerce' ), + 'is_experimental' => false, + ), ); $this->legacy_feature_ids = array( 'analytics', 'new_navigation' ); From c42fdb039c6f7b56ade6c91fbe133e9793e885ae Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Fri, 13 Jan 2023 16:05:40 +0100 Subject: [PATCH 02/53] Prevent displaying UI setting of the C&C Blocks Currently, we don't desire displaying the C&C Blocks feature's settings. We may choose otherwise in the future, but for the time being adding the C&C Block to the list of features does the work! --- .../woocommerce/src/Internal/Features/FeaturesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index 8a014af58a7..44f84e1a77a 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -540,7 +540,7 @@ class FeaturesController { return $features[ $feature_id ]['is_experimental']; } ); - $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids ); + $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, [ 'cart_checkout_blocks' ] ); $feature_ids = array_merge( $mature_feature_ids, array( 'mature_features_end' ), $experimental_feature_ids ); foreach ( $feature_ids as $id ) { From 728ba16b350780577b5789ef1c48124736457805 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Fri, 13 Jan 2023 16:16:26 +0100 Subject: [PATCH 03/53] Add a change file --- ...d-36413-support-for-cart-checkout-in-declare-compatibility | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-36413-support-for-cart-checkout-in-declare-compatibility diff --git a/plugins/woocommerce/changelog/add-36413-support-for-cart-checkout-in-declare-compatibility b/plugins/woocommerce/changelog/add-36413-support-for-cart-checkout-in-declare-compatibility new file mode 100644 index 00000000000..7724faa14f5 --- /dev/null +++ b/plugins/woocommerce/changelog/add-36413-support-for-cart-checkout-in-declare-compatibility @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Add the support for the C&C Blocks in declaring compatibility feature From 4059fbb33ba08f949bcc12835f00b45479a2cac6 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Mon, 16 Jan 2023 09:57:06 +0100 Subject: [PATCH 04/53] Fix PHPCS lint error: short array syntax --- .../woocommerce/src/Internal/Features/FeaturesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index 44f84e1a77a..47694ded9a3 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -540,7 +540,7 @@ class FeaturesController { return $features[ $feature_id ]['is_experimental']; } ); - $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, [ 'cart_checkout_blocks' ] ); + $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, array( 'cart_checkout_blocks' ) ); $feature_ids = array_merge( $mature_feature_ids, array( 'mature_features_end' ), $experimental_feature_ids ); foreach ( $feature_ids as $id ) { From 2061778b3fb658032910d4f2d5af3b7f9eefd72c Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 13 Feb 2023 17:22:48 +0100 Subject: [PATCH 05/53] Add an encoding selector to the product importer --- .../legacy/js/admin/wc-product-import.js | 32 +++++++------ .../admin/class-wc-admin-importers.php | 27 +++++++---- ...ass-wc-product-csv-importer-controller.php | 47 ++++++++++++------- .../views/html-csv-import-mapping.php | 3 ++ .../views/html-product-csv-import-form.php | 14 ++++++ .../import/class-wc-product-csv-importer.php | 25 +++++++++- 6 files changed, 105 insertions(+), 43 deletions(-) diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-product-import.js b/plugins/woocommerce/client/legacy/js/admin/wc-product-import.js index 645f11b5912..3854dc6af3b 100644 --- a/plugins/woocommerce/client/legacy/js/admin/wc-product-import.js +++ b/plugins/woocommerce/client/legacy/js/admin/wc-product-import.js @@ -5,14 +5,15 @@ * productImportForm handles the import process. */ var productImportForm = function( $form ) { - this.$form = $form; - this.xhr = false; - this.mapping = wc_product_import_params.mapping; - this.position = 0; - this.file = wc_product_import_params.file; - this.update_existing = wc_product_import_params.update_existing; - this.delimiter = wc_product_import_params.delimiter; - this.security = wc_product_import_params.import_nonce; + this.$form = $form; + this.xhr = false; + this.mapping = wc_product_import_params.mapping; + this.position = 0; + this.file = wc_product_import_params.file; + this.update_existing = wc_product_import_params.update_existing; + this.delimiter = wc_product_import_params.delimiter; + this.security = wc_product_import_params.import_nonce; + this.character_encoding = wc_product_import_params.character_encoding; // Number of import successes/failures. this.imported = 0; @@ -39,13 +40,14 @@ type: 'POST', url: ajaxurl, data: { - action : 'woocommerce_do_ajax_product_import', - position : $this.position, - mapping : $this.mapping, - file : $this.file, - update_existing : $this.update_existing, - delimiter : $this.delimiter, - security : $this.security + action : 'woocommerce_do_ajax_product_import', + position : $this.position, + mapping : $this.mapping, + file : $this.file, + update_existing : $this.update_existing, + delimiter : $this.delimiter, + security : $this.security, + character_encoding: $this.character_encoding }, dataType: 'json', success: function( response ) { diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-importers.php b/plugins/woocommerce/includes/admin/class-wc-admin-importers.php index 95f9e3bbca7..11007d8c2c6 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-importers.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-importers.php @@ -147,11 +147,13 @@ class WC_Admin_Importers { public function post_importer_compatibility() { global $wpdb; - if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) { // PHPCS: input var ok, CSRF ok. + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) { return; } - $id = absint( $_POST['import_id'] ); // PHPCS: input var ok. + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $id = absint( $_POST['import_id'] ); $file = get_attached_file( $id ); $parser = new WXR_Parser(); $import_data = $parser->parse( $file ); @@ -216,12 +218,21 @@ class WC_Admin_Importers { $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. - 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ), - 'parse' => true, + '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 + */ + 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ), + 'parse' => true, ); // Log failures. 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 a2e69186818..df11ab23963 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 @@ -70,6 +70,13 @@ class WC_Product_CSV_Importer_Controller { */ protected $update_existing = false; + /** + * The character encoding to use to interpret the input file, or empty string for autodetect. + * + * @var string + */ + protected $character_encoding = 'UTF-8'; + /** * Get importer instance. * @@ -139,11 +146,12 @@ class WC_Product_CSV_Importer_Controller { $this->steps = apply_filters( 'woocommerce_product_csv_importer_steps', $default_steps ); // phpcs:disable WordPress.Security.NonceVerification.Recommended - $this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) ); - $this->file = isset( $_REQUEST['file'] ) ? wc_clean( wp_unslash( $_REQUEST['file'] ) ) : ''; - $this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false; - $this->delimiter = ! empty( $_REQUEST['delimiter'] ) ? wc_clean( wp_unslash( $_REQUEST['delimiter'] ) ) : ','; - $this->map_preferences = isset( $_REQUEST['map_preferences'] ) ? (bool) $_REQUEST['map_preferences'] : false; + $this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) ); + $this->file = isset( $_REQUEST['file'] ) ? wc_clean( wp_unslash( $_REQUEST['file'] ) ) : ''; + $this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false; + $this->delimiter = ! empty( $_REQUEST['delimiter'] ) ? wc_clean( wp_unslash( $_REQUEST['delimiter'] ) ) : ','; + $this->map_preferences = isset( $_REQUEST['map_preferences'] ) ? (bool) $_REQUEST['map_preferences'] : false; + $this->character_encoding = isset( $_REQUEST['character_encoding'] ) ? wc_clean( wp_unslash( $_REQUEST['character_encoding'] ) ) : 'UTF-8'; // phpcs:enable // Import mappings for CSV data. @@ -180,12 +188,13 @@ class WC_Product_CSV_Importer_Controller { } $params = array( - 'step' => $keys[ $step_index + 1 ], - 'file' => str_replace( DIRECTORY_SEPARATOR, '/', $this->file ), - 'delimiter' => $this->delimiter, - 'update_existing' => $this->update_existing, - 'map_preferences' => $this->map_preferences, - '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects. + 'step' => $keys[ $step_index + 1 ], + 'file' => str_replace( DIRECTORY_SEPARATOR, '/', $this->file ), + 'delimiter' => $this->delimiter, + 'update_existing' => $this->update_existing, + 'map_preferences' => $this->map_preferences, + 'character_encoding' => $this->character_encoding, + '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects. ); return add_query_arg( $params ); @@ -365,8 +374,9 @@ class WC_Product_CSV_Importer_Controller { protected function mapping_form() { check_admin_referer( 'woocommerce-csv-importer' ); $args = array( - 'lines' => 1, - 'delimiter' => $this->delimiter, + 'lines' => 1, + 'delimiter' => $this->delimiter, + 'character_encoding' => $this->character_encoding, ); $importer = self::get_importer( $this->file, $args ); @@ -428,14 +438,15 @@ class WC_Product_CSV_Importer_Controller { 'wc-product-import', 'wc_product_import_params', array( - 'import_nonce' => wp_create_nonce( 'wc-product-import' ), - 'mapping' => array( + 'import_nonce' => wp_create_nonce( 'wc-product-import' ), + 'mapping' => array( 'from' => $mapping_from, 'to' => $mapping_to, ), - 'file' => $this->file, - 'update_existing' => $this->update_existing, - 'delimiter' => $this->delimiter, + 'file' => $this->file, + 'update_existing' => $this->update_existing, + 'delimiter' => $this->delimiter, + 'character_encoding' => $this->character_encoding, ) ); wp_enqueue_script( 'wc-product-import' ); diff --git a/plugins/woocommerce/includes/admin/importers/views/html-csv-import-mapping.php b/plugins/woocommerce/includes/admin/importers/views/html-csv-import-mapping.php index 20cdc422c7c..38a74443f3f 100644 --- a/plugins/woocommerce/includes/admin/importers/views/html-csv-import-mapping.php +++ b/plugins/woocommerce/includes/admin/importers/views/html-csv-import-mapping.php @@ -60,6 +60,9 @@ if ( ! defined( 'ABSPATH' ) ) { + + + diff --git a/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php b/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php index 3b96ec64f47..94f9979ff13 100644 --- a/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php +++ b/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php @@ -78,6 +78,20 @@ if ( ! defined( 'ABSPATH' ) ) {
+ +
+ + + diff --git a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php index c5f828f7d3a..6a32aaaf9c9 100644 --- a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php +++ b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php @@ -66,6 +66,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $this->read_file(); } + /** + * Convert a string from the input encoding to UTF-8. + * + * @param string $value The string to convert. + * @return string The converted string. + */ + private function adjust_character_encoding( $value ) { + $encoding = $this->params['character_encoding']; + return 'UTF-8' === $encoding ? $value : mb_convert_encoding( $value, 'UTF-8', $encoding ); + } + /** * Read file. */ @@ -77,7 +88,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $handle = fopen( $this->file, 'r' ); // @codingStandardsIgnoreLine. if ( false !== $handle ) { - $this->raw_keys = version_compare( PHP_VERSION, '5.3', '>=' ) ? array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) ) : array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'] ) ); // @codingStandardsIgnoreLine + $this->raw_keys = array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) ); // @codingStandardsIgnoreLine + + if ( $this->params['character_encoding'] ) { + $this->raw_keys = array_map( array( $this, 'adjust_character_encoding' ), $this->raw_keys ); + } // Remove line breaks in keys, to avoid mismatch mapping of keys. $this->raw_keys = wc_clean( wp_unslash( $this->raw_keys ) ); @@ -92,9 +107,13 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { } while ( 1 ) { - $row = version_compare( PHP_VERSION, '5.3', '>=' ) ? fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) : fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'] ); // @codingStandardsIgnoreLine + $row = fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ); // @codingStandardsIgnoreLine if ( false !== $row ) { + if ( $this->params['character_encoding'] ) { + $row = array_map( array( $this, 'adjust_character_encoding' ), $row ); + } + $this->raw_data[] = $row; $this->file_positions[ count( $this->raw_data ) ] = ftell( $handle ); @@ -1005,6 +1024,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { * * @param array $parsed_data Parsed data. * @param WC_Product_Importer $importer Importer instance. + * + * @since */ $this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this ); } From 13a130fe37d34a8609bce8637a6284aaf329d6d5 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 13 Feb 2023 17:24:08 +0100 Subject: [PATCH 06/53] Add changelog file --- .../changelog/add-encoding-selector-to-product-importer | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-encoding-selector-to-product-importer diff --git a/plugins/woocommerce/changelog/add-encoding-selector-to-product-importer b/plugins/woocommerce/changelog/add-encoding-selector-to-product-importer new file mode 100644 index 00000000000..7d2631b3da9 --- /dev/null +++ b/plugins/woocommerce/changelog/add-encoding-selector-to-product-importer @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add an encoding selector to the product importer From c4440b9586e89411476974a3b3d9b6a4a1dedca4 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Tue, 14 Feb 2023 09:56:48 +0100 Subject: [PATCH 07/53] Don't assume the character encoding parameter is present --- .../includes/import/class-wc-product-csv-importer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php index 6a32aaaf9c9..12f6a390a2f 100644 --- a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php +++ b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php @@ -6,6 +6,8 @@ * @version 3.1.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -90,7 +92,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { if ( false !== $handle ) { $this->raw_keys = array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) ); // @codingStandardsIgnoreLine - if ( $this->params['character_encoding'] ) { + if ( ArrayUtil::is_truthy( $this->params, 'character_encoding' ) ) { $this->raw_keys = array_map( array( $this, 'adjust_character_encoding' ), $this->raw_keys ); } @@ -110,7 +112,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $row = fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ); // @codingStandardsIgnoreLine if ( false !== $row ) { - if ( $this->params['character_encoding'] ) { + if ( ArrayUtil::is_truthy( $this->params, 'character_encoding' ) ) { $row = array_map( array( $this, 'adjust_character_encoding' ), $row ); } From 390efe8bdd36264e8eae7d415a6e616d02673bf4 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Wed, 22 Feb 2023 19:05:52 +0100 Subject: [PATCH 08/53] Fix PHPCS lint error --- .../woocommerce/src/Internal/Features/FeaturesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index 47694ded9a3..d623021dc85 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -105,7 +105,7 @@ class FeaturesController { 'description' => __( 'Enable the high performance order storage feature.', 'woocommerce' ), 'is_experimental' => true, ), - 'cart_checkout_blocks' => array( + 'cart_checkout_blocks' => array( 'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ), 'description' => __( 'Optimize for faster checkout', 'woocommerce' ), 'is_experimental' => false, From b5b0c28ed0a9118216c8ce62ae6b75574ee5774a Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Thu, 23 Feb 2023 08:37:05 +0100 Subject: [PATCH 09/53] Add generic approach to adding features without UI In our case for the Cart & Checkout feature, we don't want to show the UI in the "Features" tab in Woo Settings. Creating a flag for this purpose will make it easier for future features to unsubscribe from showing a UI if required --- .../src/Internal/Features/FeaturesController.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index d623021dc85..aa6803e87b8 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -94,21 +94,25 @@ class FeaturesController { 'description' => __( 'Enables WooCommerce Analytics', 'woocommerce' ), 'is_experimental' => false, 'enabled_by_default' => true, + 'disable_ui' => false, ), 'new_navigation' => array( 'name' => __( 'Navigation', 'woocommerce' ), 'description' => __( 'Adds the new WooCommerce navigation experience to the dashboard', 'woocommerce' ), 'is_experimental' => false, + 'disable_ui' => false, ), 'custom_order_tables' => array( 'name' => __( 'High-Performance order storage (COT)', 'woocommerce' ), 'description' => __( 'Enable the high performance order storage feature.', 'woocommerce' ), 'is_experimental' => true, + 'disable_ui' => false, ), 'cart_checkout_blocks' => array( 'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ), 'description' => __( 'Optimize for faster checkout', 'woocommerce' ), 'is_experimental' => false, + 'disable_ui' => true, ), ); @@ -540,7 +544,13 @@ class FeaturesController { return $features[ $feature_id ]['is_experimental']; } ); - $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, array( 'cart_checkout_blocks' ) ); + $disabled_ui_feature_ids = array_filter( + $feature_ids, + function( $feature_id ) use ( $features ) { + return $features[ $feature_id ]['disable_ui']; + } + ); + $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, $disabled_ui_feature_ids ); $feature_ids = array_merge( $mature_feature_ids, array( 'mature_features_end' ), $experimental_feature_ids ); foreach ( $feature_ids as $id ) { From b312c65a95d8f0c217ecda37c039cca42203091e Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Thu, 23 Feb 2023 08:59:19 +0100 Subject: [PATCH 10/53] Fix PHPCS errors --- .../woocommerce/src/Internal/Features/FeaturesController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index 604c573b025..e3ee40b993c 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -554,12 +554,12 @@ class FeaturesController { return $features[ $feature_id ]['is_experimental']; } ); - $disabled_ui_feature_ids = array_filter( + $disabled_ui_feature_ids = array_filter( $feature_ids, function( $feature_id ) use ( $features ) { return $features[ $feature_id ]['disable_ui']; } - ); + ); $mature_feature_ids = array_diff( $feature_ids, $experimental_feature_ids, $disabled_ui_feature_ids ); $feature_ids = array_merge( $mature_feature_ids, array( 'mature_features_end' ), $experimental_feature_ids ); From 615365d14e0e2b944f05213e366fd1a9849612db Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Thu, 23 Feb 2023 11:31:01 +0100 Subject: [PATCH 11/53] Fix "undefined `disable_ui` index" error --- plugins/woocommerce/src/Internal/Features/FeaturesController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index e3ee40b993c..fbbf4a456b9 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -107,6 +107,7 @@ class FeaturesController { 'name' => __( 'New product editor', 'woocommerce' ), 'description' => __( 'Try the new product editor (Beta)', 'woocommerce' ), 'is_experimental' => true, + 'disable_ui' => false, ), 'custom_order_tables' => array( 'name' => __( 'High-Performance order storage (COT)', 'woocommerce' ), From 3b4b85ba1ffa078b463ead80a742a9a245070249 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 23 Feb 2023 11:49:38 +0100 Subject: [PATCH 12/53] Fix: can't apply or remove a coupon whose code is "0". Also added two new methods in StringUtil: is_null_or_empty and is_null_or_whitespace. --- .../woocommerce/includes/class-wc-ajax.php | 14 +++++--- .../woocommerce/includes/class-wc-coupon.php | 3 +- .../includes/wc-coupon-functions.php | 4 ++- .../src/Internal/Orders/CouponsController.php | 7 ++-- .../woocommerce/src/Utilities/StringUtil.php | 21 +++++++++++ .../php/src/Utilities/StringUtilTest.php | 36 +++++++++++++++++++ 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php index e91584ad71a..a3567584f5e 100644 --- a/plugins/woocommerce/includes/class-wc-ajax.php +++ b/plugins/woocommerce/includes/class-wc-ajax.php @@ -10,8 +10,10 @@ use Automattic\Jetpack\Constants; use Automattic\WooCommerce\Internal\Orders\CouponsController; use Automattic\WooCommerce\Internal\Orders\TaxesController; use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox; +use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\NumberUtil; use Automattic\WooCommerce\Utilities\OrderUtil; +use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; @@ -224,8 +226,9 @@ class WC_AJAX { check_ajax_referer( 'apply-coupon', 'security' ); - if ( ! empty( $_POST['coupon_code'] ) ) { - WC()->cart->add_discount( wc_format_coupon_code( wp_unslash( $_POST['coupon_code'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $coupon_code = ArrayUtil::get_value_or_default( $_POST, 'coupon_code' ); + if ( ! StringUtil::is_null_or_whitespace( $coupon_code ) ) { + WC()->cart->add_discount( wc_format_coupon_code( wp_unslash( $coupon_code ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } else { wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' ); } @@ -242,7 +245,7 @@ class WC_AJAX { $coupon = isset( $_POST['coupon'] ) ? wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if ( empty( $coupon ) ) { + if ( StringUtil::is_null_or_whitespace( $coupon ) ) { wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' ); } else { WC()->cart->remove_coupon( $coupon ); @@ -1199,11 +1202,12 @@ class WC_AJAX { throw new Exception( __( 'Invalid order', 'woocommerce' ) ); } - if ( empty( $_POST['coupon'] ) ) { + $coupon = ArrayUtil::get_value_or_default( $_POST, 'coupon' ); + if ( StringUtil::is_null_or_whitespace( $coupon ) ) { throw new Exception( __( 'Invalid coupon', 'woocommerce' ) ); } - $order->remove_coupon( wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $order->remove_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); diff --git a/plugins/woocommerce/includes/class-wc-coupon.php b/plugins/woocommerce/includes/class-wc-coupon.php index 9d4649baa4e..646c7bf0159 100644 --- a/plugins/woocommerce/includes/class-wc-coupon.php +++ b/plugins/woocommerce/includes/class-wc-coupon.php @@ -9,6 +9,7 @@ */ use Automattic\WooCommerce\Utilities\NumberUtil; +use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; @@ -106,7 +107,7 @@ class WC_Coupon extends WC_Legacy_Coupon { // Try to load coupon using ID or code. if ( is_int( $data ) && 'shop_coupon' === get_post_type( $data ) ) { $this->set_id( $data ); - } elseif ( ! empty( $data ) ) { + } elseif ( is_string( $data ) && ! StringUtil::is_null_or_whitespace( $data ) ) { $id = wc_get_coupon_id_by_code( $data ); // Need to support numeric strings for backwards compatibility. if ( ! $id && 'shop_coupon' === get_post_type( $data ) ) { diff --git a/plugins/woocommerce/includes/wc-coupon-functions.php b/plugins/woocommerce/includes/wc-coupon-functions.php index 28ef64a281b..270133bfdb9 100644 --- a/plugins/woocommerce/includes/wc-coupon-functions.php +++ b/plugins/woocommerce/includes/wc-coupon-functions.php @@ -10,6 +10,8 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Utilities\StringUtil; + /** * Get coupon types. * @@ -91,7 +93,7 @@ function wc_get_coupon_code_by_id( $id ) { */ function wc_get_coupon_id_by_code( $code, $exclude = 0 ) { - if ( empty( $code ) ) { + if ( StringUtil::is_null_or_whitespace( $code ) ) { return 0; } diff --git a/plugins/woocommerce/src/Internal/Orders/CouponsController.php b/plugins/woocommerce/src/Internal/Orders/CouponsController.php index e4acebd8635..57dd39269e2 100644 --- a/plugins/woocommerce/src/Internal/Orders/CouponsController.php +++ b/plugins/woocommerce/src/Internal/Orders/CouponsController.php @@ -2,6 +2,8 @@ namespace Automattic\WooCommerce\Internal\Orders; +use Automattic\WooCommerce\Utilities\ArrayUtil; +use Automattic\WooCommerce\Utilities\StringUtil; use Exception; /** @@ -58,7 +60,8 @@ class CouponsController { throw new Exception( __( 'Invalid order', 'woocommerce' ) ); } - if ( empty( $post_variables['coupon'] ) ) { + $coupon = ArrayUtil::get_value_or_default( $post_variables, 'coupon' ); + if ( StringUtil::is_null_or_whitespace( $coupon ) ) { throw new Exception( __( 'Invalid coupon', 'woocommerce' ) ); } @@ -76,7 +79,7 @@ class CouponsController { $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); - $result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $post_variables['coupon'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( is_wp_error( $result ) ) { throw new Exception( html_entity_decode( wp_strip_all_tags( $result->get_error_message() ) ) ); diff --git a/plugins/woocommerce/src/Utilities/StringUtil.php b/plugins/woocommerce/src/Utilities/StringUtil.php index d120bdf6ac6..f83eecbf6ca 100644 --- a/plugins/woocommerce/src/Utilities/StringUtil.php +++ b/plugins/woocommerce/src/Utilities/StringUtil.php @@ -83,4 +83,25 @@ final class StringUtil { public static function plugin_name_from_plugin_file( string $plugin_file_path ): string { return basename( dirname( $plugin_file_path ) ) . DIRECTORY_SEPARATOR . basename( $plugin_file_path ); } + + /** + * Check if a string is null or is empty. + * + * @param string|null $value The string to check. + * @return bool True if the string is null or is empty. + */ + public static function is_null_or_empty( ?string $value ) { + return is_null( $value ) || '' === $value; + } + + /** + * Check if a string is null, is empty, or has only whitespace characters + * (space, tab, vertical tab, form feed, carriage return, new line) + * + * @param string|null $value The string to check. + * @return bool True if the string is null, is empty, or contains only whitespace characters. + */ + public static function is_null_or_whitespace( ?string $value ) { + return is_null( $value ) || '' === $value || ctype_space( $value ); + } } diff --git a/plugins/woocommerce/tests/php/src/Utilities/StringUtilTest.php b/plugins/woocommerce/tests/php/src/Utilities/StringUtilTest.php index 96f0e5f0b2c..279d6ca603e 100644 --- a/plugins/woocommerce/tests/php/src/Utilities/StringUtilTest.php +++ b/plugins/woocommerce/tests/php/src/Utilities/StringUtilTest.php @@ -74,4 +74,40 @@ class StringUtilTest extends \WC_Unit_Test_Case { $expected = 'foobar/fizzbuzz.php'; $this->assertEquals( $expected, $result ); } + + /** + * @testDox 'is_null_or_empty' should return true only if the value is null or an empty string. + * + * @testWith [null, true] + * ["", true] + * [" ", false] + * ["0", false] + * ["foo", false] + * [" foo ", false] + * + * @param string $value Value to test. + * @param bool $expected Expected result from the method. + */ + public function test_is_null_or_empty( $value, $expected ) { + $result = StringUtil::is_null_or_empty( $value ); + $this->assertEquals( $expected, $result ); + } + + /** + * @testDox 'is_null_or_empty' should return true only if the value is null, an empty string, or consists of only whitespace characters. + * + * @testWith [null, true] + * ["", true] + * [" \n\r\t\f ", true] + * ["0", false] + * ["foo", false] + * [" foo ", false] + * + * @param string $value Value to test. + * @param bool $expected Expected result from the method. + */ + public function test_is_null_or_whitespace( $value, $expected ) { + $result = StringUtil::is_null_or_whitespace( $value ); + $this->assertEquals( $expected, $result ); + } } From 59a55b8561a00bb9ae1615cdcda159392904bac8 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 23 Feb 2023 11:52:01 +0100 Subject: [PATCH 13/53] Add changelog file --- plugins/woocommerce/changelog/fix-cant-apply-coupon-0 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-cant-apply-coupon-0 diff --git a/plugins/woocommerce/changelog/fix-cant-apply-coupon-0 b/plugins/woocommerce/changelog/fix-cant-apply-coupon-0 new file mode 100644 index 00000000000..d9066b3f2a1 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-cant-apply-coupon-0 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix the inability to apply a coupon whose code is "0" From 575146615a920ad048842aec003ac2be440030e2 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Fri, 24 Feb 2023 11:44:21 +0100 Subject: [PATCH 14/53] Fix: can't apply or remove a coupon whose code is "0" via REST api Also fix: coupon usage not incremented for coupon with code "0" --- plugins/woocommerce/includes/class-wc-discounts.php | 6 ++++-- .../Version1/class-wc-rest-coupons-v1-controller.php | 5 ++++- .../Version1/class-wc-rest-orders-v1-controller.php | 6 +++++- .../Version2/class-wc-rest-coupons-v2-controller.php | 5 ++++- .../Version2/class-wc-rest-orders-v2-controller.php | 5 ++++- .../Version3/class-wc-rest-orders-controller.php | 7 +++++-- plugins/woocommerce/includes/wc-order-functions.php | 3 ++- 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-discounts.php b/plugins/woocommerce/includes/class-wc-discounts.php index ca876c30c6f..de70dff90db 100644 --- a/plugins/woocommerce/includes/class-wc-discounts.php +++ b/plugins/woocommerce/includes/class-wc-discounts.php @@ -7,6 +7,7 @@ */ use Automattic\WooCommerce\Utilities\NumberUtil; +use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; @@ -255,8 +256,9 @@ class WC_Discounts { return $is_coupon_valid; } - if ( ! isset( $this->discounts[ $coupon->get_code() ] ) ) { - $this->discounts[ $coupon->get_code() ] = array_fill_keys( array_keys( $this->items ), 0 ); + $coupon_code = $coupon->get_code(); + if ( StringUtil::is_null_or_whitespace( $this->discounts[ $coupon_code ] ) ) { + $this->discounts[ $coupon_code ] = array_fill_keys( array_keys( $this->items ), 0 ); } $items_to_apply = $this->get_items_to_apply_coupon( $coupon ); diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php index 14c4d3ab245..c4baba6086a 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php @@ -10,6 +10,9 @@ * @since 3.0.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; +use Automattic\WooCommerce\Utilities\StringUtil; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -249,7 +252,7 @@ class WC_REST_Coupons_V1_Controller extends WC_REST_Posts_Controller { // Validate required POST fields. if ( 'POST' === $request->get_method() && 0 === $coupon->get_id() ) { - if ( empty( $request['code'] ) ) { + if ( StringUtil::is_null_or_whitespace( $request['code'] ?? null ) ) { return new WP_Error( 'woocommerce_rest_empty_coupon_code', sprintf( __( 'The coupon code cannot be empty.', 'woocommerce' ), 'code' ), array( 'status' => 400 ) ); } } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php index f18b51eeb67..cf78dbfc8be 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php @@ -10,6 +10,9 @@ * @since 3.0.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; +use Automattic\WooCommerce\Utilities\StringUtil; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -737,7 +740,8 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller { $item = new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' ); if ( 'create' === $action ) { - if ( empty( $posted['code'] ) ) { + $coupon_code = ArrayUtil::get_value_or_default( $posted, 'code' ); + if ( StringUtil::is_null_or_whitespace( $coupon_code ) ) { throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); } } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php index b96d9875fd0..334a024d098 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php @@ -8,6 +8,9 @@ * @since 2.6.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; +use Automattic\WooCommerce\Utilities\StringUtil; + defined( 'ABSPATH' ) || exit; /** @@ -268,7 +271,7 @@ class WC_REST_Coupons_V2_Controller extends WC_REST_CRUD_Controller { $data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) ); // Validate required POST fields. - if ( $creating && empty( $request['code'] ) ) { + if ( $creating && StringUtil::is_null_or_whitespace( $request['code'] ?? null ) ) { return new WP_Error( 'woocommerce_rest_empty_coupon_code', sprintf( __( 'The coupon code cannot be empty.', 'woocommerce' ), 'code' ), array( 'status' => 400 ) ); } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index 9f49bb546c9..abbe84d47c9 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -10,7 +10,9 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\OrderUtil; +use Automattic\WooCommerce\Utilities\StringUtil; // phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Legacy class name, can't change without breaking backward compat. /** @@ -944,7 +946,8 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { $item = is_null( $item ) ? new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' ) : $item; if ( 'create' === $action ) { - if ( empty( $posted['code'] ) ) { + $coupon_code = ArrayUtil::get_value_or_default( $posted, 'code' ); + if ( StringUtil::is_null_or_whitespace( $coupon_code ) ) { throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); } } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php index b594de2284f..106dac9a77f 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php @@ -8,7 +8,9 @@ * @since 2.6.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\OrderUtil; +use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; @@ -58,11 +60,12 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller { throw new WC_REST_Exception( 'woocommerce_rest_coupon_item_id_readonly', __( 'Coupon item ID is readonly.', 'woocommerce' ), 400 ); } - if ( empty( $item['code'] ) ) { + $coupon_code = ArrayUtil::get_value_or_default( $item, 'code' ); + if ( StringUtil::is_null_or_whitespace( $coupon_code ) ) { throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); } - $coupon_code = wc_format_coupon_code( wc_clean( $item['code'] ) ); + $coupon_code = wc_format_coupon_code( wc_clean( $coupon_code ) ); $coupon = new WC_Coupon( $coupon_code ); // Skip check if the coupon is already applied to the order, as this could wrongly throw an error for single-use coupons. diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php index 7a40e589768..42a7925190d 100644 --- a/plugins/woocommerce/includes/wc-order-functions.php +++ b/plugins/woocommerce/includes/wc-order-functions.php @@ -9,6 +9,7 @@ */ use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; +use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; @@ -926,7 +927,7 @@ function wc_update_coupon_usage_counts( $order_id ) { if ( count( $order->get_coupon_codes() ) > 0 ) { foreach ( $order->get_coupon_codes() as $code ) { - if ( ! $code ) { + if ( StringUtil::is_null_or_whitespace( $code ) ) { continue; } From 89ec0bc083d1e7c8595a5b556e8a1491a83a86ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Soriano?= Date: Fri, 24 Feb 2023 14:59:08 +0100 Subject: [PATCH 15/53] Change SORT_STRING to SORT_NATURAL for the encodings list Co-authored-by: Barry Hughes <3594411+barryhughes@users.noreply.github.com> --- .../admin/importers/views/html-product-csv-import-form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php b/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php index 94f9979ff13..2740d4da118 100644 --- a/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php +++ b/plugins/woocommerce/includes/admin/importers/views/html-product-csv-import-form.php @@ -84,7 +84,7 @@ if ( ! defined( 'ABSPATH' ) ) { ' . esc_html( $encoding ) . ''; } From e5d8b613734d81fa251b732021e7eb21fdb6365b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 24 Feb 2023 17:00:32 -0300 Subject: [PATCH 16/53] Add custom rendering logic to the item label (#36476) * Add type definitions * Add custom rendering logic to the item label * Add stories * Add changelog file * Fix rebase merge issue * Fix up stories after rebase --------- Co-authored-by: Matt Sherman --- .../add-35851-tree-control-custom-label | 4 + .../hooks/use-tree-item.ts | 3 + .../hooks/use-tree.ts | 2 + .../stories/index.tsx | 78 +++++++++++++++++++ .../experimental-tree-control/tree-item.tsx | 7 +- .../src/experimental-tree-control/types.ts | 15 ++++ 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 packages/js/components/changelog/add-35851-tree-control-custom-label diff --git a/packages/js/components/changelog/add-35851-tree-control-custom-label b/packages/js/components/changelog/add-35851-tree-control-custom-label new file mode 100644 index 00000000000..74d9e8185de --- /dev/null +++ b/packages/js/components/changelog/add-35851-tree-control-custom-label @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add custom rendering logic to the item label diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts index 23fe64d2c13..65f43897d28 100644 --- a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts +++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts @@ -12,6 +12,7 @@ import { useExpander } from './use-expander'; export function useTreeItem( { item, level, + getLabel, shouldItemBeExpanded, ...props }: TreeItemProps ) { @@ -26,6 +27,7 @@ export function useTreeItem( { item, level: nextLevel, expander, + getLabel, treeItemProps: { ...props, }, @@ -37,6 +39,7 @@ export function useTreeItem( { treeProps: { items: item.children, level: nextLevel, + getItemLabel: getLabel, shouldItemBeExpanded, }, }; diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts index 028a338a375..5d88b2b84e9 100644 --- a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts +++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts @@ -11,6 +11,7 @@ export function useTree( { ref, items, level = 1, + getItemLabel, shouldItemBeExpanded, ...props }: TreeProps ) { @@ -22,6 +23,7 @@ export function useTree( { }, treeItemProps: { level, + getLabel: getItemLabel, shouldItemBeExpanded, }, }; diff --git a/packages/js/components/src/experimental-tree-control/stories/index.tsx b/packages/js/components/src/experimental-tree-control/stories/index.tsx index fdfcf2aee36..e53a4852637 100644 --- a/packages/js/components/src/experimental-tree-control/stories/index.tsx +++ b/packages/js/components/src/experimental-tree-control/stories/index.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import interpolate from '@automattic/interpolate-components'; import { BaseControl, TextControl } from '@wordpress/components'; import React, { createElement, useCallback, useState } from 'react'; @@ -65,6 +66,83 @@ export const ExpandOnFilter: React.FC = () => { ); }; +export const CustomItemLabel: React.FC = () => { + function renderCustomItemLabel( item: LinkedTree ) { + return ( +
+
+
+ { item.data.label } + Some item description +
+
+ ); + } + + return ( + + + + ); +}; + +function getItemLabel( item: LinkedTree, text: string ) { + return ( + + { text + ? interpolate( { + mixedString: item.data.label.replace( + new RegExp( text, 'ig' ), + ( group ) => `{{bold}}${ group }{{/bold}}` + ), + components: { + bold: , + }, + } ) + : item.data.label } + + ); +} + +export const CustomItemLabelOnSearch: React.FC = () => { + const [ filter, setFilter ] = useState( '' ); + + return ( + <> + + + getItemLabel( item, filter ) } + shouldItemBeExpanded={ ( item ) => + shouldItemBeExpanded( item, filter ) + } + /> + + + ); +}; + export default { title: 'WooCommerce Admin/experimental/TreeControl', component: TreeControl, diff --git a/packages/js/components/src/experimental-tree-control/tree-item.tsx b/packages/js/components/src/experimental-tree-control/tree-item.tsx index 17c4d670c63..27c80c09263 100644 --- a/packages/js/components/src/experimental-tree-control/tree-item.tsx +++ b/packages/js/components/src/experimental-tree-control/tree-item.tsx @@ -24,6 +24,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem( headingProps, treeProps, expander: { isExpanded, onToggleExpand }, + getLabel, } = useTreeItem( { ...props, ref, @@ -42,7 +43,11 @@ export const TreeItem = forwardRef( function ForwardedTreeItem( className="experimental-woocommerce-tree-item__heading" >
- { item.data.label } + { typeof getLabel === 'function' ? ( + getLabel( item ) + ) : ( + { item.data.label } + ) }
{ Boolean( item.children?.length ) && ( diff --git a/packages/js/components/src/experimental-tree-control/types.ts b/packages/js/components/src/experimental-tree-control/types.ts index e7487fcfe4d..0b7246c6b08 100644 --- a/packages/js/components/src/experimental-tree-control/types.ts +++ b/packages/js/components/src/experimental-tree-control/types.ts @@ -16,6 +16,20 @@ export type TreeProps = React.DetailedHTMLProps< > & { level?: number; items: LinkedTree[]; + /** + * It gives a way to render a different Element as the + * tree item label. + * + * @example + * ${ item.data.label } } + * /> + * + * @param item The current rendering tree item + * + * @see {@link LinkedTree} + */ + getItemLabel?( item: LinkedTree ): JSX.Element; /** * Return if the tree item passed in should be expanded. * @@ -39,6 +53,7 @@ export type TreeItemProps = React.DetailedHTMLProps< > & { level: number; item: LinkedTree; + getLabel?( item: LinkedTree ): JSX.Element; shouldItemBeExpanded?( item: LinkedTree ): boolean; }; From ea1925c841c5fef7c4981dc2c46b179108900098 Mon Sep 17 00:00:00 2001 From: rodelgc Date: Mon, 27 Feb 2023 12:11:46 +0800 Subject: [PATCH 17/53] Enable "Smoke test release" workflow (#36598) * First pass at updating release test workflow * Add changelog * Set dir env variables * Update to workflow * Fix indent * Fix indent * Clean up indent * Re-order steps * Change order of jobs * Added common php versions * Update pipeline * Update some labels * Simplify for testing * Update paths * Create tmp folder * Fix path * Paths * Try outputting some debugging * Add step ID back * Remove working directory * Another path tweak * Add API release tests * Add k6 tests * Add PHP tests * Launch wp-env during PHP tests * Try default values * Tweak some settings, add WP testing * Tweak some settings * Re-order e2e steps * Update step descriptions * Reorganize tests, add plugin tests * Enable only e2e job * Initial set up to run against release smoke test site * Fix syntax * Temporarily disable update wc spec * Temporarily disable downloading woocommerce zip * Download release zip using tag name * Fix wrong job name * Fix wrong job name * Fix dir * Delete fetch-asset-id.js * REfactor update-woocommerce spec * Add error handling * Download release zip by tag * Refactor update woo spec to download zip by tag * Correct job name * fail test on invalid tag * Enable all e2e tests * Run api tests before e2e tests * Fix job dependency * Add customer credentials to api job * Separate job for WC Update * Combine e2e allure-results, then report * Enable report job * Fix context * Change job and artifact names * Use test s3 path * Minor job name change * Upload artifacts to bucket * Correct s3 path * Add quiet option * Retain video on failures * Finalize s3 path * Try WP latest-1 * Revert to wp latest * Refine search for woocommerce zip asset * Get created-at * Specify repo in gh command * Slugify env description * Trim space * Sync with bucket instead of copy * Remove invalid --recursive flag * Re-add missing step to combine e2e results from update wc test * Ensure artifact upload on test failure * Enable all e2e tests on WP latest * Retain existing data before updating WC * Make test compatible with 'Canceled' and 'Cancelled' * Set env_desc as env var * Re-add deleted file * Fix UPDATE_WC in daily smoke test workflow * Add tracing in global setup * Remove tracing * Temporarily run only basic spec * Job for WP Latest-1 & 2 * Fix "Required input 'created_at' not provided" * Minor rename * Remove install filter * Install deps in get-wp-matrix * Delete get-wp-versions.js * Add get-wp-versions.js to e2e-pw folder * REname file * REfactor * Refactor script for getting WP prev versions * Update job dependencies * Temporarily remove disabled jobs * Allow e2e-wp-latest after api test failure * Update L-1 & L-2 job deps * Fix report-wp-latest * Fix failing api test * Make get-wp-versions quicker * Publish report immediately after test * Test reporting in e2e-update-wc * Fix missing parameter * Fix env_desc, re-enable other jobs * Enable all e2e tests * Minor job name change * Fix flaky test * Add php version testing * stringify php versions * Re-enable all e2e tests * Up timeout to 2min * Remove PHP 8.0 * Add missing conditionals * Fix php version verification script * Fix starting dir * Fix flakiness * Skip e2e if api failed * Verify woocommerce.zip early * Add token * Delete test summary on github for the meantime * Use default playwright config * More meaningful variable names * Update step titles based on review * Use expect.poll() * Minor spacing corrections * Use `stable-check` endpoint, delete unnecessary loop * Update locators to be JN-compatible * Fix erroneous getting of release tag * Fix conflict of "No thanks" button locator with that of WP Mail Logging's * Update github-script action to v6 * Revert to 'Cancelled' * Remove unnecessary step * Provide missing env variables --------- Co-authored-by: Jon Lane Co-authored-by: Jonathan Lane --- .../workflows/scripts/verify-php-version.sh | 21 + .github/workflows/smoke-test-daily.yml | 49 +- .github/workflows/smoke-test-release.yml | 770 +++++++++++++++--- .../changelog/e2e-update-release-e2e-testing | 4 + .../ignore-plugin-tests.playwright.config.js | 13 + .../analytics-overview.spec.js | 23 +- .../e2e-pw/tests/admin-tasks/payment.spec.js | 2 +- .../tests/merchant/create-coupon.spec.js | 8 +- .../merchant/create-simple-product.spec.js | 32 +- .../e2e-pw/tests/merchant/order-edit.spec.js | 8 +- .../e2e-pw/tests/merchant/page-loads.spec.js | 2 +- .../smoke-tests/update-woocommerce.spec.js | 258 +++--- .../tests/e2e-pw/utils/plugin-utils.js | 6 +- .../tests/e2e-pw/utils/wordpress.js | 59 ++ 14 files changed, 974 insertions(+), 281 deletions(-) create mode 100644 .github/workflows/scripts/verify-php-version.sh create mode 100644 plugins/woocommerce/changelog/e2e-update-release-e2e-testing create mode 100644 plugins/woocommerce/tests/e2e-pw/ignore-plugin-tests.playwright.config.js create mode 100644 plugins/woocommerce/tests/e2e-pw/utils/wordpress.js diff --git a/.github/workflows/scripts/verify-php-version.sh b/.github/workflows/scripts/verify-php-version.sh new file mode 100644 index 00000000000..df888962966 --- /dev/null +++ b/.github/workflows/scripts/verify-php-version.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# +# Verify that the PHP version in the launched WP ENV environment is equal to expected. +# + +cd $GITHUB_WORKSPACE/plugins/woocommerce +ACTUAL_PHP_VERSION=$(pnpm exec wp-env run tests-cli "wp --info | grep 'PHP version:'") +EXIT_CODE='' + +echo "PHP version found in WP Env environment: \"$ACTUAL_PHP_VERSION\"" +echo "Expected PHP version: \"$EXPECTED_PHP_VERSION\"" + +if [[ $ACTUAL_PHP_VERSION == *"$EXPECTED_PHP_VERSION"* ]] + then + EXIT_CODE=0 + else + EXIT_CODE=1 +fi + +exit $EXIT_CODE diff --git a/.github/workflows/smoke-test-daily.yml b/.github/workflows/smoke-test-daily.yml index bf4e89ea759..8134fe145b5 100644 --- a/.github/workflows/smoke-test-daily.yml +++ b/.github/workflows/smoke-test-daily.yml @@ -8,7 +8,6 @@ env: API_ARTIFACT: api-daily--run-${{ github.run_number }} E2E_ARTIFACT: e2e-daily--run-${{ github.run_number }} FORCE_COLOR: 1 - BRANCH_NAME: ${{ github.ref_name }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -21,14 +20,19 @@ jobs: name: API tests on nightly build runs-on: ubuntu-20.04 permissions: - contents: read + contents: read env: ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report + BASE_URL: ${{ secrets.SMOKE_TEST_URL }} + ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }} + ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }} + ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }} + CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }} + CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }} + DEFAULT_TIMEOUT_OVERRIDE: 120000 steps: - uses: actions/checkout@v3 - with: - ref: ${{ env.BRANCH_NAME }} - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo @@ -36,13 +40,21 @@ jobs: install-filters: woocommerce build: false + - name: Download and install Chromium browser. + working-directory: plugins/woocommerce + run: pnpm exec playwright install chromium + + - name: Run 'Update WooCommerce' test. + working-directory: plugins/woocommerce + env: + UPDATE_WC: nightly + run: pnpm exec playwright test --config=tests/e2e-pw/daily.playwright.config.js update-woocommerce.spec.js + - name: Run API tests. working-directory: plugins/woocommerce env: - BASE_URL: ${{ secrets.SMOKE_TEST_URL }} USER_KEY: ${{ secrets.SMOKE_TEST_ADMIN_USER }} USER_SECRET: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }} - DEFAULT_TIMEOUT_OVERRIDE: 120000 run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js - name: Generate API Test report. @@ -60,12 +72,12 @@ jobs: ${{ env.ALLURE_REPORT_DIR }} if-no-files-found: ignore retention-days: 5 - + e2e-tests: name: E2E tests on nightly build runs-on: ubuntu-20.04 permissions: - contents: read + contents: read needs: [api-tests] env: ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }} @@ -80,8 +92,6 @@ jobs: steps: - uses: actions/checkout@v3 - with: - ref: ${{ env.BRANCH_NAME }} - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo @@ -93,13 +103,7 @@ jobs: working-directory: plugins/woocommerce run: pnpm exec playwright install chromium - - name: Run 'Update WooCommerce' test. - working-directory: plugins/woocommerce - env: - UPDATE_WC: true - run: pnpm exec playwright test --config=tests/e2e-pw/daily.playwright.config.js update-woocommerce.spec.js - - - name: Run the rest of E2E tests. + - name: Run E2E tests. timeout-minutes: 60 working-directory: plugins/woocommerce env: @@ -127,13 +131,11 @@ jobs: name: k6 tests on nightly build runs-on: ubuntu-20.04 permissions: - contents: read + contents: read needs: [api-tests] if: success() || failure() steps: - uses: actions/checkout@v3 - with: - ref: ${{ env.BRANCH_NAME }} - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo @@ -179,7 +181,7 @@ jobs: name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed runs-on: ubuntu-20.04 permissions: - contents: read + contents: read needs: [api-tests] env: USE_WP_ENV: 1 @@ -204,8 +206,6 @@ jobs: repo: 'takayukister/contact-form-7' steps: - uses: actions/checkout@v3 - with: - ref: ${{ env.BRANCH_NAME }} - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo @@ -255,7 +255,7 @@ jobs: ! github.event.pull_request.head.repo.fork runs-on: ubuntu-20.04 permissions: - contents: read + contents: read needs: [e2e-tests, test-plugins, k6-tests] steps: - name: Create dirs @@ -269,7 +269,6 @@ jobs: uses: actions/checkout@v3 with: path: repo - ref: ${{ env.BRANCH_NAME }} - name: Download API test report artifact uses: actions/download-artifact@v3 diff --git a/.github/workflows/smoke-test-release.yml b/.github/workflows/smoke-test-release.yml index 411538d05a4..9e1850e284b 100644 --- a/.github/workflows/smoke-test-release.yml +++ b/.github/workflows/smoke-test-release.yml @@ -1,185 +1,691 @@ name: Smoke test release on: + release: + types: [published] workflow_dispatch: inputs: - release_id: - description: 'WooCommerce Release Id' + tag: + description: 'WooCommerce Release Tag' required: true - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: {} +env: + E2E_WP_LATEST_ARTIFACT: e2e-wp-latest--run-${{ github.run_number }} + E2E_UPDATE_WC_ARTIFACT: e2e-update-wc--run-${{ github.run_number }} + FORCE_COLOR: 1 jobs: - login-run: - name: Daily smoke test on release. - runs-on: ubuntu-20.04 + get-tag: + name: Get WooCommerce release tag permissions: - contents: read + contents: read + runs-on: ubuntu-20.04 + outputs: + tag: ${{ steps.tag.outputs.result }} + created: ${{ steps.created-at.outputs.created }} + steps: + - name: Validate tag + if: ${{ github.event_name == 'workflow_dispatch' }} + env: + GH_TOKEN: ${{ github.token }} + run: gh release view "${{ github.event.inputs.tag }}" --repo=woocommerce/woocommerce + + - name: Get tag + uses: actions/github-script@v6 + id: tag + with: + result-encoding: string + script: | + console.log( "${{ github.event_name }}" ); + return "${{ github.event.release.tag_name }}" || "${{ github.event.inputs.tag }}" + + - name: Verify woocommerce.zip asset + env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ steps.tag.outputs.result }} + run: | + gh release download $RELEASE_TAG --repo woocommerce/woocommerce + if [[ -f "woocommerce.zip" ]] + then + echo "$RELEASE_TAG has a valid woocommerce.zip asset." + exit 0 + fi + + echo "$RELEASE_TAG does not have a valid woocommerce.zip asset." + exit 1 + + - name: Get 'created-at' of WooCommerce zip + id: created-at + env: + GH_TOKEN: ${{ github.token }} + run: echo "created=$(gh release view ${{ steps.tag.outputs.result }} --json assets --jq .assets[0].createdAt --repo woocommerce/woocommerce)" >> $GITHUB_OUTPUT + + e2e-update-wc: + name: Test WooCommerce update + runs-on: ubuntu-20.04 + needs: [get-tag] + permissions: + contents: read + env: + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results steps: - uses: actions/checkout@v3 - with: - ref: trunk - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo with: - build-filters: woocommerce + install-filters: woocommerce + build: false - - name: Install Jest - run: npm install -g jest + - name: Download and install Chromium browser. + working-directory: plugins/woocommerce + run: pnpm exec playwright install chromium - - name: Run smoke test. + - name: Run 'Update WooCommerce' test. working-directory: plugins/woocommerce env: - SMOKE_TEST_URL: ${{ secrets.RELEASE_TEST_URL }} - SMOKE_TEST_ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }} - SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} - SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }} - SMOKE_TEST_CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} - SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }} - WC_E2E_SCREENSHOTS: 1 - E2E_RETEST: 1 - E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} - E2E_SLACK_CHANNEL: 'C02DS4NE72S' - TEST_RELEASE: 1 - UPDATE_WC: 1 + ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} + ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }} + BASE_URL: ${{ secrets.RELEASE_TEST_URL }} + CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }} + CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} DEFAULT_TIMEOUT_OVERRIDE: 120000 + UPDATE_WC: ${{ needs.get-tag.outputs.tag }} + run: | + pnpm exec playwright test \ + --config=tests/e2e-pw/playwright.config.js \ + update-woocommerce.spec.js + + - name: Generate 'Update WooCommerce' test report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }} + + - name: Configure AWS credentials + if: success() || failure() + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ secrets.REPORTS_AWS_REGION }} + aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }} + + - name: Upload Allure files to bucket + if: success() || failure() + run: | + aws s3 sync ${{ env.ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-report \ + --quiet + + - name: Publish E2E Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: wp-latest + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-tag.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-tag.outputs.tag }} \ + -f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="e2e" \ + --repo woocommerce/woocommerce-test-reports + + - name: Archive 'Update WooCommerce' test report + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.E2E_UPDATE_WC_ARTIFACT }} + path: | + ${{ env.ALLURE_RESULTS_DIR }} + ${{ env.ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + api-wp-latest: + name: API on WP Latest + runs-on: ubuntu-20.04 + needs: [get-tag, e2e-update-wc] + permissions: + contents: read + env: + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results + API_WP_LATEST_ARTIFACT: api-wp-latest--run-${{ github.run_number }} + steps: + - uses: actions/checkout@v3 + + - name: Setup WooCommerce Monorepo + uses: ./.github/actions/setup-woocommerce-monorepo + with: + install-filters: woocommerce + build: false + + - name: Run API tests. + working-directory: plugins/woocommerce + env: BASE_URL: ${{ secrets.RELEASE_TEST_URL }} USER_KEY: ${{ secrets.RELEASE_TEST_ADMIN_USER }} USER_SECRET: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} - run: | - pnpm exec wc-e2e docker:up - pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/update-woocommerce.js - pnpm exec wc-e2e test:e2e - pnpm exec wc-api-tests test api - test-wp-version: - name: Smoke test on L-${{ matrix.wp }} WordPress version - runs-on: ubuntu-20.04 - permissions: - contents: read - strategy: - matrix: - wp: ['1', '2'] - steps: - - name: Create dirs. - run: | - mkdir -p package/woocommerce - mkdir -p tmp/woocommerce + run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello - - uses: actions/checkout@v3 + - name: Generate API Test report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }} + + - name: Configure AWS credentials + if: success() || failure() + uses: aws-actions/configure-aws-credentials@v1 with: - path: package/woocommerce + aws-region: ${{ secrets.REPORTS_AWS_REGION }} + aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }} + + - name: Upload Allure files to bucket + if: success() || failure() + run: | + aws s3 cp ${{ env.ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_ARTIFACT }}/allure-results \ + --recursive \ + --quiet + aws s3 cp ${{ env.ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_ARTIFACT }}/allure-report \ + --recursive \ + --quiet + + - name: Publish API Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: wp-latest + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-tag.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-tag.outputs.tag }} \ + -f artifact="${{ env.API_WP_LATEST_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="api" \ + --repo woocommerce/woocommerce-test-reports + + - name: Archive API test report + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.API_WP_LATEST_ARTIFACT }} + path: | + ${{ env.ALLURE_RESULTS_DIR }} + ${{ env.ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + e2e-wp-latest: + name: E2E on WP Latest + runs-on: ubuntu-20.04 + needs: [get-tag, api-wp-latest] + permissions: + contents: read + env: + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results + steps: + - uses: actions/checkout@v3 - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo with: - build-filters: woocommerce + install-filters: woocommerce + build: false - - name: Fetch Asset ID - id: fetch_asset_id - uses: actions/github-script@v5 + - name: Download and install Chromium browser. + working-directory: plugins/woocommerce + run: pnpm exec playwright install chromium + + - name: Run E2E tests env: - RELEASE_ID: ${{ github.event.inputs.release_id }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} + ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} + ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }} + ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }} + BASE_URL: ${{ secrets.RELEASE_TEST_URL }} + CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }} + CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} + DEFAULT_TIMEOUT_OVERRIDE: 120000 + E2E_MAX_FAILURES: 25 + RESET_SITE: true + timeout-minutes: 60 + working-directory: plugins/woocommerce + run: pnpm exec playwright test --config=tests/e2e-pw/ignore-plugin-tests.playwright.config.js + + - name: Download 'e2e-update-wc' artifact + if: success() || failure() + uses: actions/download-artifact@v3 with: - script: | - const script = require( './package/woocommerce/.github/workflows/scripts/fetch-asset-id.js' ) - await script({github, context, core}) + name: ${{ env.E2E_UPDATE_WC_ARTIFACT }} + path: plugins/woocommerce/tmp - - name: Download WooCommerce release zip - working-directory: tmp + - name: Add allure-results from 'e2e-update-wc' + if: success() || failure() + working-directory: plugins/woocommerce + run: cp -r tmp/allure-results tests/e2e-pw/test-results + + - name: Generate E2E Test report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }} + + - name: Configure AWS credentials + if: success() || failure() + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ secrets.REPORTS_AWS_REGION }} + aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }} + + - name: Upload report to bucket + if: success() || failure() run: | - curl https://api.github.com/repos/${{ github.repository }}/releases/assets/${{ steps.fetch_asset_id.outputs.asset_id }} -LJOH 'Accept: application/octet-stream' + aws s3 sync ${{ env.ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-report \ + --quiet - unzip woocommerce.zip -d woocommerce - rsync -a woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/ - - - name: Load docker images and start containers. - working-directory: package/woocommerce + - name: Publish E2E Allure report + if: success() || failure() env: - LATEST_WP_VERSION_MINUS: ${{ matrix.wp }} - run: pnpm docker:up --filter=woocommerce + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: wp-latest + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-tag.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-tag.outputs.tag }} \ + -f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="e2e" \ + --repo woocommerce/woocommerce-test-reports - - name: Run tests command. - working-directory: package/woocommerce/plugins/woocommerce - env: - WC_E2E_SCREENSHOTS: 1 - E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} - E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }} - run: pnpm exec wc-e2e test:e2e + - name: Archive E2E test report + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.E2E_WP_LATEST_ARTIFACT }} + path: | + ${{ env.ALLURE_RESULTS_DIR }} + ${{ env.ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 - test-plugins: - name: Smoke tests with ${{ matrix.plugin }} plugin installed + get-wp-versions: + name: Get WP L-1 & L-2 version numbers + needs: [get-tag] runs-on: ubuntu-20.04 permissions: - contents: read - strategy: - fail-fast: false - matrix: - include: - - plugin: 'WooCommerce Payments' - repo: 'automattic/woocommerce-payments' - - plugin: 'WooCommerce PayPal Payments' - repo: 'woocommerce/woocommerce-paypal-payments' - - plugin: 'WooCommerce Shipping & Tax' - repo: 'automattic/woocommerce-services' - - plugin: 'WooCommerce Subscriptions' - repo: WC_SUBSCRIPTIONS_REPO - private: true - - plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo - repo: 'Yoast/wordpress-seo' - - plugin: 'Contact Form 7' - repo: 'takayukister/contact-form-7' + contents: read + outputs: + matrix: ${{ steps.get-versions.outputs.versions }} + tag: ${{ needs.get-tag.outputs.tag }} + created: ${{ needs.get-tag.outputs.created }} steps: - - name: Create dirs. + - name: Create dirs run: | - mkdir -p package/woocommerce - mkdir -p tmp/woocommerce + mkdir script + mkdir repo - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: - path: package/woocommerce + path: repo + + - name: Copy script to get previous WP versions + run: cp repo/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js script + + - name: Install axios + working-directory: script + run: npm install axios + + - name: Get version numbers + id: get-versions + uses: actions/github-script@v6 + with: + script: | + const { getPreviousTwoVersions } = require('./script/wordpress'); + const versions = await getPreviousTwoVersions(); + console.log(versions); + core.setOutput('versions', versions); + + test-wp-versions: + name: Test against ${{ matrix.version.description }} (${{ matrix.version.number }}) + runs-on: ubuntu-20.04 + needs: [get-wp-versions] + strategy: + matrix: ${{ fromJSON(needs.get-wp-versions.outputs.matrix) }} + env: + API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-report + API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-results + API_WP_LATEST_X_ARTIFACT: api-${{ matrix.version.env_description }}--run-${{ github.run_number }} + E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-report + E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-results + E2E_WP_LATEST_X_ARTIFACT: e2e-${{ matrix.version.env_description }}--run-${{ github.run_number }} + permissions: + contents: read + steps: + - name: Checkout WooCommerce repo + uses: actions/checkout@v3 - name: Setup WooCommerce Monorepo uses: ./.github/actions/setup-woocommerce-monorepo - with: - build-filters: woocommerce - - name: Fetch Asset ID - id: fetch_asset_id - uses: actions/github-script@v5 + - name: Launch WP Env + working-directory: plugins/woocommerce + run: pnpm run env:test + + - name: Download release zip env: - RELEASE_ID: ${{ github.event.inputs.release_id }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - with: - script: | - const script = require( './package/woocommerce/.github/workflows/scripts/fetch-asset-id.js' ) - await script({github, context, core}) + GITHUB_TOKEN: ${{ github.token }} + run: gh release download ${{ needs.get-wp-versions.outputs.tag }} --dir tmp - - name: Download WooCommerce release zip - working-directory: tmp + - name: Replace `plugins/woocommerce` with unzipped woocommerce release build + run: unzip -d plugins -o tmp/woocommerce.zip + + - name: Downgrade WordPress version to ${{ matrix.version.number }} + working-directory: plugins/woocommerce run: | - curl https://api.github.com/repos/${{ github.repository }}/releases/assets/${{ steps.fetch_asset_id.outputs.asset_id }} -LJOH 'Accept: application/octet-stream' + pnpm exec wp-env run tests-cli "wp core update --version=${{ matrix.version.number }} --force" + pnpm exec wp-env run tests-cli "wp core update-db" - unzip woocommerce.zip -d woocommerce - rsync -a woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/ - - - name: Load docker images and start containers. - working-directory: package/woocommerce - env: - LATEST_WP_VERSION_MINUS: ${{ matrix.wp }} - run: pnpm docker:up --filter=woocommerce - - - name: Run tests command. - working-directory: package/woocommerce/plugins/woocommerce - env: - WC_E2E_SCREENSHOTS: 1 - E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} - E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }} - PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }} - PLUGIN_NAME: ${{ matrix.plugin }} - GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }} + - name: Verify environment details + working-directory: plugins/woocommerce run: | - pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/upload-plugin.js - pnpm exec wc-e2e test:e2e + pnpm exec wp-env run tests-cli "wp core version" + pnpm exec wp-env run tests-cli "wp plugin list" + pnpm exec wp-env run tests-cli "wp theme list" + pnpm exec wp-env run tests-cli "wp user list" + + - name: Run API tests. + id: api + working-directory: plugins/woocommerce + env: + ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }} + run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello + + - name: Generate API Allure report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.API_ALLURE_RESULTS_DIR }} --output ${{ env.API_ALLURE_REPORT_DIR }} + + - name: Configure AWS credentials + if: success() || failure() + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ secrets.REPORTS_AWS_REGION }} + aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }} + + - name: Upload API Allure artifacts to bucket + if: success() || failure() + run: | + aws s3 sync ${{ env.API_ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_X_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.API_ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_X_ARTIFACT }}/allure-report \ + --quiet + + - name: Publish API Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: ${{ matrix.version.env_description }} + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-wp-versions.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-wp-versions.outputs.tag }} \ + -f artifact="${{ env.API_WP_LATEST_X_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="api" \ + --repo woocommerce/woocommerce-test-reports + + - name: Archive API Allure reports + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.API_WP_LATEST_X_ARTIFACT }} + path: | + ${{ env.API_ALLURE_RESULTS_DIR }} + ${{ env.API_ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + - name: Download and install Chromium browser. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec playwright install chromium + + - name: Run E2E tests. + if: | + success() || + ( failure() && steps.api.conclusion == 'success' ) + timeout-minutes: 60 + id: e2e + env: + USE_WP_ENV: 1 + E2E_MAX_FAILURES: 15 + FORCE_COLOR: 1 + ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }} + DEFAULT_TIMEOUT_OVERRIDE: 120000 + working-directory: plugins/woocommerce + run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js + + - name: Generate E2E Allure report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.E2E_ALLURE_RESULTS_DIR }} --output ${{ env.E2E_ALLURE_REPORT_DIR }} + + - name: Upload E2E Allure artifacts to bucket + if: success() || failure() + run: | + aws s3 sync ${{ env.E2E_ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_X_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.E2E_ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_X_ARTIFACT }}/allure-report \ + --quiet + + - name: Archive E2E Allure reports + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.E2E_WP_LATEST_X_ARTIFACT }} + path: | + ${{ env.E2E_ALLURE_RESULTS_DIR }} + ${{ env.E2E_ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + - name: Publish E2E Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: ${{ matrix.version.env_description }} + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-wp-versions.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-wp-versions.outputs.tag }} \ + -f artifact="${{ env.E2E_WP_LATEST_X_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="e2e" \ + --repo woocommerce/woocommerce-test-reports + + test-php-versions: + name: Test against PHP ${{ matrix.php_version }} + runs-on: ubuntu-20.04 + needs: [get-tag] + strategy: + matrix: + php_version: ['7.4', '8.1'] + env: + API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report + API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results + API_ARTIFACT: api-php-${{ matrix.php_version }}--run-${{ github.run_number }} + E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report + E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results + E2E_ARTIFACT: e2e-php-${{ matrix.php_version }}--run-${{ github.run_number }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup WooCommerce Monorepo + uses: ./.github/actions/setup-woocommerce-monorepo + + - name: Launch WP Env + working-directory: plugins/woocommerce + env: + WP_ENV_PHP_VERSION: ${{ matrix.php_version }} + run: pnpm run env:test + + - name: Verify PHP version + working-directory: .github/workflows/scripts + env: + EXPECTED_PHP_VERSION: ${{ matrix.php_version }} + run: bash verify-php-version.sh + + - name: Download release zip + env: + GITHUB_TOKEN: ${{ github.token }} + run: gh release download ${{ needs.get-tag.outputs.tag }} --dir tmp + + - name: Replace `plugins/woocommerce` with unzipped woocommerce release build + run: unzip -d plugins -o tmp/woocommerce.zip + + - name: Run API tests. + id: api + working-directory: plugins/woocommerce + env: + ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }} + run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello + + - name: Generate API Allure report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.API_ALLURE_RESULTS_DIR }} --output ${{ env.API_ALLURE_REPORT_DIR }} + + - name: Configure AWS credentials + if: success() || failure() + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ secrets.REPORTS_AWS_REGION }} + aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }} + + - name: Upload API Allure artifacts to bucket + if: success() || failure() + run: | + aws s3 sync ${{ env.API_ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.API_ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_ARTIFACT }}/allure-report \ + --quiet + + - name: Publish API Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: php-${{ matrix.php_version }} + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-tag.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-tag.outputs.tag }} \ + -f artifact="${{ env.API_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="api" \ + --repo woocommerce/woocommerce-test-reports + + - name: Archive API Allure reports + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.API_ARTIFACT }} + path: | + ${{ env.API_ALLURE_RESULTS_DIR }} + ${{ env.API_ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + - name: Download and install Chromium browser. + working-directory: plugins/woocommerce + run: pnpm exec playwright install chromium + + - name: Run E2E tests. + if: | + success() || + ( failure() && steps.api.conclusion == 'success' ) + timeout-minutes: 60 + env: + USE_WP_ENV: 1 + E2E_MAX_FAILURES: 15 + FORCE_COLOR: 1 + ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }} + DEFAULT_TIMEOUT_OVERRIDE: 120000 + working-directory: plugins/woocommerce + run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js + + - name: Generate E2E Allure report. + if: success() || failure() + working-directory: plugins/woocommerce + run: pnpm exec allure generate --clean ${{ env.E2E_ALLURE_RESULTS_DIR }} --output ${{ env.E2E_ALLURE_REPORT_DIR }} + + - name: Upload E2E Allure artifacts to bucket + if: success() || failure() + run: | + aws s3 sync ${{ env.E2E_ALLURE_RESULTS_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_ARTIFACT }}/allure-results \ + --quiet + aws s3 sync ${{ env.E2E_ALLURE_REPORT_DIR }} \ + ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_ARTIFACT }}/allure-report \ + --quiet + + - name: Archive E2E Allure reports + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ env.E2E_ARTIFACT }} + path: | + ${{ env.E2E_ALLURE_RESULTS_DIR }} + ${{ env.E2E_ALLURE_REPORT_DIR }} + if-no-files-found: ignore + retention-days: 5 + + - name: Publish E2E Allure report + if: success() || failure() + env: + GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} + ENV_DESCRIPTION: php-${{ matrix.php_version }} + run: | + gh workflow run publish-test-reports-release.yml \ + -f created_at="${{ needs.get-tag.outputs.created }}" \ + -f run_id=${{ github.run_id }} \ + -f run_number=${{ github.run_number }} \ + -f release_tag=${{ needs.get-tag.outputs.tag }} \ + -f artifact="${{ env.E2E_ARTIFACT }}" \ + -f env_description="${{ env.ENV_DESCRIPTION }}" \ + -f test_type="e2e" \ + --repo woocommerce/woocommerce-test-reports diff --git a/plugins/woocommerce/changelog/e2e-update-release-e2e-testing b/plugins/woocommerce/changelog/e2e-update-release-e2e-testing new file mode 100644 index 00000000000..6f907635c94 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-update-release-e2e-testing @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Updates automated release testing workflow to use Playwright diff --git a/plugins/woocommerce/tests/e2e-pw/ignore-plugin-tests.playwright.config.js b/plugins/woocommerce/tests/e2e-pw/ignore-plugin-tests.playwright.config.js new file mode 100644 index 00000000000..61e1a1f256b --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/ignore-plugin-tests.playwright.config.js @@ -0,0 +1,13 @@ +const defaultConfig = require( './playwright.config' ); +const defaultUse = defaultConfig.use; + +const config = { + ...defaultConfig, + testIgnore: '**/smoke-tests/**', + use: { + ...defaultUse, + video: 'retain-on-failure', + }, +}; + +module.exports = config; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-overview.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-overview.spec.js index 4a697f42cd9..9c1e273bcb3 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-overview.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-overview.spec.js @@ -49,18 +49,19 @@ test.describe( 'Analytics pages', () => { 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview', { waitForLoadState: 'networkidle' } ); - // Grab all of the section headings - const sections = await page.$$( - 'h2.woocommerce-section-header__title' - ); - // Create an array with the section headings - const arrFoundSections = new Array(); - for await ( const section of sections ) { - arrFoundSections.push( await section.innerText() ); + + for ( const expectedSection of arrExpectedSections ) { + await test.step( + `Assert that the "${ expectedSection }" section is visible`, + async () => { + await expect( + page.locator( 'h2.woocommerce-section-header__title', { + hasText: expectedSection, + } ) + ).toBeVisible(); + } + ); } - await expect( arrFoundSections.sort() ).toEqual( - arrExpectedSections.sort() - ); } ); test.describe( 'moving sections', () => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js index 79168eca6c7..78435a0548b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js @@ -9,7 +9,7 @@ test.describe( 'Payment setup task', () => { 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard' ); await page.click( 'text=Skip setup store details' ); - await page.click( 'text=No thanks' ); + await page.click( 'button >> text=No thanks' ); await page.waitForLoadState( 'networkidle' ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-coupon.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-coupon.spec.js index 8db035ece97..eb98753bdac 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-coupon.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-coupon.spec.js @@ -38,8 +38,10 @@ test.describe( 'Add New Coupon Page', () => { await expect( page.locator( '#publish:not(.disabled)' ) ).toBeVisible(); await page.click( '#publish' ); - await expect( page.locator( 'div.notice.notice-success' ) ).toHaveText( - 'Coupon updated.Dismiss this notice.' - ); + await expect( + page + .locator( 'div.notice-success > p' ) + .filter( { hasText: 'Coupon updated.' } ) + ).toBeVisible(); } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-simple-product.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-simple-product.spec.js index d3485abe405..5831a35e70b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-simple-product.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-simple-product.spec.js @@ -52,7 +52,9 @@ test.describe.serial( 'Add New Simple Product Page', () => { } ); test( 'can create simple virtual product', async ( { page } ) => { - await page.goto( 'wp-admin/post-new.php?post_type=product' ); + await page.goto( 'wp-admin/post-new.php?post_type=product', { + waitUntil: 'networkidle', + } ); await page.fill( '#title', virtualProductName ); await page.fill( '#_regular_price', productPrice ); await page.click( '#_virtual' ); @@ -69,9 +71,11 @@ test.describe.serial( 'Add New Simple Product Page', () => { await page.waitForLoadState( 'networkidle' ); } - await expect( page.locator( 'div.notice-success > p' ) ).toContainText( - 'Product published.' - ); + await expect( + page + .locator( 'div.notice-success > p' ) + .filter( { hasText: 'Product published.' } ) + ).toBeVisible(); // Save product ID virtualProductId = page.url().match( /(?<=post=)\d+/ ); @@ -81,7 +85,9 @@ test.describe.serial( 'Add New Simple Product Page', () => { test( 'can have a shopper add the simple virtual product to the cart', async ( { page, } ) => { - await page.goto( `/?post_type=product&p=${ virtualProductId }` ); + await page.goto( `/?post_type=product&p=${ virtualProductId }`, { + waitUntil: 'networkidle', + } ); await expect( page.locator( '.product_title' ) ).toHaveText( virtualProductName ); @@ -104,7 +110,9 @@ test.describe.serial( 'Add New Simple Product Page', () => { } ); test( 'can create simple non-virtual product', async ( { page } ) => { - await page.goto( 'wp-admin/post-new.php?post_type=product' ); + await page.goto( 'wp-admin/post-new.php?post_type=product', { + waitUntil: 'networkidle', + } ); await page.fill( '#title', nonVirtualProductName ); await page.fill( '#_regular_price', productPrice ); await expect( page.locator( '#publish:not(.disabled)' ) ).toBeVisible(); @@ -121,9 +129,11 @@ test.describe.serial( 'Add New Simple Product Page', () => { await page.waitForLoadState( 'networkidle' ); } - await expect( page.locator( 'div.notice-success > p' ) ).toContainText( - 'Product published.' - ); + await expect( + page + .locator( 'div.notice-success > p' ) + .filter( { hasText: 'Product published.' } ) + ).toBeVisible(); // Save product ID nonVirtualProductId = page.url().match( /(?<=post=)\d+/ ); @@ -133,7 +143,9 @@ test.describe.serial( 'Add New Simple Product Page', () => { test( 'can have a shopper add the simple non-virtual product to the cart', async ( { page, } ) => { - await page.goto( `/?post_type=product&p=${ nonVirtualProductId }` ); + await page.goto( `/?post_type=product&p=${ nonVirtualProductId }`, { + waitUntil: 'networkidle', + } ); await expect( page.locator( '.product_title' ) ).toHaveText( nonVirtualProductName ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js index 7cd8c268535..923928c5006 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js @@ -77,9 +77,11 @@ test.describe( 'Edit order', () => { await page.click( 'button.save_order' ); // verify changes - await expect( page.locator( 'div.notice-success' ) ).toContainText( - 'Order updated.' - ); + await expect( + page + .locator( 'div.notice-success > p' ) + .filter( { hasText: 'Order updated.' } ) + ).toBeVisible(); await expect( page.locator( 'input[name=order_date]' ) ).toHaveValue( '2018-12-14' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js index 661a18930c8..0093cf09ed8 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js @@ -62,7 +62,7 @@ for ( const currentPage of wcPages ) { 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard' ); await page.click( 'text=Skip setup store details' ); - await page.click( 'text=No thanks' ); + await page.click( 'button >> text=No thanks' ); await page.waitForLoadState( 'networkidle' ); await page.goto( 'wp-admin/admin.php?page=wc-admin' ); } diff --git a/plugins/woocommerce/tests/e2e-pw/tests/smoke-tests/update-woocommerce.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/smoke-tests/update-woocommerce.spec.js index 9276e5fd2ad..5f43b78a3d8 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/smoke-tests/update-woocommerce.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/smoke-tests/update-woocommerce.spec.js @@ -1,64 +1,84 @@ +const axios = require( 'axios' ).default; +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { ADMINSTATE, UPDATE_WC } = process.env; -const { admin } = require( '../../test-data/data' ); +const { downloadZip, deleteZip } = require( '../../utils/plugin-utils' ); const { test, expect } = require( '@playwright/test' ); -const { - deletePlugin, - downloadZip, - deleteZip, -} = require( '../../utils/plugin-utils' ); -const skipMessage = 'Skipping this test because UPDATE_WC is not "true"'; +let woocommerceZipPath; -let pluginZipPath; +const skipTestIfUndefined = () => { + const skipMessage = `Skipping this test because UPDATE_WC was undefined.\nTo run this test, set UPDATE_WC to a valid WooCommerce release tag that has a WooCommerce ZIP, like "nightly" or "7.1.0-beta.2".`; -test.skip( () => { - const shouldSkip = UPDATE_WC !== 'true'; + test.skip( () => { + const shouldSkip = UPDATE_WC === undefined; - if ( shouldSkip ) { - console.log( skipMessage ); + if ( shouldSkip ) { + console.log( skipMessage ); + } + + return shouldSkip; + }, skipMessage ); +}; + +const getWCDownloadURL = async () => { + let woocommerceZipAsset; + + const response = await axios + .get( + `https://api.github.com/repos/woocommerce/woocommerce/releases/tags/${ UPDATE_WC }` + ) + .catch( ( error ) => { + console.log( error.toJSON() ); + return error.response; + } ); + + if ( + response.status === 200 && + response.data.assets && + response.data.assets.length > 0 && + ( woocommerceZipAsset = response.data.assets.find( + ( { browser_download_url } ) => + browser_download_url.match( + /woocommerce(-trunk-nightly)?\.zip$/ + ) + ) ) + ) { + return woocommerceZipAsset.browser_download_url; + } else { + const error = `You're attempting to get the download URL of a WooCommerce release zip with tag "${ UPDATE_WC }". But "${ UPDATE_WC }" is an invalid WooCommerce release tag, or a tag without a WooCommerce release zip asset like "7.2.0-rc.2".`; + throw new Error( error ); } +}; - return shouldSkip; -}, skipMessage ); +skipTestIfUndefined(); -test.describe.serial( - 'WooCommerce plugin can be uploaded and activated', - () => { - test.use( { storageState: ADMINSTATE } ); +test.describe.serial( 'WooCommerce update', () => { + test.use( { storageState: ADMINSTATE } ); - test.beforeAll( async () => { - // Download WooCommerce ZIP - pluginZipPath = await downloadZip( { - url: - 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip', - } ); + test.beforeAll( async () => { + await test.step( 'Download WooCommerce zip from GitHub', async () => { + const url = await getWCDownloadURL(); + woocommerceZipPath = await downloadZip( { url } ); } ); + } ); - test.afterAll( async () => { - // Clean up downloaded zip - await deleteZip( pluginZipPath ); + test.afterAll( async () => { + await test.step( 'Clean up downloaded WooCommerce zip', async () => { + await deleteZip( woocommerceZipPath ); } ); + } ); - test( 'can upload and activate the WooCommerce plugin', async ( { - page, - playwright, - baseURL, - } ) => { - // Delete WooCommerce if it's installed. - await deletePlugin( { - request: playwright.request, - baseURL, - slug: 'woocommerce', - username: admin.username, - password: admin.password, - } ); - - // Open the plugin install page + test( `can update WooCommerce to "${ UPDATE_WC }"`, async ( { + page, + baseURL, + } ) => { + await test.step( 'Open the plugin install page', async () => { await page.goto( 'wp-admin/plugin-install.php', { waitUntil: 'networkidle', } ); + } ); - // Upload the plugin zip + await test.step( 'Upload the WooCommerce plugin zip', async () => { await page.click( 'a.upload-view-toggle' ); await expect( page.locator( 'p.install-help' ) ).toBeVisible(); await expect( page.locator( 'p.install-help' ) ).toContainText( @@ -68,68 +88,118 @@ test.describe.serial( page.waitForEvent( 'filechooser' ), page.click( '#pluginzip' ), ] ); - await fileChooser.setFiles( pluginZipPath ); + await fileChooser.setFiles( woocommerceZipPath ); await page.click( '#install-plugin-submit' ); await page.waitForLoadState( 'networkidle' ); - - // Activate the plugin - await page.click( '.button-primary' ); - await page.waitForLoadState( 'networkidle' ); - - // Go to 'Installed plugins' page - await page.goto( 'wp-admin/plugins.php', { - waitUntil: 'networkidle', - } ); - - // Assert that 'WooCommerce' is listed and active - await expect( - page.locator( '.plugin-title strong', { - hasText: /^WooCommerce$/, - } ) - ).toBeVisible(); - await expect( - page.locator( '#deactivate-woocommerce' ) - ).toBeVisible(); } ); - test( 'can run the database update', async ( { page } ) => { - const updateButton = page.locator( - 'text=Update WooCommerce Database' - ); - const updateCompleteMessage = page.locator( - 'text=WooCommerce database update complete.' - ); + await test.step( + 'Choose the option "Replace current with uploaded"', + async () => { + await page.click( + '.button-primary.update-from-upload-overwrite' + ); + await page.waitForLoadState( 'networkidle' ); + await expect( + page.getByText( 'Plugin updated successfully.' ) + ).toBeVisible(); + } + ); - // Navigate to 'Installed Plugins' page + await test.step( 'Go to "Installed plugins" page', async () => { await page.goto( 'wp-admin/plugins.php', { waitUntil: 'networkidle', } ); + } ); - // Skip this test if the "Update WooCommerce Database" button didn't appear. - test.skip( - ! ( await updateButton.isVisible() ), - 'The "Update WooCommerce Database" button did not appear after updating WooCommerce. Verify with the team if the WooCommerce version being tested does not really trigger a database update.' + await test.step( + 'Assert that "WooCommerce" is listed and active', + async () => { + await expect( + page.locator( '.plugin-title strong', { + hasText: /^WooCommerce$/, + } ) + ).toBeVisible(); + await expect( + page.locator( '#deactivate-woocommerce' ) + ).toBeVisible(); + } + ); + + if ( UPDATE_WC !== 'nightly' ) { + await test.step( + 'Verify that WooCommerce was updated to the expected version', + async () => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + + const response = await api + .get( 'system_status' ) + .catch( ( error ) => { + throw new Error( error.message ); + } ); + + const wcPluginData = response.data.active_plugins.find( + ( { plugin } ) => + plugin === 'woocommerce/woocommerce.php' + ); + + expect( wcPluginData.version ).toEqual( UPDATE_WC ); + } ); + } + } ); - // If the notice appears, start DB update + test( 'can run the database update', async ( { page } ) => { + const updateButton = page.locator( 'text=Update WooCommerce Database' ); + const updateCompleteMessage = page.locator( + 'text=WooCommerce database update complete.' + ); + + await test.step( "Navigate to 'Installed Plugins' page", async () => { + await page.goto( 'wp-admin/plugins.php', { + waitUntil: 'networkidle', + } ); + } ); + + await test.step( + 'Skip this test if the "Update WooCommerce Database" button did not appear', + async () => { + test.skip( + ! ( await updateButton.isVisible() ), + 'The "Update WooCommerce Database" button did not appear after updating WooCommerce. Verify with the team if the WooCommerce version being tested does not really trigger a database update.' + ); + } + ); + + await test.step( 'Notice appeared. Start DB update', async () => { await updateButton.click(); await page.waitForLoadState( 'networkidle' ); - - // Repeatedly reload the Plugins page up to 10 times until the message "WooCommerce database update complete." appears. - for ( - let reloads = 0; - reloads < 10 && ! ( await updateCompleteMessage.isVisible() ); - reloads++ - ) { - await page.goto( 'wp-admin/plugins.php', { - waitUntil: 'networkidle', - } ); - - // Wait 10s before the next reload. - await page.waitForTimeout( 10000 ); - } - - await expect( updateCompleteMessage ).toBeVisible(); } ); - } -); + + await test.step( + 'Assert that the notice "WooCommerce database update complete." appears', + async () => { + await expect + .poll( + async () => { + await page.goto( 'wp-admin/plugins.php', { + waitUntil: 'networkidle', + } ); + + return await updateCompleteMessage.isVisible(); + }, + { + intervals: [ 10_000 ], + timeout: 120_000, + } + ) + .toEqual( true ); + } + ); + } ); +} ); diff --git a/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js b/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js index f8fbb43f27c..754b4c0732b 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js @@ -145,7 +145,11 @@ export const downloadZip = async ( { Accept: 'application/octet-stream', }, }; - response = await axios( options ); + + response = await axios( options ).catch( ( error ) => { + throw new Error( error.message ); + } ); + response.data.pipe( fs.createWriteStream( zipFilePath ) ); return zipFilePath; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js b/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js new file mode 100644 index 00000000000..7904302dbed --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js @@ -0,0 +1,59 @@ +const axios = require( 'axios' ).default; + +const getPreviousTwoVersions = async () => { + const response = await axios + .get( 'http://api.wordpress.org/core/stable-check/1.0/' ) + .catch( ( error ) => { + console.log( error.toJSON() ); + throw new Error( error.message ); + } ); + + const body = response.data; + const allVersions = Object.keys( body ); + const previousStableVersions = allVersions + .filter( ( version ) => body[ version ] === 'outdated' ) + .sort() + .reverse(); + const latestMinorVersion = allVersions + .find( ( version ) => body[ version ] === 'latest' ) + .match( /^\d+.\d+/ )[ 0 ]; + + const prevTwo = []; + + for ( let thisVersion of previousStableVersions ) { + if ( thisVersion.startsWith( latestMinorVersion ) ) { + continue; + } + + const hasNoPatchNumber = thisVersion.split( '.' ).length === 2; + + if ( hasNoPatchNumber ) { + thisVersion = thisVersion.concat( '.0' ); + } + + prevTwo.push( thisVersion ); + + if ( prevTwo.length === 2 ) { + break; + } + } + + const matrix = { + version: [ + { + number: prevTwo[ 0 ], + description: 'WP Latest-1', + env_description: 'wp-latest-1', + }, + { + number: prevTwo[ 1 ], + description: 'WP Latest-2', + env_description: 'wp-latest-2', + }, + ], + }; + + return matrix; +}; + +module.exports = { getPreviousTwoVersions }; From 3f0405970d5d4a849906e8bf75b81f66201194fc Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Sun, 26 Feb 2023 23:47:31 -0800 Subject: [PATCH 18/53] Product Importer: Ensure all products in the file get processed (#36839) --- .../changelog/fix-36618-product-import | 4 + .../import/abstract-wc-product-importer.php | 2 +- .../class-wc-product-csv-importer-test.php | 27 +++ .../tests/php/includes/importer/sample2.csv | 202 ++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-36618-product-import create mode 100644 plugins/woocommerce/tests/php/includes/importer/sample2.csv diff --git a/plugins/woocommerce/changelog/fix-36618-product-import b/plugins/woocommerce/changelog/fix-36618-product-import new file mode 100644 index 00000000000..67560ceed4c --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36618-product-import @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure product importer imports all lines in a CSV file. diff --git a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php index edf74a915c8..56a31d3be00 100644 --- a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php +++ b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php @@ -160,7 +160,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { return 0; } - return absint( min( NumberUtil::round( ( $this->file_position / $size ) * 100 ), 100 ) ); + return absint( min( floor( ( $this->file_position / $size ) * 100 ), 100 ) ); } /** diff --git a/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php b/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php index 5a5c8109f34..35a234c48ca 100644 --- a/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php +++ b/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php @@ -61,4 +61,31 @@ class WC_Product_CSV_Importer_Test extends \WC_Unit_Test_Case { $this->assertEquals( 'publish', $variation['status'] ); } + /** + * @testdox Test that the importer calculates the percent complete as 99 when it's >= 99.5% through the file. + */ + public function test_import_completion_issue_36618_lines_remaining() { + $csv_file = dirname( __FILE__ ) . '/sample2.csv'; + $args = array( + 'lines' => 200, + ); + + $importer = new WC_Product_CSV_Importer( $csv_file, $args ); + + $this->assertEquals( 99, $importer->get_percent_complete() ); + } + + /** + * @testdox Test that the importer calculates the percent complete as 100 when it's at the end of the file. + */ + public function test_import_completion_issue_36618_end_of_file() { + $csv_file = dirname( __FILE__ ) . '/sample2.csv'; + $args = array( + 'lines' => 201, + ); + + $importer = new WC_Product_CSV_Importer( $csv_file, $args ); + + $this->assertEquals( 100, $importer->get_percent_complete() ); + } } diff --git a/plugins/woocommerce/tests/php/includes/importer/sample2.csv b/plugins/woocommerce/tests/php/includes/importer/sample2.csv new file mode 100644 index 00000000000..ffc6f4ea373 --- /dev/null +++ b/plugins/woocommerce/tests/php/includes/importer/sample2.csv @@ -0,0 +1,202 @@ +"SKU" +"002100" +"015626" +"015771" +"015774" +"015775" +"015776" +"015777" +"015778" +"024051" +"024052" +"025008" +"025010" +"025014" +"025015" +"025016" +"025017" +"025018" +"025021" +"025026" +"025027" +"025028" +"025029" +"025031" +"025034" +"025036" +"025037" +"025038" +"025043" +"025046" +"025049" +"025051" +"025053" +"025056" +"025057" +"025058" +"025059" +"025063" +"025073" +"025074" +"025077" +"025081" +"025083" +"025087" +"025088" +"025089" +"025091" +"025095" +"025096" +"025097" +"025100" +"025105" +"025106" +"025107" +"025108" +"025112" +"025115" +"025116" +"025121" +"025123" +"025126" +"025127" +"025128" +"025129" +"025130" +"025151" +"025154" +"025155" +"025163" +"025164" +"025165" +"025167" +"025171" +"025172" +"025173" +"025179" +"025180" +"025182" +"025184" +"025185" +"025186" +"025187" +"025190" +"025192" +"025193" +"025195" +"025198" +"025204" +"025207" +"025208" +"025221" +"025222" +"025228" +"025230" +"025231" +"025232" +"025241" +"025246" +"025248" +"025249" +"025252" +"025253" +"025261" +"025263" +"025282" +"025284" +"025290" +"025292" +"025293" +"025301" +"025309" +"025317" +"025318" +"025319" +"025320" +"025323" +"025325" +"025326" +"025332" +"025333" +"025335" +"025338" +"025339" +"025341" +"025342" +"025350" +"025356" +"025358" +"025359" +"025360" +"025361" +"025362" +"025363" +"025364" +"025365" +"025367" +"025369" +"025370" +"025371" +"025372" +"025373" +"025374" +"025375" +"025376" +"025377" +"025379" +"025380" +"025381" +"025382" +"025385" +"025386" +"025388" +"025389" +"025390" +"025391" +"025392" +"025393" +"025396" +"025397" +"025398" +"025406" +"025407" +"025408" +"025417" +"025418" +"025430" +"026188" +"026266" +"025441" +"025443" +"025587" +"025705" +"025711" +"025721" +"025722" +"025723" +"025728" +"025729" +"025740" +"025753" +"025766" +"025767" +"025772" +"025773" +"025785" +"025789" +"025873" +"025875" +"025879" +"025880" +"025882" +"025956" +"025957" +"025958" +"025960" +"025961" +"025966" +"025967" +"026071" +"026086" +"026091" +"026094" From f56010334d44648b0f6c99d1ecdd8fe53898bdbf Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 27 Feb 2023 09:28:15 +0100 Subject: [PATCH 19/53] Remove unnecessary 'use's --- .../Controllers/Version1/class-wc-rest-coupons-v1-controller.php | 1 - .../Controllers/Version2/class-wc-rest-coupons-v2-controller.php | 1 - 2 files changed, 2 deletions(-) diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php index c4baba6086a..1ae0177075a 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php @@ -10,7 +10,6 @@ * @since 3.0.0 */ -use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\StringUtil; if ( ! defined( 'ABSPATH' ) ) { diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php index 334a024d098..6f17047735c 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php @@ -8,7 +8,6 @@ * @since 2.6.0 */ -use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\StringUtil; defined( 'ABSPATH' ) || exit; From a45f9c4f5544b7a2b4d116baac6800ee0317758a Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 27 Feb 2023 04:42:10 -0400 Subject: [PATCH 20/53] Fix k6 baseline load imports (#36940) * add wpLogin import to wc-baseline-load.js * add changelog --------- Co-authored-by: Ron Rennick --- plugins/woocommerce/changelog/fix-k6-baseline-imports | 4 ++++ .../woocommerce/tests/performance/tests/wc-baseline-load.js | 1 + 2 files changed, 5 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-k6-baseline-imports diff --git a/plugins/woocommerce/changelog/fix-k6-baseline-imports b/plugins/woocommerce/changelog/fix-k6-baseline-imports new file mode 100644 index 00000000000..c6a3c6e2630 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-k6-baseline-imports @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +add wpLogin import to wc-baseline-load.js diff --git a/plugins/woocommerce/tests/performance/tests/wc-baseline-load.js b/plugins/woocommerce/tests/performance/tests/wc-baseline-load.js index 936ecacfb24..cd39d327319 100644 --- a/plugins/woocommerce/tests/performance/tests/wc-baseline-load.js +++ b/plugins/woocommerce/tests/performance/tests/wc-baseline-load.js @@ -23,6 +23,7 @@ import { ordersFilter } from '../requests/merchant/orders-filter.js'; import { addOrder } from '../requests/merchant/add-order.js'; import { homeWCAdmin } from '../requests/merchant/home-wc-admin.js'; import { myAccountMerchantLogin } from '../requests/merchant/my-account-merchant.js'; +import { wpLogin } from '../requests/merchant/wp-login.js'; import { ordersAPI } from '../requests/api/orders.js'; import { admin_acc_login } from '../config.js'; From be43265761408440e86b6cd39cc7a131f6b5aced Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 27 Feb 2023 09:29:07 +0100 Subject: [PATCH 21/53] Fix GET coupons request and apply_coupon --- plugins/woocommerce/includes/class-wc-discounts.php | 2 +- .../Version1/class-wc-rest-coupons-v1-controller.php | 5 +++-- .../Version2/class-wc-rest-coupons-v2-controller.php | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/includes/class-wc-discounts.php b/plugins/woocommerce/includes/class-wc-discounts.php index de70dff90db..a202b0cc755 100644 --- a/plugins/woocommerce/includes/class-wc-discounts.php +++ b/plugins/woocommerce/includes/class-wc-discounts.php @@ -257,7 +257,7 @@ class WC_Discounts { } $coupon_code = $coupon->get_code(); - if ( StringUtil::is_null_or_whitespace( $this->discounts[ $coupon_code ] ) ) { + if ( StringUtil::is_null_or_whitespace( $this->discounts[ $coupon_code ] ?? null ) ) { $this->discounts[ $coupon_code ] = array_fill_keys( array_keys( $this->items ), 0 ); } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php index 1ae0177075a..7e4337e8233 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-coupons-v1-controller.php @@ -133,8 +133,9 @@ class WC_REST_Coupons_V1_Controller extends WC_REST_Posts_Controller { * @return array */ public function query_args( $args, $request ) { - if ( ! empty( $request['code'] ) ) { - $id = wc_get_coupon_id_by_code( $request['code'] ); + $coupon_code = $request['code'] ?? null; + if ( ! StringUtil::is_null_or_whitespace( $coupon_code ) ) { + $id = wc_get_coupon_id_by_code( $coupon_code ); $args['post__in'] = array( $id ); } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php index 6f17047735c..6c0c6a2fb1f 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-coupons-v2-controller.php @@ -235,8 +235,9 @@ class WC_REST_Coupons_V2_Controller extends WC_REST_CRUD_Controller { protected function prepare_objects_query( $request ) { $args = parent::prepare_objects_query( $request ); - if ( ! empty( $request['code'] ) ) { - $id = wc_get_coupon_id_by_code( $request['code'] ); + $coupon_code = $request['code'] ?? null; + if ( ! StringUtil::is_null_or_whitespace( $coupon_code ) ) { + $id = wc_get_coupon_id_by_code( $coupon_code ); $args['post__in'] = array( $id ); } From da1dce8f191c1f157a258e6c218d56e97f318f1a Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Tue, 28 Feb 2023 09:36:38 +1300 Subject: [PATCH 22/53] Package Release: handle initial release case (#36922) --- .github/workflows/prepare-package-release.yml | 2 +- packages/js/README.md | 10 ++- packages/js/product-editor/changelog.md | 3 + .../changelog/fix-initial-package-release | 5 ++ pnpm-lock.yaml | 87 ++++++++----------- tools/package-release/README.md | 2 + tools/package-release/src/changelogger.ts | 21 +++-- .../src/commands/prepare/index.ts | 39 +++++++-- 8 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 packages/js/product-editor/changelog.md create mode 100644 packages/js/product-editor/changelog/fix-initial-package-release diff --git a/.github/workflows/prepare-package-release.yml b/.github/workflows/prepare-package-release.yml index 3eb728e2130..e230b271ef7 100644 --- a/.github/workflows/prepare-package-release.yml +++ b/.github/workflows/prepare-package-release.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: inputs: packages: - description: 'Enter a specific package to release, or packages separated by commas, ie @woocommerce/components,@woocommerce/number. Leaving this input to the default "-a" will prepare to release all eligible packages.' + description: 'Enter a specific package to release, or packages separated by commas, ie @woocommerce/components,@woocommerce/number. Leaving this input to the default "-a" will prepare to release all eligible packages. When releasing a package for the first time, pass the "--initialRelease" flag.' required: false default: '-a' diff --git a/packages/js/README.md b/packages/js/README.md index e3ac5425675..5c7dfe15136 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -62,4 +62,12 @@ To create a new package, add a new folder to `/packages`, containing… - Usage example 4. A `src` directory for the source of your module, which will be built by default using the `pnpm run turbo:build` command. Note that you'll want an `index.js` file that exports the package contents, see other packages for examples. -5. Add the new package name to `packages/js/dependency-extraction-webpack-plugin/assets/packages.js` so that users of that plugin will also be able to use the new package without enqueuing it. +5. A blank Changelog file, `changelog.md`. + +``` +# Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +``` + +6. Add the new package name to `packages/js/dependency-extraction-webpack-plugin/assets/packages.js` so that users of that plugin will also be able to use the new package without enqueuing it. diff --git a/packages/js/product-editor/changelog.md b/packages/js/product-editor/changelog.md new file mode 100644 index 00000000000..3783eb0da3d --- /dev/null +++ b/packages/js/product-editor/changelog.md @@ -0,0 +1,3 @@ +# Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/packages/js/product-editor/changelog/fix-initial-package-release b/packages/js/product-editor/changelog/fix-initial-package-release new file mode 100644 index 00000000000..1ead14034ee --- /dev/null +++ b/packages/js/product-editor/changelog/fix-initial-package-release @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Added a blank changelog file + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f8dbbb89d1..74cb3bb3805 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2705,32 +2705,6 @@ packages: semver: 6.3.0 dev: true - /@babel/helper-compilation-targets/7.17.7_@babel+core@7.12.9: - resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.3 - '@babel/core': 7.12.9 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.19.3 - semver: 6.3.0 - dev: true - - /@babel/helper-compilation-targets/7.17.7_@babel+core@7.16.12: - resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.3 - '@babel/core': 7.16.12 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.19.3 - semver: 6.3.0 - dev: false - /@babel/helper-compilation-targets/7.17.7_@babel+core@7.17.8: resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} engines: {node: '>=6.9.0'} @@ -6393,9 +6367,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.16.12 - '@babel/helper-module-imports': 7.16.0 - '@babel/helper-plugin-utils': 7.14.5 - babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.16.12 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.16.12 babel-plugin-polyfill-corejs3: 0.4.0_@babel+core@7.16.12 babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.16.12 semver: 6.3.0 @@ -6410,9 +6384,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-module-imports': 7.16.0 - '@babel/helper-plugin-utils': 7.14.5 - babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.17.8 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.17.8 babel-plugin-polyfill-corejs3: 0.4.0_@babel+core@7.17.8 babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.17.8 semver: 6.3.0 @@ -6921,7 +6895,7 @@ packages: dependencies: '@babel/compat-data': 7.17.7 '@babel/core': 7.12.9 - '@babel/helper-compilation-targets': 7.17.7_@babel+core@7.12.9 + '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.12.9 '@babel/helper-plugin-utils': 7.18.9 '@babel/helper-validator-option': 7.16.7 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.7_@babel+core@7.12.9 @@ -6988,7 +6962,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.16.7_@babel+core@7.12.9 '@babel/plugin-transform-unicode-regex': 7.16.7_@babel+core@7.12.9 '@babel/preset-modules': 0.1.5_@babel+core@7.12.9 - '@babel/types': 7.17.0 + '@babel/types': 7.19.3 babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.12.9 babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.12.9 babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.12.9 @@ -7006,7 +6980,7 @@ packages: dependencies: '@babel/compat-data': 7.17.7 '@babel/core': 7.16.12 - '@babel/helper-compilation-targets': 7.17.7_@babel+core@7.16.12 + '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.16.12 '@babel/helper-plugin-utils': 7.18.9 '@babel/helper-validator-option': 7.16.7 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.7_@babel+core@7.16.12 @@ -7073,7 +7047,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.16.7_@babel+core@7.16.12 '@babel/plugin-transform-unicode-regex': 7.16.7_@babel+core@7.16.12 '@babel/preset-modules': 0.1.5_@babel+core@7.16.12 - '@babel/types': 7.17.0 + '@babel/types': 7.19.3 babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.16.12 babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.16.12 babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.16.12 @@ -14511,7 +14485,7 @@ packages: dependencies: '@babel/runtime': 7.19.0 '@wordpress/dom-ready': 3.10.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 dev: false /@wordpress/a11y/3.5.0: @@ -14546,7 +14520,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.19.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 '@wordpress/url': 3.20.0 dev: true @@ -14796,7 +14770,7 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.19.0 '@wordpress/i18n': 4.26.0 '@wordpress/icons': 9.10.0 @@ -14995,9 +14969,9 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.19.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 '@wordpress/is-shallow-equal': 4.19.0 '@wordpress/shortcode': 3.19.0 colord: 2.9.2 @@ -15326,7 +15300,7 @@ packages: '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/i18n': 4.26.0 '@wordpress/icons': 9.10.0 '@wordpress/is-shallow-equal': 4.19.0 @@ -15380,7 +15354,7 @@ packages: '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/i18n': 4.26.0 '@wordpress/icons': 9.10.0 '@wordpress/is-shallow-equal': 4.19.0 @@ -15762,14 +15736,14 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.19.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 /@wordpress/deprecated/3.19.0: resolution: {integrity: sha512-0YdT5emjDd7RPwQ0SMlUqqwxlxRDvF3hDsFJkUmMU5CtMVbWh+vnaGSmFEYJB7LyGc7ZkN0/BOvD2THl8bNnoA==} engines: {node: '>=12'} dependencies: '@babel/runtime': 7.19.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 /@wordpress/deprecated/3.2.3: resolution: {integrity: sha512-YoJos/hW216PIlxbtNyb24kPR3TUFTSsfeVT23SxudW4jhmwM12vkl3KY1RDbhD/qi89OE4k+8xsBo5cM3lCSw==} @@ -16205,7 +16179,7 @@ packages: hasBin: true dependencies: '@babel/runtime': 7.19.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 gettext-parser: 1.4.0 lodash: 4.17.21 memize: 1.1.0 @@ -16219,7 +16193,7 @@ packages: hasBin: true dependencies: '@babel/runtime': 7.19.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 gettext-parser: 1.4.0 lodash: 4.17.21 memize: 1.1.0 @@ -16233,7 +16207,7 @@ packages: hasBin: true dependencies: '@babel/runtime': 7.19.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 gettext-parser: 1.4.0 memize: 1.1.0 sprintf-js: 1.1.2 @@ -16724,7 +16698,7 @@ packages: dependencies: '@babel/runtime': 7.19.0 '@wordpress/element': 4.20.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 utility-types: 3.10.0 dev: false @@ -16853,7 +16827,7 @@ packages: '@wordpress/data': 6.15.0_react@17.0.2 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 '@wordpress/keycodes': 3.19.0 lodash: 4.17.21 memize: 1.1.0 @@ -18498,6 +18472,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.16.12: + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.19.3 + '@babel/core': 7.16.12 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.16.12 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: false + /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.17.8: resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} peerDependencies: diff --git a/tools/package-release/README.md b/tools/package-release/README.md index b50b3f17aa9..7667b396dff 100644 --- a/tools/package-release/README.md +++ b/tools/package-release/README.md @@ -36,6 +36,8 @@ In order to prepare a package for release, a changelog will need to be compiled ./tools/package-release/bin/dev prepare -a ``` +When making an initial release for a new package, pass the `--initialRelease` flag to signify a new release for a new package. + 2. Create a pull request with the resulting changes and merge it. See more about the prepare script using `./tools/package-release/bin/dev publish --help`. diff --git a/tools/package-release/src/changelogger.ts b/tools/package-release/src/changelogger.ts index a8febe6a5f3..9525204ea07 100644 --- a/tools/package-release/src/changelogger.ts +++ b/tools/package-release/src/changelogger.ts @@ -14,7 +14,7 @@ import { getFilepathFromPackageName } from './validate'; * Call changelogger's next version function to get the version for the next release. * * @param {string} name Package name. - * @return {string} Next release version. + * @return {string|null} Next release version or null if none exists. */ export const getNextVersion = ( name: string ) => { try { @@ -25,9 +25,7 @@ export const getNextVersion = ( name: string ) => { } ).trim(); } catch ( e ) { if ( e instanceof Error ) { - // eslint-disable-next-line no-console - console.log( e ); - throw e; + return null; } } }; @@ -59,13 +57,18 @@ export const validateChangelogEntries = ( name: string ) => { * * @param {string} name Package name. */ -export const writeChangelog = ( name: string ) => { +export const writeChangelog = ( name: string, nextVersion?: string ) => { try { const cwd = getFilepathFromPackageName( name ); - execSync( './vendor/bin/changelogger write --add-pr-num', { - cwd, - encoding: 'utf-8', - } ); + execSync( + `./vendor/bin/changelogger write --add-pr-num ${ + nextVersion ? '--use-version ' + nextVersion : '' + }`, + { + cwd, + encoding: 'utf-8', + } + ); } catch ( e ) { if ( e instanceof Error ) { // eslint-disable-next-line no-console diff --git a/tools/package-release/src/commands/prepare/index.ts b/tools/package-release/src/commands/prepare/index.ts index 3cb16e9ced0..dcf7dd6ba12 100644 --- a/tools/package-release/src/commands/prepare/index.ts +++ b/tools/package-release/src/commands/prepare/index.ts @@ -49,6 +49,10 @@ export default class PackagePrepare extends Command { default: false, description: 'Perform prepare function on all packages.', } ), + initialRelease: Flags.boolean( { + default: false, + description: "Create a package's first release to NPM", + } ), }; /** @@ -62,17 +66,23 @@ export default class PackagePrepare extends Command { } if ( flags.all ) { - this.preparePackages( getAllPackges() ); + await this.preparePackages( getAllPackges() ); return; } const packages = args.packages.split( ',' ); + if ( flags.initialRelease && packages.length > 1 ) { + this.error( + 'Please release only a single package when making an initial release' + ); + } + packages.forEach( ( name: string ) => validatePackage( name, ( e: string ): void => this.error( e ) ) ); - this.preparePackages( packages ); + await this.preparePackages( packages, flags.initialRelease ); } /** @@ -80,18 +90,33 @@ export default class PackagePrepare extends Command { * * @param {Array} packages Packages to prepare. */ - private preparePackages( packages: Array< string > ) { - packages.forEach( ( name ) => { + private async preparePackages( + packages: Array< string >, + initialRelease?: Boolean + ) { + packages.forEach( async ( name ) => { CliUx.ux.action.start( `Preparing ${ name }` ); try { if ( hasValidChangelogs( name ) ) { validateChangelogEntries( name ); - const nextVersion = getNextVersion( name ); - writeChangelog( name ); + let nextVersion = getNextVersion( name ); if ( nextVersion ) { - this.bumpPackageVersion( name, nextVersion ); + writeChangelog( name ); + } else { + if ( initialRelease ) { + nextVersion = '1.0.0'; + } else { + throw new Error( + `Error reading version number for ${ name }. Check that a Changelog file exists and has a version number. If making an initial release, pass the --initialRelease flag.` + ); + } + + writeChangelog( name, nextVersion ); } + + this.bumpPackageVersion( name, nextVersion ); + CliUx.ux.action.stop(); } else { CliUx.ux.action.stop( From 3992e25d7ab5366edd6fee54a307ac1ed8f3b402 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Tue, 28 Feb 2023 07:43:48 +0100 Subject: [PATCH 23/53] Bump Woo Blocks version to 9.6.3 --- plugins/woocommerce/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index ed3eebd8f0b..50141bff819 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -21,7 +21,7 @@ "maxmind-db/reader": "^1.11", "pelago/emogrifier": "^6.0", "woocommerce/action-scheduler": "3.5.4", - "woocommerce/woocommerce-blocks": "9.6.2" + "woocommerce/woocommerce-blocks": "9.6.3" }, "require-dev": { "automattic/jetpack-changelogger": "^3.3.0", From 42d75e5529f8ef986cca39a3b48ca44a60ce448d Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Tue, 28 Feb 2023 07:44:18 +0100 Subject: [PATCH 24/53] Run "composer update woocommerce/woocommerce-blocks" --- .../woocommerce/bin/composer/phpcs/composer.lock | 13 +++++++------ plugins/woocommerce/bin/composer/wp/composer.lock | 12 ++++++------ plugins/woocommerce/composer.lock | 14 +++++++------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/plugins/woocommerce/bin/composer/phpcs/composer.lock b/plugins/woocommerce/bin/composer/phpcs/composer.lock index 7717377063e..c56f5561815 100644 --- a/plugins/woocommerce/bin/composer/phpcs/composer.lock +++ b/plugins/woocommerce/bin/composer/phpcs/composer.lock @@ -314,16 +314,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -359,14 +359,15 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], "support": { "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2022-06-18T07:21:10+00:00" + "time": "2023-02-22T23:07:41+00:00" }, { "name": "woocommerce/woocommerce-sniffs", diff --git a/plugins/woocommerce/bin/composer/wp/composer.lock b/plugins/woocommerce/bin/composer/wp/composer.lock index 6f8bf7a24a2..b69daf16722 100644 --- a/plugins/woocommerce/bin/composer/wp/composer.lock +++ b/plugins/woocommerce/bin/composer/wp/composer.lock @@ -434,16 +434,16 @@ }, { "name": "wp-cli/i18n-command", - "version": "v2.4.1", + "version": "v2.4.2", "source": { "type": "git", "url": "https://github.com/wp-cli/i18n-command.git", - "reference": "22f7e6aa6ba23d0b50c45c75386c8151b991477e" + "reference": "43d5cf8968dbddf90907083ad048f12be5ca3d43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/22f7e6aa6ba23d0b50c45c75386c8151b991477e", - "reference": "22f7e6aa6ba23d0b50c45c75386c8151b991477e", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/43d5cf8968dbddf90907083ad048f12be5ca3d43", + "reference": "43d5cf8968dbddf90907083ad048f12be5ca3d43", "shasum": "" }, "require": { @@ -496,9 +496,9 @@ "homepage": "https://github.com/wp-cli/i18n-command", "support": { "issues": "https://github.com/wp-cli/i18n-command/issues", - "source": "https://github.com/wp-cli/i18n-command/tree/v2.4.1" + "source": "https://github.com/wp-cli/i18n-command/tree/v2.4.2" }, - "time": "2022-12-09T19:09:17+00:00" + "time": "2023-02-17T18:14:41+00:00" }, { "name": "wp-cli/mustangostang-spyc", diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index c0536382164..aab43344d05 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": "3114b2ae01803fb5d37d2e848b566b8e", + "content-hash": "9b132fec1fe92496ca5d4f3708b366e3", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -628,16 +628,16 @@ }, { "name": "woocommerce/woocommerce-blocks", - "version": "v9.6.2", + "version": "v9.6.3", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-blocks.git", - "reference": "1eb30afa09310d0dcbe8be1222ac3e5214c60328" + "reference": "258c5c8423b10782fae278eda3c957363f0ad46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/1eb30afa09310d0dcbe8be1222ac3e5214c60328", - "reference": "1eb30afa09310d0dcbe8be1222ac3e5214c60328", + "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/258c5c8423b10782fae278eda3c957363f0ad46f", + "reference": "258c5c8423b10782fae278eda3c957363f0ad46f", "shasum": "" }, "require": { @@ -683,9 +683,9 @@ ], "support": { "issues": "https://github.com/woocommerce/woocommerce-blocks/issues", - "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v9.6.2" + "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v9.6.3" }, - "time": "2023-02-22T13:56:14+00:00" + "time": "2023-02-27T17:19:52+00:00" } ], "packages-dev": [ From ac47d1892eb322b683c21f01568c120229523d5a Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Tue, 28 Feb 2023 07:46:25 +0100 Subject: [PATCH 25/53] Add changelog --- plugins/woocommerce/changelog/update-woocommerce-blocks-9.6.3 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/update-woocommerce-blocks-9.6.3 diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-9.6.3 b/plugins/woocommerce/changelog/update-woocommerce-blocks-9.6.3 new file mode 100644 index 00000000000..2d8f61daef0 --- /dev/null +++ b/plugins/woocommerce/changelog/update-woocommerce-blocks-9.6.3 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update WooCommerce Blocks to 9.6.3 From 6a345ac7ebd3f73907f2f1ef1a8a8ed76314292b Mon Sep 17 00:00:00 2001 From: Joel Thiessen <444632+joelclimbsthings@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:55:49 -0800 Subject: [PATCH 26/53] Moving currencyContext to currency package and updating references (#36959) --- .../update-refactor-currency-context | 4 ++ .../src/advanced-filters/number-filter.js | 3 +- packages/js/components/src/filters/index.js | 2 +- .../update-36395-move-product-fields | 4 ++ packages/js/currency/package.json | 1 + .../js/currency/src}/currency-context.js | 7 +-- packages/js/currency/src/index.ts | 9 ++++ .../js/currency/src/{index.tsx => utils.tsx} | 4 +- .../components/leaderboard/test/index.js | 2 +- .../components/report-chart/index.js | 2 +- .../components/report-filters/index.js | 2 +- .../components/report-summary/index.js | 2 +- .../analytics/report/categories/table.js | 2 +- .../client/analytics/report/coupons/table.js | 2 +- .../analytics/report/customers/table.js | 2 +- .../analytics/report/downloads/table.js | 2 +- .../client/analytics/report/index.js | 8 ++-- .../client/analytics/report/orders/table.js | 2 +- .../client/analytics/report/products/table.js | 2 +- .../client/analytics/report/revenue/table.js | 2 +- .../client/analytics/report/stock/table.js | 2 +- .../client/analytics/report/taxes/table.js | 3 +- .../analytics/report/variations/table.js | 2 +- .../client/dashboard/customizable.js | 8 ++-- .../dashboard/store-performance/index.js | 2 +- .../homescreen/activity-panel/orders/index.js | 2 +- .../activity-panel/reviews/index.js | 2 +- .../homescreen/stats-overview/stats-list.js | 2 +- .../products/fields/variations/variations.tsx | 3 +- .../pricing-section/pricing-field-list.tsx | 2 +- .../pricing-section/pricing-field-sale.tsx | 2 +- .../pricing-section/pricing-section-fills.tsx | 2 +- .../client/products/use-product-helper.ts | 2 +- .../flows/selective-bundle/index.js | 2 +- .../steps/store-details/index.js | 2 +- .../client/tasks/fills/shipping/rates.js | 6 +-- .../update-refactor-currency-context | 4 ++ pnpm-lock.yaml | 46 +++++++++---------- 38 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 packages/js/components/changelog/update-refactor-currency-context create mode 100644 packages/js/currency/changelog/update-36395-move-product-fields rename {plugins/woocommerce-admin/client/lib => packages/js/currency/src}/currency-context.js (85%) create mode 100644 packages/js/currency/src/index.ts rename packages/js/currency/src/{index.tsx => utils.tsx} (98%) create mode 100644 plugins/woocommerce/changelog/update-refactor-currency-context diff --git a/packages/js/components/changelog/update-refactor-currency-context b/packages/js/components/changelog/update-refactor-currency-context new file mode 100644 index 00000000000..80984834ed2 --- /dev/null +++ b/packages/js/components/changelog/update-refactor-currency-context @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Small tweak to update reference to currencyContext component. diff --git a/packages/js/components/src/advanced-filters/number-filter.js b/packages/js/components/src/advanced-filters/number-filter.js index b03594d17e3..6dddd826fc6 100644 --- a/packages/js/components/src/advanced-filters/number-filter.js +++ b/packages/js/components/src/advanced-filters/number-filter.js @@ -7,8 +7,7 @@ import { get, find, isArray } from 'lodash'; import interpolateComponents from '@automattic/interpolate-components'; import classnames from 'classnames'; import { sprintf, __, _x } from '@wordpress/i18n'; - -import CurrencyFactory from '@woocommerce/currency'; +import { CurrencyFactory } from '@woocommerce/currency'; /** * Internal dependencies diff --git a/packages/js/components/src/filters/index.js b/packages/js/components/src/filters/index.js index a6051655d54..ec82939bd46 100644 --- a/packages/js/components/src/filters/index.js +++ b/packages/js/components/src/filters/index.js @@ -7,7 +7,7 @@ import { find } from 'lodash'; import PropTypes from 'prop-types'; import { updateQueryString } from '@woocommerce/navigation'; import { getDateParamsFromQuery, getCurrentDates } from '@woocommerce/date'; -import CurrencyFactory from '@woocommerce/currency'; +import { CurrencyFactory } from '@woocommerce/currency'; /** * Internal dependencies diff --git a/packages/js/currency/changelog/update-36395-move-product-fields b/packages/js/currency/changelog/update-36395-move-product-fields new file mode 100644 index 00000000000..9d7a38a5a48 --- /dev/null +++ b/packages/js/currency/changelog/update-36395-move-product-fields @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Adding currencyContext component. diff --git a/packages/js/currency/package.json b/packages/js/currency/package.json index c7be92bd4d8..f8f812ad494 100644 --- a/packages/js/currency/package.json +++ b/packages/js/currency/package.json @@ -29,6 +29,7 @@ "@woocommerce/number": "workspace:*", "@wordpress/deprecated": "^2.12.3", "@wordpress/element": "^4.1.1", + "@wordpress/hooks": "^3.5.0", "@wordpress/html-entities": "^3.3.1", "@wordpress/i18n": "^3.20.0" }, diff --git a/plugins/woocommerce-admin/client/lib/currency-context.js b/packages/js/currency/src/currency-context.js similarity index 85% rename from plugins/woocommerce-admin/client/lib/currency-context.js rename to packages/js/currency/src/currency-context.js index 05c70550402..ac4251de474 100644 --- a/plugins/woocommerce-admin/client/lib/currency-context.js +++ b/packages/js/currency/src/currency-context.js @@ -3,14 +3,15 @@ */ import { createContext } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; -import CurrencyFactory from '@woocommerce/currency'; +import { getSetting } from '@woocommerce/settings'; + /** * Internal dependencies */ -import { CURRENCY } from '~/utils/admin-settings'; +import { CurrencyFactory } from './index'; +const CURRENCY = getSetting( 'currency' ); const appCurrency = CurrencyFactory( CURRENCY ); - export const getFilteredCurrencyInstance = ( query ) => { const config = appCurrency.getCurrencyConfig(); /** diff --git a/packages/js/currency/src/index.ts b/packages/js/currency/src/index.ts new file mode 100644 index 00000000000..6a8854f4516 --- /dev/null +++ b/packages/js/currency/src/index.ts @@ -0,0 +1,9 @@ +/** + * Internal dependencies + */ +import { CurrencyFactory } from './utils'; + +export default CurrencyFactory; + +export * from './utils'; +export * from './currency-context'; diff --git a/packages/js/currency/src/index.tsx b/packages/js/currency/src/utils.tsx similarity index 98% rename from packages/js/currency/src/index.tsx rename to packages/js/currency/src/utils.tsx index 50d8b233e18..f8fb8858510 100644 --- a/packages/js/currency/src/index.tsx +++ b/packages/js/currency/src/utils.tsx @@ -62,7 +62,7 @@ export type CountryInfo = { * @param {CurrencyConfig} currencySetting * @return {Object} currency object */ -const CurrencyFactory = function ( currencySetting?: CurrencyConfig ) { +const CurrencyFactoryBase = function ( currencySetting?: CurrencyConfig ) { let currency: Currency; function stripTags( str: string ) { @@ -272,7 +272,7 @@ const CurrencyFactory = function ( currencySetting?: CurrencyConfig ) { }; }; -export default CurrencyFactory; +export const CurrencyFactory = CurrencyFactoryBase; /** * Returns currency data by country/region. Contains code, symbol, position, thousands separator, decimal separator, and precision. diff --git a/plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js b/plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js index 6ff834803ac..c5c9cacd89d 100644 --- a/plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js @@ -3,7 +3,7 @@ */ import { render } from '@testing-library/react'; import { numberFormat } from '@woocommerce/number'; -import CurrencyFactory from '@woocommerce/currency'; +import { CurrencyFactory } from '@woocommerce/currency'; /** * Internal dependencies diff --git a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js index 864931302b4..80da9038f0a 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js @@ -23,11 +23,11 @@ import { getChartTypeForQuery, getPreviousDate, } from '@woocommerce/date'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ -import { CurrencyContext } from '../../../lib/currency-context'; import ReportError from '../report-error'; import { getChartMode, getSelectedFilter, createDateFormatter } from './utils'; diff --git a/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js b/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js index 0c5be890925..3c4a5b691ef 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js @@ -14,11 +14,11 @@ import { isoDateFormat, } from '@woocommerce/date'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ -import { CurrencyContext } from '../../../lib/currency-context'; import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants'; import { LOCALE } from '~/utils/admin-settings'; diff --git a/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js b/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js index ca3964e58b4..57eefcd17f2 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js @@ -16,12 +16,12 @@ import { calculateDelta, formatValue } from '@woocommerce/number'; import { getSummaryNumbers, SETTINGS_STORE_NAME } from '@woocommerce/data'; import { getDateParamsFromQuery } from '@woocommerce/date'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportError from '../report-error'; -import { CurrencyContext } from '../../../lib/currency-context'; /** * Component to render summary numbers in reports. diff --git a/plugins/woocommerce-admin/client/analytics/report/categories/table.js b/plugins/woocommerce-admin/client/analytics/report/categories/table.js index a5d03127f12..95be9f91001 100644 --- a/plugins/woocommerce-admin/client/analytics/report/categories/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/categories/table.js @@ -10,13 +10,13 @@ import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { Link } from '@woocommerce/components'; import { formatValue } from '@woocommerce/number'; import { ITEMS_STORE_NAME } from '@woocommerce/data'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import CategoryBreacrumbs from './breadcrumbs'; import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; class CategoriesReportTable extends Component { constructor( props ) { diff --git a/plugins/woocommerce-admin/client/analytics/report/coupons/table.js b/plugins/woocommerce-admin/client/analytics/report/coupons/table.js index 42ee099cc43..d1f9bdfd6f5 100644 --- a/plugins/woocommerce-admin/client/analytics/report/coupons/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/coupons/table.js @@ -8,12 +8,12 @@ import { Date, Link } from '@woocommerce/components'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { formatValue } from '@woocommerce/number'; import { defaultTableDateFormat } from '@woocommerce/date'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; class CouponsReportTable extends Component { diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/table.js b/plugins/woocommerce-admin/client/analytics/report/customers/table.js index 73f58fb42c7..874d70108b7 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/table.js @@ -10,12 +10,12 @@ import { formatValue } from '@woocommerce/number'; import { getAdminLink } from '@woocommerce/settings'; import { defaultTableDateFormat } from '@woocommerce/date'; import { COUNTRIES_STORE_NAME } from '@woocommerce/data'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; function CustomersReportTable( { diff --git a/plugins/woocommerce-admin/client/analytics/report/downloads/table.js b/plugins/woocommerce-admin/client/analytics/report/downloads/table.js index 7f95bc9236d..232d22511c0 100644 --- a/plugins/woocommerce-admin/client/analytics/report/downloads/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/downloads/table.js @@ -12,12 +12,12 @@ import { formatValue } from '@woocommerce/number'; import { getAdminLink } from '@woocommerce/settings'; import { SETTINGS_STORE_NAME } from '@woocommerce/data'; import { getCurrentDates, defaultTableDateFormat } from '@woocommerce/date'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; class DownloadsReportTable extends Component { diff --git a/plugins/woocommerce-admin/client/analytics/report/index.js b/plugins/woocommerce-admin/client/analytics/report/index.js index 499385bce7b..85118becdbf 100644 --- a/plugins/woocommerce-admin/client/analytics/report/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/index.js @@ -8,6 +8,10 @@ import PropTypes from 'prop-types'; import { find } from 'lodash'; import { getQuery, getSearchWords } from '@woocommerce/navigation'; import { searchItemsByString, ITEMS_STORE_NAME } from '@woocommerce/data'; +import { + CurrencyContext, + getFilteredCurrencyInstance, +} from '@woocommerce/currency'; /** * Internal dependencies @@ -15,10 +19,6 @@ import { searchItemsByString, ITEMS_STORE_NAME } from '@woocommerce/data'; import './style.scss'; import { NoMatch } from '~/layout/NoMatch'; import ReportError from '../components/report-error'; -import { - CurrencyContext, - getFilteredCurrencyInstance, -} from '../../lib/currency-context'; import getReports from './get-reports'; /** diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/table.js b/plugins/woocommerce-admin/client/analytics/report/orders/table.js index 3f810191f17..247333db1b0 100644 --- a/plugins/woocommerce-admin/client/analytics/report/orders/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/orders/table.js @@ -8,13 +8,13 @@ import { Date, Link, OrderStatus, ViewMoreList } from '@woocommerce/components'; import { formatValue } from '@woocommerce/number'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { defaultTableDateFormat } from '@woocommerce/date'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; import { getAdminSetting } from '~/utils/admin-settings'; -import { CurrencyContext } from '../../../lib/currency-context'; const capitalizeFirstLetter = ( expr ) => expr.charAt( 0 ).toUpperCase() + expr.slice( 1 ); diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table.js b/plugins/woocommerce-admin/client/analytics/report/products/table.js index 8927edad1a7..e913860553a 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/table.js @@ -12,6 +12,7 @@ import { Link, Tag } from '@woocommerce/components'; import { formatValue } from '@woocommerce/number'; import { getAdminLink } from '@woocommerce/settings'; import { ITEMS_STORE_NAME } from '@woocommerce/data'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -19,7 +20,6 @@ import { ITEMS_STORE_NAME } from '@woocommerce/data'; import CategoryBreacrumbs from '../categories/breadcrumbs'; import { isLowStock } from './utils'; import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; import './style.scss'; diff --git a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js index 80385cfd18e..8f50ffd6fd5 100644 --- a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js @@ -21,13 +21,13 @@ import { getCurrentDates, } from '@woocommerce/date'; import { stringify } from 'qs'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; import { getAdminSetting } from '~/utils/admin-settings'; -import { CurrencyContext } from '../../../lib/currency-context'; const EMPTY_ARRAY = []; diff --git a/plugins/woocommerce-admin/client/analytics/report/stock/table.js b/plugins/woocommerce-admin/client/analytics/report/stock/table.js index 3ea22f1ca3e..33b8824e654 100644 --- a/plugins/woocommerce-admin/client/analytics/report/stock/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/stock/table.js @@ -8,13 +8,13 @@ import { Link } from '@woocommerce/components'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { formatValue } from '@woocommerce/number'; import { getAdminLink } from '@woocommerce/settings'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; import { isLowStock } from './utils'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; const stockStatuses = getAdminSetting( 'stockStatuses', {} ); diff --git a/plugins/woocommerce-admin/client/analytics/report/taxes/table.js b/plugins/woocommerce-admin/client/analytics/report/taxes/table.js index 6b1d9bb30ab..7146faac1d7 100644 --- a/plugins/woocommerce-admin/client/analytics/report/taxes/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/taxes/table.js @@ -7,14 +7,13 @@ import { map } from 'lodash'; import { Link } from '@woocommerce/components'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { formatValue } from '@woocommerce/number'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import { getTaxCode } from './utils'; import ReportTable from '../../components/report-table'; -import { CurrencyContext } from '../../../lib/currency-context'; - class TaxesReportTable extends Component { constructor() { super(); diff --git a/plugins/woocommerce-admin/client/analytics/report/variations/table.js b/plugins/woocommerce-admin/client/analytics/report/variations/table.js index 6b2ea7311e8..f68adf59cb5 100644 --- a/plugins/woocommerce-admin/client/analytics/report/variations/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/variations/table.js @@ -9,13 +9,13 @@ import { Link } from '@woocommerce/components'; import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; import { formatValue } from '@woocommerce/number'; import { getAdminLink } from '@woocommerce/settings'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import ReportTable from '../../components/report-table'; import { isLowStock } from '../products/utils'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getVariationName } from '../../../lib/async-requests'; import { getAdminSetting } from '~/utils/admin-settings'; diff --git a/plugins/woocommerce-admin/client/dashboard/customizable.js b/plugins/woocommerce-admin/client/dashboard/customizable.js index 57731267b7c..3468bdb9dad 100644 --- a/plugins/woocommerce-admin/client/dashboard/customizable.js +++ b/plugins/woocommerce-admin/client/dashboard/customizable.js @@ -18,6 +18,10 @@ import { isoDateFormat, } from '@woocommerce/date'; import { recordEvent } from '@woocommerce/tracks'; +import { + CurrencyContext, + getFilteredCurrencyInstance, +} from '@woocommerce/currency'; /** * Internal dependencies @@ -26,10 +30,6 @@ import './style.scss'; import defaultSections from './default-sections'; import Section from './section'; import ReportFilters from '../analytics/components/report-filters'; -import { - CurrencyContext, - getFilteredCurrencyInstance, -} from '../lib/currency-context'; const DASHBOARD_FILTERS_FILTER = 'woocommerce_admin_dashboard_filters'; diff --git a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js index fbd0b79d52b..db5fba7bfb2 100644 --- a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js +++ b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js @@ -17,12 +17,12 @@ import { } from '@woocommerce/components'; import { getDateParamsFromQuery } from '@woocommerce/date'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ import './style.scss'; -import { CurrencyContext } from '../../lib/currency-context'; import { getIndicatorData, getIndicatorValues } from './utils'; import { getAdminSetting } from '~/utils/admin-settings'; diff --git a/plugins/woocommerce-admin/client/homescreen/activity-panel/orders/index.js b/plugins/woocommerce-admin/client/homescreen/activity-panel/orders/index.js index d74d3acac32..bbae6bd19dd 100644 --- a/plugins/woocommerce-admin/client/homescreen/activity-panel/orders/index.js +++ b/plugins/woocommerce-admin/client/homescreen/activity-panel/orders/index.js @@ -18,6 +18,7 @@ import { getNewPath } from '@woocommerce/navigation'; import { getAdminLink } from '@woocommerce/settings'; import { ORDERS_STORE_NAME, ITEMS_STORE_NAME } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -28,7 +29,6 @@ import { } from '~/activity-panel/activity-card'; import { getAdminSetting } from '~/utils/admin-settings'; import { getCountryCode } from '~/dashboard/utils'; -import { CurrencyContext } from '~/lib/currency-context'; import './style.scss'; function recordOrderEvent( eventName ) { diff --git a/plugins/woocommerce-admin/client/homescreen/activity-panel/reviews/index.js b/plugins/woocommerce-admin/client/homescreen/activity-panel/reviews/index.js index a95643960c2..5bd24bb33c8 100644 --- a/plugins/woocommerce-admin/client/homescreen/activity-panel/reviews/index.js +++ b/plugins/woocommerce-admin/client/homescreen/activity-panel/reviews/index.js @@ -22,6 +22,7 @@ import { getAdminLink } from '@woocommerce/settings'; import { get, isNull } from 'lodash'; import { REVIEWS_STORE_NAME } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -32,7 +33,6 @@ import { ActivityCardPlaceholder, } from '~/activity-panel/activity-card'; import CheckmarkCircleIcon from './checkmark-circle-icon'; -import { CurrencyContext } from '../../../lib/currency-context'; import sanitizeHTML from '../../../lib/sanitize-html'; import { REVIEW_PAGE_LIMIT, unapprovedReviewsQuery } from './utils'; diff --git a/plugins/woocommerce-admin/client/homescreen/stats-overview/stats-list.js b/plugins/woocommerce-admin/client/homescreen/stats-overview/stats-list.js index 0f8bf904038..e4be5f325cb 100644 --- a/plugins/woocommerce-admin/client/homescreen/stats-overview/stats-list.js +++ b/plugins/woocommerce-admin/client/homescreen/stats-overview/stats-list.js @@ -11,11 +11,11 @@ import { } from '@woocommerce/components'; import { getPersistedQuery } from '@woocommerce/navigation'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ -import { CurrencyContext } from '../../lib/currency-context'; import { getIndicatorData, getIndicatorValues, diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index 16298715f47..55c972ec0af 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -22,10 +22,10 @@ import { } from '@woocommerce/components'; import { getNewPath } from '@woocommerce/navigation'; import { useContext, useState } from '@wordpress/element'; -import { useParams } from 'react-router-dom'; import { useSelect, useDispatch } from '@wordpress/data'; import classnames from 'classnames'; import truncate from 'lodash/truncate'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -34,7 +34,6 @@ import { PRODUCT_VARIATION_TITLE_LIMIT } from '~/products/constants'; import useVariationsOrder from '~/products/hooks/use-variations-order'; import HiddenIcon from '~/products/images/hidden-icon'; import VisibleIcon from '~/products/images/visible-icon'; -import { CurrencyContext } from '../../../lib/currency-context'; import './variations.scss'; /** diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx index fb089980cab..01cb22e5079 100644 --- a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx @@ -9,6 +9,7 @@ import { useContext } from '@wordpress/element'; import { Product, SETTINGS_STORE_NAME } from '@woocommerce/data'; import { useSelect } from '@wordpress/data'; import interpolateComponents from '@automattic/interpolate-components'; +import { CurrencyContext } from '@woocommerce/currency'; import { BaseControl, // @ts-expect-error `__experimentalInputControl` does exist. @@ -19,7 +20,6 @@ import { * Internal dependencies */ import { CurrencyInputProps } from './pricing-section-fills'; -import { CurrencyContext } from '../../../lib/currency-context'; import { ADMIN_URL } from '~/utils/admin-settings'; type PricingListFieldProps = { diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx index b46361f5fbf..e3ad76fc7ea 100644 --- a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx @@ -16,6 +16,7 @@ import interpolateComponents from '@automattic/interpolate-components'; import { format as formatDate } from '@wordpress/date'; import { formatCurrencyDisplayValue } from '@woocommerce/product-editor'; import moment from 'moment'; +import { CurrencyContext } from '@woocommerce/currency'; import { BaseControl, // @ts-expect-error `__experimentalInputControl` does exist. @@ -27,7 +28,6 @@ import { * Internal dependencies */ import { CurrencyInputProps } from './pricing-section-fills'; -import { CurrencyContext } from '../../../lib/currency-context'; type PricingListFieldProps = { currencyInputProps: CurrencyInputProps; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx index 8a794f94a11..854534be269 100644 --- a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx @@ -16,6 +16,7 @@ import { recordEvent } from '@woocommerce/tracks'; import { Product } from '@woocommerce/data'; import { useContext } from '@wordpress/element'; import { Card, CardBody } from '@wordpress/components'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -28,7 +29,6 @@ import { } from './index'; import { useProductHelper } from '../../use-product-helper'; import { PLUGIN_ID } from '../constants'; -import { CurrencyContext } from '../../../lib/currency-context'; import './pricing-section.scss'; diff --git a/plugins/woocommerce-admin/client/products/use-product-helper.ts b/plugins/woocommerce-admin/client/products/use-product-helper.ts index 57d02daf711..a26a3e60558 100644 --- a/plugins/woocommerce-admin/client/products/use-product-helper.ts +++ b/plugins/woocommerce-admin/client/products/use-product-helper.ts @@ -20,11 +20,11 @@ import { ProductVariation, } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ -import { CurrencyContext } from '../lib/currency-context'; import { NUMBERS_AND_DECIMAL_SEPARATOR, ONLY_ONE_DECIMAL_SEPARATOR, diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js index 08e18e6bd38..fd253935a29 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/business-details/flows/selective-bundle/index.js @@ -25,11 +25,11 @@ import { import { getSetting } from '@woocommerce/settings'; import { recordEvent } from '@woocommerce/tracks'; import classnames from 'classnames'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies */ -import { CurrencyContext } from '~/lib/currency-context'; import { createNoticesFromResponse } from '~/lib/notices'; import { platformOptions } from '../../data/platform-options'; import { employeeOptions } from '../../data/employee-options'; diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js index b6f3b1d6800..cfd148cab43 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js @@ -26,6 +26,7 @@ import { recordEvent } from '@woocommerce/tracks'; import { Text } from '@woocommerce/experimental'; import { Icon, info } from '@wordpress/icons'; import { isEmail } from '@wordpress/url'; +import { CurrencyContext } from '@woocommerce/currency'; /** * Internal dependencies @@ -36,7 +37,6 @@ import { getStoreAddressValidator, } from '../../../dashboard/components/settings/general/store-address'; import UsageModal from '../usage-modal'; -import { CurrencyContext } from '../../../lib/currency-context'; import { getAdminSetting } from '~/utils/admin-settings'; import './style.scss'; diff --git a/plugins/woocommerce-admin/client/tasks/fills/shipping/rates.js b/plugins/woocommerce-admin/client/tasks/fills/shipping/rates.js index 597428b9cc5..64bf99cda85 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/shipping/rates.js +++ b/plugins/woocommerce-admin/client/tasks/fills/shipping/rates.js @@ -10,11 +10,7 @@ import { Flag, Form, TextControlWithAffixes } from '@woocommerce/components'; import { recordEvent } from '@woocommerce/tracks'; import { Icon, globe } from '@wordpress/icons'; import classnames from 'classnames'; - -/** - * Internal dependencies - */ -import { CurrencyContext } from '../../../lib/currency-context'; +import { CurrencyContext } from '@woocommerce/currency'; const ShippingRateIcon = ( { zone } ) => (
diff --git a/plugins/woocommerce/changelog/update-refactor-currency-context b/plugins/woocommerce/changelog/update-refactor-currency-context new file mode 100644 index 00000000000..542b256874c --- /dev/null +++ b/plugins/woocommerce/changelog/update-refactor-currency-context @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Moving currencyContext to relevant package, and updating all references. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74cb3bb3805..61d76ea793a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,6 +429,7 @@ importers: '@woocommerce/number': workspace:* '@wordpress/deprecated': ^2.12.3 '@wordpress/element': ^4.1.1 + '@wordpress/hooks': ^3.5.0 '@wordpress/html-entities': ^3.3.1 '@wordpress/i18n': ^3.20.0 eslint: ^8.32.0 @@ -441,6 +442,7 @@ importers: '@woocommerce/number': link:../number '@wordpress/deprecated': 2.12.3 '@wordpress/element': 4.8.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 3.20.0 devDependencies: @@ -3394,7 +3396,7 @@ packages: dependencies: '@babel/core': 7.12.9 '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.12.9 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 transitivePeerDependencies: - supports-color dev: true @@ -3407,7 +3409,7 @@ packages: dependencies: '@babel/core': 7.16.12 '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.16.12 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 transitivePeerDependencies: - supports-color dev: false @@ -4075,7 +4077,7 @@ packages: dependencies: '@babel/core': 7.12.9 '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.12.9 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 transitivePeerDependencies: - supports-color dev: true @@ -4088,7 +4090,7 @@ packages: dependencies: '@babel/core': 7.16.12 '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.16.12 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 transitivePeerDependencies: - supports-color dev: false @@ -4834,7 +4836,7 @@ packages: dependencies: '@babel/core': 7.12.9 '@babel/helper-module-imports': 7.16.7 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 '@babel/helper-remap-async-to-generator': 7.16.8 transitivePeerDependencies: - supports-color @@ -4848,7 +4850,7 @@ packages: dependencies: '@babel/core': 7.16.12 '@babel/helper-module-imports': 7.16.7 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 '@babel/helper-remap-async-to-generator': 7.16.8 transitivePeerDependencies: - supports-color @@ -14828,7 +14830,7 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.19.0 '@wordpress/i18n': 4.19.0 '@wordpress/icons': 9.10.0 @@ -14883,7 +14885,7 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.19.0 '@wordpress/i18n': 4.19.0 '@wordpress/icons': 9.10.0 @@ -14936,7 +14938,7 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/html-entities': 3.19.0 '@wordpress/i18n': 4.19.0 '@wordpress/is-shallow-equal': 4.19.0 @@ -15084,7 +15086,7 @@ packages: '@wordpress/dom': 3.19.0 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 - '@wordpress/hooks': 3.19.0 + '@wordpress/hooks': 3.26.0 '@wordpress/i18n': 4.19.0 '@wordpress/icons': 9.10.0 '@wordpress/is-shallow-equal': 4.19.0 @@ -15137,7 +15139,7 @@ packages: '@wordpress/dom': 3.4.1 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.4.1 - '@wordpress/hooks': 3.5.0 + '@wordpress/hooks': 3.26.0 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 8.0.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -15245,7 +15247,7 @@ packages: '@wordpress/dom': 3.4.1 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.4.1 - '@wordpress/hooks': 3.5.0 + '@wordpress/hooks': 3.26.0 '@wordpress/i18n': 4.4.1 '@wordpress/icons': 8.0.1 '@wordpress/is-shallow-equal': 4.4.1 @@ -15758,7 +15760,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 - '@wordpress/hooks': 3.5.0 + '@wordpress/hooks': 3.26.0 dev: false /@wordpress/dom-ready/2.13.2: @@ -16115,12 +16117,6 @@ packages: dependencies: '@babel/runtime': 7.19.0 - /@wordpress/hooks/3.19.0: - resolution: {integrity: sha512-iJNZnQ08ZFFlXpVBbSA2NuVMiKxGpNLQsDiwIuIzTmwWl8ZECYikuGC3vMCiG3xUz47JR5eGA5wN63kjkPm5bA==} - engines: {node: '>=12'} - dependencies: - '@babel/runtime': 7.19.0 - /@wordpress/hooks/3.2.2: resolution: {integrity: sha512-MlFWyu2ttJhmzDFBVWPRwZwIMqQdHFZTjFWFWm50NlzUzIJ3gEtNA95mHNtav1Fone24N+I2YkaYMNb6PEPTyA==} engines: {node: '>=12'} @@ -16547,7 +16543,7 @@ packages: '@babel/runtime': 7.17.7 '@wordpress/compose': 5.2.1_react@17.0.2 '@wordpress/element': 4.20.0 - '@wordpress/hooks': 3.5.0 + '@wordpress/hooks': 3.26.0 '@wordpress/icons': 8.1.0 lodash: 4.17.21 memize: 1.1.0 @@ -20857,7 +20853,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 2.7.1 semver: 6.3.0 - webpack: 5.70.0_webpack-cli@3.3.12 + webpack: 5.70.0 /css-loader/5.2.7_webpack@5.70.0: resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} @@ -27201,7 +27197,7 @@ packages: '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 '@types/babel__traverse': 7.14.2 - '@types/node': 17.0.21 + '@types/node': 18.11.18 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -27753,7 +27749,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 17.0.21 + '@types/node': 18.11.18 jest-mock: 26.6.2 jest-util: 26.6.2 dev: true @@ -36050,7 +36046,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.1.1 semver: 7.3.5 - webpack: 5.70.0_webpack-cli@3.3.12 + webpack: 5.70.0 /sass-loader/12.6.0_sass@1.49.9+webpack@5.70.0: resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} @@ -37960,7 +37956,7 @@ packages: serialize-javascript: 6.0.0 source-map: 0.6.1 terser: 5.10.0_acorn@8.8.1 - webpack: 5.70.0_webpack-cli@3.3.12 + webpack: 5.70.0 transitivePeerDependencies: - acorn From 7d0669dcb101188fb6dd95d1e09b264fb33adc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Tue, 28 Feb 2023 15:10:12 -0300 Subject: [PATCH 27/53] Add selection to the tree control (#36435) * Create tree-control component * Remove items from treeItemProps * Add tree-control expand/collapse on click the expander button or by a custom logic * Add stories * Add the type definitions * Add use selection hook * Upgrade WP components dependency to v19.8.5 to support indeterminate checkbox control * Add selection logic to the tree control * Create stories * Add changelog file * Fix linter error * Add styles to fit the disign * Highlight selected item when it's a single selection tree * Rebasing from trunk * Add comment suggestions * Fix unit test errors due to a new version of @wordpress/compose related to the ResizeObserve feat --- .../add-35851-tree-control-selection | 4 + packages/js/components/package.json | 4 +- .../test/__snapshots__/index.js.snap | 4 +- .../hooks/use-selection.ts | 167 ++++++++++++++++++ .../hooks/use-tree-item.ts | 21 +++ .../hooks/use-tree.ts | 8 + .../stories/index.tsx | 64 +++++-- .../experimental-tree-control/tree-item.scss | 49 ++++- .../experimental-tree-control/tree-item.tsx | 36 +++- .../src/experimental-tree-control/tree.tsx | 3 +- .../src/experimental-tree-control/types.ts | 119 ++++++++----- .../typings/global.d.ts | 9 + .../test/__snapshots__/index.js.snap | 132 +++++++------- .../js/components/src/select-control/index.js | 6 +- packages/js/internal-js-tests/package.json | 1 + .../js/internal-js-tests/src/setup-globals.js | 4 + .../test/__snapshots__/index.js.snap | 48 ++--- .../add-35851-tree-control-selection | 4 + pnpm-lock.yaml | 166 +++++++++++++---- 19 files changed, 655 insertions(+), 194 deletions(-) create mode 100644 packages/js/components/changelog/add-35851-tree-control-selection create mode 100644 packages/js/components/src/experimental-tree-control/hooks/use-selection.ts create mode 100644 packages/js/components/src/experimental-tree-control/typings/global.d.ts create mode 100644 plugins/woocommerce/changelog/add-35851-tree-control-selection diff --git a/packages/js/components/changelog/add-35851-tree-control-selection b/packages/js/components/changelog/add-35851-tree-control-selection new file mode 100644 index 00000000000..e2cedaf2afd --- /dev/null +++ b/packages/js/components/changelog/add-35851-tree-control-selection @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add selection logic to tree control component diff --git a/packages/js/components/package.json b/packages/js/components/package.json index 1f810a551cd..3af65872964 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -48,8 +48,8 @@ "@wordpress/block-editor": "^9.8.0", "@wordpress/block-library": "^7.16.0", "@wordpress/blocks": "^11.18.0", - "@wordpress/components": "^19.5.0", - "@wordpress/compose": "^5.1.2", + "@wordpress/components": "19.8.5", + "@wordpress/compose": "5.4.1", "@wordpress/core-data": "^4.2.1", "@wordpress/date": "^4.3.1", "@wordpress/deprecated": "^3.3.1", diff --git a/packages/js/components/src/abbreviated-card/test/__snapshots__/index.js.snap b/packages/js/components/src/abbreviated-card/test/__snapshots__/index.js.snap index 4d273e3095f..18b50eef22f 100644 --- a/packages/js/components/src/abbreviated-card/test/__snapshots__/index.js.snap +++ b/packages/js/components/src/abbreviated-card/test/__snapshots__/index.js.snap @@ -3,7 +3,7 @@ exports[`AbbreviatedCard it renders correctly 1`] = `
@@ -11,7 +11,7 @@ exports[`AbbreviatedCard it renders correctly 1`] = ` class="css-mgwsf4-View-Content em57xhy0" >
diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-selection.ts b/packages/js/components/src/experimental-tree-control/hooks/use-selection.ts new file mode 100644 index 00000000000..255799940cc --- /dev/null +++ b/packages/js/components/src/experimental-tree-control/hooks/use-selection.ts @@ -0,0 +1,167 @@ +/** + * External dependencies + */ +import { useMemo } from 'react'; + +/** + * Internal dependencies + */ +import { CheckedStatus, Item, LinkedTree, TreeItemProps } from '../types'; + +let selectedItemsMap: Record< string, number > = {}; +let indeterminateMemo: Record< string, boolean > = {}; + +function getDeepChildren( item: LinkedTree ) { + if ( item.children.length ) { + const children = item.children.map( ( { data } ) => data ); + item.children.forEach( ( child ) => { + children.push( ...getDeepChildren( child ) ); + } ); + return children; + } + return []; +} + +function isIndeterminate( + selectedItems: Record< string, number >, + children?: LinkedTree[], + memo: Record< string, boolean > = indeterminateMemo +): boolean { + if ( children?.length ) { + for ( const child of children ) { + if ( child.data.value in indeterminateMemo ) { + return true; + } + const isChildSelected = child.data.value in selectedItems; + if ( + ! isChildSelected || + isIndeterminate( selectedItems, child.children, memo ) + ) { + indeterminateMemo[ child.data.value ] = true; + return true; + } + } + } + return false; +} + +function mapSelectedItems( + selected: Item | Item[] = [] +): Record< string, number > { + const selectedArray = Array.isArray( selected ) ? selected : [ selected ]; + return selectedArray.reduce( + ( map, selectedItem, index ) => ( { + ...map, + [ selectedItem.value ]: index, + } ), + {} as Record< string, number > + ); +} + +function hasSelectedSibblingChildren( + children: LinkedTree[], + values: Item[], + selectedItems: Record< string, number > +) { + return children.some( ( child ) => { + const isChildSelected = child.data.value in selectedItems; + if ( ! isChildSelected ) return false; + return ! values.some( + ( childValue ) => childValue.value === child.data.value + ); + } ); +} + +export function useSelection( { + item, + multiple, + selected, + level, + index, + onSelect, + onRemove, +}: Pick< + TreeItemProps, + | 'item' + | 'multiple' + | 'selected' + | 'level' + | 'index' + | 'onSelect' + | 'onRemove' +> ) { + const selectedItems = useMemo( () => { + if ( level === 1 && index === 0 ) { + selectedItemsMap = mapSelectedItems( selected ); + indeterminateMemo = {} as Record< string, boolean >; + } + return selectedItemsMap; + }, [ selected, level, index ] ); + + const checkedStatus: CheckedStatus = useMemo( () => { + if ( item.data.value in selectedItems ) { + if ( multiple && isIndeterminate( selectedItems, item.children ) ) { + return 'indeterminate'; + } + return 'checked'; + } + return 'unchecked'; + }, [ selectedItems, item, multiple ] ); + + function onSelectChild( checked: boolean ) { + let value: Item | Item[] = item.data; + + if ( multiple ) { + value = [ item.data ]; + if ( item.children.length ) { + value.push( ...getDeepChildren( item ) ); + } + } else if ( item.children?.length ) { + return; + } + + if ( checked ) { + if ( typeof onSelect === 'function' ) { + onSelect( value ); + } + } else if ( typeof onRemove === 'function' ) { + onRemove( value ); + } + } + + function onSelectChildren( value: Item | Item[] ) { + if ( typeof onSelect !== 'function' ) return; + + if ( multiple ) { + value = [ item.data, ...( value as Item[] ) ]; + } + + onSelect( value ); + } + + function onRemoveChildren( value: Item | Item[] ) { + if ( typeof onRemove !== 'function' ) return; + + if ( multiple && item.children?.length ) { + const hasSelectedSibbling = hasSelectedSibblingChildren( + item.children, + value as Item[], + selectedItems + ); + if ( ! hasSelectedSibbling ) { + value = [ item.data, ...( value as Item[] ) ]; + } + } + + onRemove( value ); + } + + return { + multiple, + selected, + checkedStatus, + onSelectChild, + onSelectChildren, + onRemoveChildren, + }; +} diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts index 65f43897d28..3a8d266c570 100644 --- a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts +++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts @@ -8,12 +8,18 @@ import React from 'react'; */ import { TreeItemProps } from '../types'; import { useExpander } from './use-expander'; +import { useSelection } from './use-selection'; export function useTreeItem( { item, level, + multiple, + selected, + index, getLabel, shouldItemBeExpanded, + onSelect, + onRemove, ...props }: TreeItemProps ) { const nextLevel = level + 1; @@ -23,10 +29,21 @@ export function useTreeItem( { shouldItemBeExpanded, } ); + const selection = useSelection( { + item, + multiple, + selected, + level, + index, + onSelect, + onRemove, + } ); + return { item, level: nextLevel, expander, + selection, getLabel, treeItemProps: { ...props, @@ -39,8 +56,12 @@ export function useTreeItem( { treeProps: { items: item.children, level: nextLevel, + multiple: selection.multiple, + selected: selection.selected, getItemLabel: getLabel, shouldItemBeExpanded, + onSelect: selection.onSelectChildren, + onRemove: selection.onRemoveChildren, }, }; } diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts index 5d88b2b84e9..385c98b4d99 100644 --- a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts +++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts @@ -11,8 +11,12 @@ export function useTree( { ref, items, level = 1, + multiple, + selected, getItemLabel, shouldItemBeExpanded, + onSelect, + onRemove, ...props }: TreeProps ) { return { @@ -23,8 +27,12 @@ export function useTree( { }, treeItemProps: { level, + multiple, + selected, getLabel: getItemLabel, shouldItemBeExpanded, + onSelect, + onRemove, }, }; } diff --git a/packages/js/components/src/experimental-tree-control/stories/index.tsx b/packages/js/components/src/experimental-tree-control/stories/index.tsx index e53a4852637..5ec34fdb145 100644 --- a/packages/js/components/src/experimental-tree-control/stories/index.tsx +++ b/packages/js/components/src/experimental-tree-control/stories/index.tsx @@ -10,6 +10,7 @@ import React, { createElement, useCallback, useState } from 'react'; */ import { TreeControl } from '../tree-control'; import { Item, LinkedTree } from '../types'; +import '../tree.scss'; const listItems: Item[] = [ { value: '1', label: 'Technology' }, @@ -120,25 +121,64 @@ function getItemLabel( item: LinkedTree, text: string ) { ); } -export const CustomItemLabelOnSearch: React.FC = () => { - const [ filter, setFilter ] = useState( '' ); +export const SelectionSingle: React.FC = () => { + const [ selected, setSelected ] = useState( listItems[ 1 ] ); return ( <> - - + getItemLabel( item, filter ) } - shouldItemBeExpanded={ ( item ) => - shouldItemBeExpanded( item, filter ) - } + selected={ selected } + onSelect={ ( value: Item ) => setSelected( value ) } /> + +
{ JSON.stringify( selected, null, 2 ) }
+ + ); +}; + +export const SelectionMultiple: React.FC = () => { + const [ selected, setSelected ] = useState( [ + listItems[ 0 ], + listItems[ 1 ], + ] ); + + function handleSelect( values: Item[] ) { + setSelected( ( items ) => { + const newItems = values.filter( + ( { value } ) => + ! items.some( ( item ) => item.value === value ) + ); + return [ ...items, ...newItems ]; + } ); + } + + function handleRemove( values: Item[] ) { + setSelected( ( items ) => + items.filter( + ( item ) => + ! values.some( ( { value } ) => item.value === value ) + ) + ); + } + + return ( + <> + + + + +
{ JSON.stringify( selected, null, 2 ) }
); }; diff --git a/packages/js/components/src/experimental-tree-control/tree-item.scss b/packages/js/components/src/experimental-tree-control/tree-item.scss index 4c36ce5ab6f..c3f51d28813 100644 --- a/packages/js/components/src/experimental-tree-control/tree-item.scss +++ b/packages/js/components/src/experimental-tree-control/tree-item.scss @@ -1,12 +1,21 @@ +$control-size: $gap-large; + .experimental-woocommerce-tree-item { margin: 0; + &--highlighted { + > .experimental-woocommerce-tree-item__heading { + background-color: $gray-100; + } + } + &__heading { display: flex; flex-grow: 1; gap: $gap-smaller; min-height: $gap-largest; - padding: 0 $gap-small 0 calc( ( var( --level ) - 1 ) * ( $gap + $gap-small ) + $gap-small ); + padding: 0 $gap-small 0 + calc( ( var( --level ) - 1 ) * ( $gap + $gap-small ) + $gap-small ); border-radius: 2px; &:hover, @@ -20,6 +29,7 @@ background-color: $gray-100; } } + &__label { display: flex; flex-grow: 1; @@ -30,13 +40,50 @@ > span { display: block; } + + .components-base-control__field { + margin: 0; + } + + .components-radio-control__input { + @include screen-reader-only(); + } + + .components-checkbox-control__label { + display: none; + } + + .components-checkbox-control__input-container { + display: block; + width: $control-size; + height: $control-size; + } + + svg.components-checkbox-control__checked, + svg.components-checkbox-control__indeterminate, + .components-checkbox-control__input[type='checkbox'] { + position: absolute; + border-color: $gray-700; + width: $control-size; + height: $control-size; + top: 0; + left: 0; + &:focus { + outline: none; + box-shadow: none; + } + } } + &__expander { display: flex; align-items: center; .components-button { padding: 0; + height: $control-size; + width: $control-size; + min-width: $control-size; } } } diff --git a/packages/js/components/src/experimental-tree-control/tree-item.tsx b/packages/js/components/src/experimental-tree-control/tree-item.tsx index 27c80c09263..c6fba054118 100644 --- a/packages/js/components/src/experimental-tree-control/tree-item.tsx +++ b/packages/js/components/src/experimental-tree-control/tree-item.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Button } from '@wordpress/components'; +import { Button, CheckboxControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp } from '@wordpress/icons'; import classNames from 'classnames'; @@ -24,6 +24,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem( headingProps, treeProps, expander: { isExpanded, onToggleExpand }, + selection, getLabel, } = useTreeItem( { ...props, @@ -35,20 +36,47 @@ export const TreeItem = forwardRef( function ForwardedTreeItem( { ...treeItemProps } className={ classNames( treeItemProps.className, - 'experimental-woocommerce-tree-item' + 'experimental-woocommerce-tree-item', + { + 'experimental-woocommerce-tree-item--highlighted': + ! selection.multiple && + selection.checkedStatus === 'checked', + } ) } >
-
+ { /* eslint-disable-next-line jsx-a11y/label-has-for */ } +
+ { Boolean( item.children?.length ) && (
diff --git a/packages/js/components/src/experimental-tree-control/tree.tsx b/packages/js/components/src/experimental-tree-control/tree.tsx index da3a2200839..64a975481ca 100644 --- a/packages/js/components/src/experimental-tree-control/tree.tsx +++ b/packages/js/components/src/experimental-tree-control/tree.tsx @@ -30,11 +30,12 @@ export const Tree = forwardRef( function ForwardedTree( `experimental-woocommerce-tree--level-${ level }` ) } > - { items.map( ( child ) => ( + { items.map( ( child, index ) => ( ) ) } diff --git a/packages/js/components/src/experimental-tree-control/types.ts b/packages/js/components/src/experimental-tree-control/types.ts index 0b7246c6b08..0fef356573b 100644 --- a/packages/js/components/src/experimental-tree-control/types.ts +++ b/packages/js/components/src/experimental-tree-control/types.ts @@ -10,52 +10,91 @@ export interface LinkedTree { children: LinkedTree[]; } -export type TreeProps = React.DetailedHTMLProps< - React.OlHTMLAttributes< HTMLOListElement >, - HTMLOListElement -> & { - level?: number; - items: LinkedTree[]; +export type CheckedStatus = 'checked' | 'unchecked' | 'indeterminate'; + +type BaseTreeProps = { /** - * It gives a way to render a different Element as the - * tree item label. - * - * @example - * ${ item.data.label } } - * /> - * - * @param item The current rendering tree item - * - * @see {@link LinkedTree} + * It contians one item if `multiple` value is false or + * a list of items if it is true. */ - getItemLabel?( item: LinkedTree ): JSX.Element; + selected?: Item | Item[]; /** - * Return if the tree item passed in should be expanded. - * - * @example - * checkExpanded( item, filter ) - * } - * /> - * - * @param item The tree item to determine if should be expanded. - * - * @see {@link LinkedTree} + * Whether the tree items are single or multiple selected. */ - shouldItemBeExpanded?( item: LinkedTree ): boolean; + multiple?: boolean; + /** + * When `multiple` is true and a child item is selected, all its + * ancestors and its descendants are also selected. If it's false + * only the clicked item is selected. + * + * @param value The selection + */ + onSelect?( value: Item | Item[] ): void; + /** + * When `multiple` is true and a child item is unselected, all its + * ancestors (if no sibblings are selected) and its descendants + * are also unselected. If it's false only the clicked item is + * unselected. + * + * @param value The unselection + */ + onRemove?( value: Item | Item[] ): void; }; -export type TreeItemProps = React.DetailedHTMLProps< - React.LiHTMLAttributes< HTMLLIElement >, - HTMLLIElement -> & { - level: number; - item: LinkedTree; - getLabel?( item: LinkedTree ): JSX.Element; - shouldItemBeExpanded?( item: LinkedTree ): boolean; -}; +export type TreeProps = BaseTreeProps & + Omit< + React.DetailedHTMLProps< + React.OlHTMLAttributes< HTMLOListElement >, + HTMLOListElement + >, + 'onSelect' + > & { + level?: number; + items: LinkedTree[]; + /** It gives a way to render a different Element as the + * tree item label. + * + * @example + * ${ item.data.label } } + * /> + * + * @param item The current rendering tree item + * + * @see {@link LinkedTree} + */ + getItemLabel?( item: LinkedTree ): JSX.Element; + /** + * Return if the tree item passed in should be expanded. + * + * @example + * checkExpanded( item, filter ) + * } + * /> + * + * @param item The tree item to determine if should be expanded. + * + * @see {@link LinkedTree} + */ + shouldItemBeExpanded?( item: LinkedTree ): boolean; + }; + +export type TreeItemProps = BaseTreeProps & + Omit< + React.DetailedHTMLProps< + React.LiHTMLAttributes< HTMLLIElement >, + HTMLLIElement + >, + 'onSelect' + > & { + level: number; + item: LinkedTree; + index: number; + getLabel?( item: LinkedTree ): JSX.Element; + shouldItemBeExpanded?( item: LinkedTree ): boolean; + }; export type TreeControlProps = Omit< TreeProps, 'items' | 'level' > & { items: Item[]; diff --git a/packages/js/components/src/experimental-tree-control/typings/global.d.ts b/packages/js/components/src/experimental-tree-control/typings/global.d.ts new file mode 100644 index 00000000000..ba0ce7abdf0 --- /dev/null +++ b/packages/js/components/src/experimental-tree-control/typings/global.d.ts @@ -0,0 +1,9 @@ +import * as components from '@wordpress/components'; + +declare module '@wordpress/components' { + declare namespace CheckboxControl { + interface Props { + indeterminate?: boolean; + } + } +} diff --git a/packages/js/components/src/search-list-control/test/__snapshots__/index.js.snap b/packages/js/components/src/search-list-control/test/__snapshots__/index.js.snap index c404f59cb55..67b48f026a7 100644 --- a/packages/js/components/src/search-list-control/test/__snapshots__/index.js.snap +++ b/packages/js/components/src/search-list-control/test/__snapshots__/index.js.snap @@ -47,13 +47,13 @@ Object { class="woocommerce-search-list__search" >