From 60085f43fd2e26b163f5ea3c750eafe9e264f735 Mon Sep 17 00:00:00 2001 From: LuigiPulcini Date: Mon, 1 Apr 2019 13:52:02 -0700 Subject: [PATCH 001/405] Add $item to wc_downloadable_file_permission Adding the parameter `$item` to both the function `wc_downloadable_file_permission` and the filter hook `woocommerce_downloadable_file_permission` would provide a further level of customization for the downloadable file being granted, depending on the metadata assigned to the single item when purchasing a product. --- includes/wc-order-functions.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index ee68ae42d6b..a6d9228350c 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -342,9 +342,10 @@ function wc_orders_count( $status ) { * @param int|WC_Product $product Product instance or ID. * @param WC_Order $order Order data. * @param int $qty Quantity purchased. + * @param WC_Order_Item $item Item of the order. * @return int|bool insert id or false on failure. */ -function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1 ) { +function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1, $item = null ) { if ( is_numeric( $product ) ) { $product = wc_get_product( $product ); } @@ -366,7 +367,7 @@ function wc_downloadable_file_permission( $download_id, $product, $order, $qty = $download->set_access_expires( strtotime( $from_date . ' + ' . $expiry . ' DAY' ) ); } - $download = apply_filters( 'woocommerce_downloadable_file_permission', $download, $product, $order, $qty ); + $download = apply_filters( 'woocommerce_downloadable_file_permission', $download, $product, $order, $qty, $item ); return $download->save(); } @@ -396,7 +397,7 @@ function wc_downloadable_product_permissions( $order_id, $force = false ) { $downloads = $product->get_downloads(); foreach ( array_keys( $downloads ) as $download_id ) { - wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity() ); + wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity(), $item ); } } } From 1dc4c8b8bfc73a3879d8caed6b5a437ddc4b9dc9 Mon Sep 17 00:00:00 2001 From: LuigiPulcini Date: Sun, 18 Oct 2020 13:25:02 +0200 Subject: [PATCH 002/405] Update class-wc-ajax.php --- includes/class-wc-ajax.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index d3eb96aa941..82c36be6ad3 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -765,7 +765,7 @@ class WC_AJAX { check_ajax_referer( 'grant-access', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['loop'], $_POST['order_id'], $_POST['product_ids'] ) ) { wp_die( -1 ); } @@ -774,17 +774,17 @@ class WC_AJAX { $wpdb->hide_errors(); $order_id = intval( $_POST['order_id'] ); - $product_ids = $_POST['product_ids']; + $product_ids = array_filter( array_map( 'absint', (array) wp_unslash( $_POST['product_ids'] ) ) ); $loop = intval( $_POST['loop'] ); $file_counter = 0; $order = wc_get_order( $order_id ); + $items = $order->get_items(); - if ( ! is_array( $product_ids ) ) { - $product_ids = array( $product_ids ); - } - - foreach ( $product_ids as $product_id ) { - $product = wc_get_product( $product_id ); + foreach ( $items as $item ) { + $product = $item->get_product(); + if ( ! in_array( $product->get_id(), $product_ids ) ) { + continue; + } $files = $product->get_downloads(); if ( ! $order->get_billing_email() ) { @@ -793,7 +793,8 @@ class WC_AJAX { if ( ! empty( $files ) ) { foreach ( $files as $download_id => $file ) { - if ( $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ) ) { + $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order, $item->get_quantity(), $item ); + if ( $inserted_id ) { $download = new WC_Customer_Download( $inserted_id ); $loop ++; $file_counter ++; @@ -801,9 +802,10 @@ class WC_AJAX { if ( $file->get_name() ) { $file_count = $file->get_name(); } else { + /* translators: %d file count */ $file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter ); } - include 'admin/meta-boxes/views/html-order-download-permission.php'; + include __DIR__ . '/admin/meta-boxes/views/html-order-download-permission.php'; } } } From 020a061895d6a56a8b7f2ab026d5ee9eb31fea16 Mon Sep 17 00:00:00 2001 From: LuigiPulcini Date: Sun, 18 Oct 2020 16:17:42 +0200 Subject: [PATCH 003/405] Update class-wc-ajax.php --- includes/class-wc-ajax.php | 1302 +++++++++++++++++++++--------------- 1 file changed, 776 insertions(+), 526 deletions(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 82c36be6ad3..f277ec2f082 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -2,10 +2,13 @@ /** * WooCommerce WC_AJAX. AJAX Event Handlers. * - * @class WC_AJAX - * @package WooCommerce/Classes + * @class WC_AJAX + * @package WooCommerce\Classes */ +use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Utilities\NumberUtil; + defined( 'ABSPATH' ) || exit; /** @@ -25,7 +28,8 @@ class WC_AJAX { /** * Get WC Ajax Endpoint. * - * @param string $request Optional. + * @param string $request Optional. + * * @return string */ public static function get_endpoint( $request = '' ) { @@ -36,6 +40,7 @@ class WC_AJAX { * Set WC AJAX constant and headers. */ public static function define_ajax() { + // phpcs:disable if ( ! empty( $_GET['wc-ajax'] ) ) { wc_maybe_define_constant( 'DOING_AJAX', true ); wc_maybe_define_constant( 'WC_DOING_AJAX', true ); @@ -44,6 +49,7 @@ class WC_AJAX { } $GLOBALS['wpdb']->hide_errors(); } + // phpcs:enable } /** @@ -52,12 +58,17 @@ class WC_AJAX { * @since 2.5.0 */ private static function wc_ajax_headers() { - send_origin_headers(); - @header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) ); - @header( 'X-Robots-Tag: noindex' ); - send_nosniff_header(); - wc_nocache_headers(); - status_header( 200 ); + if ( ! headers_sent() ) { + send_origin_headers(); + send_nosniff_header(); + wc_nocache_headers(); + header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) ); + header( 'X-Robots-Tag: noindex' ); + status_header( 200 ); + } elseif ( Constants::is_true( 'WP_DEBUG' ) ) { + headers_sent( $file, $line ); + trigger_error( "wc_ajax_headers cannot set headers - headers already sent by {$file} on line {$line}", E_USER_NOTICE ); // @codingStandardsIgnoreLine + } } /** @@ -66,6 +77,7 @@ class WC_AJAX { public static function do_wc_ajax() { global $wp_query; + // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['wc-ajax'] ) ) { $wp_query->set( 'wc-ajax', sanitize_text_field( wp_unslash( $_GET['wc-ajax'] ) ) ); } @@ -78,87 +90,91 @@ class WC_AJAX { do_action( 'wc_ajax_' . $action ); wp_die(); } + // phpcs:enable } /** * Hook in methods - uses WordPress ajax handlers (admin-ajax). */ public static function add_ajax_events() { - // woocommerce_EVENT => nopriv. - $ajax_events = array( - 'get_refreshed_fragments' => true, - 'apply_coupon' => true, - 'remove_coupon' => true, - 'update_shipping_method' => true, - 'get_cart_totals' => true, - 'update_order_review' => true, - 'add_to_cart' => true, - 'remove_from_cart' => true, - 'checkout' => true, - 'get_variation' => true, - 'get_customer_location' => true, - 'feature_product' => false, - 'mark_order_status' => false, - 'get_order_details' => false, - 'add_attribute' => false, - 'add_new_attribute' => false, - 'remove_variation' => false, - 'remove_variations' => false, - 'save_attributes' => false, - 'add_variation' => false, - 'link_all_variations' => false, - 'revoke_access_to_download' => false, - 'grant_access_to_download' => false, - 'get_customer_details' => false, - 'add_order_item' => false, - 'add_order_fee' => false, - 'add_order_shipping' => false, - 'add_order_tax' => false, - 'add_coupon_discount' => false, - 'remove_order_coupon' => false, - 'remove_order_item' => false, - 'remove_order_tax' => false, - 'reduce_order_item_stock' => false, - 'increase_order_item_stock' => false, - 'add_order_item_meta' => false, - 'remove_order_item_meta' => false, - 'calc_line_taxes' => false, - 'save_order_items' => false, - 'load_order_items' => false, - 'add_order_note' => false, - 'delete_order_note' => false, - 'json_search_products' => false, - 'json_search_products_and_variations' => false, - 'json_search_downloadable_products_and_variations' => false, - 'json_search_customers' => false, - 'json_search_categories' => false, - 'term_ordering' => false, - 'product_ordering' => false, - 'refund_line_items' => false, - 'delete_refund' => false, - 'rated' => false, - 'update_api_key' => false, - 'load_variations' => false, - 'save_variations' => false, - 'bulk_edit_variations' => false, - 'tax_rates_save_changes' => false, - 'shipping_zones_save_changes' => false, - 'shipping_zone_add_method' => false, - 'shipping_zone_methods_save_changes' => false, - 'shipping_zone_methods_save_settings' => false, - 'shipping_classes_save_changes' => false, - 'toggle_gateway_enabled' => false, + $ajax_events_nopriv = array( + 'get_refreshed_fragments', + 'apply_coupon', + 'remove_coupon', + 'update_shipping_method', + 'get_cart_totals', + 'update_order_review', + 'add_to_cart', + 'remove_from_cart', + 'checkout', + 'get_variation', + 'get_customer_location', ); - foreach ( $ajax_events as $ajax_event => $nopriv ) { + foreach ( $ajax_events_nopriv as $ajax_event ) { add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) ); + add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) ); - if ( $nopriv ) { - add_action( 'wp_ajax_nopriv_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) ); + // WC AJAX can be used for frontend ajax requests. + add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) ); + } - // WC AJAX can be used for frontend ajax requests. - add_action( 'wc_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) ); - } + $ajax_events = array( + 'feature_product', + 'mark_order_status', + 'get_order_details', + 'add_attribute', + 'add_new_attribute', + 'remove_variation', + 'remove_variations', + 'save_attributes', + 'add_variation', + 'link_all_variations', + 'revoke_access_to_download', + 'grant_access_to_download', + 'get_customer_details', + 'add_order_item', + 'add_order_fee', + 'add_order_shipping', + 'add_order_tax', + 'add_coupon_discount', + 'remove_order_coupon', + 'remove_order_item', + 'remove_order_tax', + 'reduce_order_item_stock', + 'increase_order_item_stock', + 'add_order_item_meta', + 'remove_order_item_meta', + 'calc_line_taxes', + 'save_order_items', + 'load_order_items', + 'add_order_note', + 'delete_order_note', + 'json_search_products', + 'json_search_products_and_variations', + 'json_search_downloadable_products_and_variations', + 'json_search_customers', + 'json_search_categories', + 'term_ordering', + 'product_ordering', + 'refund_line_items', + 'delete_refund', + 'rated', + 'update_api_key', + 'load_variations', + 'save_variations', + 'bulk_edit_variations', + 'tax_rates_save_changes', + 'shipping_zones_save_changes', + 'shipping_zone_add_method', + 'shipping_zone_methods_save_changes', + 'shipping_zone_methods_save_settings', + 'shipping_classes_save_changes', + 'toggle_gateway_enabled', + ); + + foreach ( $ajax_events as $ajax_event ) { + add_action( 'wp_ajax_woocommerce_' . $ajax_event, array( __CLASS__, $ajax_event ) ); } } @@ -174,11 +190,12 @@ class WC_AJAX { $data = array( 'fragments' => apply_filters( - 'woocommerce_add_to_cart_fragments', array( + 'woocommerce_add_to_cart_fragments', + array( 'div.widget_shopping_cart_content' => '
' . $mini_cart . '
', ) ), - 'cart_hash' => apply_filters( 'woocommerce_add_to_cart_hash', WC()->cart->get_cart_for_session() ? md5( json_encode( WC()->cart->get_cart_for_session() ) ) : '', WC()->cart->get_cart_for_session() ), + 'cart_hash' => WC()->cart->get_cart_hash(), ); wp_send_json( $data ); @@ -192,7 +209,7 @@ class WC_AJAX { check_ajax_referer( 'apply-coupon', 'security' ); if ( ! empty( $_POST['coupon_code'] ) ) { - WC()->cart->add_discount( sanitize_text_field( wp_unslash( $_POST['coupon_code'] ) ) ); + WC()->cart->add_discount( wc_format_coupon_code( wp_unslash( $_POST['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' ); } @@ -207,7 +224,7 @@ class WC_AJAX { public static function remove_coupon() { check_ajax_referer( 'remove-coupon', 'security' ); - $coupon = isset( $_POST['coupon'] ) ? wc_clean( $_POST['coupon'] ) : false; + $coupon = isset( $_POST['coupon'] ) ? wc_format_coupon_code( wp_unslash( $_POST['coupon'] ) ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $coupon ) ) { wc_add_notice( __( 'Sorry there was a problem removing this coupon.', 'woocommerce' ), 'error' ); @@ -229,10 +246,11 @@ class WC_AJAX { wc_maybe_define_constant( 'WOOCOMMERCE_CART', true ); $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); + $posted_shipping_methods = isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : array(); - if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) { - foreach ( $_POST['shipping_method'] as $i => $value ) { - $chosen_shipping_methods[ $i ] = wc_clean( $value ); + if ( is_array( $posted_shipping_methods ) ) { + foreach ( $posted_shipping_methods as $i => $value ) { + $chosen_shipping_methods[ $i ] = $value; } } @@ -258,7 +276,8 @@ class WC_AJAX { wp_send_json( array( 'fragments' => apply_filters( - 'woocommerce_update_order_review_fragments', array( + 'woocommerce_update_order_review_fragments', + array( 'form.woocommerce-checkout' => '
' . __( 'Sorry, your session has expired.', 'woocommerce' ) . ' ' . __( 'Return to shop', 'woocommerce' ) . '
', ) ), @@ -278,74 +297,79 @@ class WC_AJAX { self::update_order_review_expired(); } - do_action( 'woocommerce_checkout_update_order_review', $_POST['post_data'] ); + do_action( 'woocommerce_checkout_update_order_review', isset( $_POST['post_data'] ) ? wp_unslash( $_POST['post_data'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); + $posted_shipping_methods = isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : array(); - if ( isset( $_POST['shipping_method'] ) && is_array( $_POST['shipping_method'] ) ) { - foreach ( $_POST['shipping_method'] as $i => $value ) { - $chosen_shipping_methods[ $i ] = wc_clean( $value ); + if ( is_array( $posted_shipping_methods ) ) { + foreach ( $posted_shipping_methods as $i => $value ) { + $chosen_shipping_methods[ $i ] = $value; } } WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); - WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] ); + WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : wc_clean( wp_unslash( $_POST['payment_method'] ) ) ); WC()->customer->set_props( array( - 'billing_country' => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null, - 'billing_state' => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null, - 'billing_postcode' => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null, - 'billing_city' => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null, - 'billing_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null, - 'billing_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ) : null, + 'billing_country' => isset( $_POST['country'] ) ? wc_clean( wp_unslash( $_POST['country'] ) ) : null, + 'billing_state' => isset( $_POST['state'] ) ? wc_clean( wp_unslash( $_POST['state'] ) ) : null, + 'billing_postcode' => isset( $_POST['postcode'] ) ? wc_clean( wp_unslash( $_POST['postcode'] ) ) : null, + 'billing_city' => isset( $_POST['city'] ) ? wc_clean( wp_unslash( $_POST['city'] ) ) : null, + 'billing_address_1' => isset( $_POST['address'] ) ? wc_clean( wp_unslash( $_POST['address'] ) ) : null, + 'billing_address_2' => isset( $_POST['address_2'] ) ? wc_clean( wp_unslash( $_POST['address_2'] ) ) : null, ) ); if ( wc_ship_to_billing_address_only() ) { WC()->customer->set_props( array( - 'shipping_country' => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null, - 'shipping_state' => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null, - 'shipping_postcode' => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null, - 'shipping_city' => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null, - 'shipping_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null, - 'shipping_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ) : null, + 'shipping_country' => isset( $_POST['country'] ) ? wc_clean( wp_unslash( $_POST['country'] ) ) : null, + 'shipping_state' => isset( $_POST['state'] ) ? wc_clean( wp_unslash( $_POST['state'] ) ) : null, + 'shipping_postcode' => isset( $_POST['postcode'] ) ? wc_clean( wp_unslash( $_POST['postcode'] ) ) : null, + 'shipping_city' => isset( $_POST['city'] ) ? wc_clean( wp_unslash( $_POST['city'] ) ) : null, + 'shipping_address_1' => isset( $_POST['address'] ) ? wc_clean( wp_unslash( $_POST['address'] ) ) : null, + 'shipping_address_2' => isset( $_POST['address_2'] ) ? wc_clean( wp_unslash( $_POST['address_2'] ) ) : null, ) ); } else { WC()->customer->set_props( array( - 'shipping_country' => isset( $_POST['s_country'] ) ? wp_unslash( $_POST['s_country'] ) : null, - 'shipping_state' => isset( $_POST['s_state'] ) ? wp_unslash( $_POST['s_state'] ) : null, - 'shipping_postcode' => isset( $_POST['s_postcode'] ) ? wp_unslash( $_POST['s_postcode'] ) : null, - 'shipping_city' => isset( $_POST['s_city'] ) ? wp_unslash( $_POST['s_city'] ) : null, - 'shipping_address_1' => isset( $_POST['s_address'] ) ? wp_unslash( $_POST['s_address'] ) : null, - 'shipping_address_2' => isset( $_POST['s_address_2'] ) ? wp_unslash( $_POST['s_address_2'] ) : null, + 'shipping_country' => isset( $_POST['s_country'] ) ? wc_clean( wp_unslash( $_POST['s_country'] ) ) : null, + 'shipping_state' => isset( $_POST['s_state'] ) ? wc_clean( wp_unslash( $_POST['s_state'] ) ) : null, + 'shipping_postcode' => isset( $_POST['s_postcode'] ) ? wc_clean( wp_unslash( $_POST['s_postcode'] ) ) : null, + 'shipping_city' => isset( $_POST['s_city'] ) ? wc_clean( wp_unslash( $_POST['s_city'] ) ) : null, + 'shipping_address_1' => isset( $_POST['s_address'] ) ? wc_clean( wp_unslash( $_POST['s_address'] ) ) : null, + 'shipping_address_2' => isset( $_POST['s_address_2'] ) ? wc_clean( wp_unslash( $_POST['s_address_2'] ) ) : null, ) ); } - if ( wc_string_to_bool( $_POST['has_full_address'] ) ) { + if ( isset( $_POST['has_full_address'] ) && wc_string_to_bool( wc_clean( wp_unslash( $_POST['has_full_address'] ) ) ) ) { WC()->customer->set_calculated_shipping( true ); } else { WC()->customer->set_calculated_shipping( false ); } WC()->customer->save(); + + // Calculate shipping before totals. This will ensure any shipping methods that affect things like taxes are chosen prior to final totals being calculated. Ref: #22708. + WC()->cart->calculate_shipping(); WC()->cart->calculate_totals(); - // Get order review fragment + // Get order review fragment. ob_start(); woocommerce_order_review(); $woocommerce_order_review = ob_get_clean(); - // Get checkout payment fragment + // Get checkout payment fragment. ob_start(); woocommerce_checkout_payment(); $woocommerce_checkout_payment = ob_get_clean(); - // Get messages if reload checkout is not true - if ( ! isset( WC()->session->reload_checkout ) ) { + // Get messages if reload checkout is not true. + $reload_checkout = isset( WC()->session->reload_checkout ) ? true : false; + if ( ! $reload_checkout ) { $messages = wc_print_notices( true ); } else { $messages = ''; @@ -357,9 +381,10 @@ class WC_AJAX { array( 'result' => empty( $messages ) ? 'success' : 'failure', 'messages' => $messages, - 'reload' => isset( WC()->session->reload_checkout ) ? 'true' : 'false', + 'reload' => $reload_checkout, 'fragments' => apply_filters( - 'woocommerce_update_order_review_fragments', array( + 'woocommerce_update_order_review_fragments', + array( '.woocommerce-checkout-review-order-table' => $woocommerce_order_review, '.woocommerce-checkout-payment' => $woocommerce_checkout_payment, ) @@ -374,9 +399,14 @@ class WC_AJAX { public static function add_to_cart() { ob_start(); + // phpcs:disable WordPress.Security.NonceVerification.Missing + if ( ! isset( $_POST['product_id'] ) ) { + return; + } + $product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) ); $product = wc_get_product( $product_id ); - $quantity = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] ); + $quantity = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_POST['quantity'] ) ); $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity ); $product_status = get_post_status( $product_id ); $variation_id = 0; @@ -396,12 +426,11 @@ class WC_AJAX { wc_add_to_cart_message( array( $product_id => $quantity ), true ); } - // Return fragments self::get_refreshed_fragments(); } else { - // If there was an error adding to the cart, redirect to the product page to show any errors + // If there was an error adding to the cart, redirect to the product page to show any errors. $data = array( 'error' => true, 'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id ), @@ -409,6 +438,7 @@ class WC_AJAX { wp_send_json( $data ); } + // phpcs:enable } /** @@ -417,7 +447,8 @@ class WC_AJAX { public static function remove_from_cart() { ob_start(); - $cart_item_key = wc_clean( $_POST['cart_item_key'] ); + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $cart_item_key = wc_clean( isset( $_POST['cart_item_key'] ) ? wp_unslash( $_POST['cart_item_key'] ) : '' ); if ( $cart_item_key && false !== WC()->cart->remove_cart_item( $cart_item_key ) ) { self::get_refreshed_fragments(); @@ -441,7 +472,14 @@ class WC_AJAX { public static function get_variation() { ob_start(); - if ( empty( $_POST['product_id'] ) || ! ( $variable_product = wc_get_product( absint( $_POST['product_id'] ) ) ) ) { + // phpcs:disable WordPress.Security.NonceVerification.Missing + if ( empty( $_POST['product_id'] ) ) { + wp_die(); + } + + $variable_product = wc_get_product( absint( $_POST['product_id'] ) ); + + if ( ! $variable_product ) { wp_die(); } @@ -449,6 +487,7 @@ class WC_AJAX { $variation_id = $data_store->find_matching_product_variation( $variable_product, wp_unslash( $_POST ) ); $variation = $variation_id ? $variable_product->get_available_variation( $variation_id ) : false; wp_send_json( $variation ); + // phpcs:enable } /** @@ -463,7 +502,7 @@ class WC_AJAX { * Toggle Featured status of a product from admin. */ public static function feature_product() { - if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) ) { + if ( current_user_can( 'edit_products' ) && check_admin_referer( 'woocommerce-feature-product' ) && isset( $_GET['product_id'] ) ) { $product = wc_get_product( absint( $_GET['product_id'] ) ); if ( $product ) { @@ -480,13 +519,13 @@ class WC_AJAX { * Mark an order with a status. */ public static function mark_order_status() { - if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) ) { - $status = sanitize_text_field( $_GET['status'] ); - $order = wc_get_order( absint( $_GET['order_id'] ) ); + if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'woocommerce-mark-order-status' ) && isset( $_GET['status'], $_GET['order_id'] ) ) { + $status = sanitize_text_field( wp_unslash( $_GET['status'] ) ); + $order = wc_get_order( absint( wp_unslash( $_GET['order_id'] ) ) ); if ( wc_is_order_status( 'wc-' . $status ) && $order ) { // Initialize payment gateways in case order has hooked status transition actions. - wc()->payment_gateways(); + WC()->payment_gateways(); $order->update_status( $status, '', true ); do_action( 'woocommerce_order_edit_status', $order->get_id(), $status ); @@ -503,14 +542,14 @@ class WC_AJAX { public static function get_order_details() { check_admin_referer( 'woocommerce-preview-order', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_GET['order_id'] ) ) { wp_die( -1 ); } - $order = wc_get_order( absint( $_GET['order_id'] ) ); // WPCS: sanitization ok. + $order = wc_get_order( absint( $_GET['order_id'] ) ); if ( $order ) { - include_once 'admin/list-tables/class-wc-admin-list-table-orders.php'; + include_once __DIR__ . '/admin/list-tables/class-wc-admin-list-table-orders.php'; wp_send_json_success( WC_Admin_List_Table_Orders::order_preview_get_order_details( $order ) ); } @@ -525,7 +564,7 @@ class WC_AJAX { check_ajax_referer( 'add-attribute', 'security' ); - if ( ! current_user_can( 'edit_products' ) ) { + if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['taxonomy'], $_POST['i'] ) ) { wp_die( -1 ); } @@ -533,8 +572,8 @@ class WC_AJAX { $metabox_class = array(); $attribute = new WC_Product_Attribute(); - $attribute->set_id( wc_attribute_taxonomy_id_by_name( sanitize_text_field( $_POST['taxonomy'] ) ) ); - $attribute->set_name( sanitize_text_field( $_POST['taxonomy'] ) ); + $attribute->set_id( wc_attribute_taxonomy_id_by_name( sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) ) ); + $attribute->set_name( sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) ); $attribute->set_visible( apply_filters( 'woocommerce_attribute_default_visibility', 1 ) ); $attribute->set_variation( apply_filters( 'woocommerce_attribute_default_is_variation', 0 ) ); @@ -543,7 +582,7 @@ class WC_AJAX { $metabox_class[] = $attribute->get_name(); } - include 'admin/meta-boxes/views/html-product-attribute.php'; + include __DIR__ . '/admin/meta-boxes/views/html-product-attribute.php'; wp_die(); } @@ -553,9 +592,9 @@ class WC_AJAX { public static function add_new_attribute() { check_ajax_referer( 'add-attribute', 'security' ); - if ( current_user_can( 'manage_product_terms' ) ) { - $taxonomy = esc_attr( $_POST['taxonomy'] ); - $term = wc_clean( $_POST['term'] ); + if ( current_user_can( 'manage_product_terms' ) && isset( $_POST['taxonomy'], $_POST['term'] ) ) { + $taxonomy = esc_attr( wp_unslash( $_POST['taxonomy'] ) ); // phpcs:ignore + $term = wc_clean( wp_unslash( $_POST['term'] ) ); if ( taxonomy_exists( $taxonomy ) ) { @@ -588,8 +627,8 @@ class WC_AJAX { public static function remove_variations() { check_ajax_referer( 'delete-variations', 'security' ); - if ( current_user_can( 'edit_products' ) ) { - $variation_ids = (array) $_POST['variation_ids']; + if ( current_user_can( 'edit_products' ) && isset( $_POST['variation_ids'] ) ) { + $variation_ids = array_map( 'absint', (array) wp_unslash( $_POST['variation_ids'] ) ); foreach ( $variation_ids as $variation_id ) { if ( 'product_variation' === get_post_type( $variation_id ) ) { @@ -608,50 +647,52 @@ class WC_AJAX { public static function save_attributes() { check_ajax_referer( 'save-attributes', 'security' ); - if ( ! current_user_can( 'edit_products' ) ) { + if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['data'], $_POST['post_id'] ) ) { wp_die( -1 ); } + $response = array(); + try { - parse_str( $_POST['data'], $data ); + parse_str( wp_unslash( $_POST['data'] ), $data ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $attributes = WC_Meta_Box_Product_Data::prepare_attributes( $data ); - $product_id = absint( $_POST['post_id'] ); - $product_type = ! empty( $_POST['product_type'] ) ? wc_clean( $_POST['product_type'] ) : 'simple'; + $product_id = absint( wp_unslash( $_POST['post_id'] ) ); + $product_type = ! empty( $_POST['product_type'] ) ? wc_clean( wp_unslash( $_POST['product_type'] ) ) : 'simple'; $classname = WC_Product_Factory::get_product_classname( $product_id, $product_type ); $product = new $classname( $product_id ); $product->set_attributes( $attributes ); $product->save(); - $response = array(); - ob_start(); $attributes = $product->get_attributes( 'edit' ); $i = -1; + if ( ! empty( $data['attribute_names'] ) ) { + foreach ( $data['attribute_names'] as $attribute_name ) { + $attribute = isset( $attributes[ sanitize_title( $attribute_name ) ] ) ? $attributes[ sanitize_title( $attribute_name ) ] : false; + if ( ! $attribute ) { + continue; + } + $i++; + $metabox_class = array(); - foreach ( $attributes as $attribute ) { - if ( ! $attribute ) { - continue; + if ( $attribute->is_taxonomy() ) { + $metabox_class[] = 'taxonomy'; + $metabox_class[] = $attribute->get_name(); + } + + include __DIR__ . '/admin/meta-boxes/views/html-product-attribute.php'; } - $i++; - $metabox_class = array(); - - if ( $attribute->is_taxonomy() ) { - $metabox_class[] = 'taxonomy'; - $metabox_class[] = $attribute->get_name(); - } - - include( 'admin/meta-boxes/views/html-product-attribute.php' ); } $response['html'] = ob_get_clean(); - - wp_send_json_success( $response ); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } - wp_die(); + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** @@ -660,23 +701,23 @@ class WC_AJAX { public static function add_variation() { check_ajax_referer( 'add-variation', 'security' ); - if ( ! current_user_can( 'edit_products' ) ) { + if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['post_id'], $_POST['loop'] ) ) { wp_die( -1 ); } global $post; // Set $post global so its available, like within the admin screens. $product_id = intval( $_POST['post_id'] ); - $post = get_post( $product_id ); + $post = get_post( $product_id ); // phpcs:ignore $loop = intval( $_POST['loop'] ); - $product_object = wc_get_product( $product_id ); - $variation_object = new WC_Product_Variation(); + $product_object = wc_get_product_object( 'variable', $product_id ); // Forces type to variable in case product is unsaved. + $variation_object = wc_get_product_object( 'variation' ); $variation_object->set_parent_id( $product_id ); $variation_object->set_attributes( array_fill_keys( array_map( 'sanitize_title', array_keys( $product_object->get_variation_attributes() ) ), '' ) ); $variation_id = $variation_object->save(); $variation = get_post( $variation_id ); - $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. - include 'admin/meta-boxes/views/html-variation-admin.php'; + $variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. + include __DIR__ . '/admin/meta-boxes/views/html-variation-admin.php'; wp_die(); } @@ -690,49 +731,24 @@ class WC_AJAX { wp_die( -1 ); } - wc_maybe_define_constant( 'WC_MAX_LINKED_VARIATIONS', 49 ); + wc_maybe_define_constant( 'WC_MAX_LINKED_VARIATIONS', 50 ); wc_set_time_limit( 0 ); - $post_id = intval( $_POST['post_id'] ); + $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; if ( ! $post_id ) { wp_die(); } $product = wc_get_product( $post_id ); - $attributes = wc_list_pluck( array_filter( $product->get_attributes(), 'wc_attributes_array_filter_variation' ), 'get_slugs' ); + $data_store = $product->get_data_store(); - if ( ! empty( $attributes ) ) { - // Get existing variations so we don't create duplicates. - $existing_variations = array_map( 'wc_get_product', $product->get_children() ); - $existing_attributes = array(); - - foreach ( $existing_variations as $existing_variation ) { - $existing_attributes[] = $existing_variation->get_attributes(); - } - - $added = 0; - $possible_attributes = array_reverse( wc_array_cartesian( $attributes ) ); - - foreach ( $possible_attributes as $possible_attribute ) { - if ( in_array( $possible_attribute, $existing_attributes ) ) { - continue; - } - $variation = new WC_Product_Variation(); - $variation->set_parent_id( $post_id ); - $variation->set_attributes( $possible_attribute ); - - do_action( 'product_variation_linked', $variation->save() ); - - if ( ( $added ++ ) > WC_MAX_LINKED_VARIATIONS ) { - break; - } - } - - echo $added; + if ( ! is_callable( array( $data_store, 'create_all_product_variations' ) ) ) { + wp_die(); } - $data_store = $product->get_data_store(); + echo esc_html( $data_store->create_all_product_variations( $product, Constants::get_constant( 'WC_MAX_LINKED_VARIATIONS' ) ) ); + $data_store->sort_all_product_variations( $product->get_id() ); wp_die(); } @@ -743,10 +759,10 @@ class WC_AJAX { public static function revoke_access_to_download() { check_ajax_referer( 'revoke-access', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['download_id'], $_POST['product_id'], $_POST['order_id'], $_POST['permission_id'] ) ) { wp_die( -1 ); } - $download_id = $_POST['download_id']; + $download_id = wc_clean( wp_unslash( $_POST['download_id'] ) ); $product_id = intval( $_POST['product_id'] ); $order_id = intval( $_POST['order_id'] ); $permission_id = absint( $_POST['permission_id'] ); @@ -819,7 +835,7 @@ class WC_AJAX { public static function get_customer_details() { check_ajax_referer( 'get-customer-details', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['user_id'] ) ) { wp_die( -1 ); } @@ -839,7 +855,9 @@ class WC_AJAX { } /** - * Add order item via ajax. + * Add order item via ajax. Used on the edit order screen in WP Admin. + * + * @throws Exception If order is invalid. */ public static function add_order_item() { check_ajax_referer( 'order-item', 'security' ); @@ -848,31 +866,51 @@ class WC_AJAX { wp_die( -1 ); } - try { - if ( ! isset( $_POST['order_id'] ) ) { - throw new Exception( __( 'Invalid order', 'woocommerce' ) ); - } + if ( ! isset( $_POST['order_id'] ) ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); + } + $order_id = absint( wp_unslash( $_POST['order_id'] ) ); - $order_id = absint( wp_unslash( $_POST['order_id'] ) ); // WPCS: input var ok. - $order = wc_get_order( $order_id ); + // If we passed through items it means we need to save first before adding a new one. + $items = ( ! empty( $_POST['items'] ) ) ? wp_unslash( $_POST['items'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + $items_to_add = isset( $_POST['data'] ) ? array_filter( wp_unslash( (array) $_POST['data'] ) ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + try { + $response = self::maybe_add_order_item( $order_id, $items, $items_to_add ); + wp_send_json_success( $response ); + } catch ( Exception $e ) { + wp_send_json_error( array( 'error' => $e->getMessage() ) ); + } + } + + /** + * Add order item via AJAX. This is refactored for better unit testing. + * + * @param int $order_id ID of order to add items to. + * @param string|array $items Existing items in order. Empty string if no items to add. + * @param array $items_to_add Array of items to add. + * + * @return array Fragments to render and notes HTML. + * @throws Exception When unable to add item. + */ + private static function maybe_add_order_item( $order_id, $items, $items_to_add ) { + try { + $order = wc_get_order( $order_id ); if ( ! $order ) { throw new Exception( __( 'Invalid order', 'woocommerce' ) ); } - // If we passed through items it means we need to save first before adding a new one. - $items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : ''; - if ( ! empty( $items ) ) { $save_items = array(); parse_str( $items, $save_items ); - // Save order items. wc_save_order_items( $order->get_id(), $save_items ); } - $items_to_add = array_filter( wp_unslash( (array) $_POST['data'] ) ); - // Add items to order. + $order_notes = array(); + foreach ( $items_to_add as $item ) { if ( ! isset( $item['id'], $item['qty'] ) || empty( $item['id'] ) ) { continue; @@ -884,32 +922,57 @@ class WC_AJAX { if ( ! $product ) { throw new Exception( __( 'Invalid product ID', 'woocommerce' ) . ' ' . $product_id ); } + if ( 'variable' === $product->get_type() ) { + /* translators: %s product name */ + throw new Exception( sprintf( __( '%s is a variable product parent and cannot be added.', 'woocommerce' ), $product->get_name() ) ); + } + $validation_error = new WP_Error(); + $validation_error = apply_filters( 'woocommerce_ajax_add_order_item_validation', $validation_error, $product, $order, $qty ); + if ( $validation_error->get_error_code() ) { + /* translators: %s: error message */ + throw new Exception( sprintf( __( 'Error: %s', 'woocommerce' ), $validation_error->get_error_message() ) ); + } $item_id = $order->add_product( $product, $qty ); - $item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id ); + $item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id, $order, $product ); $added_items[ $item_id ] = $item; + $order_notes[ $item_id ] = $product->get_formatted_name(); + + // We do not perform any stock operations here because they will be handled when order is moved to a status where stock operations are applied (like processing, completed etc). do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order ); } + /* translators: %s item name. */ + $order->add_order_note( sprintf( __( 'Added line items: %s', 'woocommerce' ), implode( ', ', $order_notes ) ), false, true ); + do_action( 'woocommerce_ajax_order_items_added', $added_items, $order ); $data = get_post_meta( $order_id ); + // Get HTML to return. ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $items_html = ob_get_clean(); + + ob_start(); + $notes = wc_get_order_notes( array( 'order_id' => $order_id ) ); + include __DIR__ . '/admin/meta-boxes/views/html-order-notes.php'; + $notes_html = ob_get_clean(); + + return array( + 'html' => $items_html, + 'notes_html' => $notes_html, ); } catch ( Exception $e ) { - wp_send_json_error( array( 'error' => $e->getMessage() ) ); + throw $e; // Forward exception to caller. } } /** * Add order fee via ajax. + * + * @throws Exception If order is invalid. */ public static function add_order_fee() { check_ajax_referer( 'order-item', 'security' ); @@ -918,21 +981,25 @@ class WC_AJAX { wp_die( -1 ); } + $response = array(); + try { - $order_id = absint( $_POST['order_id'] ); - $amount = wc_clean( $_POST['amount'] ); - $order = wc_get_order( $order_id ); - $calculate_tax_args = array( - 'country' => strtoupper( wc_clean( $_POST['country'] ) ), - 'state' => strtoupper( wc_clean( $_POST['state'] ) ), - 'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ), - 'city' => strtoupper( wc_clean( $_POST['city'] ) ), - ); + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); if ( ! $order ) { - throw new exception( __( 'Invalid order', 'woocommerce' ) ); + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); } + $amount = isset( $_POST['amount'] ) ? wc_clean( wp_unslash( $_POST['amount'] ) ) : 0; + + $calculate_tax_args = array( + 'country' => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '', + 'state' => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '', + 'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '', + 'city' => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '', + ); + if ( strstr( $amount, '%' ) ) { $formatted_amount = $amount; $percent = floatval( trim( $amount, '%' ) ); @@ -945,7 +1012,8 @@ class WC_AJAX { $fee = new WC_Order_Item_Fee(); $fee->set_amount( $amount ); $fee->set_total( $amount ); - $fee->set_name( sprintf( __( '%s fee', 'woocommerce' ), $formatted_amount ) ); + /* translators: %s fee amount */ + $fee->set_name( sprintf( __( '%s fee', 'woocommerce' ), wc_clean( $formatted_amount ) ) ); $order->add_item( $fee ); $order->calculate_taxes( $calculate_tax_args ); @@ -953,20 +1021,20 @@ class WC_AJAX { $order->save(); ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; - - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Add order shipping cost via ajax. + * + * @throws Exception If order is invalid. */ public static function add_order_shipping() { check_ajax_referer( 'order-item', 'security' ); @@ -975,33 +1043,40 @@ class WC_AJAX { wp_die( -1 ); } - try { - $order_id = absint( $_POST['order_id'] ); - $order = wc_get_order( $order_id ); - $order_taxes = $order->get_taxes(); - $shipping_methods = WC()->shipping() ? WC()->shipping->load_shipping_methods() : array(); + $response = array(); - // Add new shipping + try { + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); + } + + $order_taxes = $order->get_taxes(); + $shipping_methods = WC()->shipping() ? WC()->shipping()->load_shipping_methods() : array(); + + // Add new shipping. $item = new WC_Order_Item_Shipping(); $item->set_shipping_rate( new WC_Shipping_Rate() ); $item->set_order_id( $order_id ); $item_id = $item->save(); ob_start(); - include 'admin/meta-boxes/views/html-order-shipping.php'; - - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + include __DIR__ . '/admin/meta-boxes/views/html-order-shipping.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Add order tax column via ajax. + * + * @throws Exception If order or tax rate is invalid. */ public static function add_order_tax() { check_ajax_referer( 'order-item', 'security' ); @@ -1010,33 +1085,45 @@ class WC_AJAX { wp_die( -1 ); } - try { - $order_id = absint( $_POST['order_id'] ); - $rate_id = absint( $_POST['rate_id'] ); - $order = wc_get_order( $order_id ); - $data = get_post_meta( $order_id ); + $response = array(); - // Add new tax + try { + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); + } + + $rate_id = isset( $_POST['rate_id'] ) ? absint( $_POST['rate_id'] ) : ''; + + if ( ! $rate_id ) { + throw new Exception( __( 'Invalid rate', 'woocommerce' ) ); + } + + $data = get_post_meta( $order_id ); + + // Add new tax. $item = new WC_Order_Item_Tax(); $item->set_rate( $rate_id ); $item->set_order_id( $order_id ); $item->save(); ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; - - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Add order discount via ajax. + * + * @throws Exception If order or coupon is invalid. */ public static function add_coupon_discount() { check_ajax_referer( 'order-item', 'security' ); @@ -1045,30 +1132,61 @@ class WC_AJAX { wp_die( -1 ); } - try { - $order_id = absint( $_POST['order_id'] ); - $order = wc_get_order( $order_id ); - $result = $order->apply_coupon( wc_clean( $_POST['coupon'] ) ); + $response = array(); - if ( is_wp_error( $result ) ) { - throw new Exception( $result->get_error_message() ); + try { + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); + $calculate_tax_args = array( + 'country' => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '', + 'state' => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '', + 'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '', + 'city' => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '', + ); + + if ( ! $order ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); } - ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; + if ( empty( $_POST['coupon'] ) ) { + throw new Exception( __( 'Invalid coupon', 'woocommerce' ) ); + } - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + // Add user ID and/or email so validation for coupon limits works. + $user_id_arg = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0; + $user_email_arg = isset( $_POST['user_email'] ) ? sanitize_email( wp_unslash( $_POST['user_email'] ) ) : ''; + + if ( $user_id_arg ) { + $order->set_customer_id( $user_id_arg ); + } + if ( $user_email_arg ) { + $order->set_billing_email( $user_email_arg ); + } + + $result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $_POST['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() ) ) ); + } + + $order->calculate_taxes( $calculate_tax_args ); + $order->calculate_totals( false ); + + ob_start(); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Remove coupon from an order via ajax. + * + * @throws Exception If order or coupon is invalid. */ public static function remove_order_coupon() { check_ajax_referer( 'order-item', 'security' ); @@ -1077,44 +1195,74 @@ class WC_AJAX { wp_die( -1 ); } - try { - $order_id = absint( $_POST['order_id'] ); - $order = wc_get_order( $order_id ); + $response = array(); - $order->remove_coupon( wc_clean( $_POST['coupon'] ) ); + try { + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); + $calculate_tax_args = array( + 'country' => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '', + 'state' => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '', + 'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '', + 'city' => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '', + ); + + if ( ! $order ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); + } + + if ( empty( $_POST['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->calculate_taxes( $calculate_tax_args ); + $order->calculate_totals( false ); ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; - - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Remove an order item. + * + * @throws Exception If order is invalid. */ public static function remove_order_item() { check_ajax_referer( 'order-item', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['order_item_ids'] ) ) { wp_die( -1 ); } + $response = array(); + try { - $order_id = absint( $_POST['order_id'] ); - $order_item_ids = $_POST['order_item_ids']; - $items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : ''; + $order_id = absint( $_POST['order_id'] ); + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + throw new Exception( __( 'Invalid order', 'woocommerce' ) ); + } + + if ( ! isset( $_POST['order_item_ids'] ) ) { + throw new Exception( __( 'Invalid items', 'woocommerce' ) ); + } + + $order_item_ids = wp_unslash( $_POST['order_item_ids'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $items = ( ! empty( $_POST['items'] ) ) ? wp_unslash( $_POST['items'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $calculate_tax_args = array( - 'country' => strtoupper( wc_clean( $_POST['country'] ) ), - 'state' => strtoupper( wc_clean( $_POST['state'] ) ), - 'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ), - 'city' => strtoupper( wc_clean( $_POST['city'] ) ), + 'country' => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '', + 'state' => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '', + 'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '', + 'city' => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '', ); if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) { @@ -1125,13 +1273,30 @@ class WC_AJAX { if ( ! empty( $items ) ) { $save_items = array(); parse_str( $items, $save_items ); - // Save order items - wc_save_order_items( $order_id, $save_items ); + wc_save_order_items( $order->get_id(), $save_items ); } - if ( sizeof( $order_item_ids ) > 0 ) { - foreach ( $order_item_ids as $id ) { - wc_delete_order_item( absint( $id ) ); + if ( ! empty( $order_item_ids ) ) { + $order_notes = array(); + + foreach ( $order_item_ids as $item_id ) { + $item_id = absint( $item_id ); + $item = $order->get_item( $item_id ); + + // Before deleting the item, adjust any stock values already reduced. + if ( $item->is_type( 'line_item' ) ) { + $changed_stock = wc_maybe_adjust_line_item_product_stock( $item, 0 ); + + if ( $changed_stock && ! is_wp_error( $changed_stock ) ) { + /* translators: %1$s: item name %2$s: stock change */ + $order->add_order_note( sprintf( __( 'Deleted %1$s and adjusted stock (%2$s)', 'woocommerce' ), $item->get_name(), $changed_stock['from'] . '→' . $changed_stock['to'] ), false, true ); + } else { + /* translators: %s item name. */ + $order->add_order_note( sprintf( __( 'Deleted %s', 'woocommerce' ), $item->get_name() ), false, true ); + } + } + + wc_delete_order_item( $item_id ); } } @@ -1139,49 +1304,68 @@ class WC_AJAX { $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); + // Get HTML to return. ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $items_html = ob_get_clean(); + + ob_start(); + $notes = wc_get_order_notes( array( 'order_id' => $order_id ) ); + include __DIR__ . '/admin/meta-boxes/views/html-order-notes.php'; + $notes_html = ob_get_clean(); wp_send_json_success( array( - 'html' => ob_get_clean(), + 'html' => $items_html, + 'notes_html' => $notes_html, ) ); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** * Remove an order tax. + * + * @throws Exception If there is an error whilst deleting the rate. */ public static function remove_order_tax() { check_ajax_referer( 'order-item', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['rate_id'] ) ) { wp_die( -1 ); } + $response = array(); + try { $order_id = absint( $_POST['order_id'] ); $rate_id = absint( $_POST['rate_id'] ); + $order = wc_get_order( $order_id ); + if ( ! $order->is_editable() ) { + throw new Exception( __( 'Order not editable', 'woocommerce' ) ); + } + wc_delete_order_item( $rate_id ); + // Need to load order again after deleting to have latest items before calculating. $order = wc_get_order( $order_id ); $order->calculate_totals( false ); ob_start(); - include 'admin/meta-boxes/views/html-order-items.php'; - - wp_send_json_success( - array( - 'html' => ob_get_clean(), - ) - ); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $response['html'] = ob_get_clean(); } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** @@ -1190,30 +1374,30 @@ class WC_AJAX { public static function calc_line_taxes() { check_ajax_referer( 'calc-totals', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['items'] ) ) { wp_die( -1 ); } $order_id = absint( $_POST['order_id'] ); $calculate_tax_args = array( - 'country' => strtoupper( wc_clean( $_POST['country'] ) ), - 'state' => strtoupper( wc_clean( $_POST['state'] ) ), - 'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ), - 'city' => strtoupper( wc_clean( $_POST['city'] ) ), + 'country' => isset( $_POST['country'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ) : '', + 'state' => isset( $_POST['state'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ) : '', + 'postcode' => isset( $_POST['postcode'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ) : '', + 'city' => isset( $_POST['city'] ) ? wc_strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ) : '', ); - // Parse the jQuery serialized items + // Parse the jQuery serialized items. $items = array(); - parse_str( $_POST['items'], $items ); + parse_str( wp_unslash( $_POST['items'] ), $items ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - // Save order items first + // Save order items first. wc_save_order_items( $order_id, $items ); - // Grab the order and recalculate taxes + // Grab the order and recalculate taxes. $order = wc_get_order( $order_id ); $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); - include 'admin/meta-boxes/views/html-order-items.php'; + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; wp_die(); } @@ -1223,23 +1407,39 @@ class WC_AJAX { public static function save_order_items() { check_ajax_referer( 'order-item', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'], $_POST['items'] ) ) { wp_die( -1 ); } if ( isset( $_POST['order_id'], $_POST['items'] ) ) { $order_id = absint( $_POST['order_id'] ); - // Parse the jQuery serialized items + // Parse the jQuery serialized items. $items = array(); - parse_str( $_POST['items'], $items ); + parse_str( wp_unslash( $_POST['items'] ), $items ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - // Save order items + // Save order items. wc_save_order_items( $order_id, $items ); - // Return HTML items + // Return HTML items. $order = wc_get_order( $order_id ); - include 'admin/meta-boxes/views/html-order-items.php'; + + // Get HTML to return. + ob_start(); + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; + $items_html = ob_get_clean(); + + ob_start(); + $notes = wc_get_order_notes( array( 'order_id' => $order_id ) ); + include __DIR__ . '/admin/meta-boxes/views/html-order-notes.php'; + $notes_html = ob_get_clean(); + + wp_send_json_success( + array( + 'html' => $items_html, + 'notes_html' => $notes_html, + ) + ); } wp_die(); } @@ -1250,14 +1450,14 @@ class WC_AJAX { public static function load_order_items() { check_ajax_referer( 'order-item', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'] ) ) { wp_die( -1 ); } - // Return HTML items + // Return HTML items. $order_id = absint( $_POST['order_id'] ); $order = wc_get_order( $order_id ); - include 'admin/meta-boxes/views/html-order-items.php'; + include __DIR__ . '/admin/meta-boxes/views/html-order-items.php'; wp_die(); } @@ -1267,13 +1467,13 @@ class WC_AJAX { public static function add_order_note() { check_ajax_referer( 'add-order-note', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['post_id'], $_POST['note'], $_POST['note_type'] ) ) { wp_die( -1 ); } $post_id = absint( $_POST['post_id'] ); - $note = wp_kses_post( trim( wp_unslash( $_POST['note'] ) ) ); - $note_type = $_POST['note_type']; + $note = wp_kses_post( trim( wp_unslash( $_POST['note'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $note_type = wc_clean( wp_unslash( $_POST['note_type'] ) ); $is_customer_note = ( 'customer' === $note_type ) ? 1 : 0; @@ -1288,19 +1488,22 @@ class WC_AJAX { ?>
  • - content ) ) ); ?> + content ) ) ) ); ?>

    - - date_created->date_i18n( wc_date_format() ), $note->date_created->date_i18n( wc_time_format() ) ); ?> + + date_created->date_i18n( wc_date_format() ) ), esc_html( $note->date_created->date_i18n( wc_time_format() ) ) ); + ?> added_by ) : /* translators: %s: note author */ - printf( ' ' . __( 'by %s', 'woocommerce' ), $note->added_by ); + printf( ' ' . esc_html__( 'by %s', 'woocommerce' ), esc_html( $note->added_by ) ); endif; ?> - +

  • search_products( $term, '', (bool) $include_variations, false, $limit ); - - if ( ! empty( $_GET['exclude'] ) ) { - $ids = array_diff( $ids, (array) $_GET['exclude'] ); - } - - if ( ! empty( $_GET['include'] ) ) { - $ids = array_intersect( $ids, (array) $_GET['include'] ); - } + $ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids ); $product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' ); $products = array(); @@ -1365,8 +1583,14 @@ class WC_AJAX { $formatted_name = $product_object->get_formatted_name(); $managing_stock = $product_object->managing_stock(); + if ( in_array( $product_object->get_type(), $exclude_types, true ) ) { + continue; + } + if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) { - $formatted_name .= ' – ' . wc_format_stock_for_display( $product_object ); + $stock_amount = $product_object->get_stock_quantity(); + /* Translators: %d stock amount */ + $formatted_name .= ' – ' . sprintf( __( 'Stock: %d', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product_object ) ); } $products[ $product_object->get_id() ] = rawurldecode( $formatted_name ); @@ -1392,22 +1616,19 @@ class WC_AJAX { public static function json_search_downloadable_products_and_variations() { check_ajax_referer( 'search-products', 'security' ); - $term = (string) wc_clean( wp_unslash( $_GET['term'] ) ); - $data_store = WC_Data_Store::load( 'product' ); - $ids = $data_store->search_products( $term, 'downloadable', true ); - - if ( ! empty( $_GET['exclude'] ) ) { - $ids = array_diff( $ids, (array) $_GET['exclude'] ); - } - - if ( ! empty( $_GET['include'] ) ) { - $ids = array_intersect( $ids, (array) $_GET['include'] ); - } - if ( ! empty( $_GET['limit'] ) ) { - $ids = array_slice( $ids, 0, absint( $_GET['limit'] ) ); + $limit = absint( $_GET['limit'] ); + } else { + $limit = absint( apply_filters( 'woocommerce_json_search_limit', 30 ) ); } + $include_ids = ! empty( $_GET['include'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['include'] ) ) : array(); + $exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array(); + + $term = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $data_store = WC_Data_Store::load( 'product' ); + $ids = $data_store->search_products( $term, 'downloadable', true, false, $limit ); + $product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' ); $products = array(); @@ -1430,9 +1651,8 @@ class WC_AJAX { wp_die( -1 ); } - $term = wc_clean( wp_unslash( $_GET['term'] ) ); - $exclude = array(); - $limit = ''; + $term = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $limit = 0; if ( empty( $term ) ) { wp_die(); @@ -1464,13 +1684,14 @@ class WC_AJAX { $found_customers = array(); if ( ! empty( $_GET['exclude'] ) ) { - $ids = array_diff( $ids, (array) $_GET['exclude'] ); + $ids = array_diff( $ids, array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) ); } foreach ( $ids as $id ) { $customer = new WC_Customer( $id ); /* translators: 1: user display name 2: user ID 3: user email */ $found_customers[ $id ] = sprintf( + /* translators: $1: customer name, $2 customer id, $3: customer email */ esc_html__( '%1$s (#%2$s – %3$s)', 'woocommerce' ), $customer->get_first_name() . ' ' . $customer->get_last_name(), $customer->get_id(), @@ -1493,7 +1714,9 @@ class WC_AJAX { wp_die( -1 ); } - if ( ! $search_text = wc_clean( wp_unslash( $_GET['term'] ) ) ) { + $search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + + if ( ! $search_text ) { wp_die(); } @@ -1507,14 +1730,17 @@ class WC_AJAX { 'name__like' => $search_text, ); - if ( $terms = get_terms( $args ) ) { + $terms = get_terms( $args ); + + if ( $terms ) { foreach ( $terms as $term ) { $term->formatted_name = ''; if ( $term->parent ) { $ancestors = array_reverse( get_ancestors( $term->term_id, 'product_cat' ) ); foreach ( $ancestors as $ancestor ) { - if ( $ancestor_term = get_term( $ancestor, 'product_cat' ) ) { + $ancestor_term = get_term( $ancestor, 'product_cat' ); + if ( $ancestor_term ) { $term->formatted_name .= $ancestor_term->name . ' > '; } } @@ -1532,15 +1758,14 @@ class WC_AJAX { * Ajax request handling for categories ordering. */ public static function term_ordering() { - - // check permissions again and make sure we have what we need + // phpcs:disable WordPress.Security.NonceVerification.Missing if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) { wp_die( -1 ); } $id = (int) $_POST['id']; $next_id = isset( $_POST['nextid'] ) && (int) $_POST['nextid'] ? (int) $_POST['nextid'] : null; - $taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( $_POST['thetaxonomy'] ) : null; + $taxonomy = isset( $_POST['thetaxonomy'] ) ? esc_attr( wp_unslash( $_POST['thetaxonomy'] ) ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $term = get_term_by( 'id', $id, $taxonomy ); if ( ! $id || ! $term || ! $taxonomy ) { @@ -1551,10 +1776,11 @@ class WC_AJAX { $children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" ); - if ( $term && sizeof( $children ) ) { + if ( $term && count( $children ) ) { echo 'children'; wp_die(); } + // phpcs:enable } /** @@ -1565,6 +1791,7 @@ class WC_AJAX { public static function product_ordering() { global $wpdb; + // phpcs:disable WordPress.Security.NonceVerification.Missing if ( ! current_user_can( 'edit_products' ) || empty( $_POST['id'] ) ) { wp_die( -1 ); } @@ -1606,8 +1833,11 @@ class WC_AJAX { $wpdb->update( $wpdb->posts, array( 'menu_order' => $menu_orders[ $sorting_id ] ), array( 'ID' => $sorting_id ) ); + WC_Post_Data::delete_product_query_transients(); + do_action( 'woocommerce_after_product_ordering', $sorting_id, $menu_orders ); wp_send_json( $menu_orders ); + // phpcs:enable } /** @@ -1624,34 +1854,33 @@ class WC_AJAX { wp_die( -1 ); } - $order_id = absint( $_POST['order_id'] ); - $refund_amount = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refund_amount'] ) ), wc_get_price_decimals() ); - $refunded_amount = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refunded_amount'] ) ), wc_get_price_decimals() ); - $refund_reason = sanitize_text_field( $_POST['refund_reason'] ); - $line_item_qtys = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_qtys'] ) ), true ); - $line_item_totals = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_totals'] ) ), true ); - $line_item_tax_totals = json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_tax_totals'] ) ), true ); - $api_refund = 'true' === $_POST['api_refund']; - $restock_refunded_items = 'true' === $_POST['restock_refunded_items']; + $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0; + $refund_amount = isset( $_POST['refund_amount'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refund_amount'] ) ), wc_get_price_decimals() ) : 0; + $refunded_amount = isset( $_POST['refunded_amount'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['refunded_amount'] ) ), wc_get_price_decimals() ) : 0; + $refund_reason = isset( $_POST['refund_reason'] ) ? sanitize_text_field( wp_unslash( $_POST['refund_reason'] ) ) : ''; + $line_item_qtys = isset( $_POST['line_item_qtys'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_qtys'] ) ), true ) : array(); + $line_item_totals = isset( $_POST['line_item_totals'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_totals'] ) ), true ) : array(); + $line_item_tax_totals = isset( $_POST['line_item_tax_totals'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['line_item_tax_totals'] ) ), true ) : array(); + $api_refund = isset( $_POST['api_refund'] ) && 'true' === $_POST['api_refund']; + $restock_refunded_items = isset( $_POST['restock_refunded_items'] ) && 'true' === $_POST['restock_refunded_items']; $refund = false; - $response_data = array(); + $response = array(); try { - $order = wc_get_order( $order_id ); - $order_items = $order->get_items(); - $max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() ); + $order = wc_get_order( $order_id ); + $max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded(), wc_get_price_decimals() ); if ( ! $refund_amount || $max_refund < $refund_amount || 0 > $refund_amount ) { - throw new exception( __( 'Invalid refund amount', 'woocommerce' ) ); + throw new Exception( __( 'Invalid refund amount', 'woocommerce' ) ); } - if ( $refunded_amount !== wc_format_decimal( $order->get_total_refunded(), wc_get_price_decimals() ) ) { - throw new exception( __( 'Error processing refund. Please try again.', 'woocommerce' ) ); + if ( wc_format_decimal( $order->get_total_refunded(), wc_get_price_decimals() ) !== $refunded_amount ) { + throw new Exception( __( 'Error processing refund. Please try again.', 'woocommerce' ) ); } // Prepare line items which we are refunding. $line_items = array(); - $item_ids = array_unique( array_merge( array_keys( $line_item_qtys, $line_item_totals ) ) ); + $item_ids = array_unique( array_merge( array_keys( $line_item_qtys ), array_keys( $line_item_totals ) ) ); foreach ( $item_ids as $item_id ) { $line_items[ $item_id ] = array( @@ -1687,14 +1916,14 @@ class WC_AJAX { } if ( did_action( 'woocommerce_order_fully_refunded' ) ) { - $response_data['status'] = 'fully_refunded'; + $response['status'] = 'fully_refunded'; } - - wp_send_json_success( $response_data ); - } catch ( Exception $e ) { wp_send_json_error( array( 'error' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** @@ -1703,11 +1932,11 @@ class WC_AJAX { public static function delete_refund() { check_ajax_referer( 'order-item', 'security' ); - if ( ! current_user_can( 'edit_shop_orders' ) ) { + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['refund_id'] ) ) { wp_die( -1 ); } - $refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? $_POST['refund_id'] : array( $_POST['refund_id'] ) ); + $refund_ids = array_map( 'absint', is_array( $_POST['refund_id'] ) ? wp_unslash( $_POST['refund_id'] ) : array( wp_unslash( $_POST['refund_id'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( $refund_ids as $refund_id ) { if ( $refund_id && 'shop_order_refund' === get_post_type( $refund_id ) ) { $refund = wc_get_order( $refund_id ); @@ -1732,6 +1961,8 @@ class WC_AJAX { /** * Create/Update API key. + * + * @throws Exception On invalid or empty description, user, or permissions. */ public static function update_api_key() { ob_start(); @@ -1744,6 +1975,8 @@ class WC_AJAX { wp_die( -1 ); } + $response = array(); + try { if ( empty( $_POST['description'] ) ) { throw new Exception( __( 'Description is missing.', 'woocommerce' ) ); @@ -1755,11 +1988,18 @@ class WC_AJAX { throw new Exception( __( 'Permissions is missing.', 'woocommerce' ) ); } - $key_id = absint( $_POST['key_id'] ); + $key_id = isset( $_POST['key_id'] ) ? absint( $_POST['key_id'] ) : 0; $description = sanitize_text_field( wp_unslash( $_POST['description'] ) ); - $permissions = ( in_array( $_POST['permissions'], array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $_POST['permissions'] ) : 'read'; + $permissions = ( in_array( wp_unslash( $_POST['permissions'] ), array( 'read', 'write', 'read_write' ), true ) ) ? sanitize_text_field( wp_unslash( $_POST['permissions'] ) ) : 'read'; $user_id = absint( $_POST['user'] ); + // Check if current user can edit other users. + if ( $user_id && ! current_user_can( 'edit_user', $user_id ) ) { + if ( get_current_user_id() !== $user_id ) { + throw new Exception( __( 'You do not have permission to assign API Keys to the selected user.', 'woocommerce' ) ); + } + } + if ( 0 < $key_id ) { $data = array( 'user_id' => $user_id, @@ -1779,9 +2019,10 @@ class WC_AJAX { array( '%d' ) ); - $data['consumer_key'] = ''; - $data['consumer_secret'] = ''; - $data['message'] = __( 'API Key updated successfully.', 'woocommerce' ); + $response = $data; + $response['consumer_key'] = ''; + $response['consumer_secret'] = ''; + $response['message'] = __( 'API Key updated successfully.', 'woocommerce' ); } else { $consumer_key = 'ck_' . wc_rand_hash(); $consumer_secret = 'cs_' . wc_rand_hash(); @@ -1808,17 +2049,19 @@ class WC_AJAX { ) ); - $key_id = $wpdb->insert_id; - $data['consumer_key'] = $consumer_key; - $data['consumer_secret'] = $consumer_secret; - $data['message'] = __( 'API Key generated successfully. Make sure to copy your new keys now as the secret key will be hidden once you leave this page.', 'woocommerce' ); - $data['revoke_url'] = '' . __( 'Revoke key', 'woocommerce' ) . ''; + $key_id = $wpdb->insert_id; + $response = $data; + $response['consumer_key'] = $consumer_key; + $response['consumer_secret'] = $consumer_secret; + $response['message'] = __( 'API Key generated successfully. Make sure to copy your new keys now as the secret key will be hidden once you leave this page.', 'woocommerce' ); + $response['revoke_url'] = '' . __( 'Revoke key', 'woocommerce' ) . ''; } - - wp_send_json_success( $data ); } catch ( Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ) ); } + + // wp_send_json_success must be outside the try block not to break phpunit tests. + wp_send_json_success( $response ); } /** @@ -1833,12 +2076,12 @@ class WC_AJAX { wp_die( -1 ); } - // Set $post global so its available, like within the admin screens + // Set $post global so its available, like within the admin screens. global $post; $loop = 0; $product_id = absint( $_POST['product_id'] ); - $post = get_post( $product_id ); + $post = get_post( $product_id ); // phpcs:ignore $product_object = wc_get_product( $product_id ); $per_page = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10; $page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; @@ -1858,11 +2101,13 @@ class WC_AJAX { ); if ( $variations ) { + wc_render_invalid_variation_notice( $product_object ); + foreach ( $variations as $variation_object ) { $variation_id = $variation_object->get_id(); $variation = get_post( $variation_id ); - $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. - include 'admin/meta-boxes/views/html-variation-admin.php'; + $variation_data = array_merge( get_post_custom( $variation_id ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. + include __DIR__ . '/admin/meta-boxes/views/html-variation-admin.php'; $loop++; } } @@ -1877,7 +2122,7 @@ class WC_AJAX { check_ajax_referer( 'save-variations', 'security' ); - // Check permissions again and make sure we have what we need + // Check permissions again and make sure we have what we need. if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['product_id'] ) ) { wp_die( -1 ); } @@ -1888,14 +2133,16 @@ class WC_AJAX { do_action( 'woocommerce_ajax_save_product_variations', $product_id ); - if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) { + $errors = WC_Admin_Meta_Boxes::$meta_box_errors; + + if ( $errors ) { echo '
    '; foreach ( $errors as $error ) { echo '

    ' . wp_kses_post( $error ) . '

    '; } - echo ''; + echo ''; echo '
    '; delete_option( 'woocommerce_meta_box_errors' ); @@ -1907,10 +2154,10 @@ class WC_AJAX { /** * Bulk action - Toggle Enabled. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_toggle_enabled( $variations, $data ) { foreach ( $variations as $variation_id ) { @@ -1923,10 +2170,10 @@ class WC_AJAX { /** * Bulk action - Toggle Downloadable Checkbox. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_toggle_downloadable( $variations, $data ) { self::variation_bulk_toggle( $variations, 'downloadable' ); @@ -1935,10 +2182,10 @@ class WC_AJAX { /** * Bulk action - Toggle Virtual Checkbox. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_toggle_virtual( $variations, $data ) { self::variation_bulk_toggle( $variations, 'virtual' ); @@ -1947,10 +2194,10 @@ class WC_AJAX { /** * Bulk action - Toggle Manage Stock Checkbox. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_toggle_manage_stock( $variations, $data ) { self::variation_bulk_toggle( $variations, 'manage_stock' ); @@ -1959,10 +2206,10 @@ class WC_AJAX { /** * Bulk action - Set Regular Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_regular_price( $variations, $data ) { self::variation_bulk_set( $variations, 'regular_price', $data['value'] ); @@ -1971,10 +2218,10 @@ class WC_AJAX { /** * Bulk action - Set Sale Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_sale_price( $variations, $data ) { self::variation_bulk_set( $variations, 'sale_price', $data['value'] ); @@ -1983,10 +2230,10 @@ class WC_AJAX { /** * Bulk action - Set Stock Status as In Stock. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_stock_status_instock( $variations, $data ) { self::variation_bulk_set( $variations, 'stock_status', 'instock' ); @@ -1995,10 +2242,10 @@ class WC_AJAX { /** * Bulk action - Set Stock Status as Out of Stock. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_stock_status_outofstock( $variations, $data ) { self::variation_bulk_set( $variations, 'stock_status', 'outofstock' ); @@ -2007,10 +2254,10 @@ class WC_AJAX { /** * Bulk action - Set Stock Status as On Backorder. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_stock_status_onbackorder( $variations, $data ) { self::variation_bulk_set( $variations, 'stock_status', 'onbackorder' ); @@ -2019,10 +2266,10 @@ class WC_AJAX { /** * Bulk action - Set Stock. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_stock( $variations, $data ) { if ( ! isset( $data['value'] ) ) { @@ -2045,10 +2292,10 @@ class WC_AJAX { /** * Bulk action - Set Weight. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_weight( $variations, $data ) { self::variation_bulk_set( $variations, 'weight', $data['value'] ); @@ -2057,10 +2304,10 @@ class WC_AJAX { /** * Bulk action - Set Length. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_length( $variations, $data ) { self::variation_bulk_set( $variations, 'length', $data['value'] ); @@ -2069,10 +2316,10 @@ class WC_AJAX { /** * Bulk action - Set Width. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_width( $variations, $data ) { self::variation_bulk_set( $variations, 'width', $data['value'] ); @@ -2081,10 +2328,10 @@ class WC_AJAX { /** * Bulk action - Set Height. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_height( $variations, $data ) { self::variation_bulk_set( $variations, 'height', $data['value'] ); @@ -2093,10 +2340,10 @@ class WC_AJAX { /** * Bulk action - Set Download Limit. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_download_limit( $variations, $data ) { self::variation_bulk_set( $variations, 'download_limit', $data['value'] ); @@ -2105,10 +2352,10 @@ class WC_AJAX { /** * Bulk action - Set Download Expiry. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_download_expiry( $variations, $data ) { self::variation_bulk_set( $variations, 'download_expiry', $data['value'] ); @@ -2117,10 +2364,10 @@ class WC_AJAX { /** * Bulk action - Delete all. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_delete_all( $variations, $data ) { if ( isset( $data['allowed'] ) && 'true' === $data['allowed'] ) { @@ -2134,10 +2381,10 @@ class WC_AJAX { /** * Bulk action - Sale Schedule. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_sale_schedule( $variations, $data ) { if ( ! isset( $data['date_from'] ) && ! isset( $data['date_to'] ) ) { @@ -2162,10 +2409,10 @@ class WC_AJAX { /** * Bulk action - Increase Regular Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_regular_price_increase( $variations, $data ) { self::variation_bulk_adjust_price( $variations, 'regular_price', '+', wc_clean( $data['value'] ) ); @@ -2174,10 +2421,10 @@ class WC_AJAX { /** * Bulk action - Decrease Regular Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_regular_price_decrease( $variations, $data ) { self::variation_bulk_adjust_price( $variations, 'regular_price', '-', wc_clean( $data['value'] ) ); @@ -2186,10 +2433,10 @@ class WC_AJAX { /** * Bulk action - Increase Sale Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_sale_price_increase( $variations, $data ) { self::variation_bulk_adjust_price( $variations, 'sale_price', '+', wc_clean( $data['value'] ) ); @@ -2198,10 +2445,10 @@ class WC_AJAX { /** * Bulk action - Decrease Sale Prices. * - * @access private + * @param array $variations List of variations. + * @param array $data Data to set. + * * @used-by bulk_edit_variations - * @param array $variations - * @param array $data */ private static function variation_bulk_action_variable_sale_price_decrease( $variations, $data ) { self::variation_bulk_adjust_price( $variations, 'sale_price', '-', wc_clean( $data['value'] ) ); @@ -2210,12 +2457,12 @@ class WC_AJAX { /** * Bulk action - Set Price. * - * @access private + * @param array $variations List of variations. + * @param string $field price being adjusted _regular_price or _sale_price. + * @param string $operator + or -. + * @param string $value Price or Percent. + * * @used-by bulk_edit_variations - * @param array $variations - * @param string $operator + or - - * @param string $field price being adjusted _regular_price or _sale_price - * @param string $value Price or Percent */ private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) { foreach ( $variations as $variation_id ) { @@ -2224,7 +2471,7 @@ class WC_AJAX { if ( '%' === substr( $value, -1 ) ) { $percent = wc_format_decimal( substr( $value, 0, -1 ) ); - $field_value += ( ( $field_value / 100 ) * $percent ) * "{$operator}1"; + $field_value += NumberUtil::round( ( $field_value / 100 ) * $percent, wc_get_price_decimals() ) * "{$operator}1"; } else { $field_value += $value * "{$operator}1"; } @@ -2237,10 +2484,9 @@ class WC_AJAX { /** * Bulk set convenience function. * - * @access private - * @param array $variations - * @param string $field - * @param string $value + * @param array $variations List of variations. + * @param string $field Field to set. + * @param string $value to set. */ private static function variation_bulk_set( $variations, $field, $value ) { foreach ( $variations as $variation_id ) { @@ -2253,9 +2499,8 @@ class WC_AJAX { /** * Bulk toggle convenience function. * - * @access private - * @param array $variations - * @param string $field + * @param array $variations List of variations. + * @param string $field Field to toggle. */ private static function variation_bulk_toggle( $variations, $field ) { foreach ( $variations as $variation_id ) { @@ -2296,14 +2541,14 @@ class WC_AJAX { check_ajax_referer( 'bulk-edit-variations', 'security' ); - // Check permissions again and make sure we have what we need + // Check permissions again and make sure we have what we need. if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['bulk_action'] ) ) { wp_die( -1 ); } $product_id = absint( $_POST['product_id'] ); - $bulk_action = wc_clean( $_POST['bulk_action'] ); - $data = ! empty( $_POST['data'] ) ? array_map( 'wc_clean', $_POST['data'] ) : array(); + $bulk_action = wc_clean( wp_unslash( $_POST['bulk_action'] ) ); + $data = ! empty( $_POST['data'] ) ? wc_clean( wp_unslash( $_POST['data'] ) ) : array(); $variations = array(); if ( apply_filters( 'woocommerce_bulk_edit_variations_need_children', true ) ) { @@ -2334,27 +2579,28 @@ class WC_AJAX { * Handle submissions from assets/js/settings-views-html-settings-tax.js Backbone model. */ public static function tax_rates_save_changes() { + // phpcs:disable WordPress.Security.NonceVerification.Missing if ( ! isset( $_POST['wc_tax_nonce'], $_POST['changes'] ) ) { wp_send_json_error( 'missing_fields' ); wp_die(); } - $current_class = ! empty( $_POST['current_class'] ) ? $_POST['current_class'] : ''; // This is sanitized seven lines later. + $current_class = ! empty( $_POST['current_class'] ) ? wp_unslash( $_POST['current_class'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - if ( ! wp_verify_nonce( $_POST['wc_tax_nonce'], 'wc_tax_nonce-class:' . $current_class ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_tax_nonce'] ), 'wc_tax_nonce-class:' . $current_class ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } $current_class = WC_Tax::format_tax_rate_class( $current_class ); - // Check User Caps + // Check User Caps. if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( 'missing_capabilities' ); wp_die(); } - $changes = $_POST['changes']; + $changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( $changes as $tax_rate_id => $data ) { if ( isset( $data['deleted'] ) ) { if ( isset( $data['newRow'] ) ) { @@ -2366,7 +2612,8 @@ class WC_AJAX { } $tax_rate = array_intersect_key( - $data, array( + $data, + array( 'tax_rate_country' => 1, 'tax_rate_state' => 1, 'tax_rate' => 1, @@ -2395,11 +2642,11 @@ class WC_AJAX { WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, $postcode ); } if ( isset( $data['city'] ) ) { - WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', $data['city'] ) ); + WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', array_map( 'wp_unslash', $data['city'] ) ) ); } } - WC_Cache_Helper::incr_cache_prefix( 'taxes' ); + WC_Cache_Helper::invalidate_cache_group( 'taxes' ); WC_Cache_Helper::get_transient_version( 'shipping', true ); wp_send_json_success( @@ -2407,6 +2654,7 @@ class WC_AJAX { 'rates' => WC_Tax::get_rates_for_tax_class( $current_class ), ) ); + // phpcs:enable } /** @@ -2418,18 +2666,18 @@ class WC_AJAX { wp_die(); } - if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } - // Check User Caps + // Check User Caps. if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( 'missing_capabilities' ); wp_die(); } - $changes = $_POST['changes']; + $changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( $changes as $zone_id => $data ) { if ( isset( $data['deleted'] ) ) { if ( isset( $data['newRow'] ) ) { @@ -2442,7 +2690,8 @@ class WC_AJAX { } $zone_data = array_intersect_key( - $data, array( + $data, + array( 'zone_id' => 1, 'zone_order' => 1, ) @@ -2475,20 +2724,20 @@ class WC_AJAX { wp_die(); } - if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } - // Check User Caps + // Check User Caps. if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( 'missing_capabilities' ); wp_die(); } - $zone_id = wc_clean( $_POST['zone_id'] ); + $zone_id = wc_clean( wp_unslash( $_POST['zone_id'] ) ); $zone = new WC_Shipping_Zone( $zone_id ); - $instance_id = $zone->add_shipping_method( wc_clean( $_POST['method_id'] ) ); + $instance_id = $zone->add_shipping_method( wc_clean( wp_unslash( $_POST['method_id'] ) ) ); wp_send_json_success( array( @@ -2509,7 +2758,7 @@ class WC_AJAX { wp_die(); } - if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } @@ -2521,9 +2770,9 @@ class WC_AJAX { global $wpdb; - $zone_id = wc_clean( $_POST['zone_id'] ); + $zone_id = wc_clean( wp_unslash( $_POST['zone_id'] ) ); $zone = new WC_Shipping_Zone( $zone_id ); - $changes = $_POST['changes']; + $changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( isset( $changes['zone_name'] ) ) { $zone->set_zone_name( wc_clean( $changes['zone_name'] ) ); @@ -2533,7 +2782,7 @@ class WC_AJAX { $zone->clear_locations( array( 'state', 'country', 'continent' ) ); $locations = array_filter( array_map( 'wc_clean', (array) $changes['zone_locations'] ) ); foreach ( $locations as $location ) { - // Each posted location will be in the format type:code + // Each posted location will be in the format type:code. $location_parts = explode( ':', $location ); switch ( $location_parts[0] ) { case 'state': @@ -2572,7 +2821,8 @@ class WC_AJAX { } $method_data = array_intersect_key( - $data, array( + $data, + array( 'method_order' => 1, 'enabled' => 1, ) @@ -2611,7 +2861,7 @@ class WC_AJAX { wp_die(); } - if ( ! wp_verify_nonce( $_POST['wc_shipping_zones_nonce'], 'wc_shipping_zones_nonce' ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_zones_nonce'] ), 'wc_shipping_zones_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } @@ -2624,7 +2874,7 @@ class WC_AJAX { $instance_id = absint( $_POST['instance_id'] ); $zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id ); $shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id ); - $shipping_method->set_post_data( $_POST['data'] ); + $shipping_method->set_post_data( wp_unslash( $_POST['data'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $shipping_method->process_admin_options(); WC_Cache_Helper::get_transient_version( 'shipping', true ); @@ -2648,7 +2898,7 @@ class WC_AJAX { wp_die(); } - if ( ! wp_verify_nonce( $_POST['wc_shipping_classes_nonce'], 'wc_shipping_classes_nonce' ) ) { + if ( ! wp_verify_nonce( wp_unslash( $_POST['wc_shipping_classes_nonce'] ), 'wc_shipping_classes_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_send_json_error( 'bad_nonce' ); wp_die(); } @@ -2658,7 +2908,7 @@ class WC_AJAX { wp_die(); } - $changes = $_POST['changes']; + $changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( $changes as $term_id => $data ) { $term_id = absint( $term_id ); @@ -2716,7 +2966,7 @@ class WC_AJAX { * @since 3.4.0 */ public static function toggle_gateway_enabled() { - if ( current_user_can( 'manage_woocommerce' ) && check_ajax_referer( 'woocommerce-toggle-payment-gateway-enabled', 'security' ) ) { + if ( current_user_can( 'manage_woocommerce' ) && check_ajax_referer( 'woocommerce-toggle-payment-gateway-enabled', 'security' ) && isset( $_POST['gateway_id'] ) ) { // Load gateways. $payment_gateways = WC()->payment_gateways->payment_gateways(); From 752d33ff675b6e57287d881aab27b4d1276730d6 Mon Sep 17 00:00:00 2001 From: LuigiPulcini Date: Sun, 18 Oct 2020 16:26:52 +0200 Subject: [PATCH 004/405] Update class-wc-ajax.php --- includes/class-wc-ajax.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index f277ec2f082..0e7a314ab6c 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -798,7 +798,7 @@ class WC_AJAX { foreach ( $items as $item ) { $product = $item->get_product(); - if ( ! in_array( $product->get_id(), $product_ids ) ) { + if ( ! in_array( $product->get_id(), $product_ids, true ) ) { continue; } $files = $product->get_downloads(); From 64b067100898c92b69c6184d0c69992db699a5d8 Mon Sep 17 00:00:00 2001 From: LuigiPulcini Date: Mon, 19 Oct 2020 11:11:30 +0200 Subject: [PATCH 005/405] Update class-wc-ajax.php --- includes/class-wc-ajax.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 0e7a314ab6c..52e6be8d2f7 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -801,7 +801,7 @@ class WC_AJAX { if ( ! in_array( $product->get_id(), $product_ids, true ) ) { continue; } - $files = $product->get_downloads(); + $files = $product->get_downloads(); if ( ! $order->get_billing_email() ) { wp_die(); From cd443581cfbad571787af10ab0f4a6bdb6a7a25f Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 4 Nov 2020 12:39:45 +0100 Subject: [PATCH 006/405] Fix code sniffer errors --- includes/customizer/class-wc-shop-customizer.php | 2 +- includes/theme-support/class-wc-twenty-nineteen.php | 2 +- includes/wc-core-functions.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/customizer/class-wc-shop-customizer.php b/includes/customizer/class-wc-shop-customizer.php index dd966b8de39..90af6a94c0b 100644 --- a/includes/customizer/class-wc-shop-customizer.php +++ b/includes/customizer/class-wc-shop-customizer.php @@ -769,7 +769,7 @@ class WC_Shop_Customizer { ); } else { $choose_pages = array( - 'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ), + 'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ), ); } $pages = get_pages( diff --git a/includes/theme-support/class-wc-twenty-nineteen.php b/includes/theme-support/class-wc-twenty-nineteen.php index 47c1aff660c..9d1f44cc64c 100644 --- a/includes/theme-support/class-wc-twenty-nineteen.php +++ b/includes/theme-support/class-wc-twenty-nineteen.php @@ -48,7 +48,7 @@ class WC_Twenty_Nineteen { // Tweak Twenty Nineteen features. add_action( 'wp', array( __CLASS__, 'tweak_theme_features' ) ); - // Color scheme CSS + // Color scheme CSS. add_filter( 'twentynineteen_custom_colors_css', array( __CLASS__, 'custom_colors_css' ), 10, 3 ); } diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 097770fede8..34b225af008 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -390,7 +390,7 @@ function wc_locate_template( $template_name, $template_path = '', $default_path // Look within passed path within the theme - this is priority. if ( false !== strpos( $template_name, 'product_cat' ) || false !== strpos( $template_name, 'product_tag' ) ) { $cs_template = str_replace( '_', '-', $template_name ); - $template = locate_template( + $template = locate_template( array( trailingslashit( $template_path ) . $cs_template, $cs_template, From d620f1d2321785f52926e067df348cb52f547c75 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 4 Nov 2020 12:56:42 +0100 Subject: [PATCH 007/405] Fix #25900 - image size customisation controls not shown - Added the `ThemeSupport class`, with methods to add and get theme support options. - It also has a new `add_default_options` method that adds the options under a `_defaults` key. - The `WC_Twenty_*` classes now use `ThemeSupport` instead of the `add_theme_support` function to define image and thumbnail sizes. - The values are defined as default options. - The `WC_Shop_Customizer` class now uses `ThemeSupport` instead of `wc_get_theme_support` to check if image and thumbnail sizes UI should be rendered. - The check is made excluding default values. With these changes the UI to change the image and thumbnail sizes is hidden only if the options are added as non-defaults elsewhere. Additional changes: - The code of the `wc_get_theme_support` function is replaced with a simple call to `get_option` in `ThemeSupport`. - Added the utility class `ArrayUtil`. --- .../customizer/class-wc-shop-customizer.php | 19 +- .../theme-support/class-wc-twenty-eleven.php | 5 +- .../theme-support/class-wc-twenty-fifteen.php | 5 +- .../class-wc-twenty-fourteen.php | 5 +- .../class-wc-twenty-nineteen.php | 4 +- .../class-wc-twenty-seventeen.php | 4 +- .../theme-support/class-wc-twenty-sixteen.php | 5 +- .../theme-support/class-wc-twenty-ten.php | 5 +- .../class-wc-twenty-thirteen.php | 5 +- .../theme-support/class-wc-twenty-twelve.php | 5 +- .../theme-support/class-wc-twenty-twenty.php | 4 +- includes/wc-core-functions.php | 34 +-- src/Container.php | 4 +- .../ThemeManagementServiceProvider.php | 31 +++ src/ThemeManagement/ThemeSupport.php | 118 ++++++++ src/Utilities/ArrayUtil.php | 48 ++++ tests/legacy/bootstrap.php | 9 +- .../class-wc-shop-customizer-test.php | 59 ++++ .../php/src/ThemeSupport/ThemeSupportTest.php | 254 ++++++++++++++++++ tests/php/src/Utilities/ArrayUtilTest.php | 66 +++++ 20 files changed, 631 insertions(+), 58 deletions(-) create mode 100644 src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php create mode 100644 src/ThemeManagement/ThemeSupport.php create mode 100644 src/Utilities/ArrayUtil.php create mode 100644 tests/php/includes/customizer/class-wc-shop-customizer-test.php create mode 100644 tests/php/src/ThemeSupport/ThemeSupportTest.php create mode 100644 tests/php/src/Utilities/ArrayUtilTest.php diff --git a/includes/customizer/class-wc-shop-customizer.php b/includes/customizer/class-wc-shop-customizer.php index 90af6a94c0b..1a42c04b10c 100644 --- a/includes/customizer/class-wc-shop-customizer.php +++ b/includes/customizer/class-wc-shop-customizer.php @@ -6,6 +6,8 @@ * @package WooCommerce */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -13,10 +15,19 @@ defined( 'ABSPATH' ) || exit; */ class WC_Shop_Customizer { + /** + * Holds the instance of ThemeSupport to use. + * + * @var ThemeSupport $theme_support The instance of ThemeSupport to use. + */ + private $theme_support; + /** * Constructor. */ public function __construct() { + $this->theme_support = wc_get_container()->get( ThemeSupport::class ); + add_action( 'customize_register', array( $this, 'add_sections' ) ); add_action( 'customize_controls_print_styles', array( $this, 'add_styles' ) ); add_action( 'customize_controls_print_scripts', array( $this, 'add_scripts' ), 30 ); @@ -545,11 +556,11 @@ class WC_Shop_Customizer { ) ); - if ( ! wc_get_theme_support( 'single_image_width' ) ) { + if ( ! $this->theme_support->has_option( 'single_image_width', false ) ) { $wp_customize->add_setting( 'woocommerce_single_image_width', array( - 'default' => 600, + 'default' => $this->theme_support->get_option( 'single_image_width', 600 ), 'type' => 'option', 'capability' => 'manage_woocommerce', 'sanitize_callback' => 'absint', @@ -573,11 +584,11 @@ class WC_Shop_Customizer { ); } - if ( ! wc_get_theme_support( 'thumbnail_image_width' ) ) { + if ( ! $this->theme_support->has_option( 'thumbnail_image_width', false ) ) { $wp_customize->add_setting( 'woocommerce_thumbnail_image_width', array( - 'default' => 300, + 'default' => $this->theme_support->get_option( 'thumbnail_image_width', 300 ), 'type' => 'option', 'capability' => 'manage_woocommerce', 'sanitize_callback' => 'absint', diff --git a/includes/theme-support/class-wc-twenty-eleven.php b/includes/theme-support/class-wc-twenty-eleven.php index fb06a7a84dc..9bb77c9e70e 100644 --- a/includes/theme-support/class-wc-twenty-eleven.php +++ b/includes/theme-support/class-wc-twenty-eleven.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Eleven { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 150, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-fifteen.php b/includes/theme-support/class-wc-twenty-fifteen.php index 83e1930cf93..828b2ed6d61 100644 --- a/includes/theme-support/class-wc-twenty-fifteen.php +++ b/includes/theme-support/class-wc-twenty-fifteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Fifteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 350, diff --git a/includes/theme-support/class-wc-twenty-fourteen.php b/includes/theme-support/class-wc-twenty-fourteen.php index ce04395299d..e068e57c96c 100644 --- a/includes/theme-support/class-wc-twenty-fourteen.php +++ b/includes/theme-support/class-wc-twenty-fourteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Fourteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 150, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-nineteen.php b/includes/theme-support/class-wc-twenty-nineteen.php index 9d1f44cc64c..795fa079208 100644 --- a/includes/theme-support/class-wc-twenty-nineteen.php +++ b/includes/theme-support/class-wc-twenty-nineteen.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -37,8 +38,7 @@ class WC_Twenty_Nineteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 300, 'single_image_width' => 450, diff --git a/includes/theme-support/class-wc-twenty-seventeen.php b/includes/theme-support/class-wc-twenty-seventeen.php index 2093d2200e7..6fff3aab843 100644 --- a/includes/theme-support/class-wc-twenty-seventeen.php +++ b/includes/theme-support/class-wc-twenty-seventeen.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -30,8 +31,7 @@ class WC_Twenty_Seventeen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 250, 'single_image_width' => 350, diff --git a/includes/theme-support/class-wc-twenty-sixteen.php b/includes/theme-support/class-wc-twenty-sixteen.php index c9681fa5e98..1dc0b137e7f 100644 --- a/includes/theme-support/class-wc-twenty-sixteen.php +++ b/includes/theme-support/class-wc-twenty-sixteen.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Sixteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 250, 'single_image_width' => 400, diff --git a/includes/theme-support/class-wc-twenty-ten.php b/includes/theme-support/class-wc-twenty-ten.php index 8a9262e6191..67f14aeab94 100644 --- a/includes/theme-support/class-wc-twenty-ten.php +++ b/includes/theme-support/class-wc-twenty-ten.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Ten { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-thirteen.php b/includes/theme-support/class-wc-twenty-thirteen.php index 4e80b3e27c6..eac4e70130f 100644 --- a/includes/theme-support/class-wc-twenty-thirteen.php +++ b/includes/theme-support/class-wc-twenty-thirteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Thirteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-twelve.php b/includes/theme-support/class-wc-twenty-twelve.php index 116dabea432..a5e7315c911 100644 --- a/includes/theme-support/class-wc-twenty-twelve.php +++ b/includes/theme-support/class-wc-twenty-twelve.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Twelve { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-twenty.php b/includes/theme-support/class-wc-twenty-twenty.php index 47296f639ab..f2327680da0 100644 --- a/includes/theme-support/class-wc-twenty-twenty.php +++ b/includes/theme-support/class-wc-twenty-twenty.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -37,8 +38,7 @@ class WC_Twenty_Twenty { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 450, 'single_image_width' => 600, diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 34b225af008..604fa4ce103 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -9,6 +9,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\ThemeManagement\ThemeSupport; use Automattic\WooCommerce\Utilities\NumberUtil; if ( ! defined( 'ABSPATH' ) ) { @@ -879,38 +880,7 @@ function wc_mail( $to, $subject, $message, $headers = "Content-Type: text/html\r * @return mixed Value of prop(s). */ function wc_get_theme_support( $prop = '', $default = null ) { - $theme_support = get_theme_support( 'woocommerce' ); - $theme_support = is_array( $theme_support ) ? $theme_support[0] : false; - - if ( ! $theme_support ) { - return $default; - } - - if ( $prop ) { - $prop_stack = explode( '::', $prop ); - $prop_key = array_shift( $prop_stack ); - - if ( isset( $theme_support[ $prop_key ] ) ) { - $value = $theme_support[ $prop_key ]; - - if ( count( $prop_stack ) ) { - foreach ( $prop_stack as $prop_key ) { - if ( is_array( $value ) && isset( $value[ $prop_key ] ) ) { - $value = $value[ $prop_key ]; - } else { - $value = $default; - break; - } - } - } - } else { - $value = $default; - } - - return $value; - } - - return $theme_support; + return wc_get_container()->get( ThemeSupport::class )->get_option( $prop, $default ); } /** diff --git a/src/Container.php b/src/Container.php index 4291867682b..91986c21a74 100644 --- a/src/Container.php +++ b/src/Container.php @@ -6,12 +6,13 @@ namespace Automattic\WooCommerce; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ThemeManagementServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer; /** * PSR11 compliant dependency injection container for WooCommerce. * - * Classes in the `src` directory should specify dependencies from that directory via constructor arguments + * Classes in the `src` directory should specify dependencies from that directory via an 'init' method having arguments * with type hints. If an instance of the container itself is needed, the type hint to use is \Psr\Container\ContainerInterface. * * Classes in the `src` directory should interact with anything outside (especially code in the `includes` directory @@ -33,6 +34,7 @@ final class Container implements \Psr\Container\ContainerInterface { */ private $service_providers = array( ProxiesServiceProvider::class, + ThemeManagementServiceProvider::class, ); /** diff --git a/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php b/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php new file mode 100644 index 00000000000..9ae7f90d848 --- /dev/null +++ b/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php @@ -0,0 +1,31 @@ +share_with_auto_arguments( ThemeSupport::class ); + } +} diff --git a/src/ThemeManagement/ThemeSupport.php b/src/ThemeManagement/ThemeSupport.php new file mode 100644 index 00000000000..82c6bff089a --- /dev/null +++ b/src/ThemeManagement/ThemeSupport.php @@ -0,0 +1,118 @@ +legacy_proxy = $legacy_proxy; + } + + /** + * Adds theme support options for the current theme. + * + * @param array $options The options to be added. + */ + public function add_options( $options ) { + $this->legacy_proxy->call_function( 'add_theme_support', 'woocommerce', $options ); + } + + /** + * Adds default theme support options for the current theme. + * + * @param array $options The options to be added. + */ + public function add_default_options( $options ) { + $default_options = $this->get_option( self::DEFAULTS_KEY, array() ); + $default_options = array_merge( $default_options, $options ); + $this->add_options( array( self::DEFAULTS_KEY => $default_options ) ); + } + + /** + * Gets "theme support" options from the current theme, if set. + * + * @param string $option_name Option name, possibly nested (key::subkey), to get specific value. Blank to get all the existing options as an array. + * @param mixed $default_value Value to return if the specified option doesn't exist. + * @return mixed The retrieved option or the default value. + */ + public function get_option( $option_name = '', $default_value = null ) { + $theme_support_options = $this->get_all_options(); + + if ( ! $theme_support_options ) { + return $default_value; + } + + if ( $option_name ) { + $value = ArrayUtil::get_nested_value( $theme_support_options, $option_name ); + if ( is_null( $value ) ) { + $value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name, $default_value ); + } + return $value; + } + + return $theme_support_options; + } + + /** + * Checks whether a given theme support option has been defined. + * + * @param string $option_name The (possibly nested) name of the option to check. + * @param bool $include_defaults True to include the default values in the check, false otherwise. + * + * @return bool True if the specified theme support option has been defined, false otherwise. + */ + public function has_option( $option_name, $include_defaults = true ) { + $theme_support_options = $this->get_all_options(); + + if ( ! $theme_support_options ) { + return false; + } + + $value = ArrayUtil::get_nested_value( $theme_support_options, $option_name ); + if ( ! is_null( $value ) ) { + return true; + } + + if ( ! $include_defaults ) { + return false; + } + $value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name ); + return ! is_null( $value ); + } + + /** + * Get all the defined theme support options for the 'woocommerce' feature. + * + * @return array An array with all the theme support options defined for the 'woocommerce' feature, or false if nothing has been defined for that feature. + */ + private function get_all_options() { + $theme_support = $this->legacy_proxy->call_function( 'get_theme_support', 'woocommerce' ); + return is_array( $theme_support ) ? $theme_support[0] : false; + } +} diff --git a/src/Utilities/ArrayUtil.php b/src/Utilities/ArrayUtil.php new file mode 100644 index 00000000000..b381dbef86a --- /dev/null +++ b/src/Utilities/ArrayUtil.php @@ -0,0 +1,48 @@ + [ 'bar' => [ 'fizz' => 'buzz' ] ] ] the value for key 'foo::bar::fizz' would be 'buzz'. + * + * @param array $array The array to get the value from. + * @param string $key The complete key hierarchy, using '::' as separator. + * @param mixed $default The value to return if the key doesn't exist in the array. + * + * @return mixed The retrieved value, or the supplied default value. + * @throws \Exception $array is not an array. + */ + public static function get_nested_value( array $array, string $key, $default = null ) { + $key_stack = explode( '::', $key ); + $subkey = array_shift( $key_stack ); + + if ( isset( $array[ $subkey ] ) ) { + $value = $array[ $subkey ]; + + if ( count( $key_stack ) ) { + foreach ( $key_stack as $subkey ) { + if ( is_array( $value ) && isset( $value[ $subkey ] ) ) { + $value = $value[ $subkey ]; + } else { + $value = $default; + break; + } + } + } + } else { + $value = $default; + } + + return $value; + } +} + diff --git a/tests/legacy/bootstrap.php b/tests/legacy/bootstrap.php index bfbcd11edeb..62d93d87254 100644 --- a/tests/legacy/bootstrap.php +++ b/tests/legacy/bootstrap.php @@ -103,7 +103,14 @@ class WC_Unit_Tests_Bootstrap { * @throws Exception Error when initializing one of the hacks. */ private function initialize_code_hacker() { - CodeHacker::initialize( array( __DIR__ . '/../../includes/' ) ); + $wp_dir = getenv( 'WP_TESTS_WP_DIR' ) ? getenv( 'WP_TESTS_WP_DIR' ) : sys_get_temp_dir() . '/wordpress'; + CodeHacker::initialize( + array( + $this->plugin_dir . '/includes/', + $wp_dir . '/wp-includes/class-wp-customize-manager.php', + ) + ); + $replaceable_functions = include_once __DIR__ . '/mockable-functions.php'; if ( ! empty( $replaceable_functions ) ) { FunctionsMockerHack::initialize( $replaceable_functions ); diff --git a/tests/php/includes/customizer/class-wc-shop-customizer-test.php b/tests/php/includes/customizer/class-wc-shop-customizer-test.php new file mode 100644 index 00000000000..c1c73f66c56 --- /dev/null +++ b/tests/php/includes/customizer/class-wc-shop-customizer-test.php @@ -0,0 +1,59 @@ +createMock( WP_Customize_Manager::class ); + $added_settings = array(); + $added_controls = array(); + + $add_setting_callback = function( $id, $args = array() ) use ( &$added_settings ) { + array_push( $added_settings, $id ); + }; + $add_control_callback = function( $id, $args = array() ) use ( &$added_controls ) { + array_push( $added_controls, $id ); + }; + $customize_manager->method( 'add_setting' )->will( $this->returnCallback( $add_setting_callback ) ); + $customize_manager->method( 'add_control' )->will( $this->returnCallback( $add_control_callback ) ); + + $theme_support = $this->get_instance_of( ThemeSupport::class ); + $add_support_method = $add_explicit_theme_support ? 'add_options' : 'add_default_options'; + $theme_support->$add_support_method( array( $option_name => 1234 ) ); + + $sut = $this->get_legacy_instance_of( WC_Shop_Customizer::class ); + $sut->add_sections( $customize_manager ); + + $this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_settings, true ) ); + $this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_controls, true ) ); + } +} diff --git a/tests/php/src/ThemeSupport/ThemeSupportTest.php b/tests/php/src/ThemeSupport/ThemeSupportTest.php new file mode 100644 index 00000000000..3b3a5f9304f --- /dev/null +++ b/tests/php/src/ThemeSupport/ThemeSupportTest.php @@ -0,0 +1,254 @@ +sut = $this->get_instance_of( ThemeSupport::class ); + remove_theme_support( 'woocommerce' ); + } + + /** + * @testdox add_options should add the supplied options under the 'woocommerce' feature. + */ + public function test_add_options() { + $actual_added_feature = null; + $actual_added_options = null; + + $this->register_legacy_proxy_function_mocks( + array( + 'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_feature, &$actual_added_options ) { + $actual_added_feature = $feature; + $actual_added_options = $args; + }, + ) + ); + + $options = array( 'foo' => 'bar' ); + $this->sut->add_options( $options ); + + $this->assertEquals( 'woocommerce', $actual_added_feature ); + $this->assertEquals( $options, $actual_added_options[0] ); + + $this->reset_legacy_proxy_mocks(); + + $this->sut->add_options( $options ); + + $actual_retrieved_options = get_theme_support( 'woocommerce' )[0]; + $this->assertEquals( $options, $actual_retrieved_options ); + } + + /** + * @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key. + */ + public function test_2_add_default_options() { + $actual_added_options = array(); + + $this->register_legacy_proxy_function_mocks( + array( + 'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_options ) { + array_push( $actual_added_options, $args ); + }, + ) + ); + + $this->sut->add_default_options( array( 'foo' => 'bar' ) ); + $this->sut->add_default_options( array( 'fizz' => 'buzz' ) ); + + $expected_added_options = array( + array( + array( + ThemeSupport::DEFAULTS_KEY => + array( + 'foo' => 'bar', + ), + ), + ), + array( + array( + ThemeSupport::DEFAULTS_KEY => + array( + 'fizz' => 'buzz', + ), + ), + ), + ); + + $this->assertEquals( $expected_added_options, $actual_added_options ); + + $this->reset_legacy_proxy_mocks(); + + $this->sut->add_default_options( array( 'foo' => 'bar' ) ); + $this->sut->add_default_options( array( 'fizz' => 'buzz' ) ); + + $actual_retrieved_options = get_theme_support( 'woocommerce' )[0]; + $expected_retrieved_options = array( + ThemeSupport::DEFAULTS_KEY => array( + 'foo' => 'bar', + 'fizz' => 'buzz', + ), + ); + $this->assertEquals( $expected_retrieved_options, $actual_retrieved_options ); + } + + /** + * @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key. + */ + public function test_add_default_options() { + $this->sut->add_default_options( array( 'foo' => 'bar' ) ); + $this->sut->add_default_options( array( 'fizz' => 'buzz' ) ); + + $actual = get_theme_support( 'woocommerce' )[0]; + $expected = array( + ThemeSupport::DEFAULTS_KEY => array( + 'foo' => 'bar', + 'fizz' => 'buzz', + ), + ); + $this->assertEquals( $expected, $actual ); + } + + /** + * @testdox get_option should return all the options under the 'woocommerce' feature when invoked with blank option name. + */ + public function test_get_option_with_no_option_name() { + $options = array( 'foo' => 'bar' ); + $this->sut->add_options( $options ); + + $actual = $this->sut->get_option(); + $this->assertEquals( $options, $actual ); + } + + /** + * @testdox get_option should return null if no 'woocommerce' feature exists and no default value is supplied. + */ + public function test_get_option_with_no_option_name_when_no_options_exist_and_no_default_value_supplied() { + $actual = $this->sut->get_option(); + $this->assertNull( $actual ); + } + + /** + * @testdox get_option should return the supplied default value if no 'woocommerce' feature exists. + */ + public function test_get_option_with_no_option_name_when_no_options_exist_and_default_value_supplied() { + $actual = $this->sut->get_option( '', 'DEFAULT' ); + $this->assertEquals( 'DEFAULT', $actual ); + } + + /** + * @testdox get_theme_support should return the value of the requested option if it exists. + */ + public function test_get_theme_support_with_option_name() { + $options = array( 'foo' => array( 'bar' => 'fizz' ) ); + $this->sut->add_options( $options ); + + $actual = $this->sut->get_option( 'foo::bar' ); + $this->assertEquals( 'fizz', $actual ); + } + + /** + * @testdox get_option should return null if the requested option doesn't exist and no default value is supplied. + */ + public function test_get_option_with_option_name_when_option_does_not_exist_and_no_default_value_supplied() { + $options = array( 'foo' => array( 'bar' => 'fizz' ) ); + $this->sut->add_options( $options ); + + $actual = $this->sut->get_option( 'buzz' ); + $this->assertNull( $actual ); + } + + /** + * @testdox get_option should return the supplied default value if the requested option doesn't exist. + */ + public function test_get_option_with_option_name_when_option_does_not_exist_and_default_value_supplied() { + $options = array( 'foo' => array( 'bar' => 'fizz' ) ); + $this->sut->add_options( $options ); + + $actual = $this->sut->get_option( 'buzz', 'DEFAULT' ); + $this->assertEquals( 'DEFAULT', $actual ); + } + + /** + * @testdox get_option should return the value of the requested option if it has been defined as a default. + */ + public function test_get_option_with_option_name_and_option_defined_as_default() { + $options = array( 'foo' => array( 'bar' => 'fizz' ) ); + $this->sut->add_default_options( $options ); + + $actual = $this->sut->get_option( 'foo::bar' ); + $this->assertEquals( 'fizz', $actual ); + } + + /** + * @testdox has_option should return false if no 'woocommerce' feature exists. + * + * @testWith [true] + * [false] + * + * @param bool $include_defaults Whether to include defaults in the search or not. + */ + public function test_has_option_when_no_woocommerce_feature_is_defined( $include_defaults ) { + $this->assertFalse( $this->sut->has_option( 'foo::bar', $include_defaults ) ); + } + + /** + * @testdox has_option should return false if the specified option has not been defined. + * + * @testWith [true] + * [false] + * + * @param bool $include_defaults Whether to include defaults in the search or not. + */ + public function test_has_option_when_option_is_not_defined( $include_defaults ) { + $this->sut->add_options( array( 'foo' => 'bar' ) ); + $this->assertFalse( $this->sut->has_option( 'fizz::buzz', $include_defaults ) ); + } + + /** + * @testdox has_option should return true if the specified option has been defined. + * + * @testWith [true] + * [false] + * + * @param bool $include_defaults Whether to include defaults in the search or not. + */ + public function test_has_option_when_option_is_defined( $include_defaults ) { + $this->sut->add_options( array( 'foo' => 'bar' ) ); + $this->assertTrue( $this->sut->has_option( 'foo', $include_defaults ) ); + } + + /** + * @testdox If an option has been defined as a default, has_theme_support should return true if $include_defaults is passed as true, should return false otherwise. + * + * @testWith [true, true] + * [false, false] + * + * @param bool $include_defaults Whether to include defaults in the search or not. + * @param bool $expected_result The expected return value from the tested method. + */ + public function test_has_option_when_option_is_defined_as_default( $include_defaults, $expected_result ) { + $this->sut->add_default_options( array( 'foo' => 'bar' ) ); + $this->assertEquals( $expected_result, $this->sut->has_option( 'foo', $include_defaults ) ); + } +} diff --git a/tests/php/src/Utilities/ArrayUtilTest.php b/tests/php/src/Utilities/ArrayUtilTest.php new file mode 100644 index 00000000000..77f225942ac --- /dev/null +++ b/tests/php/src/Utilities/ArrayUtilTest.php @@ -0,0 +1,66 @@ + 'buzz' ); + $actual = ArrayUtil::get_nested_value( $array, $key ); + + $this->assertNull( $actual ); + } + + /** + * @testdox `get_nested_value` should return the supplied default value if the requested key doesn't exist. + * + * @testWith ["foo"] + * ["foo::bar"] + * + * @param string $key The key to test. + */ + public function test_get_nested_value_returns_supplied_default_if_non_existing_key( $key ) { + $array = array( 'fizz' => 'buzz' ); + $actual = ArrayUtil::get_nested_value( $array, $key, 'DEFAULT' ); + + $this->assertEquals( 'DEFAULT', $actual ); + } + + /** + * @testdox `get_nested_value` should return the proper value when a simple key is passed. + */ + public function test_get_nested_value_works_for_simple_keys() { + $array = array( 'foo' => 'bar' ); + $actual = ArrayUtil::get_nested_value( $array, 'foo' ); + + $this->assertEquals( 'bar', $actual ); + } + + /** + * @testdox `get_nested_value` should return the proper value when a nested key is passed. + */ + public function test_get_nested_value_works_for_nested_keys() { + $array = array( + 'foo' => array( + 'bar' => array( + 'fizz' => 'buzz', + ), + ), + ); + $actual = ArrayUtil::get_nested_value( $array, 'foo::bar::fizz' ); + + $this->assertEquals( 'buzz', $actual ); + } +} From 98823ba8d036a6f9406c9d3bd4b841e6151ecc9c Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 14 Jan 2021 13:45:12 +0100 Subject: [PATCH 008/405] Add new e2e test case customer can pay for his order --- tests/e2e/core-tests/specs/index.js | 3 ++ .../front-end-my-account-pay-order.test.js | 46 +++++++++++++++++++ tests/e2e/env/config/default.json | 4 +- .../front-end/test-my-account-pay-order.js | 6 +++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js create mode 100644 tests/e2e/specs/front-end/test-my-account-pay-order.js diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8c8bd03b152..8496219af50 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -11,6 +11,7 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) // Shopper tests const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); +const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); @@ -35,6 +36,7 @@ const runSetupOnboardingTests = () => { const runShopperTests = () => { runCartPageTest(); runCheckoutPageTest(); + runMyAccountPayOrderTest(); runMyAccountPageTest(); runSingleProductPageTest(); }; @@ -60,6 +62,7 @@ module.exports = { runSetupOnboardingTests, runCartPageTest, runCheckoutPageTest, + runMyAccountPayOrderTest, runMyAccountPageTest, runSingleProductPageTest, runShopperTests, diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js new file mode 100644 index 00000000000..dde62dbf3b8 --- /dev/null +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js @@ -0,0 +1,46 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests */ +/** + * Internal dependencies + */ +const { + shopper, + merchant, + createSimpleProduct, + uiUnblocked +} = require( '@woocommerce/e2e-utils' ); + +let simplePostIdValue; +const config = require( 'config' ); +const simpleProductName = config.get( 'products.simple.name' ); + +const runMyAccountPayOrderTest = () => { + describe('Customer can pay for his order through my account', () => { + beforeAll(async () => { + await merchant.login(); + simplePostIdValue = await createSimpleProduct(); + await merchant.logout(); + await shopper.login(); + await shopper.goToProduct(simplePostIdValue); + await shopper.addToCartFromShopPage(simpleProductName); + await shopper.goToCheckout(); + await shopper.fillBillingDetails(config.get('addresses.customer.billing')); + await uiUnblocked(); + await shopper.placeOrder(); + const orderElement = await page.$(".order"); + const orderId = await page.evaluate(orderElement => orderElement.textContent, orderElement); + await shopper.logout(); + await merchant.login(); + await merchant.updateOrderStatus(orderId, 'Pending payment'); + await merchant.logout(); + }) + + it('allows customer to pay for his order in my account', async () => { + await shopper.login(); + await shopper.goToOrders(); + await expect(page.url()).toMatch('my-account/orders'); + await expect(page).toMatchElement('h1', {text: 'Orders'}); + }); + }); +} + +module.exports = runMyAccountPayOrderTest; diff --git a/tests/e2e/env/config/default.json b/tests/e2e/env/config/default.json index 9dd72fb0f9c..e01ee4e9a27 100644 --- a/tests/e2e/env/config/default.json +++ b/tests/e2e/env/config/default.json @@ -1,5 +1,6 @@ { "url": "http://localhost:8084/", + "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", @@ -12,8 +13,7 @@ }, "products": { "simple": { - "name": "Simple product", - "price": "9.99" + "name": "Simple product" }, "variable": { "name": "Variable Product with Three Variations" diff --git a/tests/e2e/specs/front-end/test-my-account-pay-order.js b/tests/e2e/specs/front-end/test-my-account-pay-order.js new file mode 100644 index 00000000000..a0722df86c0 --- /dev/null +++ b/tests/e2e/specs/front-end/test-my-account-pay-order.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runMyAccountPayOrderTest } = require( '@woocommerce/e2e-core-tests' ); + +runMyAccountPayOrderTest(); From 50d601355c0f60a1d76b566de3ad17d3dd56167b Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 14 Jan 2021 14:22:39 +0100 Subject: [PATCH 009/405] Update scenario shopper can pay his order --- .../shopper/front-end-my-account-pay-order.test.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js index dde62dbf3b8..1ff91716625 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js @@ -30,15 +30,17 @@ const runMyAccountPayOrderTest = () => { const orderId = await page.evaluate(orderElement => orderElement.textContent, orderElement); await shopper.logout(); await merchant.login(); - await merchant.updateOrderStatus(orderId, 'Pending payment'); - await merchant.logout(); + await merchant.updateOrderStatus(orderId, 'Pending payment'); + await merchant.logout(); }) it('allows customer to pay for his order in my account', async () => { - await shopper.login(); - await shopper.goToOrders(); - await expect(page.url()).toMatch('my-account/orders'); - await expect(page).toMatchElement('h1', {text: 'Orders'}); + await shopper.login(); + await shopper.goToOrders(); + await expect(page).toClick('.wc_payment_method woocommerce-button button pay', {text: 'Pay'}); + await expect(page).toMatchElement('.entry-title', {text: 'Pay for order'}); + await shopper.placeOrder(); + await expect(page).toMatch('Order received'); }); }); } From d56971780838ac85337193212959bd116711fadc Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 14 Jan 2021 17:19:06 +0100 Subject: [PATCH 010/405] Update scenario of shopper paying his order --- package-lock.json | 72 +++++++++++++++---- .../front-end-my-account-pay-order.test.js | 22 +++--- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0983a0cbb1..5becf90d96c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "woocommerce", - "version": "4.9.0", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20615,6 +20615,7 @@ "version": "file:tests/e2e/utils", "dev": true, "requires": { + "@wordpress/deprecated": "^2.10.0", "@wordpress/e2e-test-utils": "^4.6.0", "config": "3.3.3", "faker": "^5.1.0", @@ -20915,6 +20916,27 @@ "integrity": "sha512-pB45JlfmHuEigNFZ1X+CTgIsOT3/TTb9iZxw1DHXge/7ytY8FNhtcNwTfF9IgnS6/xaFRZBqzw4DyH4sP1Lyxg==", "dev": true }, + "@wordpress/deprecated": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-2.11.0.tgz", + "integrity": "sha512-2wfl5J8Y3hZeqkD9QAuXTRxPeXm6x5rxsz+CAFG+SS1E9FYZdB0FnRmm26iza7oDo0n917SuM+QDJ5R8P0UxlA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@wordpress/hooks": "^2.11.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "@wordpress/e2e-test-utils": { "version": "4.15.0", "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-4.15.0.tgz", @@ -20973,6 +20995,26 @@ } } }, + "@wordpress/hooks": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.11.0.tgz", + "integrity": "sha512-TbvCrHcMiSZoyiflegEqVS3DDytDTpkms+yLUaGN4sMvNdR/Mv5s0WnNKyM0T49lbmZYPWlbWhwJ1F6hr/FQDg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "@wordpress/i18n": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.16.0.tgz", @@ -21083,7 +21125,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, "acorn": { @@ -21280,7 +21322,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "are-we-there-yet": { @@ -21329,7 +21371,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-union": { @@ -22053,7 +22095,7 @@ "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, "bail": { @@ -24022,7 +24064,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -24823,7 +24865,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -25964,7 +26006,7 @@ "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs-write-stream-atomic": { @@ -27375,7 +27417,7 @@ "grunt-postcss": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/grunt-postcss/-/grunt-postcss-0.9.0.tgz", - "integrity": "sha1-++WTSmvp6siTr20FfiMYyX+unaM=", + "integrity": "sha512-lglLcVaoOIqH0sFv7RqwUKkEFGQwnlqyAKbatxZderwZGV1nDyKHN7gZS9LUiTx1t5GOvRBx0BEalHMyVwFAIA==", "dev": true, "requires": { "chalk": "^2.1.0", @@ -28738,7 +28780,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-callable": { @@ -28905,7 +28947,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -31966,7 +32008,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -33134,7 +33176,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -35071,7 +35113,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -36855,7 +36897,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js index 1ff91716625..48852fe9f6d 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js @@ -10,6 +10,7 @@ const { } = require( '@woocommerce/e2e-utils' ); let simplePostIdValue; +let orderNum; const config = require( 'config' ); const simpleProductName = config.get( 'products.simple.name' ); @@ -21,26 +22,29 @@ const runMyAccountPayOrderTest = () => { await merchant.logout(); await shopper.login(); await shopper.goToProduct(simplePostIdValue); - await shopper.addToCartFromShopPage(simpleProductName); + await shopper.addToCart(simpleProductName); await shopper.goToCheckout(); await shopper.fillBillingDetails(config.get('addresses.customer.billing')); await uiUnblocked(); await shopper.placeOrder(); - const orderElement = await page.$(".order"); - const orderId = await page.evaluate(orderElement => orderElement.textContent, orderElement); - await shopper.logout(); + + // Get order ID from the order received html element on the page + orderNum = await page.$$eval(".woocommerce-order-overview__order strong", + elements => elements.map(item => item.textContent)); + await merchant.login(); - await merchant.updateOrderStatus(orderId, 'Pending payment'); + await merchant.updateOrderStatus(orderNum, 'Pending payment'); await merchant.logout(); }) it('allows customer to pay for his order in my account', async () => { await shopper.login(); await shopper.goToOrders(); - await expect(page).toClick('.wc_payment_method woocommerce-button button pay', {text: 'Pay'}); - await expect(page).toMatchElement('.entry-title', {text: 'Pay for order'}); - await shopper.placeOrder(); - await expect(page).toMatch('Order received'); + await expect(page).toClick('a.woocommerce-button.button.pay'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }) + await expect(page).toMatchElement('.entry-title', { text: 'Pay for order' }); + await shopper.placeOrder(); + await expect(page).toMatch('Order received'); }); }); } From cbda8b17bb18ad5312718a0831c52c6f7e4283e0 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 14 Jan 2021 17:30:58 +0100 Subject: [PATCH 011/405] Update changelog for core tests --- tests/e2e/core-tests/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index ccd4894ec80..616f70a6d9f 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -6,6 +6,8 @@ - Merchant Apply Coupon tests - Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99 +- Shopper My Account Pay Order + ## Fixed - Flaky Create Product, Coupon, and Order tests From 22d7497d6aa6de8657bb675943a5601a0acd4c8b Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 14 Jan 2021 19:13:16 +0100 Subject: [PATCH 012/405] Fix code to be according to standards --- .../specs/shopper/front-end-my-account-pay-order.test.js | 9 ++++----- tests/e2e/env/config/default.json | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js index 48852fe9f6d..078b239c8b1 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js @@ -29,20 +29,19 @@ const runMyAccountPayOrderTest = () => { await shopper.placeOrder(); // Get order ID from the order received html element on the page - orderNum = await page.$$eval(".woocommerce-order-overview__order strong", - elements => elements.map(item => item.textContent)); + orderNum = await page.$$eval(".woocommerce-order-overview__order strong", elements => elements.map(item => item.textContent)); await merchant.login(); await merchant.updateOrderStatus(orderNum, 'Pending payment'); await merchant.logout(); - }) + }); it('allows customer to pay for his order in my account', async () => { await shopper.login(); await shopper.goToOrders(); await expect(page).toClick('a.woocommerce-button.button.pay'); - await page.waitForNavigation({ waitUntil: 'networkidle0' }) - await expect(page).toMatchElement('.entry-title', { text: 'Pay for order' }); + await page.waitForNavigation({waitUntil: 'networkidle0'}); + await expect(page).toMatchElement('.entry-title', {text: 'Pay for order'}); await shopper.placeOrder(); await expect(page).toMatch('Order received'); }); diff --git a/tests/e2e/env/config/default.json b/tests/e2e/env/config/default.json index e01ee4e9a27..463ec96ceef 100644 --- a/tests/e2e/env/config/default.json +++ b/tests/e2e/env/config/default.json @@ -13,7 +13,8 @@ }, "products": { "simple": { - "name": "Simple product" + "name": "Simple product", + "price": "9.99" }, "variable": { "name": "Variable Product with Three Variations" From c7f89fa8df757dca175ec641ca5073cfdb3bb346 Mon Sep 17 00:00:00 2001 From: Veljko Date: Mon, 18 Jan 2021 14:50:58 +0100 Subject: [PATCH 013/405] Fix changelog conflict --- tests/e2e/core-tests/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 616f70a6d9f..4e247139e08 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -5,7 +5,6 @@ - Merchant Order Refund tests - Merchant Apply Coupon tests - Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99 - - Shopper My Account Pay Order ## Fixed From 10b639510b8edac53005069fa5a621edbcb25fe2 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 19 Jan 2021 09:49:18 -0400 Subject: [PATCH 014/405] use api to delete products before e2e tests --- tests/e2e/config/jest.setup.js | 42 ++++++++++++------------------- tests/e2e/env/config/default.json | 4 +-- tests/e2e/utils/src/factories.js | 22 +++++++++++----- tests/e2e/utils/src/index.js | 3 ++- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/e2e/config/jest.setup.js b/tests/e2e/config/jest.setup.js index b786d7aa9d7..76e3a7713cf 100644 --- a/tests/e2e/config/jest.setup.js +++ b/tests/e2e/config/jest.setup.js @@ -1,10 +1,12 @@ -const { +import { SimpleProduct } from '@woocommerce/api'; +import { switchUserToAdmin, visitAdminPage, switchUserToTest, clearLocalStorage, - setBrowserViewport -} = require( "@wordpress/e2e-test-utils" ); + setBrowserViewport, + factories, +} from '@woocommerce/e2e-utils'; /** * Navigates to the post listing screen and bulk-trashes any posts which exist. @@ -36,32 +38,21 @@ async function trashExistingPosts() { } /** - * Navigates to the product listing screen and bulk-trashes any product which exist. + * Use api package to delete products. * * @return {Promise} Promise resolving once products have been trashed. */ -async function trashExistingProducts() { - await switchUserToAdmin(); - // Visit `/wp-admin/edit.php?post_type=product` so we can see a list of products and delete them. - await visitAdminPage( 'edit.php', 'post_type=product' ); +async function deleteAllProducts() { + const repository = SimpleProduct.restRepository( factories.api.withDefaultPermalinks ); + let products; - // If this selector doesn't exist there are no products for us to delete. - const bulkSelector = await page.$( '#bulk-action-selector-top' ); - if ( ! bulkSelector ) { - return; + products = await repository.list(); + while ( products.length > 0 ) { + for( let p = 0; p < products.length; p++ ) { + await repository.delete( products[ p ].id ); + } + products = await repository.list(); } - - // Select all products. - await page.waitForSelector( '#cb-select-all-1' ); - await page.click( '#cb-select-all-1' ); - // Select the "bulk actions" > "trash" option. - await page.select( '#bulk-action-selector-top', 'trash' ); - // Submit the form to send all draft/scheduled/published posts to the trash. - await page.click( '#doaction' ); - await page.waitForXPath( - '//*[contains(@class, "updated notice")]/p[contains(text(), "moved to the Trash.")]' - ); - await switchUserToTest(); } // Before every test suite run, delete all content created by the test. This ensures @@ -69,8 +60,7 @@ async function trashExistingProducts() { // each other's side-effects. beforeAll( async () => { await trashExistingPosts(); - await trashExistingProducts(); - await switchUserToTest(); + await deleteAllProducts(); await clearLocalStorage(); await setBrowserViewport( 'large' ); } ); diff --git a/tests/e2e/env/config/default.json b/tests/e2e/env/config/default.json index 9dd72fb0f9c..e01ee4e9a27 100644 --- a/tests/e2e/env/config/default.json +++ b/tests/e2e/env/config/default.json @@ -1,5 +1,6 @@ { "url": "http://localhost:8084/", + "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", @@ -12,8 +13,7 @@ }, "products": { "simple": { - "name": "Simple product", - "price": "9.99" + "name": "Simple product" }, "variable": { "name": "Variable Product with Three Variations" diff --git a/tests/e2e/utils/src/factories.js b/tests/e2e/utils/src/factories.js index d8ea9dcf5ac..46f3b91f948 100644 --- a/tests/e2e/utils/src/factories.js +++ b/tests/e2e/utils/src/factories.js @@ -1,15 +1,25 @@ import { HTTPClientFactory } from '@woocommerce/api'; const config = require( 'config' ); - -const httpClient = HTTPClientFactory.build( config.get( 'url' ) ) - .withBasicAuth( config.get( 'users.admin.username' ), config.get( 'users.admin.password' ) ) - .create(); - import { simpleProductFactory } from './factories/simple-product'; +const apiUrl = config.get( 'url' ); +const adminUsername = config.get( 'users.admin.username' ); +const adminPassword = config.get( 'users.admin.password' ); +const withDefaultPermalinks = HTTPClientFactory.build( apiUrl ) + .withBasicAuth( adminUsername, adminPassword ) + .create(); +const withIndexPermalinks = HTTPClientFactory.build( apiUrl ) + .withBasicAuth( adminUsername, adminPassword ) + .withIndexPermalinks() + .create(); + const factories = { + api: { + withDefaultPermalinks, + withIndexPermalinks, + }, products: { - simple: simpleProductFactory( httpClient ), + simple: simpleProductFactory( withDefaultPermalinks ), }, }; diff --git a/tests/e2e/utils/src/index.js b/tests/e2e/utils/src/index.js index 1681b1eebc9..6b5c918db7f 100644 --- a/tests/e2e/utils/src/index.js +++ b/tests/e2e/utils/src/index.js @@ -5,8 +5,9 @@ export * from '@wordpress/e2e-test-utils'; /* * Internal dependencies */ +import factories from './factories'; +export { factories }; export * from './flows'; export * from './old-flows'; export * from './components'; export * from './page-utils'; - From bfa9cc1788a1bd40f64152985d78169c0ec9dcb2 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 28 Jan 2021 11:12:56 +0100 Subject: [PATCH 015/405] Reorder test in index and update readme I added also missing for other pull requests that were merged already --- tests/e2e/core-tests/README.md | 3 +++ tests/e2e/core-tests/specs/index.js | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index a75a039f18c..c4bd0caf7bc 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -63,6 +63,9 @@ The functions to access the core tests are: - `runCheckoutPageTest` - Shopper can complete checkout - `runMyAccountPageTest` - Shopper can access my account page - `runSingleProductPageTest` - Shopper can view single product page + - `runMyAccountPayOrderTest` - Shopper can pay for his order in My Account + - `runCartApplyCouponsTest` - Shopper can apply coupons in the cart + - `runCheckoutApplyCouponsTest` - Shopper can apply coupons in the checkout ## Contributing a new test diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8968b87a15f..5078a7fde71 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -11,11 +11,11 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) // Shopper tests const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test'); const runCartPageTest = require( './shopper/front-end-cart.test' ); -const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); +const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); // Merchant tests const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' ); @@ -38,11 +38,11 @@ const runSetupOnboardingTests = () => { const runShopperTests = () => { runCartApplyCouponsTest(); runCartPageTest(); - runCheckoutApplyCouponsTest(); runCheckoutPageTest(); runMyAccountPayOrderTest(); runMyAccountPageTest(); runSingleProductPageTest(); + runCheckoutApplyCouponsTest(); }; const runMerchantTests = () => { @@ -66,7 +66,6 @@ module.exports = { runSetupOnboardingTests, runCartApplyCouponsTest, runCartPageTest, - runCheckoutApplyCouponsTest, runCheckoutPageTest, runMyAccountPayOrderTest, runMyAccountPageTest, @@ -83,4 +82,5 @@ module.exports = { runOrderRefundTest, runOrderApplyCouponTest, runMerchantTests, + runCheckoutApplyCouponsTest, }; From 6a64a39b39746bc18d6bc31a5cced3bb632f54fe Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 28 Jan 2021 12:10:50 +0100 Subject: [PATCH 016/405] Reorder test in index to avoid flakyness --- tests/e2e/core-tests/specs/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 5078a7fde71..8277a68615d 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -12,10 +12,10 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test'); const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); +const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); -const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); // Merchant tests const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' ); @@ -39,10 +39,10 @@ const runShopperTests = () => { runCartApplyCouponsTest(); runCartPageTest(); runCheckoutPageTest(); + runCheckoutApplyCouponsTest(); runMyAccountPayOrderTest(); runMyAccountPageTest(); runSingleProductPageTest(); - runCheckoutApplyCouponsTest(); }; const runMerchantTests = () => { @@ -67,6 +67,7 @@ module.exports = { runCartApplyCouponsTest, runCartPageTest, runCheckoutPageTest, + runCheckoutApplyCouponsTest, runMyAccountPayOrderTest, runMyAccountPageTest, runSingleProductPageTest, @@ -82,5 +83,4 @@ module.exports = { runOrderRefundTest, runOrderApplyCouponTest, runMerchantTests, - runCheckoutApplyCouponsTest, }; From 6b15fe98e5e6aded34eccadbf888fb75c71cc6ce Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 28 Jan 2021 19:45:39 +0100 Subject: [PATCH 017/405] Revert back index reorder changes --- tests/e2e/core-tests/specs/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8277a68615d..8968b87a15f 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -11,8 +11,8 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) // Shopper tests const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test'); const runCartPageTest = require( './shopper/front-end-cart.test' ); -const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); +const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); @@ -38,8 +38,8 @@ const runSetupOnboardingTests = () => { const runShopperTests = () => { runCartApplyCouponsTest(); runCartPageTest(); - runCheckoutPageTest(); runCheckoutApplyCouponsTest(); + runCheckoutPageTest(); runMyAccountPayOrderTest(); runMyAccountPageTest(); runSingleProductPageTest(); @@ -66,8 +66,8 @@ module.exports = { runSetupOnboardingTests, runCartApplyCouponsTest, runCartPageTest, - runCheckoutPageTest, runCheckoutApplyCouponsTest, + runCheckoutPageTest, runMyAccountPayOrderTest, runMyAccountPageTest, runSingleProductPageTest, From 649cb4cd8df82361bcb1f0902625c44b1d04002c Mon Sep 17 00:00:00 2001 From: Veljko Date: Mon, 1 Feb 2021 18:29:15 +0100 Subject: [PATCH 018/405] Reorder shopper test execution for new test --- tests/e2e/core-tests/specs/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8968b87a15f..4c15782da82 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -13,8 +13,8 @@ const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test' const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); -const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); +const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); // Merchant tests @@ -40,8 +40,8 @@ const runShopperTests = () => { runCartPageTest(); runCheckoutApplyCouponsTest(); runCheckoutPageTest(); - runMyAccountPayOrderTest(); runMyAccountPageTest(); + runMyAccountPayOrderTest(); runSingleProductPageTest(); }; @@ -68,8 +68,8 @@ module.exports = { runCartPageTest, runCheckoutApplyCouponsTest, runCheckoutPageTest, - runMyAccountPayOrderTest, runMyAccountPageTest, + runMyAccountPayOrderTest, runSingleProductPageTest, runShopperTests, runCreateCouponTest, From 98fc4d5f805c21773e5c47df3b9bd75caddd89c4 Mon Sep 17 00:00:00 2001 From: Veljko Date: Tue, 2 Feb 2021 13:38:36 +0100 Subject: [PATCH 019/405] Remove appName from config file --- tests/e2e/config/default.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index e01ee4e9a27..19693eece88 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", From 585d96f2372baf5827da68344a3c5d44dd1fac96 Mon Sep 17 00:00:00 2001 From: Max WP Punk Date: Mon, 8 Feb 2021 03:28:43 +0200 Subject: [PATCH 020/405] Fix #29048 --- includes/admin/meta-boxes/views/html-order-shipping.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-order-shipping.php b/includes/admin/meta-boxes/views/html-order-shipping.php index 86633b06ca9..e972e204b18 100644 --- a/includes/admin/meta-boxes/views/html-order-shipping.php +++ b/includes/admin/meta-boxes/views/html-order-shipping.php @@ -31,11 +31,11 @@ if ( ! defined( 'ABSPATH' ) ) { $found_method = false; foreach ( $shipping_methods as $method ) { - $current_method = ( 0 === strpos( $item->get_method_id(), $method->id ) ) ? $item->get_method_id() : $method->id; + $is_active = $item->get_method_id() === $method->id; - echo ''; + echo ''; - if ( $item->get_method_id() === $current_method ) { + if ( $is_active ) { $found_method = true; } } From 9fe10b23ba3cc00825bd04a89aee80975b2244bd Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 10 Feb 2021 09:13:35 +0100 Subject: [PATCH 021/405] Update test description --- .../specs/shopper/front-end-my-account-pay-order.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js index 078b239c8b1..ae17eeb617f 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-pay-order.test.js @@ -15,7 +15,7 @@ const config = require( 'config' ); const simpleProductName = config.get( 'products.simple.name' ); const runMyAccountPayOrderTest = () => { - describe('Customer can pay for his order through my account', () => { + describe('Customer can pay for their order through My Account', () => { beforeAll(async () => { await merchant.login(); simplePostIdValue = await createSimpleProduct(); @@ -36,7 +36,7 @@ const runMyAccountPayOrderTest = () => { await merchant.logout(); }); - it('allows customer to pay for his order in my account', async () => { + it('allows customer to pay for their order in My Account', async () => { await shopper.login(); await shopper.goToOrders(); await expect(page).toClick('a.woocommerce-button.button.pay'); From 5a16e5b972b19683b1fe0fda3e151edd373ac467 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 10 Feb 2021 09:24:40 +0100 Subject: [PATCH 022/405] Update changelogs --- tests/e2e/core-tests/CHANGELOG.md | 4 +--- tests/e2e/core-tests/README.md | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 9c64777498d..dd77c4e292f 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -9,11 +9,9 @@ - Merchant Order Refund tests - Merchant Apply Coupon tests - Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99 +- Shopper Single Product tests - Shopper My Account Pay Order - - Shopper Checkout Apply Coupon - - - Shopper Cart Apply Coupon ## Fixed diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index c4bd0caf7bc..bba3722f655 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -62,7 +62,7 @@ The functions to access the core tests are: - `runCartPageTest` - Shopper can view and update cart - `runCheckoutPageTest` - Shopper can complete checkout - `runMyAccountPageTest` - Shopper can access my account page - - `runSingleProductPageTest` - Shopper can view single product page + - `runSingleProductPageTest` - Shopper can view single product page in many variations (simple, variable, grouped) - `runMyAccountPayOrderTest` - Shopper can pay for his order in My Account - `runCartApplyCouponsTest` - Shopper can apply coupons in the cart - `runCheckoutApplyCouponsTest` - Shopper can apply coupons in the checkout From 0694092a3c9e5c7f816152a50b9fc0aa0f424111 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 10 Feb 2021 09:28:40 +0100 Subject: [PATCH 023/405] Merge package-lock from master --- package-lock.json | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5becf90d96c..94c5510032c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "woocommerce", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20619,7 +20619,7 @@ "@wordpress/e2e-test-utils": "^4.6.0", "config": "3.3.3", "faker": "^5.1.0", - "fishery": "^1.0.1" + "fishery": "^1.2.0" }, "dependencies": { "@babel/runtime": { @@ -20743,15 +20743,6 @@ "integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==", "dev": true }, - "fishery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fishery/-/fishery-1.0.1.tgz", - "integrity": "sha512-VV8H4ZuCbZ9cCWkrYWLLPoAfpTp0t+hlJVoNWkRRHdXOgQ08wjd8ab9di8/Ed/QhgwxY3h7Y17HgDZ9osaHSSQ==", - "dev": true, - "requires": { - "lodash.mergewith": "^4.6.2" - } - }, "gettext-parser": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz", @@ -20789,12 +20780,6 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, "memize": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/memize/-/memize-1.1.0.tgz", @@ -20917,13 +20902,13 @@ "dev": true }, "@wordpress/deprecated": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-2.11.0.tgz", - "integrity": "sha512-2wfl5J8Y3hZeqkD9QAuXTRxPeXm6x5rxsz+CAFG+SS1E9FYZdB0FnRmm26iza7oDo0n917SuM+QDJ5R8P0UxlA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-2.11.1.tgz", + "integrity": "sha512-ri5M3TSAhonRN9G67KDwu8AXthrxay/1lLwBVbRA+6Dpj6hpC4qUBxOP4Yx5VLYOJEJW2YJx3w3G3XFYiyqfFg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@wordpress/hooks": "^2.11.0" + "@wordpress/hooks": "^2.11.1" }, "dependencies": { "@babel/runtime": { @@ -20996,9 +20981,9 @@ } }, "@wordpress/hooks": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.11.0.tgz", - "integrity": "sha512-TbvCrHcMiSZoyiflegEqVS3DDytDTpkms+yLUaGN4sMvNdR/Mv5s0WnNKyM0T49lbmZYPWlbWhwJ1F6hr/FQDg==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.11.1.tgz", + "integrity": "sha512-20nsvmLH5/iw9P6M7kiEBBQ7X7G3pEbqED/aN5dqkMCklDyar+OZqYBzdpGGsthXVYgomfNy6QQZWELkGJbcbw==", "dev": true, "requires": { "@babel/runtime": "^7.12.5" @@ -25882,6 +25867,15 @@ "parse-filepath": "^1.0.1" } }, + "fishery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fishery/-/fishery-1.2.0.tgz", + "integrity": "sha512-0GG029KHF3p8Q0NiAl/ZOK1fvyAprOiHdtRWUNS46x9QXuQhMwzcGLNDbZ7XIEEBowwBmMsw7StkaU0ek9dSbg==", + "dev": true, + "requires": { + "lodash.mergewith": "^4.6.2" + } + }, "flagged-respawn": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", @@ -31494,6 +31488,12 @@ "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -38470,4 +38470,4 @@ } } } -} +} \ No newline at end of file From 8891509478d10377b7b4d73f0f5b37881dd7677a Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 11 Feb 2021 17:47:58 +0100 Subject: [PATCH 024/405] Add new e2e test merchant shipping zones In progress still --- tests/e2e/config/default.json | 1 - tests/e2e/core-tests/CHANGELOG.md | 1 + tests/e2e/core-tests/README.md | 1 + tests/e2e/core-tests/specs/index.js | 3 + .../wp-admin-settings-shipping-zones.test.js | 59 +++++++++++++++++++ .../specs/wp-admin/test-add-shipping-zones.js | 6 ++ tests/e2e/utils/src/components.js | 27 +++++++++ tests/e2e/utils/src/flows/constants.js | 1 + tests/e2e/utils/src/flows/merchant.js | 6 ++ 9 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js create mode 100644 tests/e2e/specs/wp-admin/test-add-shipping-zones.js diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index e01ee4e9a27..19693eece88 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 66bfb48daa8..c8fa962a503 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -13,6 +13,7 @@ - Shopper Checkout Apply Coupon - Merchant Orders Customer Checkout Page - Shopper Cart Apply Coupon +- Merchant Settings Shipping Zones ## Fixed diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index 2c22cc29d43..ae6c46ff3bb 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -56,6 +56,7 @@ The functions to access the core tests are: - `runOrderRefundTest` - Merchant can refund an order - `runOrderApplyCouponTest` - Merchant can apply a coupon to an order - `runMerchantOrdersCustomerPaymentPage` - Merchant can visit the customer payment page + - `runAddNewShippingZoneTest` - Merchant can create shipping zones and let shopper test them ### Shopper diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index bc694a6e88d..03141dc3914 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -27,6 +27,7 @@ const runOrderStatusFiltersTest = require( './merchant/wp-admin-order-status-fil const runOrderRefundTest = require( './merchant/wp-admin-order-refund.test' ); const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon.test' ); const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' ); +const runAddNewShippingZoneTest = require ( './merchant/wp-admin-settings-shipping-zones.test' ); const runSetupOnboardingTests = () => { runActivationTest(); @@ -56,6 +57,7 @@ const runMerchantTests = () => { runOrderRefundTest(); runOrderApplyCouponTest(); runMerchantOrdersCustomerPaymentPage(); + runAddNewShippingZoneTest(); } module.exports = { @@ -83,4 +85,5 @@ module.exports = { runOrderApplyCouponTest, runMerchantOrdersCustomerPaymentPage, runMerchantTests, + runAddNewShippingZoneTest, }; diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js new file mode 100644 index 00000000000..57617948e8c --- /dev/null +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -0,0 +1,59 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests */ +/** + * Internal dependencies + */ +const { + merchant, + addShippingZoneAndMethod, +} = require( '@woocommerce/e2e-utils' ); + +/** + * External dependencies + */ +const { + it, + describe, + beforeAll, +} = require( '@jest/globals' ); + +// Shipping Zone Names +const nameUSFlatRate = 'US with Flat rate'; +const nameCAFreeShipping = 'CA with Free shipping'; +const nameSFLocalPickup = 'SF with Local pickup'; +// Shipping Zone Locations +const californiaUS = 'California, United States (US)'; +const sanFranciscoCA = 'San Francisco'; + +const runAddNewShippingZoneTest = () => { + describe('WooCommerce Shipping Settings - Add new shipping zone', () => { + beforeAll(async () => { + await merchant.login(); + }); + + it('add new shipping zone for the US with Flat rate', async () => { + // Add a new shipping zone for the US with Flat rate + await addShippingZoneAndMethod(shippingZoneNameUSFlatRate); + + // Verify that settings have been saved + await Promise.all([ + verifyValueOfInputField('input#zone_name', nameUSFlatRate), + expect(page).toMatchElement('li.select2-selection__choice', {text: shippingZoneNameUSFlatRate}), + expect(page).toMatchElement('a.wc-shipping-zone-method-settings', {text: 'Flat rate'}) + ]); + }); + + it('add new shipping zone for California with Free shipping', async () => { + // Add a new shipping zone for California with Free shipping + await addShippingZoneAndMethod(nameCAFreeShipping, californiaUS, 'Free shipping'); + + }); + + it('add new shipping zone for San Francisco with Local pickup for free', async () => { + // Add a new shipping zone for the US with Flat Rate + await addShippingZoneAndMethod(nameSFLocalPickup, sanFranciscoCA, 'Local pickup'); + + }); + }); +}; + +module.exports = runAddNewShippingZoneTest; diff --git a/tests/e2e/specs/wp-admin/test-add-shipping-zones.js b/tests/e2e/specs/wp-admin/test-add-shipping-zones.js new file mode 100644 index 00000000000..bf084d01847 --- /dev/null +++ b/tests/e2e/specs/wp-admin/test-add-shipping-zones.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runAddNewShippingZoneTest } = require( '@woocommerce/e2e-core-tests' ); + +runAddNewShippingZoneTest(); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 56cb7b4a75a..6d026452037 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -426,6 +426,32 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc return couponCode; }; +/** + * Adds a shipping zone along with a shipping method. + * + * @param zoneName Shipping zone name. + * @param zoneLocation Shiping zone location. Defaults to United States (US). + * @param zoneMethod Shipping method type. Defaults to Flat rate method. + */ +const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zoneMethod = 'flat_rate' ) => { + await merchant.openNewShipping(); + + // Fill shipping zone name + await expect(page).toFill('input#zone_name', zoneName); + + // Select shipping zone location + await expect(page).toSelect('#zone_locations', "   " + zoneLocation); + + // Add shipping zone method + await expect(page).toClick('button.button.wc-shipping-zone-add-method', {text:'Add shipping method'}); + await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); + await expect(page).toClick('button#btn-ok'); + await page.waitForSelector('#zone_locations'); + + // Save the shipping zone with method + await expect(page).toClick('#submit'); +}; + export { completeOnboardingWizard, createSimpleProduct, @@ -435,4 +461,5 @@ export { verifyAndPublish, addProductToOrder, createCoupon, + addShippingZoneAndMethod, }; diff --git a/tests/e2e/utils/src/flows/constants.js b/tests/e2e/utils/src/flows/constants.js index ae8790f3288..7d1de1c57df 100644 --- a/tests/e2e/utils/src/flows/constants.js +++ b/tests/e2e/utils/src/flows/constants.js @@ -15,6 +15,7 @@ export const WP_ADMIN_NEW_ORDER = baseUrl + 'wp-admin/post-new.php?post_type=sho export const WP_ADMIN_NEW_PRODUCT = baseUrl + 'wp-admin/post-new.php?post_type=product'; export const WP_ADMIN_WC_SETTINGS = baseUrl + 'wp-admin/admin.php?page=wc-settings&tab='; export const WP_ADMIN_PERMALINK_SETTINGS = baseUrl + 'wp-admin/options-permalink.php'; +export const WP_ADMIN_NEW_SHIPPING_ZONE = baseUrl + 'wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=new'; export const SHOP_PAGE = baseUrl + 'shop'; export const SHOP_PRODUCT_PAGE = baseUrl + '?p='; diff --git a/tests/e2e/utils/src/flows/merchant.js b/tests/e2e/utils/src/flows/merchant.js index f58d184a829..41765c9dada 100644 --- a/tests/e2e/utils/src/flows/merchant.js +++ b/tests/e2e/utils/src/flows/merchant.js @@ -156,6 +156,12 @@ const merchant = { await expect( page ).toMatchElement( 'label[for="customer_user"] a[href*=user-edit]', { text: 'Profile' } ); } }, + + openNewShipping: async () => { + await page.goto( WP_ADMIN_NEW_SHIPPING_ZONE, { + waitUntil: 'networkidle0', + } ); + }, }; module.exports = merchant; From 58b564f1cb30ce174971bfcb0e4fdc0374d8ddb3 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 11 Feb 2021 19:05:50 +0100 Subject: [PATCH 025/405] Add new component for shipping zones --- .../wp-admin-settings-shipping-zones.test.js | 40 ++++++++++--------- tests/e2e/utils/src/components.js | 7 ++-- tests/e2e/utils/src/flows/merchant.js | 3 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 57617948e8c..2817c2ae6ff 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -5,6 +5,7 @@ const { merchant, addShippingZoneAndMethod, + clearAndFillInput, } = require( '@woocommerce/e2e-utils' ); /** @@ -17,12 +18,12 @@ const { } = require( '@jest/globals' ); // Shipping Zone Names -const nameUSFlatRate = 'US with Flat rate'; -const nameCAFreeShipping = 'CA with Free shipping'; -const nameSFLocalPickup = 'SF with Local pickup'; +const shippingZoneNameUS = 'US with Flat rate'; +const shippingZoneNameCA = 'CA with Free shipping'; +const shippingZoneNameSF = 'SF with Local pickup'; // Shipping Zone Locations -const californiaUS = 'California, United States (US)'; -const sanFranciscoCA = 'San Francisco'; +const california = 'California, United States (US)'; +const sanFranciscoZIP = '94110'; const runAddNewShippingZoneTest = () => { describe('WooCommerce Shipping Settings - Add new shipping zone', () => { @@ -30,28 +31,31 @@ const runAddNewShippingZoneTest = () => { await merchant.login(); }); - it('add new shipping zone for the US with Flat rate', async () => { + it('add shipping zone for the US with Flat rate', async () => { // Add a new shipping zone for the US with Flat rate - await addShippingZoneAndMethod(shippingZoneNameUSFlatRate); + await addShippingZoneAndMethod(shippingZoneNameUS); - // Verify that settings have been saved - await Promise.all([ - verifyValueOfInputField('input#zone_name', nameUSFlatRate), - expect(page).toMatchElement('li.select2-selection__choice', {text: shippingZoneNameUSFlatRate}), - expect(page).toMatchElement('a.wc-shipping-zone-method-settings', {text: 'Flat rate'}) - ]); + // Set Flat rate cost + await expect(page).toClick('a.wc-shipping-zone-method-settings', {text: 'Edit'}); + await clearAndFillInput('#woocommerce_flat_rate_cost', '10'); + await expect(page).toClick('button#btn-ok'); }); - it('add new shipping zone for California with Free shipping', async () => { + it('add shipping zone for California with Free shipping', async () => { // Add a new shipping zone for California with Free shipping - await addShippingZoneAndMethod(nameCAFreeShipping, californiaUS, 'Free shipping'); - + await addShippingZoneAndMethod(shippingZoneNameCA, california, 'free_shipping'); }); - it('add new shipping zone for San Francisco with Local pickup for free', async () => { + it('add shipping zone for San Francisco with free Local pickup', async () => { // Add a new shipping zone for the US with Flat Rate - await addShippingZoneAndMethod(nameSFLocalPickup, sanFranciscoCA, 'Local pickup'); + await addShippingZoneAndMethod(shippingZoneNameSF, california, 'local_pickup'); + // Set San Francisco as a local pickup city + await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); + await expect(page).toFill('#zone_postcodes', sanFranciscoZIP); + + // Save the shipping zone + await expect(page).toClick('button#submit'); }); }); }; diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 6d026452037..e794071ba4f 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -440,16 +440,15 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await expect(page).toFill('input#zone_name', zoneName); // Select shipping zone location - await expect(page).toSelect('#zone_locations', "   " + zoneLocation); + await expect(page).toFill('#zone_locations', zoneLocation); + await page.keyboard.press('Tab'); + await page.keyboard.press('Enter'); // Add shipping zone method await expect(page).toClick('button.button.wc-shipping-zone-add-method', {text:'Add shipping method'}); await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); - - // Save the shipping zone with method - await expect(page).toClick('#submit'); }; export { diff --git a/tests/e2e/utils/src/flows/merchant.js b/tests/e2e/utils/src/flows/merchant.js index 41765c9dada..ef2d2f8dbee 100644 --- a/tests/e2e/utils/src/flows/merchant.js +++ b/tests/e2e/utils/src/flows/merchant.js @@ -17,7 +17,8 @@ const { WP_ADMIN_PERMALINK_SETTINGS, WP_ADMIN_PLUGINS, WP_ADMIN_SETUP_WIZARD, - WP_ADMIN_WC_SETTINGS + WP_ADMIN_WC_SETTINGS, + WP_ADMIN_NEW_SHIPPING_ZONE } = require( './constants' ); const baseUrl = config.get( 'url' ); From e724f21f6d621a5eb086d15d047e16ce64e38c94 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 11 Feb 2021 19:11:58 +0100 Subject: [PATCH 026/405] Update changelog and readme --- tests/e2e/utils/CHANGELOG.md | 1 + tests/e2e/utils/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 8b770fb8b4d..ab42dac9bb1 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -16,6 +16,7 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field +- `addShippingZoneAndMethod( zoneName, zoneLocation, zoneMethod )` util helper method for adding shipping zones with shipping methods ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 4b03105f644..fb40c629d1c 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -99,6 +99,7 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside +| `addShippingZoneAndMethod` | `zoneName, zoneLocation, zoneMethod` | util helper method for adding shipping zones with shipping methods ### Test Utilities From 7b42416718b92a45927424be653bafef708ca046 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 12 Feb 2021 11:18:25 +0100 Subject: [PATCH 027/405] Improve test and add new scenarios --- .../wp-admin-settings-shipping-zones.test.js | 50 ++++++++++++++----- tests/e2e/utils/src/components.js | 1 + 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 2817c2ae6ff..3ccd9fd0acd 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -1,34 +1,30 @@ /* eslint-disable jest/no-export, jest/no-disabled-tests */ + /** * Internal dependencies */ const { + shopper, merchant, + createSimpleProduct, addShippingZoneAndMethod, clearAndFillInput, + selectOptionInSelect2, } = require( '@woocommerce/e2e-utils' ); -/** - * External dependencies - */ -const { - it, - describe, - beforeAll, -} = require( '@jest/globals' ); - -// Shipping Zone Names +const config = require( 'config' ); +const simpleProductName = config.get( 'products.simple.name' ); +const california = 'California, United States (US)'; +const sanFranciscoZIP = '94107'; const shippingZoneNameUS = 'US with Flat rate'; const shippingZoneNameCA = 'CA with Free shipping'; const shippingZoneNameSF = 'SF with Local pickup'; -// Shipping Zone Locations -const california = 'California, United States (US)'; -const sanFranciscoZIP = '94110'; const runAddNewShippingZoneTest = () => { describe('WooCommerce Shipping Settings - Add new shipping zone', () => { beforeAll(async () => { await merchant.login(); + await createSimpleProduct(); }); it('add shipping zone for the US with Flat rate', async () => { @@ -56,7 +52,35 @@ const runAddNewShippingZoneTest = () => { // Save the shipping zone await expect(page).toClick('button#submit'); + await merchant.logout(); }); + + it('allows customer to pay for a Flat rate shipping method', async() => { + await shopper.login(); + + // Add product to cart as a shopper + await shopper.goToShop(); + await shopper.addToCartFromShopPage(simpleProductName); + await shopper.goToCart(); + + // Set shipping country to United States (US) + await expect(page).toClick('a.shipping-calculator-button'); + await expect(page).toClick('#select2-calc_shipping_state-container'); + await selectOptionInSelect2('United States (US)'); + + // Set shipping state to New York + await expect(page).toClick('#select2-calc_shipping_state-container'); + await selectOptionInSelect2('New York'); + await expect(page).toClick('button[name="calc_shipping"]'); + }) + + it('allows customer to benefit from a Free shipping if in CA', async () => { + //test + }) + + it('allows customer to benefit from a Free shipping if in SF', async () => { + //test + }) }); }; diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index e794071ba4f..b01e929d1a8 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -440,6 +440,7 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await expect(page).toFill('input#zone_name', zoneName); // Select shipping zone location + // (.toSelect is not best option here because a lot of   are present in country/state names) await expect(page).toFill('#zone_locations', zoneLocation); await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); From 1e26d880c10ff15fc9b11b228b159c9f1b80109e Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 12 Feb 2021 16:10:23 +0100 Subject: [PATCH 028/405] Finalize test cases and scenarios --- .../wp-admin-settings-shipping-zones.test.js | 79 ++++++++++++++----- tests/e2e/utils/src/components.js | 3 +- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 3ccd9fd0acd..f00aa5b9a57 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -17,7 +17,7 @@ const simpleProductName = config.get( 'products.simple.name' ); const california = 'California, United States (US)'; const sanFranciscoZIP = '94107'; const shippingZoneNameUS = 'US with Flat rate'; -const shippingZoneNameCA = 'CA with Free shipping'; +const shippingZoneNameFL = 'CA with Free shipping'; const shippingZoneNameSF = 'SF with Local pickup'; const runAddNewShippingZoneTest = () => { @@ -27,21 +27,6 @@ const runAddNewShippingZoneTest = () => { await createSimpleProduct(); }); - it('add shipping zone for the US with Flat rate', async () => { - // Add a new shipping zone for the US with Flat rate - await addShippingZoneAndMethod(shippingZoneNameUS); - - // Set Flat rate cost - await expect(page).toClick('a.wc-shipping-zone-method-settings', {text: 'Edit'}); - await clearAndFillInput('#woocommerce_flat_rate_cost', '10'); - await expect(page).toClick('button#btn-ok'); - }); - - it('add shipping zone for California with Free shipping', async () => { - // Add a new shipping zone for California with Free shipping - await addShippingZoneAndMethod(shippingZoneNameCA, california, 'free_shipping'); - }); - it('add shipping zone for San Francisco with free Local pickup', async () => { // Add a new shipping zone for the US with Flat Rate await addShippingZoneAndMethod(shippingZoneNameSF, california, 'local_pickup'); @@ -52,6 +37,21 @@ const runAddNewShippingZoneTest = () => { // Save the shipping zone await expect(page).toClick('button#submit'); + }); + + it('add shipping zone for California with Free shipping', async () => { + // Add a new shipping zone for California with Free shipping + await addShippingZoneAndMethod(shippingZoneNameFL, california, 'free_shipping'); + }); + + it('add shipping zone for the US with Flat rate', async () => { + // Add a new shipping zone for the US with Flat rate + await addShippingZoneAndMethod(shippingZoneNameUS); + + // Set Flat rate cost + await expect(page).toClick('a.wc-shipping-zone-method-settings', {text: 'Edit'}); + await clearAndFillInput('#woocommerce_flat_rate_cost', '10'); + await expect(page).toClick('button#btn-ok'); await merchant.logout(); }); @@ -65,21 +65,60 @@ const runAddNewShippingZoneTest = () => { // Set shipping country to United States (US) await expect(page).toClick('a.shipping-calculator-button'); - await expect(page).toClick('#select2-calc_shipping_state-container'); + await expect(page).toClick('#select2-calc_shipping_country-container'); await selectOptionInSelect2('United States (US)'); // Set shipping state to New York await expect(page).toClick('#select2-calc_shipping_state-container'); await selectOptionInSelect2('New York'); await expect(page).toClick('button[name="calc_shipping"]'); + + // Verify shipping method and costs + await page.waitForSelector('.order-total'); + await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$19.99'}); + + await shopper.removeFromCart(simpleProductName); }) it('allows customer to benefit from a Free shipping if in CA', async () => { - //test + await shopper.goToShop(); + await shopper.addToCartFromShopPage(simpleProductName); + await shopper.goToCart(); + + // Set shipping state to California + await expect(page).toClick('a.shipping-calculator-button'); + await expect(page).toClick('#select2-calc_shipping_state-container'); + await selectOptionInSelect2('California'); + + // Set shipping postcode to 99000 + await clearAndFillInput('#calc_shipping_postcode', '94000'); + await expect(page).toClick('button[name="calc_shipping"]'); + + // Verify shipping method and costs + await page.waitForSelector('.order-total'); + await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); + + await shopper.removeFromCart(simpleProductName); }) - it('allows customer to benefit from a Free shipping if in SF', async () => { - //test + it('allows customer to benefit from a free Local pickup if in SF', async () => { + await shopper.goToShop(); + await shopper.addToCartFromShopPage(simpleProductName); + await shopper.goToCart(); + + // Set shipping postcode to 94107 + await expect(page).toClick('a.shipping-calculator-button'); + await clearAndFillInput('#calc_shipping_postcode', '94107'); + await expect(page).toClick('button[name="calc_shipping"]'); + + // Verify shipping method and costs + await page.waitForSelector('.order-total'); + await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); + + await shopper.removeFromCart(simpleProductName); }) }); }; diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index b01e929d1a8..439adc5d67d 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -431,7 +431,7 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc * * @param zoneName Shipping zone name. * @param zoneLocation Shiping zone location. Defaults to United States (US). - * @param zoneMethod Shipping method type. Defaults to Flat rate method. + * @param zoneMethod Shipping method type. Defaults to flat_rate (use also: free_shipping or local_pickup) */ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zoneMethod = 'flat_rate' ) => { await merchant.openNewShipping(); @@ -447,6 +447,7 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States // Add shipping zone method await expect(page).toClick('button.button.wc-shipping-zone-add-method', {text:'Add shipping method'}); + await page.waitForSelector('woocommerce_flat_rate_cost'); await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); From 2b0127f9e3e9da30f4b821f94f25bffb3d1e0930 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 12 Feb 2021 17:43:01 +0100 Subject: [PATCH 029/405] Fix flaky scenarios and improve code --- .../wp-admin-settings-shipping-zones.test.js | 11 ++--------- tests/e2e/utils/src/components.js | 14 +++++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index f00aa5b9a57..cdcea6ccca2 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -29,19 +29,12 @@ const runAddNewShippingZoneTest = () => { it('add shipping zone for San Francisco with free Local pickup', async () => { // Add a new shipping zone for the US with Flat Rate - await addShippingZoneAndMethod(shippingZoneNameSF, california, 'local_pickup'); - - // Set San Francisco as a local pickup city - await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); - await expect(page).toFill('#zone_postcodes', sanFranciscoZIP); - - // Save the shipping zone - await expect(page).toClick('button#submit'); + await addShippingZoneAndMethod(shippingZoneNameSF, california, sanFranciscoZIP, 'local_pickup'); }); it('add shipping zone for California with Free shipping', async () => { // Add a new shipping zone for California with Free shipping - await addShippingZoneAndMethod(shippingZoneNameFL, california, 'free_shipping'); + await addShippingZoneAndMethod(shippingZoneNameFL, california, ' ', 'free_shipping'); }); it('add shipping zone for the US with Flat rate', async () => { diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 439adc5d67d..77b2960b393 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -431,12 +431,14 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc * * @param zoneName Shipping zone name. * @param zoneLocation Shiping zone location. Defaults to United States (US). + * @param zipCode TBD * @param zoneMethod Shipping method type. Defaults to flat_rate (use also: free_shipping or local_pickup) */ -const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zoneMethod = 'flat_rate' ) => { +const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zipCode = ' ', zoneMethod = 'flat_rate' ) => { await merchant.openNewShipping(); // Fill shipping zone name + await page.waitForSelector('input#zone_name'); await expect(page).toFill('input#zone_name', zoneName); // Select shipping zone location @@ -445,9 +447,15 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); + // Fill shipping zone postcode if needed otherwise just put empty space + await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); + await expect(page).toFill('#zone_postcodes', zipCode); + await expect(page).toClick('button#submit'); + // Add shipping zone method - await expect(page).toClick('button.button.wc-shipping-zone-add-method', {text:'Add shipping method'}); - await page.waitForSelector('woocommerce_flat_rate_cost'); + await page.waitFor(1000); // avoiding flakiness + await expect(page).toClick('button.wc-shipping-zone-add-method', {text:'Add shipping method'}); + await page.waitFor(1000); // avoiding flakiness await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); From fc3b3425459d3c99eadc742b74b666881694f08b Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 12 Feb 2021 18:19:48 +0100 Subject: [PATCH 030/405] Update changelogs and comments --- .../wp-admin-settings-shipping-zones.test.js | 12 ++++++------ tests/e2e/utils/CHANGELOG.md | 2 +- tests/e2e/utils/README.md | 2 +- tests/e2e/utils/src/components.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index cdcea6ccca2..d13857b8369 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -28,12 +28,12 @@ const runAddNewShippingZoneTest = () => { }); it('add shipping zone for San Francisco with free Local pickup', async () => { - // Add a new shipping zone for the US with Flat Rate + // Add a new shipping zone for San Francisco 94107, CA, US with Local pickup await addShippingZoneAndMethod(shippingZoneNameSF, california, sanFranciscoZIP, 'local_pickup'); }); it('add shipping zone for California with Free shipping', async () => { - // Add a new shipping zone for California with Free shipping + // Add a new shipping zone for CA, US with Free shipping await addShippingZoneAndMethod(shippingZoneNameFL, california, ' ', 'free_shipping'); }); @@ -66,7 +66,7 @@ const runAddNewShippingZoneTest = () => { await selectOptionInSelect2('New York'); await expect(page).toClick('button[name="calc_shipping"]'); - // Verify shipping method and costs + // Verify shipping costs await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$19.99'}); @@ -84,11 +84,11 @@ const runAddNewShippingZoneTest = () => { await expect(page).toClick('#select2-calc_shipping_state-container'); await selectOptionInSelect2('California'); - // Set shipping postcode to 99000 + // Set shipping postcode to 94000 await clearAndFillInput('#calc_shipping_postcode', '94000'); await expect(page).toClick('button[name="calc_shipping"]'); - // Verify shipping method and costs + // Verify shipping method and cost await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'}); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); @@ -106,7 +106,7 @@ const runAddNewShippingZoneTest = () => { await clearAndFillInput('#calc_shipping_postcode', '94107'); await expect(page).toClick('button[name="calc_shipping"]'); - // Verify shipping method and costs + // Verify shipping method and cost await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index ab42dac9bb1..837f05b79d7 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -16,7 +16,7 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field -- `addShippingZoneAndMethod( zoneName, zoneLocation, zoneMethod )` util helper method for adding shipping zones with shipping methods +- `addShippingZoneAndMethod( zoneName, zoneLocation, zipCode, zoneMethod )` util helper method for adding shipping zones with shipping methods ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index fb40c629d1c..b59e7d89cd2 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -99,7 +99,7 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside -| `addShippingZoneAndMethod` | `zoneName, zoneLocation, zoneMethod` | util helper method for adding shipping zones with shipping methods +| `addShippingZoneAndMethod` | `zoneName, zoneLocation, zipCode, zoneMethod` | util helper method for adding shipping zones with shipping methods ### Test Utilities diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 77b2960b393..3f0d33d0240 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -431,7 +431,7 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc * * @param zoneName Shipping zone name. * @param zoneLocation Shiping zone location. Defaults to United States (US). - * @param zipCode TBD + * @param zipCode Shipping zone zip code. Defaults to empty one space. * @param zoneMethod Shipping method type. Defaults to flat_rate (use also: free_shipping or local_pickup) */ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zipCode = ' ', zoneMethod = 'flat_rate' ) => { From 46655e83a2336b6d0098a5d087a9c27737068464 Mon Sep 17 00:00:00 2001 From: Veljko Date: Sat, 13 Feb 2021 09:59:14 +0100 Subject: [PATCH 031/405] Add timeout waits in tests --- .../specs/merchant/wp-admin-settings-shipping-zones.test.js | 6 +++--- tests/e2e/utils/src/components.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index d13857b8369..db705deefda 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -68,7 +68,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping costs await page.waitForSelector('.order-total'); - await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}); + await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}, 2000); await expect(page).toMatchElement('.order-total .amount', {text: '$19.99'}); await shopper.removeFromCart(simpleProductName); @@ -91,7 +91,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping method and cost await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}, 2000); await shopper.removeFromCart(simpleProductName); }) @@ -108,7 +108,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping method and cost await page.waitForSelector('.order-total'); - await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}); + await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}, 2000); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); await shopper.removeFromCart(simpleProductName); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 3f0d33d0240..f2ca0e8e31f 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -450,6 +450,7 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States // Fill shipping zone postcode if needed otherwise just put empty space await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); await expect(page).toFill('#zone_postcodes', zipCode); + await expect(page).toMatchElement('#zone_postcodes', zipCode); await expect(page).toClick('button#submit'); // Add shipping zone method From 9a8cbfbd73d78820d88a91582a6b170a5bac45b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Valney?= Date: Tue, 16 Feb 2021 14:49:25 -0300 Subject: [PATCH 032/405] Support to optgroups on select from Settings API Add the optgroups support to single select from Settings API. The multiselect input already has this feature. --- includes/abstracts/abstract-wc-settings-api.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index f33a62365fc..095403d65c0 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -696,6 +696,7 @@ abstract class WC_Settings_API { ); $data = wp_parse_args( $data, $defaults ); + $value = $this->get_option( $key ); ob_start(); ?> @@ -708,7 +709,15 @@ abstract class WC_Settings_API { get_description_html( $data ); // WPCS: XSS ok. ?> From 323fc329599f6e0f843f802a0ff63ed1385ea208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Valney?= Date: Tue, 16 Feb 2021 15:01:07 -0300 Subject: [PATCH 033/405] Added esc_attr to selected like before --- includes/abstracts/abstract-wc-settings-api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index 095403d65c0..35bdbe522b7 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -712,11 +712,11 @@ abstract class WC_Settings_API { $option_value_inner ) : ?> - + - + From eabee48a32b694e36d673aca0ea24b24abc58abb Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 18 Feb 2021 12:49:04 +0100 Subject: [PATCH 034/405] Remove waits and reorder test --- tests/e2e/core-tests/specs/index.js | 2 +- .../specs/merchant/wp-admin-settings-shipping-zones.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 03141dc3914..61fe57e7d2f 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -51,13 +51,13 @@ const runMerchantTests = () => { runAddSimpleProductTest(); runAddVariableProductTest(); runUpdateGeneralSettingsTest(); + runAddNewShippingZoneTest(); runProductSettingsTest(); runTaxSettingsTest(); runOrderStatusFiltersTest(); runOrderRefundTest(); runOrderApplyCouponTest(); runMerchantOrdersCustomerPaymentPage(); - runAddNewShippingZoneTest(); } module.exports = { diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index db705deefda..d13857b8369 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -68,7 +68,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping costs await page.waitForSelector('.order-total'); - await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}, 2000); + await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}); await expect(page).toMatchElement('.order-total .amount', {text: '$19.99'}); await shopper.removeFromCart(simpleProductName); @@ -91,7 +91,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping method and cost await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}, 2000); + await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); await shopper.removeFromCart(simpleProductName); }) @@ -108,7 +108,7 @@ const runAddNewShippingZoneTest = () => { // Verify shipping method and cost await page.waitForSelector('.order-total'); - await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}, 2000); + await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Local pickup'}); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); await shopper.removeFromCart(simpleProductName); From a77a4f264e73852a60343d463ab1bf7f07b3dbac Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 18 Feb 2021 21:53:30 +0100 Subject: [PATCH 035/405] Add new e2e test merchant order searching --- tests/e2e/config/default.json | 1 - tests/e2e/core-tests/CHANGELOG.md | 1 + tests/e2e/core-tests/README.md | 1 + tests/e2e/core-tests/specs/index.js | 3 + .../merchant/wp-admin-order-searching.test.js | 136 ++++++++++++++++++ .../specs/wp-admin/test-order-searching.js | 6 + tests/e2e/utils/CHANGELOG.md | 1 + tests/e2e/utils/README.md | 1 + tests/e2e/utils/src/components.js | 8 +- tests/e2e/utils/src/page-utils.js | 14 ++ 10 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js create mode 100644 tests/e2e/specs/wp-admin/test-order-searching.js diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index e01ee4e9a27..19693eece88 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 66bfb48daa8..be9634a2294 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -13,6 +13,7 @@ - Shopper Checkout Apply Coupon - Merchant Orders Customer Checkout Page - Shopper Cart Apply Coupon +- Merchant Order Searching ## Fixed diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index 2c22cc29d43..940cf6f0102 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -56,6 +56,7 @@ The functions to access the core tests are: - `runOrderRefundTest` - Merchant can refund an order - `runOrderApplyCouponTest` - Merchant can apply a coupon to an order - `runMerchantOrdersCustomerPaymentPage` - Merchant can visit the customer payment page + - `runOrderSearchingTest` - Merchant can search for order via different terms ### Shopper diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index bc694a6e88d..00662050244 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -27,6 +27,7 @@ const runOrderStatusFiltersTest = require( './merchant/wp-admin-order-status-fil const runOrderRefundTest = require( './merchant/wp-admin-order-refund.test' ); const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon.test' ); const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' ); +const runOrderSearchingTest = require( './merchant/wp-admin-order-searching.test' ); const runSetupOnboardingTests = () => { runActivationTest(); @@ -56,6 +57,7 @@ const runMerchantTests = () => { runOrderRefundTest(); runOrderApplyCouponTest(); runMerchantOrdersCustomerPaymentPage(); + runOrderSearchingTest(); } module.exports = { @@ -83,4 +85,5 @@ module.exports = { runOrderApplyCouponTest, runMerchantOrdersCustomerPaymentPage, runMerchantTests, + runOrderSearchingTest, }; diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js new file mode 100644 index 00000000000..ccd9de8c8c3 --- /dev/null +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -0,0 +1,136 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests, */ + +/** + * Internal dependencies + */ +const { + merchant, + clearAndFillInput, + selectOptionInSelect2, + searchForOrder, + createSimpleProduct, + addProductToOrder, +} = require( '@woocommerce/e2e-utils' ); + +let orderId; + +const runOrderSearchingTest = () => { + describe('WooCommerce Orders > Search orders', () => { + beforeAll(async () => { + await merchant.login(); + + await createSimpleProduct('Wanted Product'); + + // Create new order for testing + await merchant.openNewOrder(); + await page.select('#order_status', 'Pending payment'); + await page.click('#customer_user'); + await selectOptionInSelect2('Customer', 'input.select2-search__field'); + + // Change the shipping data + const input = await page.$('#_shipping_first_name'); // to avoid flakiness + await input.click({ clickCount: 3 }); // to avoid flakiness + await page.keyboard.press('Backspace'); // to avoid flakiness + await clearAndFillInput('#_shipping_first_name', 'Jane'); + await clearAndFillInput('#_shipping_last_name', 'Doherty'); + await clearAndFillInput('#_shipping_address_1', 'Oxford Ave'); + await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); + await clearAndFillInput('#_shipping_city', 'Buffalo'); + await clearAndFillInput('#_shipping_postcode', '14201'); + await page.select('#_shipping_state', 'NY'); + + // Save new order + await page.waitFor(2000); // wait for autosave + await page.click('button.save_order'); + await page.waitForSelector('#message'); + + // Get the post id + const variablePostId = await page.$('#post_ID'); + orderId = (await(await variablePostId.getProperty('value')).jsonValue()); + + await merchant.openAllOrdersView(); + + await addProductToOrder(orderId, 'Wanted Product'); + + await merchant.openAllOrdersView(); + }); + + it('can search for order by order id', async () => { + await searchForOrder(orderId, orderId); + }); + + it('can search for order by billing first name', async () => { + await searchForOrder('John', orderId); + }) + + it('can search for order by billing last name', async () => { + await searchForOrder('Doe', orderId); + }) + + it('can search for order by billing company name', async () => { + await searchForOrder('Automattic', orderId); + }) + + it('can search for order by billing first address', async () => { + await searchForOrder('addr 1', orderId); + }) + + it('can search for order by billing second address', async () => { + await searchForOrder('addr 2', orderId); + }) + + it('can search for order by billing city name', async () => { + await searchForOrder('San Francisco', orderId); + }) + + it('can search for order by billing post code', async () => { + await searchForOrder('94107', orderId); + }) + + it('can search for order by billing email', async () => { + await searchForOrder('john.doe@example.com', orderId); + }) + + it('can search for order by billing phone', async () => { + await searchForOrder('123456789', orderId); + }) + + it('can search for order by billing state', async () => { + await searchForOrder('CA', orderId); + }) + + it('can search for order by shipping first name', async () => { + await searchForOrder('Jane', orderId); + }) + + it('can search for order by shipping last name', async () => { + await searchForOrder('Doherty', orderId); + }) + + it('can search for order by shipping first address', async () => { + await searchForOrder('Oxford Ave', orderId); + }) + + it('can search for order by shipping second address', async () => { + await searchForOrder('Linwood Ave', orderId); + }) + + it('can search for order by shipping city name', async () => { + await searchForOrder('Buffalo', orderId); + }) + + it('can search for order by shipping postcode name', async () => { + await searchForOrder('14201', orderId); + }) + + it('can search for order by shipping state name', async () => { + await searchForOrder('NY', orderId); + }) + + it('can search for order by item name', async () => { + await searchForOrder('Wanted Product', orderId); + }) + }); +}; + +module.exports = runOrderSearchingTest; diff --git a/tests/e2e/specs/wp-admin/test-order-searching.js b/tests/e2e/specs/wp-admin/test-order-searching.js new file mode 100644 index 00000000000..1dd73167d24 --- /dev/null +++ b/tests/e2e/specs/wp-admin/test-order-searching.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runOrderSearchingTest } = require( '@woocommerce/e2e-core-tests' ); + +runOrderSearchingTest(); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 8b770fb8b4d..78c8e4243a7 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -16,6 +16,7 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field +- `searchForOrder( value, orderId )` util helper method that search order with different terms ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 4b03105f644..4b3d906c2bd 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -99,6 +99,7 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside +| `searchForOrder` | `value, orderId` | helper method that searchs for an order via many different terms ### Test Utilities diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 56cb7b4a75a..1fd1b6586bf 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -170,11 +170,13 @@ const completeOnboardingWizard = async () => { /** * Create simple product. + * + * @param simpleProductName - Defaults to Simple Product. Customizable title. */ -const createSimpleProduct = async () => { +const createSimpleProduct = async ( productTitle = simpleProductName, productPrice = simpleProductPrice ) => { const product = await factories.products.simple.create( { - name: simpleProductName, - regularPrice: simpleProductPrice + name: productTitle, + regularPrice: productPrice } ); return product.id; } ; diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 7679c15f2b7..b8b8c108a03 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -209,6 +209,19 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f await page.keyboard.press('Enter'); }; +/** + * Search by any term for an order + * + * @param {string} value Value to be entered into the search field + * @param {string} orderId Order ID + */ +const searchForOrder = async (value, orderId) => { + await clearAndFillInput('#post-search-input', value); + await expect(page).toClick('#search-submit'); + await page.waitForSelector('#the-list'); + await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' John Doe'}); +}; + export { clearAndFillInput, clickTab, @@ -225,4 +238,5 @@ export { moveAllItemsToTrash, evalAndClick, selectOptionInSelect2, + searchForOrder, }; From 10a5c8df639cd8fb184b3428130a49bc731f4bac Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 18 Feb 2021 22:05:14 +0100 Subject: [PATCH 036/405] Update components missing params --- tests/e2e/utils/src/components.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 1fd1b6586bf..8f2bc4f8d93 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -171,7 +171,8 @@ const completeOnboardingWizard = async () => { /** * Create simple product. * - * @param simpleProductName - Defaults to Simple Product. Customizable title. + * @param productTitle - Defaults to Simple Product. Customizable title. + * @param productPrice - Defaults to $9.99. Customizable pricing. */ const createSimpleProduct = async ( productTitle = simpleProductName, productPrice = simpleProductPrice ) => { const product = await factories.products.simple.create( { From bf46077f5568d769247580e98ae55979d77f183a Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 18 Feb 2021 22:48:13 +0100 Subject: [PATCH 037/405] Fix invalid spaces --- .../specs/merchant/wp-admin-order-searching.test.js | 8 ++++---- tests/e2e/utils/src/page-utils.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index ccd9de8c8c3..f6a07a6c08c 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -8,8 +8,8 @@ const { clearAndFillInput, selectOptionInSelect2, searchForOrder, - createSimpleProduct, - addProductToOrder, + createSimpleProduct, + addProductToOrder, } = require( '@woocommerce/e2e-utils' ); let orderId; @@ -19,7 +19,7 @@ const runOrderSearchingTest = () => { beforeAll(async () => { await merchant.login(); - await createSimpleProduct('Wanted Product'); + await createSimpleProduct('Wanted Product'); // Create new order for testing await merchant.openNewOrder(); @@ -52,7 +52,7 @@ const runOrderSearchingTest = () => { await addProductToOrder(orderId, 'Wanted Product'); - await merchant.openAllOrdersView(); + await merchant.openAllOrdersView(); }); it('can search for order by order id', async () => { diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index b8b8c108a03..03185b9dbcb 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -218,8 +218,8 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f const searchForOrder = async (value, orderId) => { await clearAndFillInput('#post-search-input', value); await expect(page).toClick('#search-submit'); - await page.waitForSelector('#the-list'); - await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' John Doe'}); + await page.waitForSelector('#the-list'); + await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' John Doe'}); }; export { From 85bda2c8f2b97d34d8c523b982f3ab978f1ebd7e Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 19 Feb 2021 18:16:54 +0100 Subject: [PATCH 038/405] Fix flakiness in test --- .../specs/merchant/wp-admin-order-searching.test.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index f6a07a6c08c..0d17194d051 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -28,11 +28,9 @@ const runOrderSearchingTest = () => { await selectOptionInSelect2('Customer', 'input.select2-search__field'); // Change the shipping data - const input = await page.$('#_shipping_first_name'); // to avoid flakiness - await input.click({ clickCount: 3 }); // to avoid flakiness - await page.keyboard.press('Backspace'); // to avoid flakiness - await clearAndFillInput('#_shipping_first_name', 'Jane'); - await clearAndFillInput('#_shipping_last_name', 'Doherty'); + await page.waitFor(1000); // to avoid flakiness + await clearAndFillInput('#_shipping_first_name', 'Tim'); + await clearAndFillInput('#_shipping_last_name', 'Clark'); await clearAndFillInput('#_shipping_address_1', 'Oxford Ave'); await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); @@ -40,7 +38,6 @@ const runOrderSearchingTest = () => { await page.select('#_shipping_state', 'NY'); // Save new order - await page.waitFor(2000); // wait for autosave await page.click('button.save_order'); await page.waitForSelector('#message'); @@ -100,11 +97,11 @@ const runOrderSearchingTest = () => { }) it('can search for order by shipping first name', async () => { - await searchForOrder('Jane', orderId); + await searchForOrder('Tim', orderId); }) it('can search for order by shipping last name', async () => { - await searchForOrder('Doherty', orderId); + await searchForOrder('Clark', orderId); }) it('can search for order by shipping first address', async () => { From b1ed9e65d80f9245680666c2a6e89c8bf4a44a6f Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 19 Feb 2021 18:59:24 +0100 Subject: [PATCH 039/405] Fix failing build on TravisCI --- .../core-tests/specs/merchant/wp-admin-order-searching.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 0d17194d051..a6d294a697c 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -35,7 +35,8 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); - await page.select('#_shipping_state', 'NY'); + await page.click('._shipping_state_field'); + await selectOptionInSelect2('New York'); // Save new order await page.click('button.save_order'); From 4915785684a1bb63f6f9a2bc793e7c05d35ed697 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 19 Feb 2021 19:38:02 +0100 Subject: [PATCH 040/405] Fix flakiness in test --- .../core-tests/specs/merchant/wp-admin-order-searching.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index a6d294a697c..ef1abb0c08d 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -35,7 +35,7 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); - await page.click('._shipping_state_field'); + await page.click('#select2-_shipping_state-container'); await selectOptionInSelect2('New York'); // Save new order From 5e771d8e4fa913c3f9b174ee77c523d1390bbcd7 Mon Sep 17 00:00:00 2001 From: Christopher Allford Date: Fri, 19 Feb 2021 15:28:07 -0800 Subject: [PATCH 041/405] Started Converting Woo Page Dropdowns Since the page selection dropdowns in the Advanced settings area are populated on load we can end up taking a very long time to load on shops that have lots of pages. This commit adds page search support to wooSelect and starts converting the pages to use it. --- assets/js/admin/wc-enhanced-select.js | 121 ++++++++++++------ includes/admin/class-wc-admin-assets.php | 1 + .../settings/class-wc-settings-advanced.php | 6 +- includes/class-wc-ajax.php | 42 ++++++ 4 files changed, 129 insertions(+), 41 deletions(-) diff --git a/assets/js/admin/wc-enhanced-select.js b/assets/js/admin/wc-enhanced-select.js index 4eac44f6371..6eec665a9d4 100644 --- a/assets/js/admin/wc-enhanced-select.js +++ b/assets/js/admin/wc-enhanced-select.js @@ -72,6 +72,49 @@ jQuery( function( $ ) { $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); }); + function display_result( self, select2_args) { + select2_args = $.extend( select2_args, getEnhancedSelectFormatString() ); + + $( self ).selectWoo( select2_args ).addClass( 'enhanced' ); + + if ( $( self ).data( 'sortable' ) ) { + var $select = $(self); + var $list = $( self ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' ); + + $list.sortable({ + placeholder : 'ui-state-highlight select2-selection__choice', + forcePlaceholderSize: true, + items : 'li:not(.select2-search__field)', + tolerance : 'pointer', + stop: function() { + $( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() { + var id = $( self ).data( 'data' ).id; + var option = $select.find( 'option[value="' + id + '"]' )[0]; + $select.prepend( option ); + } ); + } + }); + // Keep multiselects ordered alphabetically if they are not sortable. + } else if ( $( self ).prop( 'multiple' ) ) { + $( self ).on( 'change', function(){ + var $children = $( self ).children(); + $children.sort(function(a, b){ + var atext = a.text.toLowerCase(); + var btext = b.text.toLowerCase(); + + if ( atext > btext ) { + return 1; + } + if ( atext < btext ) { + return -1; + } + return 0; + }); + $( self ).html( $children ); + }); + } + } + // Ajax product search box $( ':input.wc-product-search' ).filter( ':not(.enhanced)' ).each( function() { var select2_args = { @@ -112,46 +155,48 @@ jQuery( function( $ ) { } }; - select2_args = $.extend( select2_args, getEnhancedSelectFormatString() ); + display_result(this, select2_args); + }); + + // Ajax Page Search. + $( ':input.wc-page-search' ).filter( ':not(.enhanced)' ).each( function() { + var select2_args = { + allowClear: $( this ).data( 'allow_clear' ) ? true : false, + placeholder: $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3', + escapeMarkup: function( m ) { + return m; + }, + ajax: { + url: wc_enhanced_select_params.ajax_url, + dataType: 'json', + delay: 250, + data: function( params ) { + return { + term : params.term, + action : $( this ).data( 'action' ) || 'woocommerce_json_search_pages', + security : wc_enhanced_select_params.search_pages_nonce, + exclude : $( this ).data( 'exclude' ), + post_status : $( this ).data( 'post_status' ), + limit : $( this ).data( 'limit' ), + }; + }, + processResults: function( data ) { + var terms = []; + if ( data ) { + $.each( data, function( id, text ) { + terms.push( { id: id, text: text } ); + } ); + } + return { + results: terms + }; + }, + cache: true + } + }; $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); - - if ( $( this ).data( 'sortable' ) ) { - var $select = $(this); - var $list = $( this ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' ); - - $list.sortable({ - placeholder : 'ui-state-highlight select2-selection__choice', - forcePlaceholderSize: true, - items : 'li:not(.select2-search__field)', - tolerance : 'pointer', - stop: function() { - $( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() { - var id = $( this ).data( 'data' ).id; - var option = $select.find( 'option[value="' + id + '"]' )[0]; - $select.prepend( option ); - } ); - } - }); - // Keep multiselects ordered alphabetically if they are not sortable. - } else if ( $( this ).prop( 'multiple' ) ) { - $( this ).on( 'change', function(){ - var $children = $( this ).children(); - $children.sort(function(a, b){ - var atext = a.text.toLowerCase(); - var btext = b.text.toLowerCase(); - - if ( atext > btext ) { - return 1; - } - if ( atext < btext ) { - return -1; - } - return 0; - }); - $( this ).html( $children ); - }); - } }); // Ajax customer search boxes diff --git a/includes/admin/class-wc-admin-assets.php b/includes/admin/class-wc-admin-assets.php index 567e2efe7ca..01e62199ba5 100644 --- a/includes/admin/class-wc-admin-assets.php +++ b/includes/admin/class-wc-admin-assets.php @@ -145,6 +145,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : 'search_products_nonce' => wp_create_nonce( 'search-products' ), 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), 'search_categories_nonce' => wp_create_nonce( 'search-categories' ), + 'search_pages_nonce' => wp_create_nonce( 'search-pages' ), ) ); diff --git a/includes/admin/settings/class-wc-settings-advanced.php b/includes/admin/settings/class-wc-settings-advanced.php index 2c8c34a550d..b0b36d10858 100644 --- a/includes/admin/settings/class-wc-settings-advanced.php +++ b/includes/admin/settings/class-wc-settings-advanced.php @@ -94,9 +94,9 @@ class WC_Settings_Advanced extends WC_Settings_Page { /* Translators: %s Page contents. */ 'desc' => sprintf( __( 'Page contents: [%s]', 'woocommerce' ), apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) ), 'id' => 'woocommerce_checkout_page_id', - 'type' => 'single_select_page', - 'default' => '', - 'class' => 'wc-enhanced-select-nostd', + 'type' => 'select', + 'default' => wc_get_page_id( 'checkout' ), + 'class' => 'wc-page-search', 'css' => 'min-width:300px;', 'args' => array( 'exclude' => diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index a884f701fe6..442091602c3 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -155,6 +155,7 @@ class WC_AJAX { 'json_search_downloadable_products_and_variations', 'json_search_customers', 'json_search_categories', + 'json_search_pages', 'term_ordering', 'product_ordering', 'refund_line_items', @@ -1755,6 +1756,47 @@ class WC_AJAX { wp_send_json( apply_filters( 'woocommerce_json_search_found_categories', $found_categories ) ); } + /** + * Ajax request handling for page searching. + */ + public static function json_search_pages() { + ob_start(); + + check_ajax_referer( 'search-pages', 'security' ); + + if ( ! current_user_can( 'manage_woocommerce' ) ) { + wp_die( -1 ); + } + + $search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $limit = isset( $_GET['limit'] ) ? absint( wp_unslash( $_GET['limit'] ) ) : -1; + $exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array(); + + $args = array( + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'posts_per_page' => $limit, + 'post_type' => 'page', + 'post_status' => array( 'publish', 'private', 'draft' ), + 's' => $search_text, + 'post__not_in' => $exclude_ids, + ); + $search_results_query = new WP_Query( $args ); + + $pages_results = array(); + foreach ( $search_results_query->get_posts() as $post ) { + $pages_results[ $post->ID ] = sprintf( + /* translators: 1: page name 2: page ID */ + __( '%1$s (ID: %2$s)', 'woocommerce' ), + get_the_title( $post ), + $post->ID + ); + } + + wp_send_json( apply_filters( 'woocommerce_json_search_found_pages', $pages_results ) ); + } + /** * Ajax request handling for categories ordering. */ From 8605ada7cf3ffd22dafc3b4a6c79b24b9afcfa79 Mon Sep 17 00:00:00 2001 From: Warren Wang Date: Thu, 25 Feb 2021 18:01:49 +0800 Subject: [PATCH 042/405] Fix for issue #27553 REST API v3 shipping method zone endpoint input payload not allowing settings type to be of type `class` or `order`. Added missing item schema. --- ...-wc-rest-shipping-zone-methods-controller.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php index 32b59d0771f..e610849c56e 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php @@ -24,4 +24,20 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zone_Met * @var string */ protected $namespace = 'wc/v3'; + + /** + * Get the settings schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + // Get parent schema to append additional supported settings types for shipping zone method + $schema = parent::get_item_schema(); + + //append additional settings supported types (class, order) + $schema['properties']['settings']['properties']['type']['enum'][] = 'class'; + $schema['properties']['settings']['properties']['type']['enum'][] = 'order'; + + return $this->add_additional_fields_schema( $schema ); + } } From d6b2e91c9d29cc41e5635ce098115a5536238691 Mon Sep 17 00:00:00 2001 From: Warren Wang Date: Fri, 26 Feb 2021 13:45:20 +0800 Subject: [PATCH 043/405] Minor adjustments to fit coding standards Added periods to comments and proper spacing and capitalization to comments. --- .../class-wc-rest-shipping-zone-methods-controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php index e610849c56e..f03234e0633 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php @@ -31,10 +31,10 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zone_Met * @return array */ public function get_item_schema() { - // Get parent schema to append additional supported settings types for shipping zone method + // Get parent schema to append additional supported settings types for shipping zone method. $schema = parent::get_item_schema(); - //append additional settings supported types (class, order) + // Append additional settings supported types (class, order). $schema['properties']['settings']['properties']['type']['enum'][] = 'class'; $schema['properties']['settings']['properties']['type']['enum'][] = 'order'; From 626dcb1fb1f63b5ab2e2660dc45b9557f1128117 Mon Sep 17 00:00:00 2001 From: Krzysztof Grabania Date: Fri, 26 Feb 2021 11:55:49 +0100 Subject: [PATCH 044/405] Pass checkout submit result to triggered handler --- assets/js/frontend/checkout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/frontend/checkout.js b/assets/js/frontend/checkout.js index 597ef13e4f0..c7e5960df7f 100644 --- a/assets/js/frontend/checkout.js +++ b/assets/js/frontend/checkout.js @@ -525,7 +525,7 @@ jQuery( function( $ ) { wc_checkout_form.detachUnloadEventsOnSubmit(); try { - if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success' ) !== false ) { + if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success', result ) !== false ) { if ( -1 === result.redirect.indexOf( 'https://' ) || -1 === result.redirect.indexOf( 'http://' ) ) { window.location = result.redirect; } else { From 29d8efe0123fbf5b227a9864eaf621f414282115 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 3 Mar 2021 14:02:59 +0100 Subject: [PATCH 045/405] Edit default json file --- tests/e2e/config/default.json | 1 + tests/e2e/env/config/default/default.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index 19693eece88..e01ee4e9a27 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -1,5 +1,6 @@ { "url": "http://localhost:8084/", + "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", diff --git a/tests/e2e/env/config/default/default.json b/tests/e2e/env/config/default/default.json index 463ec96ceef..9dd72fb0f9c 100644 --- a/tests/e2e/env/config/default/default.json +++ b/tests/e2e/env/config/default/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", From 9ff312247734da8d9e3db54d6f267f9ba396ac2a Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 3 Mar 2021 12:25:24 -0400 Subject: [PATCH 046/405] add group api test to run all api tests --- tests/e2e/core-tests/specs/api/grouped-product.test.js | 2 +- tests/e2e/core-tests/specs/index.js | 1 + .../rest-api/{grouped-product.test.js => grouped-product.js} | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename tests/e2e/specs/rest-api/{grouped-product.test.js => grouped-product.js} (100%) diff --git a/tests/e2e/core-tests/specs/api/grouped-product.test.js b/tests/e2e/core-tests/specs/api/grouped-product.test.js index 11dea1ba7a8..369f2d65feb 100644 --- a/tests/e2e/core-tests/specs/api/grouped-product.test.js +++ b/tests/e2e/core-tests/specs/api/grouped-product.test.js @@ -71,7 +71,7 @@ const runGroupedProductAPITest = () => { expect( response.data ).toEqual( expect.objectContaining( rawProperties ) ); }); - it('can retrieve a transformed external product', async () => { + it('can retrieve a transformed grouped product', async () => { // Read product via the repository. const transformed = await repository.read( product.id ); expect( transformed ).toEqual( expect.objectContaining( baseGroupedProduct ) ); diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index c35be68e428..e2ab39da8f5 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -74,6 +74,7 @@ const runMerchantTests = () => { const runApiTests = () => { runExternalProductAPITest(); + runGroupedProductAPITest(); runVariableProductAPITest(); runCouponApiTest(); } diff --git a/tests/e2e/specs/rest-api/grouped-product.test.js b/tests/e2e/specs/rest-api/grouped-product.js similarity index 100% rename from tests/e2e/specs/rest-api/grouped-product.test.js rename to tests/e2e/specs/rest-api/grouped-product.js From cb0b92750ba4a292ec8afa530c7033e9179d1a37 Mon Sep 17 00:00:00 2001 From: roykho Date: Thu, 4 Mar 2021 09:06:03 -0800 Subject: [PATCH 047/405] Extract recount terms logic into own function closes #25375 --- .../settings/class-wc-settings-products.php | 6 +++++ ...rest-system-status-tools-v2-controller.php | 17 +------------ includes/wc-term-functions.php | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/includes/admin/settings/class-wc-settings-products.php b/includes/admin/settings/class-wc-settings-products.php index 8346e63c3ec..5bf11bde2dd 100644 --- a/includes/admin/settings/class-wc-settings-products.php +++ b/includes/admin/settings/class-wc-settings-products.php @@ -64,6 +64,12 @@ class WC_Settings_Products extends WC_Settings_Page { $settings = $this->get_settings( $current_section ); WC_Admin_Settings::save_fields( $settings ); + /* + * Product->Inventory has a setting `Out of stock visibility`. + * Because of this, we need to recount the terms to keep them in-sync. + */ + wc_recount_all_terms(); + if ( $current_section ) { do_action( 'woocommerce_update_options_' . $this->id . '_' . $current_section ); } diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php index 06838eb5469..19f212da2b6 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php @@ -504,22 +504,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller { break; case 'recount_terms': - $product_cats = get_terms( - 'product_cat', - array( - 'hide_empty' => false, - 'fields' => 'id=>parent', - ) - ); - _wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false ); - $product_tags = get_terms( - 'product_tag', - array( - 'hide_empty' => false, - 'fields' => 'id=>parent', - ) - ); - _wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false ); + wc_recount_all_terms(); $message = __( 'Terms successfully recounted', 'woocommerce' ); break; diff --git a/includes/wc-term-functions.php b/includes/wc-term-functions.php index f40a89fb6b8..50e9c6f821d 100644 --- a/includes/wc-term-functions.php +++ b/includes/wc-term-functions.php @@ -636,3 +636,28 @@ function wc_get_product_visibility_term_ids() { ) ); } + +/** + * Recounts all terms. + * + * @since 5.2 + * @return void + */ +function wc_recount_all_terms() { + $product_cats = get_terms( + 'product_cat', + array( + 'hide_empty' => false, + 'fields' => 'id=>parent', + ) + ); + _wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false ); + $product_tags = get_terms( + 'product_tag', + array( + 'hide_empty' => false, + 'fields' => 'id=>parent', + ) + ); + _wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false ); +} From 1accf7166aaf2119169a19eeb6a02ace67f69a30 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Mar 2021 19:46:47 +0100 Subject: [PATCH 048/405] Add UiUnblocked --- .../merchant/wp-admin-settings-shipping-zones.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index d13857b8369..9a6ee2a49f0 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -10,6 +10,7 @@ const { addShippingZoneAndMethod, clearAndFillInput, selectOptionInSelect2, + uiUnblocked, } = require( '@woocommerce/e2e-utils' ); const config = require( 'config' ); @@ -59,11 +60,15 @@ const runAddNewShippingZoneTest = () => { // Set shipping country to United States (US) await expect(page).toClick('a.shipping-calculator-button'); await expect(page).toClick('#select2-calc_shipping_country-container'); + await uiUnblocked(); await selectOptionInSelect2('United States (US)'); // Set shipping state to New York + await uiUnblocked(); await expect(page).toClick('#select2-calc_shipping_state-container'); + await uiUnblocked(); await selectOptionInSelect2('New York'); + await uiUnblocked(); await expect(page).toClick('button[name="calc_shipping"]'); // Verify shipping costs @@ -82,9 +87,11 @@ const runAddNewShippingZoneTest = () => { // Set shipping state to California await expect(page).toClick('a.shipping-calculator-button'); await expect(page).toClick('#select2-calc_shipping_state-container'); + await uiUnblocked(); await selectOptionInSelect2('California'); // Set shipping postcode to 94000 + await uiUnblocked(); await clearAndFillInput('#calc_shipping_postcode', '94000'); await expect(page).toClick('button[name="calc_shipping"]'); @@ -103,7 +110,9 @@ const runAddNewShippingZoneTest = () => { // Set shipping postcode to 94107 await expect(page).toClick('a.shipping-calculator-button'); + await uiUnblocked(); await clearAndFillInput('#calc_shipping_postcode', '94107'); + await uiUnblocked(); await expect(page).toClick('button[name="calc_shipping"]'); // Verify shipping method and cost From 1ab3935e1d7e7cc78cadd25a2b0d70f96a45fcfc Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 4 Mar 2021 20:32:53 +0100 Subject: [PATCH 049/405] Update test and page util --- .../specs/merchant/wp-admin-order-searching.test.js | 1 + tests/e2e/utils/CHANGELOG.md | 2 +- tests/e2e/utils/README.md | 2 +- tests/e2e/utils/src/page-utils.js | 5 +++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index ef1abb0c08d..fd073a9f16c 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -35,6 +35,7 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); + await page.waitFor(1000); // to avoid flakiness await page.click('#select2-_shipping_state-container'); await selectOptionInSelect2('New York'); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 78c8e4243a7..109b1be9355 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -16,7 +16,7 @@ - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. - `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. - `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field -- `searchForOrder( value, orderId )` util helper method that search order with different terms +- `searchForOrder( value, orderId, customerName )` util helper method that search order with different terms ## Changes diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 4b3d906c2bd..9f9fdfb6c2b 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -99,7 +99,7 @@ describe( 'Cart page', () => { | `clickFilter` | `selector` | Click on a list page filter | | `moveAllItemsToTrash` | | Moves all items in a list view to the Trash | | `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside -| `searchForOrder` | `value, orderId` | helper method that searchs for an order via many different terms +| `searchForOrder` | `value, orderId, customerName` | helper method that searchs for an order via many different terms ### Test Utilities diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 03185b9dbcb..8ca3f9c9190 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -214,12 +214,13 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f * * @param {string} value Value to be entered into the search field * @param {string} orderId Order ID + * @param {string} customerName Customer's full name attached to order ID. Defaults to John Doe. */ -const searchForOrder = async (value, orderId) => { +const searchForOrder = async (value, orderId, customerName = 'John Doe') => { await clearAndFillInput('#post-search-input', value); await expect(page).toClick('#search-submit'); await page.waitForSelector('#the-list'); - await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' John Doe'}); + await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' '+customerName}); }; export { From 24c4c7658aaaf2656eff9cf24c5742f3a48be620 Mon Sep 17 00:00:00 2001 From: hsing Date: Thu, 4 Mar 2021 16:01:07 -0500 Subject: [PATCH 050/405] Trigger country field change on address change click --- assets/js/frontend/cart.js | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/js/frontend/cart.js b/assets/js/frontend/cart.js index 622c8eb1437..b4455119c08 100644 --- a/assets/js/frontend/cart.js +++ b/assets/js/frontend/cart.js @@ -180,6 +180,7 @@ jQuery( function( $ ) { */ toggle_shipping: function() { $( '.shipping-calculator-form' ).slideToggle( 'slow' ); + $( 'select.country_to_state, input.country_to_state' ).trigger( 'change' ); $( document.body ).trigger( 'country_to_state_changed' ); // Trigger select2 to load. return false; }, From 71d8f760b09b6786e6da2d908baa00662d9d5f8b Mon Sep 17 00:00:00 2001 From: roykho Date: Thu, 4 Mar 2021 13:54:09 -0800 Subject: [PATCH 051/405] Apply recount term per product with filter to product data store --- .../class-wc-product-data-store-cpt.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index ed626f9a9b3..782e6b31037 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -710,6 +710,41 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) { wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false ); } + + /** + * Filter to allow/prevent recounting of terms as it could be expensive. + * A likely scenario for this is when bulk importing products. We could + * then prevent it from recounting per product but instead recount it once + * when import is done. Of course this means the import logic has to support this. + * + * @since 5.2 + * @param bool + */ + if ( apply_filters( 'woocommerce_product_update_recount_terms', '__return_true' ) ) { + $product_terms = get_the_terms( $product->get_id(), 'product_cat' ); + + if ( $product_terms ) { + $product_cats = array(); + + foreach ( $product_terms as $term ) { + $product_cats[ $term->term_id ] = $term->parent; + } + + _wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), false, false ); + } + + $product_terms = get_the_terms( $product->get_id(), 'product_tag' ); + + if ( $product_terms ) { + $product_tags = array(); + + foreach ( $product_terms as $term ) { + $product_tags[ $term->term_id ] = $term->parent; + } + + _wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), false, false ); + } + } } /** From 9a12de409c6e9b846e3cb68b2fda7ed8a983535b Mon Sep 17 00:00:00 2001 From: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:03:31 +0800 Subject: [PATCH 052/405] Bump woocommerce-admin package to 2.1.1 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 33cecfc6758..5c366427d1b 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "pelago/emogrifier": "3.1.0", "psr/container": "1.0.0", "woocommerce/action-scheduler": "3.1.6", - "woocommerce/woocommerce-admin": "2.0.2", + "woocommerce/woocommerce-admin": "2.1.1", "woocommerce/woocommerce-blocks": "4.4.3" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 24b5bc27a02..edf1f248348 100644 --- a/composer.lock +++ b/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": "f24a600ea103061d766dd7b06c13e8f2", + "content-hash": "510336e0569b0a906455509ae047f6f5", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -523,16 +523,16 @@ }, { "name": "woocommerce/woocommerce-admin", - "version": "2.0.2", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-admin.git", - "reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4" + "reference": "499067537597bad93a0d431c15c0f6c14d1a7700" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4", - "reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4", + "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/499067537597bad93a0d431c15c0f6c14d1a7700", + "reference": "499067537597bad93a0d431c15c0f6c14d1a7700", "shasum": "" }, "require": { @@ -566,9 +566,9 @@ "homepage": "https://github.com/woocommerce/woocommerce-admin", "support": { "issues": "https://github.com/woocommerce/woocommerce-admin/issues", - "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.0.2" + "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.1.1" }, - "time": "2021-02-25T07:29:24+00:00" + "time": "2021-03-05T03:48:54+00:00" }, { "name": "woocommerce/woocommerce-blocks", From eaf80392231f61a3c5a2422415c36e63a0ded644 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 08:50:11 +0100 Subject: [PATCH 053/405] Add waits to fix flakyness --- .../merchant/wp-admin-settings-shipping-zones.test.js | 10 ---------- tests/e2e/utils/src/components.js | 4 ++-- tests/e2e/utils/src/page-utils.js | 2 ++ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 9a6ee2a49f0..03f59b6da18 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -10,7 +10,6 @@ const { addShippingZoneAndMethod, clearAndFillInput, selectOptionInSelect2, - uiUnblocked, } = require( '@woocommerce/e2e-utils' ); const config = require( 'config' ); @@ -60,15 +59,11 @@ const runAddNewShippingZoneTest = () => { // Set shipping country to United States (US) await expect(page).toClick('a.shipping-calculator-button'); await expect(page).toClick('#select2-calc_shipping_country-container'); - await uiUnblocked(); await selectOptionInSelect2('United States (US)'); // Set shipping state to New York - await uiUnblocked(); await expect(page).toClick('#select2-calc_shipping_state-container'); - await uiUnblocked(); await selectOptionInSelect2('New York'); - await uiUnblocked(); await expect(page).toClick('button[name="calc_shipping"]'); // Verify shipping costs @@ -87,11 +82,9 @@ const runAddNewShippingZoneTest = () => { // Set shipping state to California await expect(page).toClick('a.shipping-calculator-button'); await expect(page).toClick('#select2-calc_shipping_state-container'); - await uiUnblocked(); await selectOptionInSelect2('California'); // Set shipping postcode to 94000 - await uiUnblocked(); await clearAndFillInput('#calc_shipping_postcode', '94000'); await expect(page).toClick('button[name="calc_shipping"]'); @@ -99,7 +92,6 @@ const runAddNewShippingZoneTest = () => { await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'}); await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'}); - await shopper.removeFromCart(simpleProductName); }) @@ -110,9 +102,7 @@ const runAddNewShippingZoneTest = () => { // Set shipping postcode to 94107 await expect(page).toClick('a.shipping-calculator-button'); - await uiUnblocked(); await clearAndFillInput('#calc_shipping_postcode', '94107'); - await uiUnblocked(); await expect(page).toClick('button[name="calc_shipping"]'); // Verify shipping method and cost diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index fa8111d8f9b..679ffb09974 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -463,9 +463,9 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await expect(page).toClick('button#submit'); // Add shipping zone method - await page.waitFor(1000); // avoiding flakiness + await page.waitFor(2000); // avoiding flakiness await expect(page).toClick('button.wc-shipping-zone-add-method', {text:'Add shipping method'}); - await page.waitFor(1000); // avoiding flakiness + await page.waitFor(2000); // avoiding flakiness await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 9ab8c7b3aa5..649d30bce16 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -11,7 +11,9 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; */ const clearAndFillInput = async ( selector, value ) => { await page.focus( selector ); + await page.waitFor(2000); // avoiding flakiness await pressKeyWithModifier( 'primary', 'a' ); + await page.waitFor(2000); // avoiding flakiness await page.type( selector, value ); }; From b2a395c9188104f6780949173bbc34e87b0261c7 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 09:07:23 +0100 Subject: [PATCH 054/405] Update test and util --- .../core-tests/specs/merchant/wp-admin-order-searching.test.js | 3 +-- tests/e2e/utils/src/page-utils.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index fd073a9f16c..648dc286f4d 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -36,8 +36,7 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); await page.waitFor(1000); // to avoid flakiness - await page.click('#select2-_shipping_state-container'); - await selectOptionInSelect2('New York'); + await page.select('#_shipping_state', 'NY'); // Save new order await page.click('button.save_order'); diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index e9250ba8f09..982d04ecfe0 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -11,7 +11,9 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; */ const clearAndFillInput = async ( selector, value ) => { await page.focus( selector ); + await page.waitFor(2000); // avoiding flakiness await pressKeyWithModifier( 'primary', 'a' ); + await page.waitFor(2000); // avoiding flakiness await page.type( selector, value ); }; From c10d03b6ab5c63d68b5d94250831e656bfe637bf Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 10:16:55 +0100 Subject: [PATCH 055/405] Updated page util method --- tests/e2e/utils/src/page-utils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 982d04ecfe0..baf9acedde0 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -10,10 +10,8 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; * @param {string} value */ const clearAndFillInput = async ( selector, value ) => { - await page.focus( selector ); - await page.waitFor(2000); // avoiding flakiness - await pressKeyWithModifier( 'primary', 'a' ); - await page.waitFor(2000); // avoiding flakiness + await page.click(selector ,{ clickCount: 3 }); + await page.waitFor(1000); // avoiding flakiness await page.type( selector, value ); }; From c9201e4817176e94e37f0da5323ac2bf0cdfbb57 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 10:38:55 +0100 Subject: [PATCH 056/405] Improved page util method --- tests/e2e/utils/src/page-utils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 649d30bce16..c0ee246765b 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -10,10 +10,8 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; * @param {string} value */ const clearAndFillInput = async ( selector, value ) => { - await page.focus( selector ); - await page.waitFor(2000); // avoiding flakiness - await pressKeyWithModifier( 'primary', 'a' ); - await page.waitFor(2000); // avoiding flakiness + await page.click(selector ,{ clickCount: 3 }); + await page.waitFor(1000); // avoiding flakiness await page.type( selector, value ); }; From 7eae2f2fe86bd3834efaddf65b966ecc39469655 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 11:19:43 +0100 Subject: [PATCH 057/405] Improve test and add waits --- .../specs/merchant/wp-admin-order-searching.test.js | 3 +++ tests/e2e/utils/src/page-utils.js | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 648dc286f4d..2f156b74da4 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -23,8 +23,11 @@ const runOrderSearchingTest = () => { // Create new order for testing await merchant.openNewOrder(); + await page.waitForSelector('#order_status'); await page.select('#order_status', 'Pending payment'); + await page.waitForSelector('#customer_user'); await page.click('#customer_user'); + await page.waitForSelector('input.select2-search__field'); await selectOptionInSelect2('Customer', 'input.select2-search__field'); // Change the shipping data diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index baf9acedde0..1882f43d8ad 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -10,8 +10,9 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; * @param {string} value */ const clearAndFillInput = async ( selector, value ) => { - await page.click(selector ,{ clickCount: 3 }); - await page.waitFor(1000); // avoiding flakiness + await page.waitForSelector( selector ); + await page.focus( selector ); + await pressKeyWithModifier( 'primary', 'a' ); await page.type( selector, value ); }; From 9d52012b8e3029ee41c8d2f583122123cc0f5c17 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 11:27:39 +0100 Subject: [PATCH 058/405] Improve code and fix flakyness --- tests/e2e/utils/src/components.js | 6 ++++++ tests/e2e/utils/src/page-utils.js | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 7e298593ed8..3547b378c24 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -491,10 +491,13 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States // Select shipping zone location // (.toSelect is not best option here because a lot of   are present in country/state names) await expect(page).toFill('#zone_locations', zoneLocation); + await page.waitFor(1000); // avoiding flakiness await page.keyboard.press('Tab'); + await page.waitFor(1000); // avoiding flakiness await page.keyboard.press('Enter'); // Fill shipping zone postcode if needed otherwise just put empty space + await page.waitForSelector('a.wc-shipping-zone-postcodes-toggle'); await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); await expect(page).toFill('#zone_postcodes', zipCode); await expect(page).toMatchElement('#zone_postcodes', zipCode); @@ -504,9 +507,12 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await page.waitFor(2000); // avoiding flakiness await expect(page).toClick('button.wc-shipping-zone-add-method', {text:'Add shipping method'}); await page.waitFor(2000); // avoiding flakiness + await page.waitForSelector('.wc-shipping-zone-method-description'); await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); + await page.waitFor(1000); // avoiding flakiness await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); + await page.waitFor(1000); // avoiding flakiness }; export { diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index c0ee246765b..ac1fe97ebb9 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -10,8 +10,9 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; * @param {string} value */ const clearAndFillInput = async ( selector, value ) => { - await page.click(selector ,{ clickCount: 3 }); - await page.waitFor(1000); // avoiding flakiness + await page.waitForSelector( selector ); + await page.focus( selector ); + await pressKeyWithModifier( 'primary', 'a' ); await page.type( selector, value ); }; From 7d8e5430ec1f372384f52ca59702e60f9007f458 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 12:05:07 +0100 Subject: [PATCH 059/405] Fix select element --- .../core-tests/specs/merchant/wp-admin-order-searching.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 2f156b74da4..b5a9cec1385 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -39,7 +39,7 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); await page.waitFor(1000); // to avoid flakiness - await page.select('#_shipping_state', 'NY'); + await page.select('select[name="_shipping_state"]', 'NY'); // Save new order await page.click('button.save_order'); From 4b12dfdeda8aa7aa930baaa0e273741c08d2d696 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 12:45:16 +0100 Subject: [PATCH 060/405] Improve test code --- .../specs/merchant/wp-admin-order-searching.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index b5a9cec1385..c8ac3726f88 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -39,7 +39,9 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); await page.waitFor(1000); // to avoid flakiness - await page.select('select[name="_shipping_state"]', 'NY'); + await page.click('#select2-_shipping_state-container'); + await page.waitForSelector('input.select2-search__field'); + await selectOptionInSelect2('New York', 'input.select2-search__field'); // Save new order await page.click('button.save_order'); From 26c71b9db653234f29e2d32c7941b51a018a915e Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 13:57:46 +0100 Subject: [PATCH 061/405] Improve code --- .../specs/merchant/wp-admin-order-searching.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index c8ac3726f88..b7118e8b99f 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -10,6 +10,7 @@ const { searchForOrder, createSimpleProduct, addProductToOrder, + evalAndClick, } = require( '@woocommerce/e2e-utils' ); let orderId; @@ -39,8 +40,9 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); await page.waitFor(1000); // to avoid flakiness - await page.click('#select2-_shipping_state-container'); - await page.waitForSelector('input.select2-search__field'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Enter'); await selectOptionInSelect2('New York', 'input.select2-search__field'); // Save new order From e6fe91ee0298aa01b49193ba5af5eb2eb58f0d95 Mon Sep 17 00:00:00 2001 From: Veljko Date: Fri, 5 Mar 2021 15:54:45 +0100 Subject: [PATCH 062/405] Improve code --- .../core-tests/specs/merchant/wp-admin-order-searching.test.js | 3 +++ tests/e2e/utils/src/page-utils.js | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index b7118e8b99f..3106665c526 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -41,8 +41,11 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_postcode', '14201'); await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Tab'); + await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Tab'); + await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Enter'); + await page.waitFor(1000); // to avoid flakiness await selectOptionInSelect2('New York', 'input.select2-search__field'); // Save new order diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 1882f43d8ad..6638bc2b1fa 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -219,6 +219,7 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f */ const searchForOrder = async (value, orderId, customerName = 'John Doe') => { await clearAndFillInput('#post-search-input', value); + await expect(page).toMatchElement('#post-search-input', value); await expect(page).toClick('#search-submit'); await page.waitForSelector('#the-list'); await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' '+customerName}); From 94f83cb85942215ff13875ecfc73880eee940f41 Mon Sep 17 00:00:00 2001 From: Veljko Date: Sat, 6 Mar 2021 12:09:12 +0100 Subject: [PATCH 063/405] Revert changes and add waits --- tests/e2e/core-tests/specs/index.js | 2 +- .../specs/merchant/wp-admin-order-searching.test.js | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 6ebeeb6e3ee..b496bc0d5ed 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -19,6 +19,7 @@ const runSingleProductPageTest = require( './shopper/front-end-single-product.te const runVariableProductUpdateTest = require( './shopper/front-end-variable-product-updates.test' ); // Merchant tests +const runOrderSearchingTest = require( './merchant/wp-admin-order-searching.test' ); const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' ); const runCreateOrderTest = require( './merchant/wp-admin-order-new.test' ); const runEditOrderTest = require( './merchant/wp-admin-order-edit.test' ); @@ -32,7 +33,6 @@ const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon const runProductEditDetailsTest = require( './merchant/wp-admin-product-edit-details.test' ); const runProductSearchTest = require( './merchant/wp-admin-product-search.test' ); const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' ); -const runOrderSearchingTest = require( './merchant/wp-admin-order-searching.test' ); // REST API tests const runExternalProductAPITest = require( './api/external-product.test' ); diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 3106665c526..0d270057952 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -28,7 +28,6 @@ const runOrderSearchingTest = () => { await page.select('#order_status', 'Pending payment'); await page.waitForSelector('#customer_user'); await page.click('#customer_user'); - await page.waitForSelector('input.select2-search__field'); await selectOptionInSelect2('Customer', 'input.select2-search__field'); // Change the shipping data @@ -39,17 +38,15 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); - await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Tab'); - await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Tab'); - await page.waitFor(1000); // to avoid flakiness await page.keyboard.press('Enter'); - await page.waitFor(1000); // to avoid flakiness await selectOptionInSelect2('New York', 'input.select2-search__field'); // Save new order + await page.waitFor(1000); // to avoid flakiness await page.click('button.save_order'); + await page.waitFor(2000); // to avoid flakiness await page.waitForSelector('#message'); // Get the post id From ed9b4762ae7d81ea840ce2d6664a62813cdd3623 Mon Sep 17 00:00:00 2001 From: Veljko Date: Sun, 7 Mar 2021 15:46:03 +0100 Subject: [PATCH 064/405] Update test and utils methods --- .../merchant/wp-admin-order-searching.test.js | 55 ++++++++----------- tests/e2e/utils/src/components.js | 2 +- tests/e2e/utils/src/page-utils.js | 7 ++- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 0d270057952..b56ea2bb87c 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -10,16 +10,14 @@ const { searchForOrder, createSimpleProduct, addProductToOrder, - evalAndClick, + clickUpdateOrder, } = require( '@woocommerce/e2e-utils' ); -let orderId; - const runOrderSearchingTest = () => { describe('WooCommerce Orders > Search orders', () => { + let orderId; beforeAll(async () => { await merchant.login(); - await createSimpleProduct('Wanted Product'); // Create new order for testing @@ -43,97 +41,90 @@ const runOrderSearchingTest = () => { await page.keyboard.press('Enter'); await selectOptionInSelect2('New York', 'input.select2-search__field'); - // Save new order - await page.waitFor(1000); // to avoid flakiness - await page.click('button.save_order'); - await page.waitFor(2000); // to avoid flakiness - await page.waitForSelector('#message'); - // Get the post id const variablePostId = await page.$('#post_ID'); orderId = (await(await variablePostId.getProperty('value')).jsonValue()); - await merchant.openAllOrdersView(); - + // Save new order + await clickUpdateOrder('Order updated.', true); await addProductToOrder(orderId, 'Wanted Product'); - await merchant.openAllOrdersView(); }); it('can search for order by order id', async () => { - await searchForOrder(orderId, orderId); + await searchForOrder(orderId, orderId, 'John Doe'); }); it('can search for order by billing first name', async () => { - await searchForOrder('John', orderId); + await searchForOrder('John', orderId, 'John Doe'); }) it('can search for order by billing last name', async () => { - await searchForOrder('Doe', orderId); + await searchForOrder('Doe', orderId, 'John Doe'); }) it('can search for order by billing company name', async () => { - await searchForOrder('Automattic', orderId); + await searchForOrder('Automattic', orderId, 'John Doe'); }) it('can search for order by billing first address', async () => { - await searchForOrder('addr 1', orderId); + await searchForOrder('addr 1', orderId, 'John Doe'); }) it('can search for order by billing second address', async () => { - await searchForOrder('addr 2', orderId); + await searchForOrder('addr 2', orderId, 'John Doe'); }) it('can search for order by billing city name', async () => { - await searchForOrder('San Francisco', orderId); + await searchForOrder('San Francisco', orderId, 'John Doe'); }) it('can search for order by billing post code', async () => { - await searchForOrder('94107', orderId); + await searchForOrder('94107', orderId, 'John Doe'); }) it('can search for order by billing email', async () => { - await searchForOrder('john.doe@example.com', orderId); + await searchForOrder('john.doe@example.com', orderId, 'John Doe'); }) it('can search for order by billing phone', async () => { - await searchForOrder('123456789', orderId); + await searchForOrder('123456789', orderId, 'John Doe'); }) it('can search for order by billing state', async () => { - await searchForOrder('CA', orderId); + await searchForOrder('CA', orderId, 'John Doe'); }) it('can search for order by shipping first name', async () => { - await searchForOrder('Tim', orderId); + await searchForOrder('Tim', orderId, 'John Doe'); }) it('can search for order by shipping last name', async () => { - await searchForOrder('Clark', orderId); + await searchForOrder('Clark', orderId, 'John Doe'); }) it('can search for order by shipping first address', async () => { - await searchForOrder('Oxford Ave', orderId); + await searchForOrder('Oxford Ave', orderId, 'John Doe'); }) it('can search for order by shipping second address', async () => { - await searchForOrder('Linwood Ave', orderId); + await searchForOrder('Linwood Ave', orderId, 'John Doe'); }) it('can search for order by shipping city name', async () => { - await searchForOrder('Buffalo', orderId); + await searchForOrder('Buffalo', orderId, 'John Doe'); }) it('can search for order by shipping postcode name', async () => { - await searchForOrder('14201', orderId); + await searchForOrder('14201', orderId, 'John Doe'); }) it('can search for order by shipping state name', async () => { - await searchForOrder('NY', orderId); + await searchForOrder('NY', orderId, 'John Doe'); }) it('can search for order by item name', async () => { - await searchForOrder('Wanted Product', orderId); + await searchForOrder('Wanted Product', orderId, 'John Doe'); }) }); }; diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 14bbecb1ac4..990a20d1501 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -522,6 +522,6 @@ export { addProductToOrder, createCoupon, createSimpleProductWithCategory, - clickUpdateOrder, + clickUpdateOrder, deleteAllEmailLogs, }; diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 6da27546b10..9b7dabd276c 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -215,14 +215,15 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f * * @param {string} value Value to be entered into the search field * @param {string} orderId Order ID - * @param {string} customerName Customer's full name attached to order ID. Defaults to John Doe. + * @param {string} customerName Customer's full name attached to order ID. */ -const searchForOrder = async (value, orderId, customerName = 'John Doe') => { +const searchForOrder = async (value, orderId, customerName) => { await clearAndFillInput('#post-search-input', value); await expect(page).toMatchElement('#post-search-input', value); await expect(page).toClick('#search-submit'); await page.waitForSelector('#the-list'); - await expect(page).toMatchElement('.order_number > a.order-view', {text: '#'+orderId+' '+customerName}); + await page.waitFor(1000); + await expect(page).toMatchElement('.order_number > a.order-view', {text: `#${orderId} ${customerName}`}); }; /** From e472c99a5c6e213a91c79961769995999f626315 Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Mon, 8 Mar 2021 11:38:41 -0600 Subject: [PATCH 065/405] Fix E2E shorthand script to allow for directories with spaces --- tests/e2e/env/bin/wc-e2e.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/env/bin/wc-e2e.sh b/tests/e2e/env/bin/wc-e2e.sh index 0f5eee0d23e..d5fee2dc478 100755 --- a/tests/e2e/env/bin/wc-e2e.sh +++ b/tests/e2e/env/bin/wc-e2e.sh @@ -30,8 +30,8 @@ TESTRESULT=0 # Use the script symlink to find and change directory to the root of the package SCRIPTPATH=$(dirname "$0") -REALPATH=$(readlink $0) -cd $SCRIPTPATH/$(dirname "$REALPATH")/.. +REALPATH=$(readlink "$0") +cd "$SCRIPTPATH/$(dirname "$REALPATH")/.." # Run scripts case $1 in @@ -65,6 +65,6 @@ case $1 in esac # Restore working path -cd $OLDPATH +cd "$OLDPATH" exit $TESTRESULT From 5b02c440d7adaeefdbf4d77d8d2f965993af3615 Mon Sep 17 00:00:00 2001 From: Jacob Arriola Date: Mon, 8 Mar 2021 10:38:23 -0800 Subject: [PATCH 066/405] New filter: stock check message Adds a new filter to allow customization of the stock check message when a product is out of stock, but accounting for what's already in the cart. It mimics the existing woocommerce_cart_product_not_enough_stock_message filter. --- includes/class-wc-cart.php | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 1103ee053e5..966a5bb7fdf 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1218,15 +1218,30 @@ class WC_Cart extends WC_Legacy_Cart { $products_qty_in_cart = $this->get_cart_item_quantities(); if ( isset( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] ) && ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) { - throw new Exception( - sprintf( - '%s %s', - wc_get_cart_url(), - __( 'View cart', 'woocommerce' ), - /* translators: 1: quantity in stock 2: current quantity */ - sprintf( __( 'You cannot add that amount to the cart — we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ), wc_format_stock_quantity_for_display( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ], $product_data ) ) - ) + $stock_quantity = $product_data->get_stock_quantity(); + $stock_quantity_in_cart = $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ]; + + $message = printf( + '%s %s', + wc_get_cart_url(), + __( 'View cart', 'woocommerce' ), + /* translators: 1: quantity in stock 2: current quantity */ + sprintf( __( 'You cannot add that amount to the cart — we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) ) ); + + /** + * Filters message about product not having enough stock accounting for what's already in the cart. + * + * @param string $message Message. + * @param WC_Product $product_data Product data. + * @param int $stock_quantity Quantity remaining. + * @param int $stock_quantity_in_cart + * + * @since 5.0.0 + */ + $message = apply_filters( 'woocommerce_cart_product_not_enough_stock_already_in_cart_message', $message, $product_data, $stock_quantity, $stock_quantity_in_cart ); + + throw new Exception( $message ); } } From 0e6d14b12d5dc93b729b903efbb2589497ab9e39 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 8 Mar 2021 15:07:29 -0400 Subject: [PATCH 067/405] create screenshot on e2e test failure --- .gitignore | 1 + tests/e2e/env/bin/e2e-test-integration.js | 9 +++ tests/e2e/env/config/jest.config.js | 1 + tests/e2e/env/package.json | 1 + tests/e2e/env/src/setup/jest.failure.js | 99 +++++++++++++++++++++++ 5 files changed, 111 insertions(+) create mode 100644 tests/e2e/env/src/setup/jest.failure.js diff --git a/.gitignore b/.gitignore index c86c350cc91..d5584c21452 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ tests/cli/vendor /tests/e2e/env/docker/wp-cli/initialize.sh /tests/e2e/env/build/ /tests/e2e/env/build-module/ +/tests/e2e/screenshots /tests/e2e/utils/build/ /tests/e2e/utils/build-module/ diff --git a/tests/e2e/env/bin/e2e-test-integration.js b/tests/e2e/env/bin/e2e-test-integration.js index 4279bf652b8..577a5bb8bc4 100755 --- a/tests/e2e/env/bin/e2e-test-integration.js +++ b/tests/e2e/env/bin/e2e-test-integration.js @@ -15,6 +15,15 @@ program const appPath = getAppRoot(); +// clear the screenshots folder before running tests. +const screenshotPath = path.resolve( appPath, 'tests/e2e/screenshots' ); +if ( fs.existsSync( screenshotPath ) ) { + fs.readdirSync( screenshotPath ).forEach( ( file, index ) => { + const filename = path.join( screenshotPath, file ); + fs.unlinkSync( filename ); + }); +} + const nodeConfigDirs = [ path.resolve( __dirname, '../config' ), ]; diff --git a/tests/e2e/env/config/jest.config.js b/tests/e2e/env/config/jest.config.js index abbaaa1b60e..5514019fa30 100644 --- a/tests/e2e/env/config/jest.config.js +++ b/tests/e2e/env/config/jest.config.js @@ -12,6 +12,7 @@ const { getAppRoot } = require( '../utils' ); let setupFilesAfterEnv = [ path.resolve( __dirname, '../build/setup/jest.setup.js' ), + path.resolve( __dirname, '../build/setup/jest.failure.js' ), 'expect-puppeteer', ]; diff --git a/tests/e2e/env/package.json b/tests/e2e/env/package.json index 92d3348cc2a..14bcec1da6c 100644 --- a/tests/e2e/env/package.json +++ b/tests/e2e/env/package.json @@ -27,6 +27,7 @@ "@wordpress/jest-preset-default": "^6.4.0", "app-root-path": "^3.0.0", "jest": "^25.1.0", + "jest-each": "^26.6.2", "jest-puppeteer": "^4.4.0" }, "devDependencies": { diff --git a/tests/e2e/env/src/setup/jest.failure.js b/tests/e2e/env/src/setup/jest.failure.js new file mode 100644 index 00000000000..36d87e03cc2 --- /dev/null +++ b/tests/e2e/env/src/setup/jest.failure.js @@ -0,0 +1,99 @@ +/** @format */ +/* +import { + sendFailedTestScreenshotToSlack, + sendFailedTestMessageToSlack +} from "./lib/reporter/slack-reporter"; +*/ +const path = require( 'path' ); +const mkdirp = require( 'mkdirp' ); +import { bind } from 'jest-each'; +const { getAppRoot } = require( '../../utils' ); + +/** + * Override the test case method so we can take screenshots of assertion failures. + * + * See: https://github.com/smooth-code/jest-puppeteer/issues/131#issuecomment-469439666 + */ +let currentBlock; + +/** + * We need to reference the original version of Jest. + */ +const originalDescribe = global.describe; +const originalIt = global.it; + +global.describe = (() => { + const describe = ( blockName, callback ) => { + currentBlock = blockName; + + try { + originalDescribe( blockName, callback ); + } catch ( e ) { + throw e; + } + + }; + const only = ( blockName, callback ) => { + originalDescribe.only( blockName, callback ); + }; + const skip = ( blockName, callback ) => { + originalDescribe.skip( blockName, callback ); + }; + + describe.each = bind( describe, false ); + only.each = bind( only, false ); + skip.each = bind( skip, false ); + describe.only = only; + describe.skip = skip; + + return describe; +})(); + +global.it = (() => { + const test = async ( testName, callback ) => { + const testCallback = async () => screenshotTest( testName, callback ); + return originalIt( testName, testCallback ); + }; + const only = ( blockName, callback ) => { + return originalIt.only( blockName, callback ); + }; + const skip = ( blockName, callback ) => { + return originalIt.skip( blockName, callback ); + }; + + test.each = bind( test, false ); + only.each = bind( only, false ); + skip.each = bind( skip, false ); + test.only = only; + test.skip = skip; + + return test; +})(); + +const screenshotTest = async ( testName, callback ) => { + try { + await callback(); + } catch ( e ) { + const testTitle = `${ currentBlock } - ${ testName }`.replace( /\.$/, '' ); + const appPath = getAppRoot(); + const savePath = path.resolve( appPath, 'tests/e2e/screenshots' ); + const filePath = path.join( + savePath, + `${ testTitle }.png`.replace( /[^a-z0-9.-]+/gi, '-' ) + ); + + mkdirp.sync( savePath ); + await page.screenshot( { + path: filePath, + fullPage: true, + } ); + +/* + await sendFailedTestMessageToSlack( testTitle ); + await sendFailedTestScreenshotToSlack( filePath ); +*/ + + throw ( e ); + } +}; From 4a5c6b86b3b94f868b0b20ea03f8a6a6ea139881 Mon Sep 17 00:00:00 2001 From: Jonathan Sadowski Date: Mon, 8 Mar 2021 14:06:14 -0600 Subject: [PATCH 068/405] Update e2e/env CHANGELOG for wc-e2e space fix --- tests/e2e/env/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md index 8e6272ba44b..0207623e55b 100644 --- a/tests/e2e/env/CHANGELOG.md +++ b/tests/e2e/env/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +## Fixed + +- Update `wc-e2e` script to fix an issue with directories with a space in their name + # 0.2.0 ## Fixed From b686a45339d08b160f0f2181f8fa1b05b23c3bac Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 8 Mar 2021 16:17:27 -0400 Subject: [PATCH 069/405] update package depenendencies --- package-lock.json | 244 ++++++++++++++++++++++++++++++++----- package.json | 2 +- tests/e2e/env/package.json | 2 +- 3 files changed, 213 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 687029b381d..55efc99c637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9141,6 +9141,7 @@ "@wordpress/jest-preset-default": "^6.4.0", "app-root-path": "^3.0.0", "jest": "^25.1.0", + "jest-each": "25.5.0", "jest-puppeteer": "^4.4.0" }, "dependencies": { @@ -9208,7 +9209,7 @@ } }, "prettier": { - "version": "npm:prettier@1.19.1", + "version": "npm:wp-prettier@1.19.1", "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-1.19.1.tgz", "integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg==", "dev": true @@ -16894,16 +16895,134 @@ } }, "jest-each": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", - "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz", + "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "jest-util": "^24.9.0", - "pretty-format": "^24.9.0" + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "jest-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-environment-jsdom": { @@ -17045,6 +17164,21 @@ "jest-util": "^24.9.0", "pretty-format": "^24.9.0", "throat": "^4.0.0" + }, + "dependencies": { + "jest-each": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", + "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0" + } + } } }, "jest-leak-detector": { @@ -18467,7 +18601,7 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "prettier": { - "version": "npm:prettier@1.19.1", + "version": "npm:wp-prettier@1.19.1", "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-1.19.1.tgz", "integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg==" }, @@ -20923,16 +21057,27 @@ } }, "@wordpress/e2e-test-utils": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-4.15.0.tgz", - "integrity": "sha512-mCOlNDX/yERd7hIAFB+y9x56iCQ2XyDZkWNlQNMYRH0+EdrQ5H5zE7MSxzycideIC7grxKw/j4RcuyxUdSWGDw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-4.16.1.tgz", + "integrity": "sha512-Dpsq5m0VSvjIhro2MjACSzkOkOf1jGEryzgEMW1ikbT6YI+motspHfGtisKXgYhZJOnjV4PwuEg+9lPVnd971g==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@wordpress/keycodes": "^2.16.0", - "@wordpress/url": "^2.19.0", + "@babel/runtime": "^7.12.5", + "@wordpress/keycodes": "^2.18.0", + "@wordpress/url": "^2.21.0", "lodash": "^4.17.19", "node-fetch": "^2.6.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@wordpress/eslint-plugin": { @@ -21001,17 +21146,29 @@ } }, "@wordpress/i18n": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.16.0.tgz", - "integrity": "sha512-ZyRWplETgD90caVaBuGBFcnYVpcogji1g9Ctbb5AO2bGFeHpmPpjvWm0NE64iQTtLFEJoaCiq6oqUvAOPIQJpw==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.18.0.tgz", + "integrity": "sha512-e1uFWhWYnT0B6s3hyy+xS0S3bwabrvkZA84xxitiIcQvGnZDUPntqv6M9+VrgJVlmd2MR2TbCGJ5xKFAVFr/gA==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", + "@wordpress/hooks": "^2.11.1", "gettext-parser": "^1.3.1", "lodash": "^4.17.19", "memize": "^1.1.0", "sprintf-js": "^1.1.1", "tannin": "^1.2.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@wordpress/jest-console": { @@ -21040,14 +21197,25 @@ } }, "@wordpress/keycodes": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.16.0.tgz", - "integrity": "sha512-8CfxB+9f08FXMUsaO625abmbx2ZinFUz6upzXbe0Da8W3oy7+/TZz6EWsMVBEWz+alSR3Z2FUZ7xUuopHZFcow==", + "version": "2.18.3", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.18.3.tgz", + "integrity": "sha512-Lenyw+K2KgiqddBv5fDCh2JRfXFrONWNvPfv1DKXzHXTvBSI0JkU1RVP5WZTcVuEtctCZWL5JbhrkG2I26w68g==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@wordpress/i18n": "^3.16.0", + "@babel/runtime": "^7.12.5", + "@wordpress/i18n": "^3.18.0", "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@wordpress/prettier-config": { @@ -21057,15 +21225,25 @@ "dev": true }, "@wordpress/url": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-2.19.0.tgz", - "integrity": "sha512-RizWbBxYmWBlNd+q89r3N6Y2XO8eCG3VncnXDgbGnhV4e+2z9fjzp1/9C/SORftEn+ix/qBKbqygmkmBqb+wuw==", + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-2.21.2.tgz", + "integrity": "sha512-bLHg4pTo/9mQUkK1s1MU/Sjgnzfy2AkPvPn4ObGA8/4CFkMsDhQGAVhhw5YuezcxvaJkBiKJ+BxgFJ1QKksF6w==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.12.5", "lodash": "^4.17.19", - "qs": "^6.5.2", "react-native-url-polyfill": "^1.1.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", + "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@xtuc/ieee754": { @@ -34417,9 +34595,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-native-url-polyfill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.2.0.tgz", - "integrity": "sha512-hpLZ8RyS3oGVyTOe/HjoqVoCOSkeJvrCoEB3bJsY7t9uh7kpQDV6kgvdlECEafYpxe3RzMrKLVcmWRbPU7CuAw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz", + "integrity": "sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ==", "dev": true, "requires": { "whatwg-url-without-unicode": "8.0.0-3" diff --git a/package.json b/package.json index 210c07c286d..da06d881283 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@woocommerce/e2e-utils": "file:tests/e2e/utils", "@wordpress/babel-plugin-import-jsx-pragma": "1.1.3", "@wordpress/babel-preset-default": "3.0.2", - "@wordpress/e2e-test-utils": "^4.6.0", + "@wordpress/e2e-test-utils": "^4.16.1", "@wordpress/eslint-plugin": "7.3.0", "autoprefixer": "9.8.6", "babel-eslint": "10.1.0", diff --git a/tests/e2e/env/package.json b/tests/e2e/env/package.json index 14bcec1da6c..78cf6d70a6e 100644 --- a/tests/e2e/env/package.json +++ b/tests/e2e/env/package.json @@ -27,7 +27,7 @@ "@wordpress/jest-preset-default": "^6.4.0", "app-root-path": "^3.0.0", "jest": "^25.1.0", - "jest-each": "^26.6.2", + "jest-each": "25.5.0", "jest-puppeteer": "^4.4.0" }, "devDependencies": { From 50e9ff278323d3ea9ef9dd8475e5eeb1fe7071e1 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 8 Mar 2021 16:56:47 -0400 Subject: [PATCH 070/405] make screenshots optional --- tests/e2e/env/CHANGELOG.md | 9 +++++---- tests/e2e/env/README.md | 10 ++++++++++ tests/e2e/env/bin/e2e-test-integration.js | 16 +++++++++------- tests/e2e/env/config/jest.config.js | 7 ++++++- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md index 8e6272ba44b..e7aaafd6fc4 100644 --- a/tests/e2e/env/CHANGELOG.md +++ b/tests/e2e/env/CHANGELOG.md @@ -1,11 +1,11 @@ # Unreleased +## Added + +- Support for screenshots on test errors + # 0.2.0 -## Fixed - -- Return jest exit code from `npx wc-e2e test:e2e*` - ## Added - support for custom container name @@ -15,6 +15,7 @@ ## Fixed +- Return jest exit code from `npx wc-e2e test:e2e*` - Remove redundant `puppeteer` dependency - Support for admin user configuration from `default.json` diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md index f9998325c85..c109dc4f162 100644 --- a/tests/e2e/env/README.md +++ b/tests/e2e/env/README.md @@ -71,6 +71,16 @@ module.exports = jestConfig; **NOTE:** Your project's Jest config file is expected to be: `tests/e2e/config/jest.config.js`. +#### Test Screenshots + +The test sequencer provides a screenshot function for test failures. To enable screenshots on test failure use + +```shell script +WC_E2E_SCREENSHOTS=1 npx wc-e2e test:e2e +``` + +Screenshots will be saved to `tests/e2e/screenshots` + ### Jest Puppeteer Config The test sequencer uses the following default Puppeteer configuration: diff --git a/tests/e2e/env/bin/e2e-test-integration.js b/tests/e2e/env/bin/e2e-test-integration.js index 577a5bb8bc4..46d457d974b 100755 --- a/tests/e2e/env/bin/e2e-test-integration.js +++ b/tests/e2e/env/bin/e2e-test-integration.js @@ -5,7 +5,7 @@ const program = require( 'commander' ); const path = require( 'path' ); const fs = require( 'fs' ); const { getAppRoot } = require( '../utils' ); -const { JEST_PUPPETEER_CONFIG } = process.env; +const { WC_E2E_SCREENSHOTS, JEST_PUPPETEER_CONFIG } = process.env; program .usage( ' [options]' ) @@ -16,12 +16,14 @@ program const appPath = getAppRoot(); // clear the screenshots folder before running tests. -const screenshotPath = path.resolve( appPath, 'tests/e2e/screenshots' ); -if ( fs.existsSync( screenshotPath ) ) { - fs.readdirSync( screenshotPath ).forEach( ( file, index ) => { - const filename = path.join( screenshotPath, file ); - fs.unlinkSync( filename ); - }); +if ( WC_E2E_SCREENSHOTS ) { + const screenshotPath = path.resolve(appPath, 'tests/e2e/screenshots'); + if (fs.existsSync(screenshotPath)) { + fs.readdirSync(screenshotPath).forEach((file, index) => { + const filename = path.join(screenshotPath, file); + fs.unlinkSync(filename); + }); + } } const nodeConfigDirs = [ diff --git a/tests/e2e/env/config/jest.config.js b/tests/e2e/env/config/jest.config.js index 5514019fa30..0f87fa06527 100644 --- a/tests/e2e/env/config/jest.config.js +++ b/tests/e2e/env/config/jest.config.js @@ -2,6 +2,7 @@ * External Dependencies */ const { jestConfig } = require( '@automattic/puppeteer-utils' ); +const { WC_E2E_SCREENSHOTS } = process.env; const path = require( 'path' ); const fs = require( 'fs' ); @@ -10,9 +11,13 @@ const fs = require( 'fs' ); */ const { getAppRoot } = require( '../utils' ); +let failureSetup = []; +if ( WC_E2E_SCREENSHOTS ) { + failureSetup.push( path.resolve( __dirname, '../build/setup/jest.failure.js' ) ); +} let setupFilesAfterEnv = [ path.resolve( __dirname, '../build/setup/jest.setup.js' ), - path.resolve( __dirname, '../build/setup/jest.failure.js' ), + ...failureSetup, 'expect-puppeteer', ]; From 4c8897303996006849951bcd72f7560d16827989 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 8 Mar 2021 19:22:55 -0400 Subject: [PATCH 071/405] add slack channel support --- .github/workflows/pr-build-and-e2e-tests.yml | 2 + tests/e2e/env/src/setup/jest.failure.js | 25 +++- tests/e2e/env/src/slack/index.js | 1 + tests/e2e/env/src/slack/reporter.js | 124 +++++++++++++++++++ 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/env/src/slack/index.js create mode 100644 tests/e2e/env/src/slack/reporter.js diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/pr-build-and-e2e-tests.yml index bb66e43bc44..e82982eecc7 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/pr-build-and-e2e-tests.yml @@ -106,4 +106,6 @@ jobs: - name: Run tests command. working-directory: code/woocommerce + env: + WC_E2E_SCREENSHOTS: 1 run: npx wc-e2e test:e2e diff --git a/tests/e2e/env/src/setup/jest.failure.js b/tests/e2e/env/src/setup/jest.failure.js index 36d87e03cc2..2a613b6a29a 100644 --- a/tests/e2e/env/src/setup/jest.failure.js +++ b/tests/e2e/env/src/setup/jest.failure.js @@ -1,10 +1,9 @@ /** @format */ -/* import { sendFailedTestScreenshotToSlack, - sendFailedTestMessageToSlack -} from "./lib/reporter/slack-reporter"; -*/ + sendFailedTestMessageToSlack, +} from '../slack'; + const path = require( 'path' ); const mkdirp = require( 'mkdirp' ); import { bind } from 'jest-each'; @@ -23,6 +22,10 @@ let currentBlock; const originalDescribe = global.describe; const originalIt = global.it; +/** + * A custom describe function that stores the name of the describe block. + * @type {describe} + */ global.describe = (() => { const describe = ( blockName, callback ) => { currentBlock = blockName; @@ -50,6 +53,12 @@ global.describe = (() => { return describe; })(); +/** + * A custom it function that wraps the test function in a callback + * which takes a screenshot on test failure. + * + * @type {function(*=, *=): *} + */ global.it = (() => { const test = async ( testName, callback ) => { const testCallback = async () => screenshotTest( testName, callback ); @@ -71,6 +80,12 @@ global.it = (() => { return test; })(); +/** + * Save a screenshot during a test if the test fails. + * @param testName + * @param callback + * @returns {Promise} + */ const screenshotTest = async ( testName, callback ) => { try { await callback(); @@ -89,10 +104,8 @@ const screenshotTest = async ( testName, callback ) => { fullPage: true, } ); -/* await sendFailedTestMessageToSlack( testTitle ); await sendFailedTestScreenshotToSlack( filePath ); -*/ throw ( e ); } diff --git a/tests/e2e/env/src/slack/index.js b/tests/e2e/env/src/slack/index.js new file mode 100644 index 00000000000..aeab8f42dd6 --- /dev/null +++ b/tests/e2e/env/src/slack/index.js @@ -0,0 +1 @@ +export * from './reporter'; diff --git a/tests/e2e/env/src/slack/reporter.js b/tests/e2e/env/src/slack/reporter.js new file mode 100644 index 00000000000..e99d689ec20 --- /dev/null +++ b/tests/e2e/env/src/slack/reporter.js @@ -0,0 +1,124 @@ +const { createReadStream } = require( 'fs' ); +const { WebClient, ErrorCode } = require( '@slack/web-api' ); +const { + GITHUB_ACTIONS, + GITHUB_REF, + GITHUB_SHA, + GITHUB_REPOSITORY, + GITHUB_RUN_ID, + TRAVIS_PULL_REQUEST_BRANCH, + TRAVIS_COMMIT, + TRAVIS_BUILD_WEB_URL, + SLACK_TOKEN, + SLACK_CHANNEL, + WC_E2E_SCREENSHOTS, +} = process.env; + +let web; + +/** + * Initialize the Slack web client. + * + * @returns {WebClient} + */ +const initializeWeb = () => { + if ( ! web ) { + web = new WebClient( SLACK_TOKEN ); + } + return web; +}; + +/** + * Initialize Slack parameters if tests are running in CI. + * @returns {Object|boolean} + */ +const initializeSlack = () => { + if ( ! WC_E2E_SCREENSHOTS || ! SLACK_TOKEN ) { + return false; + } + if ( ! GITHUB_ACTIONS && ! TRAVIS_PULL_REQUEST_BRANCH ) { + return false; + } + // Build PR info + if ( GITHUB_ACTIONS ) { + const refArray = GITHUB_REF.split( '/' ); + const branch = refArray.pop(); + return { + branch, + commit: GITHUB_SHA, + webUrl: `https://github.com/${ GITHUB_REPOSITORY }/actions/runs/${ GITHUB_RUN_ID }`, + }; + } + + return { + branch: TRAVIS_PULL_REQUEST_BRANCH, + commit: TRAVIS_COMMIT, + webUrl: TRAVIS_BUILD_WEB_URL, + }; +}; + +/** + * Post a message to a Slack channel for a failed test. + * + * @param testName + * @returns {Promise} + */ +export async function sendFailedTestMessageToSlack( testName ) { + const pr = initializeSlack(); + if ( ! pr ) { + return; + } + const web = initializeWeb(); + + try { + // For details, see: https://api.slack.com/methods/chat.postMessage + await web.chat.postMessage({ + text: `Test failed on *${ pr.branch }* branch. \n + The commit this build is testing is *${ pr.commit }*. \n + The name of the test that failed: *${ testName }*. \n + See screenshot of the failed test below. *Build log* could be found here: ${ pr.webUrl }`, + channel: SLACK_CHANNEL, + }); + } catch ( error ) { + // Check the code property and log the response + if ( error.code === ErrorCode.PlatformError || error.code === ErrorCode.RequestError || + error.code === ErrorCode.RateLimitedError || error.code === ErrorCode.HTTPError ) { + console.log( error.data ); + } else { + // Some other error, oh no! + console.log( 'The error occurred does not match an error we are checking for in this block.' ); + } + } +} + +/** + * Post a screenshot to a Slack channel for a failed test. + * @param screenshotOfFailedTest + * @returns {Promise} + */ +export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) { + const pr = initializeSlack(); + if ( ! pr ) { + return; + } + const web = initializeWeb(); + const filename = 'screenshot_of_failed_test.png'; + + try { + // For details, see: https://api.slack.com/methods/files.upload + await web.files.upload({ + filename, + file: createReadStream( screenshotOfFailedTest ), + channels: SLACK_CHANNEL, + }); + } catch ( error ) { + // Check the code property and log the response + if ( error.code === ErrorCode.PlatformError || error.code === ErrorCode.RequestError || + error.code === ErrorCode.RateLimitedError || error.code === ErrorCode.HTTPError ) { + console.log( error.data ); + } else { + // Some other error, oh no! + console.log( 'The error occurred does not match an error we are checking for in this block.' ); + } + } +} From c42e2db28070e1eb2340a299c3895860da7b0834 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 8 Mar 2021 22:39:30 -0400 Subject: [PATCH 072/405] use api to create products for search test --- tests/e2e/utils/src/components.js | 38 +++++++++---------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 1776478cecf..25b67f7605b 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -192,34 +192,18 @@ const createSimpleProduct = async () => { * @param categoryName Product's category which can be changed when writing a test */ const createSimpleProductWithCategory = async ( productName, productPrice, categoryName ) => { - // Go to "add product" page - await merchant.openNewProduct(); + const product = await factories.products.simple.create( { + name: productName, + regularPrice: productPrice, + categories: [ + { + name: categoryName, + } + ], + isVirtual: true, + } ); - // Add title and regular price - await expect(page).toFill('#title', productName); - await expect(page).toClick('#_virtual'); - await clickTab('General'); - await expect(page).toFill('#_regular_price', productPrice); - - // Try to select the existing category if present already, otherwise add a new and select it - try { - const [checkbox] = await page.$x('//label[contains(text(), "'+categoryName+'")]'); - await checkbox.click(); - } catch (error) { - await expect(page).toClick('#product_cat-add-toggle'); - await expect(page).toFill('#newproduct_cat', categoryName); - await expect(page).toClick('#product_cat-add-submit'); - } - - // Publish the product - await expect(page).toClick('#publish'); - await uiUnblocked(); - await page.waitForSelector('.updated.notice', {text:'Product published.'}); - - // Get the product ID - const variablePostId = await page.$('#post_ID'); - let variablePostIdValue = (await(await variablePostId.getProperty('value')).jsonValue()); - return variablePostIdValue; + return product.id; }; /** From add5d73df2b49272ce6550ba2506ad023cef3cde Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 08:49:52 -0400 Subject: [PATCH 073/405] update browse,search,sort test to be more reliable --- ...ront-end-product-browse-search-sort.test.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js index c852cf1996e..1b0535bd76d 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-product-browse-search-sort.test.js @@ -21,8 +21,8 @@ const { const config = require( 'config' ); const simpleProductName = config.get( 'products.simple.name' ); const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99'; -const singleProductPrice2 = config.has('products.simple.price') ? config.get('products.simple.price') : '19.99'; -const singleProductPrice3 = config.has('products.simple.price') ? config.get('products.simple.price') : '29.99'; +const singleProductPrice2 = config.has('products.simple.price') ? '1' + singleProductPrice : '19.99'; +const singleProductPrice3 = config.has('products.simple.price') ? '2' + singleProductPrice : '29.99'; const clothing = 'Clothing'; const audio = 'Audio'; const hardware = 'Hardware'; @@ -31,34 +31,32 @@ const productTitle = 'li.first > a > h2.woocommerce-loop-product__title'; const runProductBrowseSearchSortTest = () => { describe('Search, browse by categories and sort items in the shop', () => { beforeAll(async () => { - await merchant.login(); - // Create 1st product with Clothing category + // Create 1st product with Clothing category await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing); - // Create 2nd product with Audio category + // Create 2nd product with Audio category await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio); - // Create 3rd product with Hardware category + // Create 3rd product with Hardware category await createSimpleProductWithCategory(simpleProductName + ' 3', singleProductPrice3, hardware); - await merchant.logout(); }); it('should let user search the store', async () => { await shopper.goToShop(); await shopper.searchForProduct(simpleProductName + ' 1'); + page.waitForNavigation({waitUntil: 'networkidle0'}); }); it('should let user browse products by categories', async () => { // Browse through Clothing category link await Promise.all([ - page.waitForNavigation({waitUntil: 'networkidle0'}), page.click('span.posted_in > a', {text: clothing}), + page.waitForNavigation({waitUntil: 'networkidle0'}), ]); - await uiUnblocked(); // Verify Clothing category page await page.waitForSelector(productTitle); await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'}); await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'}); - await uiUnblocked(); + page.waitForNavigation({waitUntil: 'networkidle0'}); await page.waitForSelector('h1.entry-title'); await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1'); }); From cf634987b41a0673863b9787a5809ad6fb0b8b50 Mon Sep 17 00:00:00 2001 From: roykho Date: Tue, 9 Mar 2021 06:11:41 -0800 Subject: [PATCH 074/405] Prepare default branch --- changelog.txt | 107 +++++++++++++++++++++++++++++++++ includes/class-woocommerce.php | 2 +- readme.txt | 2 +- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index a0491ef9f77..edcc1e00bbd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,112 @@ == Changelog == += 5.1.0 2021-03-09 = + +**WooCommerce** + +* Update - WooCommerce Admin package 2.0.2. #29111 +* Update - WooCommerce Blocks package 4.4.3. #29016 +* Fix - Error in notice message of reports when WC Admin is disabled via a filter. #29095 +* Fix - Error when calculating orders with tax option rounding at subtotal level in PHP 8. #29089 +* Fix - price filtering not working properly with variable products whose variations have different prices. #29043 +* Fix - Removed extra closing brace from the Zone regions help text. #29036 +* Fix - Tax name/label is not being updated in the order when it is changed. #28983 +* Fix - Additional protection after wc_get_product to account for invalid ID. #28962 +* Fix - orders list from returning false values if orders are missing. #28927 +* Fix - Terms and Policy checkbox wording settings not shown in customizer. #28735 +* Fix - Admin notices sometimes were persisting even after dismissing. #28500 +* Fix - Calculate discount based on order location in the admin view. #26983 +* Fix - SASS variables not being compile correctly due to recent SASS version. #29120 +* Fix - Placeholder image height in cart. #29139 +* Dev - Updated admin bar icons to use SVG and Dashicons instead of custom font. #29094 +* Dev - Admin menu modification has been moved from admin_head hooks to admin_menu hooks. #29088 +* Dev - status report generation time to the Status Report. #28980 +* Dev - Add the 'woocommerce_exporter_product_types' filter to allow third-parties to filter the product types which can be imported and exported. #28950 +* Dev - Filter added to allow 'woocommerce_hold_stock_minutes' to be customized. #28933 +* Dev - Add optional semicolon to JS code for better compatibility. #28880 +* Dev - Added Guatemala states. #28755 +* Dev - jQuery 3 deprecated functions update. #28753 +* Dev - Add Woo Version as global prop in track events. #28627 +* Dev - Added orders count by payment method to Tracker data and replaced direct DB calls with CRUD. #28584 +* Dev - WC_Tax::get_tax_rate_classes() is now public. #27671 +* Dev - "Store management insights" option now is turned off by default. #29105 +* Tweak - Updated WooCommerce logo color. #29054 +* Tweak - Correctly aligns content in the checkout with Twenty Twenty-One. #28951 +* Tweak - Use assigned variable for $post_thumbnail_id instead of calling function more than once. #28919 +* Tweak - Ensure that all tracker values collected for orders are of string type. #28893 +* Tweak - Adjust CSS font size and spacing for Twenty Twenty One. #28827 + +**WooCommerce Admin - 2.0.0 & 2.0.1 & 2.0.2** + +* Tweak - Bump minimum supported version of PHP to 7.0. #6046 +* Tweak - update the content and timing of the NeedSomeInspiration note. #6076 +* Tweak - Adjust the Marketing note not to show until store is at least 5 days. #6083 +* Tweak - Refactored extended task list. #6081 +* Fix - Add support for a floating-point number as a SummaryNumber's delta. #5926 +* Fix - allow for more terms to be shown for product attributes in the Analytics orders report. #5868 +* Fix - Fixed the Add First Product email note checks. #6260 +* Fix - Onboarding - Fixed "Business Details" error. #6271 +* Fix - Show management links when only main task list is hidden. #6291 +* Fix - Correct the Klarna slug. #6440 +* Add - new inbox message - Getting started in Ecommerce - watch this webinar. #6086 +* Add - Remote inbox notifications contains comparison and fix product rule. #6073 +* Add - Task list payments - include Mollie as an option. #6257 +* Update - store deprecation welcome modal support doc link #6094 +* Update - Homescreen layout, moving Inbox panel for better interaction. #6122 +* Enhancement - Allowing users to create products by selecting a template. #5892 +* Enhancement - Use the new Paypal payments plugin for onboarding. #6261 +* Dev - Add wait script for mysql to be ready for phpunit tests in docker. #6185 +* Dev - Remove old debug code for connecting to Calypso / Wordpress.com. #6097 +* Dev - Allow highlight tooltip to use body tag as parent. #6309 + +**WooCommerce Blocks - 4.1.0 & 4.2.0 & 4.3.0 & 4.4.0 & 4.4.1 & 4.4.2 & 4.4.3** + +* Update - Jetpack Autoloader to 2.9.1. +* Update - Update package for WooCommerce core inclusion. +* Enhancements - Design tweaks to the cart page which move the quantity picker below each cart item and improve usability on mobile. ([3734](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3734)) +* Enhancements - Store API - Fix selected rate in cart shipping rates response. ([3680](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3680)) +* Enhancements - Create get_item_responses_from_schema abstraction. ([3679](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3679)) +* Enhancements - Show itemized fee rows in the cart/checkout blocks. ([3678](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3678)) +* Enhancements - Extensibility: Show item data in Cart and Checkout blocks and update the variation data styles. ([3665](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3665)) +* Enhancements - Introduce SlotFill for Sidebar. ([3361](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3361)) +* Enhancements - Add the ability to directly upload an image in Featured Category and Featured Product blocks. ([3579](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3579)) +* Enhancements - Fix coupon code button height not adapting to the font size. ([3575](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3575)) +* Enhancements - Fixed Coupon Code panel not expanding/contracting in some themes. ([3569](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3569)) +* Enhancements - Fix: Added fallback styling for screen reader text. ([3557](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3557)) +* Fix - Ensure empty categories are correctly hidden in the product categories block. ([3765](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3765)) +* Fix - Added missing wrapper div within FeaturedCategory and FeatureProduct blocks. ([3746](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3746)) +* Fix - Set correct text color in BlockErrorBoundry notices. ([3738](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3738)) +* Fix - Hidden cart item meta data will not be rendered in the Cart and Checkout blocks. ([3732](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3732)) +* Fix - Improved accessibility of product image links in the products block by using correct aria tags and hiding empty image placeholders. ([3722](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3722)) +* Fix - Add missing aria-label for stars image in the review-list-item component. ([3706](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3706)) +* Fix - Prevent "X-WC-Store-API-Nonce is invalid" error when going back to a page with the products block using the browser back button. ([3770](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3770)) +* Fix - Adds a default "features" array for payment methods which do not define supported features. Fixes conflicts with 3rd Party payment method integrations. +* Fix - Fix an error that was blocking checkout with some user saved payment methods. ([3627](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3627)) +* Fix - Fix nonce issues when adding product to cart from All Products. ([3598](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3598)) +* Fix - Fix bug inside Product Search in the editor. ([3578](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3578)) +* Fix - Fix console warnings in WordPress 5.6. ([3577](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3577)) +* Fix - Fixed text visibility in select inputs when using Twenty Twenty-One theme's dark mode. ([3554](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3554)) +* Fix - Fix product list images skewed in Widgets editor. ([3553](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3553)) +* Add address validation to values posted to the Checkout via StoreApi. ([3552](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3552)) +* Fix - Fix Fees not visible in Cart & Checkout blocks when order doesn't need shipping. ([3521](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3521)) +* Fix - Fix All Products block edit screen. ([3547](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3547)) +* Fix - Checkout block: Prevent `Create an account` from creating up a user account if the order fails coupon validation. ([3423](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3423)) +* Fix - Make sure cart is initialized before the CartItems route is used in the Store API. ([3488](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3488)) +* Fix - Fix notice close button color in Twenty Twenty One dark mode. ([3472](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3472)) +* Fix - Remove held stock for a draft order if an item is removed from the cart. ([3468](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3468)) +* Fix - Ensure correct alignment of checkout notice's dismiss button. ([3455](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3455)) +* Fix - Fixed a bug in Checkout block (Store API) causing checkout to fail when using an invalid coupon and creating an account. +* Fix - Checkout block: Correctly handle cases where the order fails with an error (e.g. invalid coupon) and a new user account is created. ([3429](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3429)) +* Update - Hide the All Products Block from the new Gutenberg Widget Areas until full support is achieved. ([3737](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3737)) +* Update - Legacy `star-rating` class name has been removed from Product rating block (inside All Products block). That element is still selectable with the `.wc-block-components-product-rating` class name. ([3717](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3717)) +* Update - Update input colors and alignment. ([3597](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3597)) +* Update - Removed compatibility with packages in WordPress 5.3. ([3541](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3541)) +* Update - Bumped the minimum WP required version to 5.4. ([3537](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3537)) +* Dev - Change register_endpoint_data to use an array of params instead of individual params. ([3478](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3478)) +* Dev - Expose store/cart via ExtendRestApi to extensions. ([3445](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3445)) +* Dev - Added formatting classes to the Store API for extensions to consume. +* Dev - Refactored and reordered Store API checkout processing to handle various edge cases and better support future extensibility. ([3454](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3454)) + = 5.0.0 - 2021-02-09 = **WooCommerce** diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 85a7c5bad71..715c05883cb 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -23,7 +23,7 @@ final class WooCommerce { * * @var string */ - public $version = '5.1.0'; + public $version = '5.2.0'; /** * WooCommerce Schema version. diff --git a/readme.txt b/readme.txt index eab39a7fb1f..52f2d26062f 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d Requires at least: 5.4 Tested up to: 5.6 Requires PHP: 7.0 -Stable tag: 5.0.0 +Stable tag: 5.1.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html From 2d8ab6e2f8123c7a334b72414801315583690ac7 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 10:19:43 -0400 Subject: [PATCH 075/405] include the current block in screenshot params --- tests/e2e/env/src/setup/jest.failure.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/env/src/setup/jest.failure.js b/tests/e2e/env/src/setup/jest.failure.js index 2a613b6a29a..de8c655ec2e 100644 --- a/tests/e2e/env/src/setup/jest.failure.js +++ b/tests/e2e/env/src/setup/jest.failure.js @@ -61,14 +61,14 @@ global.describe = (() => { */ global.it = (() => { const test = async ( testName, callback ) => { - const testCallback = async () => screenshotTest( testName, callback ); + const testCallback = async () => screenshotTest( currentBlock, testName, callback ); return originalIt( testName, testCallback ); }; - const only = ( blockName, callback ) => { - return originalIt.only( blockName, callback ); + const only = ( testName, callback ) => { + return originalIt.only( testName, callback ); }; - const skip = ( blockName, callback ) => { - return originalIt.skip( blockName, callback ); + const skip = ( testName, callback ) => { + return originalIt.skip( testName, callback ); }; test.each = bind( test, false ); @@ -86,11 +86,11 @@ global.it = (() => { * @param callback * @returns {Promise} */ -const screenshotTest = async ( testName, callback ) => { +const screenshotTest = async ( blockName, testName, callback ) => { try { await callback(); } catch ( e ) { - const testTitle = `${ currentBlock } - ${ testName }`.replace( /\.$/, '' ); + const testTitle = `${ blockName } - ${ testName }`.replace( /\.$/, '' ); const appPath = getAppRoot(); const savePath = path.resolve( appPath, 'tests/e2e/screenshots' ); const filePath = path.join( From 954f19817f6a060abfd8a0e1a5256b4c0ae3df5f Mon Sep 17 00:00:00 2001 From: roykho Date: Tue, 9 Mar 2021 06:45:14 -0800 Subject: [PATCH 076/405] Update WP tested up to --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 52f2d26062f..e29b492dfc0 100644 --- a/readme.txt +++ b/readme.txt @@ -2,7 +2,7 @@ Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce Requires at least: 5.4 -Tested up to: 5.6 +Tested up to: 5.7 Requires PHP: 7.0 Stable tag: 5.1.0 License: GPLv3 From 0395457e0c10c08e9b3236b4c577821794329d68 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 11:33:29 -0400 Subject: [PATCH 077/405] fix api package build errors --- tests/e2e/api/src/framework/model-repository.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/api/src/framework/model-repository.ts b/tests/e2e/api/src/framework/model-repository.ts index ca598404b93..9c30993a83a 100644 --- a/tests/e2e/api/src/framework/model-repository.ts +++ b/tests/e2e/api/src/framework/model-repository.ts @@ -401,7 +401,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements } return ( this.listHook as ListChildFn< T > )( - paramsOrParent as ParentID< T >, + ( paramsOrParent as unknown ) as ParentID< T >, params, ); } @@ -428,7 +428,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements } return ( this.createHook as CreateChildFn< T > )( - propertiesOrParent as ParentID, + ( propertiesOrParent as unknown ) as ParentID, properties as Partial< ModelClass >, ); } @@ -455,7 +455,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements } return ( this.readHook as ReadChildFn< T > )( - idOrParent as ParentID< T >, + ( idOrParent as unknown ) as ParentID< T >, childID, ); } @@ -480,14 +480,14 @@ export class ModelRepository< T extends ModelRepositoryParams > implements if ( properties === undefined ) { return ( this.updateHook as UpdateFn< T > )( idOrParent as ModelID, - propertiesOrChildID as UpdateParams< T >, + ( propertiesOrChildID as unknown ) as UpdateParams< T >, ); } return ( this.updateHook as UpdateChildFn< T > )( - idOrParent as ParentID< T >, + ( idOrParent as unknown ) as ParentID< T >, propertiesOrChildID as ModelID, - properties, + ( properties as unknown ) as UpdateParams< T >, ); } @@ -513,7 +513,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements } return ( this.deleteHook as DeleteChildFn< T > )( - idOrParent as ParentID< T >, + ( idOrParent as unknown ) as ParentID< T >, childID, ); } From f266f2010182a3af0dde5d69417dd698d10072f2 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 13:45:05 -0400 Subject: [PATCH 078/405] delete vs trash product in e2e api --- tests/e2e/api/src/models/products/abstract/common.ts | 8 ++++++++ .../src/repositories/rest/products/external-product.ts | 3 ++- .../api/src/repositories/rest/products/grouped-product.ts | 3 ++- .../api/src/repositories/rest/products/simple-product.ts | 3 ++- .../src/repositories/rest/products/variable-product.ts | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index 88601fa6ad0..d9a4a3525db 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -27,6 +27,14 @@ export const baseProductURL = () => '/wc/v3/products/'; */ export const buildProductURL = ( id: ModelID ) => baseProductURL() + id; +/** + * A common delete product URL builder. + * + * @param {ModelID} id the id of the product. + * @return {string} RESTful Url. + */ +export const deleteProductURL = ( id: ModelID ) => buildProductURL( id ) + '?force=true'; + /** * The base for all product types. */ diff --git a/tests/e2e/api/src/repositories/rest/products/external-product.ts b/tests/e2e/api/src/repositories/rest/products/external-product.ts index f2b323ad7b8..bd78edce17f 100644 --- a/tests/e2e/api/src/repositories/rest/products/external-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/external-product.ts @@ -3,6 +3,7 @@ import { ModelRepository } from '../../../framework'; import { baseProductURL, buildProductURL, + deleteProductURL, ExternalProduct, CreatesExternalProducts, DeletesExternalProducts, @@ -61,6 +62,6 @@ export function externalProductRESTRepository( httpClient: HTTPClient ): ListsEx restCreate< ExternalProductRepositoryParams >( baseProductURL, ExternalProduct, httpClient, transformer ), restRead< ExternalProductRepositoryParams >( buildProductURL, ExternalProduct, httpClient, transformer ), restUpdate< ExternalProductRepositoryParams >( buildProductURL, ExternalProduct, httpClient, transformer ), - restDelete< ExternalProductRepositoryParams >( buildProductURL, httpClient ), + restDelete< ExternalProductRepositoryParams >( deleteProductURL, httpClient ), ); } diff --git a/tests/e2e/api/src/repositories/rest/products/grouped-product.ts b/tests/e2e/api/src/repositories/rest/products/grouped-product.ts index 94f1bef6a89..c2a9a4dd338 100644 --- a/tests/e2e/api/src/repositories/rest/products/grouped-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/grouped-product.ts @@ -10,6 +10,7 @@ import { UpdatesGroupedProducts, baseProductURL, buildProductURL, + deleteProductURL, } from '../../../models'; import { createProductTransformer, @@ -55,6 +56,6 @@ export function groupedProductRESTRepository( httpClient: HTTPClient ): ListsGro restCreate< GroupedProductRepositoryParams >( baseProductURL, GroupedProduct, httpClient, transformer ), restRead< GroupedProductRepositoryParams >( buildProductURL, GroupedProduct, httpClient, transformer ), restUpdate< GroupedProductRepositoryParams >( buildProductURL, GroupedProduct, httpClient, transformer ), - restDelete< GroupedProductRepositoryParams >( buildProductURL, httpClient ), + restDelete< GroupedProductRepositoryParams >( deleteProductURL, httpClient ), ); } diff --git a/tests/e2e/api/src/repositories/rest/products/simple-product.ts b/tests/e2e/api/src/repositories/rest/products/simple-product.ts index b4f9b5d0d2d..fb83634b643 100644 --- a/tests/e2e/api/src/repositories/rest/products/simple-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/simple-product.ts @@ -4,6 +4,7 @@ import { SimpleProduct, baseProductURL, buildProductURL, + deleteProductURL, CreatesSimpleProducts, DeletesSimpleProducts, ListsSimpleProducts, @@ -70,6 +71,6 @@ export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimp restCreate< SimpleProductRepositoryParams >( baseProductURL, SimpleProduct, httpClient, transformer ), restRead< SimpleProductRepositoryParams >( buildProductURL, SimpleProduct, httpClient, transformer ), restUpdate< SimpleProductRepositoryParams >( buildProductURL, SimpleProduct, httpClient, transformer ), - restDelete< SimpleProductRepositoryParams >( buildProductURL, httpClient ), + restDelete< SimpleProductRepositoryParams >( deleteProductURL, httpClient ), ); } diff --git a/tests/e2e/api/src/repositories/rest/products/variable-product.ts b/tests/e2e/api/src/repositories/rest/products/variable-product.ts index 9133b4ccdee..2868ebfbeca 100644 --- a/tests/e2e/api/src/repositories/rest/products/variable-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/variable-product.ts @@ -10,6 +10,7 @@ import { UpdatesVariableProducts, baseProductURL, buildProductURL, + deleteProductURL, } from '../../../models'; import { createProductTransformer, @@ -67,6 +68,6 @@ export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVa restCreate< VariableProductRepositoryParams >( baseProductURL, VariableProduct, httpClient, transformer ), restRead< VariableProductRepositoryParams >( buildProductURL, VariableProduct, httpClient, transformer ), restUpdate< VariableProductRepositoryParams >( buildProductURL, VariableProduct, httpClient, transformer ), - restDelete< VariableProductRepositoryParams >( buildProductURL, httpClient ), + restDelete< VariableProductRepositoryParams >( deleteProductURL, httpClient ), ); } From 1f32c1f8401ec97e697e97bcbcc54b451bcc52a9 Mon Sep 17 00:00:00 2001 From: roykho Date: Tue, 9 Mar 2021 10:04:15 -0800 Subject: [PATCH 079/405] Update changelog --- changelog.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index edcc1e00bbd..0c69319d959 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,7 +4,7 @@ **WooCommerce** -* Update - WooCommerce Admin package 2.0.2. #29111 +* Update - WooCommerce Admin package 2.0.2. #29229 * Update - WooCommerce Blocks package 4.4.3. #29016 * Fix - Error in notice message of reports when WC Admin is disabled via a filter. #29095 * Fix - Error when calculating orders with tax option rounding at subtotal level in PHP 8. #29089 @@ -30,6 +30,7 @@ * Dev - Added orders count by payment method to Tracker data and replaced direct DB calls with CRUD. #28584 * Dev - WC_Tax::get_tax_rate_classes() is now public. #27671 * Dev - "Store management insights" option now is turned off by default. #29105 +* Dev - Update the woo widget stock links to the new analytics page. #29093 * Tweak - Updated WooCommerce logo color. #29054 * Tweak - Correctly aligns content in the checkout with Twenty Twenty-One. #28951 * Tweak - Use assigned variable for $post_thumbnail_id instead of calling function more than once. #28919 From f6b4614225ea8645ed9949b17201ecb7e78aee16 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 9 Mar 2021 16:57:08 -0300 Subject: [PATCH 080/405] Return 0 if order isn't available --- includes/abstracts/abstract-wc-payment-gateway.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/abstracts/abstract-wc-payment-gateway.php b/includes/abstracts/abstract-wc-payment-gateway.php index fe29740e569..3dcc281464d 100644 --- a/includes/abstracts/abstract-wc-payment-gateway.php +++ b/includes/abstracts/abstract-wc-payment-gateway.php @@ -262,7 +262,9 @@ abstract class WC_Payment_Gateway extends WC_Settings_API { // Gets order total from "pay for order" page. if ( 0 < $order_id ) { $order = wc_get_order( $order_id ); - $total = (float) $order->get_total(); + if ( $order ) { + $total = (float) $order->get_total(); + } // Gets order total from cart/checkout. } elseif ( 0 < WC()->cart->total ) { From bb907a6acdd2b7ff73d63cda4dfdbf80367b3f6e Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 16:11:48 -0400 Subject: [PATCH 081/405] add slack secrets, deliberately break test --- .github/workflows/pr-build-and-e2e-tests.yml | 2 ++ .../specs/shopper/front-end-my-account.test.js | 2 +- tests/e2e/env/src/slack/reporter.js | 12 ++++++------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/pr-build-and-e2e-tests.yml index e82982eecc7..d43ee761be2 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/pr-build-and-e2e-tests.yml @@ -108,4 +108,6 @@ jobs: working-directory: code/woocommerce env: WC_E2E_SCREENSHOTS: 1 + E2E_SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + E2E_SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }} run: npx wc-e2e test:e2e diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account.test.js index ac283f2f20a..5ad893c0a1e 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account.test.js @@ -11,7 +11,7 @@ const runMyAccountPageTest = () => { describe('My account page', () => { it('allows customer to login', async () => { await merchant.logout(); - await shopper.login(); +// await shopper.login(); await expect(page).toMatch('Hello'); await expect(page).toMatchElement('.woocommerce-MyAccount-navigation-link', {text: 'Dashboard'}); await expect(page).toMatchElement('.woocommerce-MyAccount-navigation-link', {text: 'Orders'}); diff --git a/tests/e2e/env/src/slack/reporter.js b/tests/e2e/env/src/slack/reporter.js index e99d689ec20..fedb5593280 100644 --- a/tests/e2e/env/src/slack/reporter.js +++ b/tests/e2e/env/src/slack/reporter.js @@ -9,8 +9,8 @@ const { TRAVIS_PULL_REQUEST_BRANCH, TRAVIS_COMMIT, TRAVIS_BUILD_WEB_URL, - SLACK_TOKEN, - SLACK_CHANNEL, + E2E_SLACK_TOKEN, + E2E_SLACK_CHANNEL, WC_E2E_SCREENSHOTS, } = process.env; @@ -23,7 +23,7 @@ let web; */ const initializeWeb = () => { if ( ! web ) { - web = new WebClient( SLACK_TOKEN ); + web = new WebClient( E2E_SLACK_TOKEN ); } return web; }; @@ -33,7 +33,7 @@ const initializeWeb = () => { * @returns {Object|boolean} */ const initializeSlack = () => { - if ( ! WC_E2E_SCREENSHOTS || ! SLACK_TOKEN ) { + if ( ! WC_E2E_SCREENSHOTS || ! E2E_SLACK_TOKEN ) { return false; } if ( ! GITHUB_ACTIONS && ! TRAVIS_PULL_REQUEST_BRANCH ) { @@ -77,7 +77,7 @@ export async function sendFailedTestMessageToSlack( testName ) { The commit this build is testing is *${ pr.commit }*. \n The name of the test that failed: *${ testName }*. \n See screenshot of the failed test below. *Build log* could be found here: ${ pr.webUrl }`, - channel: SLACK_CHANNEL, + channel: E2E_SLACK_CHANNEL, }); } catch ( error ) { // Check the code property and log the response @@ -109,7 +109,7 @@ export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) await web.files.upload({ filename, file: createReadStream( screenshotOfFailedTest ), - channels: SLACK_CHANNEL, + channels: E2E_SLACK_CHANNEL, }); } catch ( error ) { // Check the code property and log the response From 12c0793217a2bc9cfde86cffb29627ffc1a9195b Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 9 Mar 2021 16:49:47 -0400 Subject: [PATCH 082/405] add token to calls, remove block from test title --- tests/e2e/env/src/setup/jest.failure.js | 8 +++----- tests/e2e/env/src/slack/reporter.js | 6 ++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/env/src/setup/jest.failure.js b/tests/e2e/env/src/setup/jest.failure.js index de8c655ec2e..31a906a10ee 100644 --- a/tests/e2e/env/src/setup/jest.failure.js +++ b/tests/e2e/env/src/setup/jest.failure.js @@ -14,7 +14,6 @@ const { getAppRoot } = require( '../../utils' ); * * See: https://github.com/smooth-code/jest-puppeteer/issues/131#issuecomment-469439666 */ -let currentBlock; /** * We need to reference the original version of Jest. @@ -28,7 +27,6 @@ const originalIt = global.it; */ global.describe = (() => { const describe = ( blockName, callback ) => { - currentBlock = blockName; try { originalDescribe( blockName, callback ); @@ -61,7 +59,7 @@ global.describe = (() => { */ global.it = (() => { const test = async ( testName, callback ) => { - const testCallback = async () => screenshotTest( currentBlock, testName, callback ); + const testCallback = async () => screenshotTest( testName, callback ); return originalIt( testName, testCallback ); }; const only = ( testName, callback ) => { @@ -86,11 +84,11 @@ global.it = (() => { * @param callback * @returns {Promise} */ -const screenshotTest = async ( blockName, testName, callback ) => { +const screenshotTest = async ( testName, callback ) => { try { await callback(); } catch ( e ) { - const testTitle = `${ blockName } - ${ testName }`.replace( /\.$/, '' ); + const testTitle = testName.replace( /\.$/, '' ); const appPath = getAppRoot(); const savePath = path.resolve( appPath, 'tests/e2e/screenshots' ); const filePath = path.join( diff --git a/tests/e2e/env/src/slack/reporter.js b/tests/e2e/env/src/slack/reporter.js index fedb5593280..9c63467e668 100644 --- a/tests/e2e/env/src/slack/reporter.js +++ b/tests/e2e/env/src/slack/reporter.js @@ -73,11 +73,12 @@ export async function sendFailedTestMessageToSlack( testName ) { try { // For details, see: https://api.slack.com/methods/chat.postMessage await web.chat.postMessage({ + channel: E2E_SLACK_CHANNEL, + token: E2E_SLACK_TOKEN, text: `Test failed on *${ pr.branch }* branch. \n The commit this build is testing is *${ pr.commit }*. \n The name of the test that failed: *${ testName }*. \n See screenshot of the failed test below. *Build log* could be found here: ${ pr.webUrl }`, - channel: E2E_SLACK_CHANNEL, }); } catch ( error ) { // Check the code property and log the response @@ -107,9 +108,10 @@ export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) try { // For details, see: https://api.slack.com/methods/files.upload await web.files.upload({ + channels: E2E_SLACK_CHANNEL, + token: E2E_SLACK_TOKEN, filename, file: createReadStream( screenshotOfFailedTest ), - channels: E2E_SLACK_CHANNEL, }); } catch ( error ) { // Check the code property and log the response From a6b45d08bfacff1aacb0bd7c2313fff134f10d4b Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 11:47:22 +0100 Subject: [PATCH 083/405] Added low stock threshold input to the Admin UI. --- .../meta-boxes/views/html-variation-admin.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 01a0a20e7eb..0122b732cca 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -210,6 +210,23 @@ defined( 'ABSPATH' ) || exit; ) ); + woocommerce_wp_text_input( + array( + 'id' => "variable_low_stock_threshold{$loop}", + 'name' => "variable_low_stock_threshold[{$loop}]", + 'value' => $variation_object->get_low_stock_amount( 'edit' ), + 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), + 'label' => __( 'Low stock threshold', 'woocommerce' ), + 'desc_tip' => true, + 'description' => __( 'When product stock reaches this amount you will be notified by email', 'woocommerce' ), + 'type' => 'number', + 'custom_attributes' => array( + 'step' => 'any', + ), + 'wrapper_class' => 'form-row', + ) + ); + /** * Variation options inventory action. * From 1fc1b604b9c0fddda6cdd8e674f4ebaf43a755e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Rey=20L=C3=B3pez?= Date: Wed, 10 Mar 2021 11:52:01 +0000 Subject: [PATCH 084/405] IE 11 compatibility for the site tracking enable function --- includes/tracks/class-wc-site-tracking.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php index 74e06215985..e924d250448 100644 --- a/includes/tracks/class-wc-site-tracking.php +++ b/includes/tracks/class-wc-site-tracking.php @@ -104,7 +104,7 @@ class WC_Site_Tracking { ?>