merge master
This commit is contained in:
commit
7282e0bca6
|
@ -563,7 +563,7 @@ jQuery( function( $ ) {
|
||||||
wc_checkout_form.$checkout_form.removeClass( 'processing' ).unblock();
|
wc_checkout_form.$checkout_form.removeClass( 'processing' ).unblock();
|
||||||
wc_checkout_form.$checkout_form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' ).blur();
|
wc_checkout_form.$checkout_form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' ).blur();
|
||||||
wc_checkout_form.scroll_to_notices();
|
wc_checkout_form.scroll_to_notices();
|
||||||
$( document.body ).trigger( 'checkout_error' );
|
$( document.body ).trigger( 'checkout_error' , [ error_message ] );
|
||||||
},
|
},
|
||||||
scroll_to_notices: function() {
|
scroll_to_notices: function() {
|
||||||
var scrollElement = $( '.woocommerce-NoticeGroup-updateOrderReview, .woocommerce-NoticeGroup-checkout' );
|
var scrollElement = $( '.woocommerce-NoticeGroup-updateOrderReview, .woocommerce-NoticeGroup-checkout' );
|
||||||
|
|
|
@ -1,5 +1,64 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 4.7.0 - 2020-11-10 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Tweak - Update `product_cat/tag` taxonomy template file names to `product-cat/tag`. #27736
|
||||||
|
* Tweak - Exclude draft pages from the "Shop page" setting. #27890
|
||||||
|
* Tweak - Styling to properly display product reviews within the dashboard activity widget. #27968
|
||||||
|
* Fix - Fixes Photoswipe action buttons being obscured by admin bar. #27010
|
||||||
|
* Fix - Allow variation image to be removed via REST API. #27299
|
||||||
|
* Fix - Fixed WP CLI command to delete tax classes. #27310
|
||||||
|
* Fix - Prevent regenerate image filter loop. #27483
|
||||||
|
* Fix - Fixed some race conditions in `WC_Install`. #27696
|
||||||
|
* Fix - Improved PHP 8 support for `Automattic\WooCommerce\RestApi\Utilities\SingletonTrait`. #27707
|
||||||
|
* Fix - Adjust stock even if `reduce_stock` meta is not set in `wc_maybe_reduce_stock_levels`. #27763
|
||||||
|
* Fix - Removed duplicated CSS code from jQuery UI. #27767
|
||||||
|
* Fix - HTML syntax error in scheduled product message. #27842
|
||||||
|
* Fix - Update logic to determine if an order requires payment to check the order instead of the cart. #27893
|
||||||
|
* Fix - Use `Set password` title for lost password reset form when applicable. #27898
|
||||||
|
* Fix - REST API - Fixed deprecated notices while querying orders and refunds through REST API v1 endpoints. #27934
|
||||||
|
* Fix - Email address starting with `www` being displayed as a URL link in the admin order details page. #27983
|
||||||
|
* Fix - Unexpected HTTP 401 "Sorry, you cannot list resources" REST API responses that occur when a plugin or custom code determines the current WordPress user before WooCommerce is fully initialized. #27587
|
||||||
|
* Dev - Add `woocommerce_should_send_low_stock_notification` filter. #27819
|
||||||
|
* Dev - Introduce (again) a dependency injection framework for the code in the src directory. #27733
|
||||||
|
* Dev - Remove leftover code and data from the reverted improvement for variations filtering by attribute. #27748
|
||||||
|
* Dev - Escaped labels in `woocommerce_form_field()`. #27800
|
||||||
|
* Dev - Add a `NumberUtil::round` method to workaround a breaking change in the buil-in round function in PHP8. #27830
|
||||||
|
* Dev - Remove default value from optional parameters that are followed by required parameters in functions/methods, since those are de-facto required and trigger a deprectation notice in PHP 8. #27840
|
||||||
|
* Dev - REST API - Add user-friendly attribute names and values to order line items metadata.
|
||||||
|
* Dev - REST API - Adds `parent_name` to `line_items` of the GET /orders endpoint.
|
||||||
|
* Localization - Added Serbia districts. #27778
|
||||||
|
* Localization - Make city, and postcode non-required fields. #27779
|
||||||
|
* Localization - Add i18n locale information for Uganda, Kenya and Tanzania. #27164
|
||||||
|
* Localization - Renamed "Postcode / ZIP" to "Pin code", and renamed "State / County" to "State" for India. #27516
|
||||||
|
* Localization - Added postcode validation for addresses in India. #27546
|
||||||
|
|
||||||
|
**WooCommerce Blocks - 3.5.0 & 3.6.0**
|
||||||
|
|
||||||
|
* Make 'retry' property on errors from checkoutAfterProcessingWithSuccess/Error observers default to true if it's undefined. ([3261](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3261))
|
||||||
|
* Ensure new payment methods are only displayed when no saved payment method is selected. ([3247](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3247))
|
||||||
|
* Load WC Blocks CSS after editor CSS. ([3219](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3219))
|
||||||
|
* Restore saved payment method data after closing an express payment method. ([3210](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3210))
|
||||||
|
* Use light default background colour for country/state dropdowns. ([3189](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3189))
|
||||||
|
* Fix broken Express Payment Method use in the Checkout block for logged out or incognito users. ([3165](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3165))
|
||||||
|
* Fix State label for Spain. ([3147](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3147))
|
||||||
|
* Don't throw an error when registering a payment method fails. ([3134](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3134))
|
||||||
|
* Don't load contents of payment method hidden tabs. ([3227](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3227))
|
||||||
|
* Use noticeContexts from useEmitResponse instead of hardcoded values. ([3161](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3161))
|
||||||
|
|
||||||
|
**WooCommerce Admin - 1.6.3**
|
||||||
|
|
||||||
|
* Tweak: Add BR and IN to list of stripe countries [#5377](https://github.com/woocommerce/woocommerce-admin/pull/5377)
|
||||||
|
* Fix: Redirect instead of stalling on WCPay Inbox note action [#5413](https://github.com/woocommerce/woocommerce-admin/pull/5413)
|
||||||
|
|
||||||
|
= 4.6.2 - 2020-11-05 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Prevent checkout from creating accounts when related setting is disabled.
|
||||||
|
|
||||||
= 4.6.1 - 2020-10-21 =
|
= 4.6.1 - 2020-10-21 =
|
||||||
|
|
||||||
**WooCommerce**
|
**WooCommerce**
|
||||||
|
|
|
@ -259,6 +259,12 @@
|
||||||
"provider",
|
"provider",
|
||||||
"service"
|
"service"
|
||||||
],
|
],
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/philipobenito",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
"time": "2020-09-28T13:38:44+00:00"
|
"time": "2020-09-28T13:38:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -446,27 +452,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/css-selector",
|
"name": "symfony/css-selector",
|
||||||
"version": "v3.4.45",
|
"version": "v3.4.46",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/css-selector.git",
|
"url": "https://github.com/symfony/css-selector.git",
|
||||||
"reference": "9ccf6e78077a3fc1596e6c7b5958008965a11518"
|
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/9ccf6e78077a3fc1596e6c7b5958008965a11518",
|
"url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
||||||
"reference": "9ccf6e78077a3fc1596e6c7b5958008965a11518",
|
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.5.9|>=7.0.8"
|
"php": "^5.5.9|>=7.0.8"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "3.4-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Symfony\\Component\\CssSelector\\": ""
|
"Symfony\\Component\\CssSelector\\": ""
|
||||||
|
@ -495,7 +496,21 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony CssSelector Component",
|
"description": "Symfony CssSelector Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2020-03-16T08:31:04+00:00"
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-10-24T10:57:07+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "woocommerce/action-scheduler",
|
"name": "woocommerce/action-scheduler",
|
||||||
|
@ -686,5 +701,6 @@
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.1"
|
"php": "7.1"
|
||||||
}
|
},
|
||||||
|
"plugin-api-version": "1.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -667,15 +667,18 @@ class WC_Checkout {
|
||||||
* @return array of data.
|
* @return array of data.
|
||||||
*/
|
*/
|
||||||
public function get_posted_data() {
|
public function get_posted_data() {
|
||||||
$skipped = array();
|
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||||
$data = array(
|
$data = array(
|
||||||
'terms' => (int) isset( $_POST['terms'] ), // WPCS: input var ok, CSRF ok.
|
'terms' => (int) isset( $_POST['terms'] ),
|
||||||
'createaccount' => (int) ! empty( $_POST['createaccount'] ), // WPCS: input var ok, CSRF ok.
|
'createaccount' => (int) ( $this->is_registration_enabled() ? ! empty( $_POST['createaccount'] ) : false ),
|
||||||
'payment_method' => isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
'payment_method' => isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '',
|
||||||
'shipping_method' => isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
'shipping_method' => isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : '',
|
||||||
'ship_to_different_address' => ! empty( $_POST['ship_to_different_address'] ) && ! wc_ship_to_billing_address_only(), // WPCS: input var ok, CSRF ok.
|
'ship_to_different_address' => ! empty( $_POST['ship_to_different_address'] ) && ! wc_ship_to_billing_address_only(),
|
||||||
'woocommerce_checkout_update_totals' => isset( $_POST['woocommerce_checkout_update_totals'] ), // WPCS: input var ok, CSRF ok.
|
'woocommerce_checkout_update_totals' => isset( $_POST['woocommerce_checkout_update_totals'] ),
|
||||||
);
|
);
|
||||||
|
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||||
|
|
||||||
|
$skipped = array();
|
||||||
foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
|
foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
|
||||||
if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
|
if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
|
||||||
$skipped[] = $fieldset_key;
|
$skipped[] = $fieldset_key;
|
||||||
|
|
|
@ -565,7 +565,7 @@ class WC_Shortcodes {
|
||||||
$single_product = new WP_Query( $args );
|
$single_product = new WP_Query( $args );
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
jQuery( document ).ready( function( $ ) {
|
jQuery( function( $ ) {
|
||||||
var $variations_form = $( '[data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>"]' ).find( 'form.variations_form' );
|
var $variations_form = $( '[data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>"]' ).find( 'form.variations_form' );
|
||||||
|
|
||||||
<?php foreach ( $attributes as $attr => $value ) { ?>
|
<?php foreach ( $attributes as $attr => $value ) { ?>
|
||||||
|
|
|
@ -56,6 +56,9 @@ class WC_Validation {
|
||||||
case 'BA':
|
case 'BA':
|
||||||
$valid = (bool) preg_match( '/^([7-8]{1})([0-9]{4})$/', $postcode );
|
$valid = (bool) preg_match( '/^([7-8]{1})([0-9]{4})$/', $postcode );
|
||||||
break;
|
break;
|
||||||
|
case 'BE':
|
||||||
|
$valid = (bool) preg_match( '/^([0-9]{4})$/i', $postcode );
|
||||||
|
break;
|
||||||
case 'BR':
|
case 'BR':
|
||||||
$valid = (bool) preg_match( '/^([0-9]{5})([-])?([0-9]{3})$/', $postcode );
|
$valid = (bool) preg_match( '/^([0-9]{5})([-])?([0-9]{3})$/', $postcode );
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -102,7 +102,7 @@ class WC_Shop_Customizer {
|
||||||
$max_notice = __( 'The maximum allowed setting is %d', 'woocommerce' );
|
$max_notice = __( 'The maximum allowed setting is %d', 'woocommerce' );
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
jQuery( document ).ready( function( $ ) {
|
jQuery( function( $ ) {
|
||||||
$( document.body ).on( 'change', '.woocommerce-cropping-control input[type="radio"]', function() {
|
$( document.body ).on( 'change', '.woocommerce-cropping-control input[type="radio"]', function() {
|
||||||
var $wrapper = $( this ).closest( '.woocommerce-cropping-control' ),
|
var $wrapper = $( this ).closest( '.woocommerce-cropping-control' ),
|
||||||
value = $wrapper.find( 'input:checked' ).val();
|
value = $wrapper.find( 'input:checked' ).val();
|
||||||
|
|
|
@ -108,8 +108,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
|
||||||
$order->set_props(
|
$order->set_props(
|
||||||
array(
|
array(
|
||||||
'parent_id' => $post_object->post_parent,
|
'parent_id' => $post_object->post_parent,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -123,8 +123,8 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
|
||||||
array(
|
array(
|
||||||
'code' => $post_object->post_title,
|
'code' => $post_object->post_title,
|
||||||
'description' => $post_object->post_excerpt,
|
'description' => $post_object->post_excerpt,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'date_expires' => metadata_exists( 'post', $coupon_id, 'date_expires' ) ? get_post_meta( $coupon_id, 'date_expires', true ) : get_post_meta( $coupon_id, 'expiry_date', true ), // @todo: Migrate expiry_date meta to date_expires in upgrade routine.
|
'date_expires' => metadata_exists( 'post', $coupon_id, 'date_expires' ) ? get_post_meta( $coupon_id, 'date_expires', true ) : get_post_meta( $coupon_id, 'expiry_date', true ), // @todo: Migrate expiry_date meta to date_expires in upgrade routine.
|
||||||
'discount_type' => get_post_meta( $coupon_id, 'discount_type', true ),
|
'discount_type' => get_post_meta( $coupon_id, 'discount_type', true ),
|
||||||
'amount' => get_post_meta( $coupon_id, 'coupon_amount', true ),
|
'amount' => get_post_meta( $coupon_id, 'coupon_amount', true ),
|
||||||
|
|
|
@ -630,4 +630,16 @@ class WC_Data_Store_WP {
|
||||||
);
|
);
|
||||||
wp_cache_delete( 'lookup_table', 'object_' . $id );
|
wp_cache_delete( 'lookup_table', 'object_' . $id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a WP post date string into a timestamp.
|
||||||
|
*
|
||||||
|
* @since 4.8.0
|
||||||
|
*
|
||||||
|
* @param string $time_string The WP post date string.
|
||||||
|
* @return int|null The date string converted to a timestamp or null.
|
||||||
|
*/
|
||||||
|
protected function string_to_timestamp( $time_string ) {
|
||||||
|
return '0000-00-00 00:00:00' !== $time_string ? wc_string_to_timestamp( $time_string ) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,8 +170,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
||||||
array(
|
array(
|
||||||
'name' => $post_object->post_title,
|
'name' => $post_object->post_title,
|
||||||
'slug' => $post_object->post_name,
|
'slug' => $post_object->post_name,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
'description' => $post_object->post_content,
|
'description' => $post_object->post_content,
|
||||||
'short_description' => $post_object->post_excerpt,
|
'short_description' => $post_object->post_excerpt,
|
||||||
|
|
|
@ -62,8 +62,8 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
|
||||||
array(
|
array(
|
||||||
'name' => $post_object->post_title,
|
'name' => $post_object->post_title,
|
||||||
'slug' => $post_object->post_name,
|
'slug' => $post_object->post_name,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
'menu_order' => $post_object->menu_order,
|
'menu_order' => $post_object->menu_order,
|
||||||
'reviews_allowed' => 'open' === $post_object->comment_status,
|
'reviews_allowed' => 'open' === $post_object->comment_status,
|
||||||
|
|
|
@ -316,15 +316,16 @@ function wc_cart_totals_order_total_html() {
|
||||||
|
|
||||||
if ( ! empty( $tax_string_array ) ) {
|
if ( ! empty( $tax_string_array ) ) {
|
||||||
$taxable_address = WC()->customer->get_taxable_address();
|
$taxable_address = WC()->customer->get_taxable_address();
|
||||||
/* translators: %s: country name */
|
if ( WC()->customer->is_customer_outside_base() && ! WC()->customer->has_calculated_shipping() ) {
|
||||||
$estimated_text = WC()->customer->is_customer_outside_base() && ! WC()->customer->has_calculated_shipping() ? sprintf( ' ' . __( 'estimated for %s', 'woocommerce' ), WC()->countries->estimated_for_prefix( $taxable_address[0] ) . WC()->countries->countries[ $taxable_address[0] ] ) : '';
|
$country = WC()->countries->estimated_for_prefix( $taxable_address[0] ) . WC()->countries->countries[ $taxable_address[0] ];
|
||||||
$value .= '<small class="includes_tax">('
|
/* translators: 1: tax amount 2: country name */
|
||||||
/* translators: includes tax information */
|
$tax_text = wp_kses_post( sprintf( __( '(includes %1$s estimated for %2$s)', 'woocommerce' ), implode( ', ', $tax_string_array ), $country ) );
|
||||||
. esc_html__( 'includes', 'woocommerce' )
|
} else {
|
||||||
. ' '
|
/* translators: %s: tax amount */
|
||||||
. wp_kses_post( implode( ', ', $tax_string_array ) )
|
$tax_text = wp_kses_post( sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) ) );
|
||||||
. esc_html( $estimated_text )
|
}
|
||||||
. ')</small>';
|
|
||||||
|
$value .= '<small class="includes_tax">' . $tax_text . '</small>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -83,7 +83,6 @@
|
||||||
"mocha": "7.2.0",
|
"mocha": "7.2.0",
|
||||||
"node-sass": "4.13.1",
|
"node-sass": "4.13.1",
|
||||||
"prettier": "npm:wp-prettier@2.0.5",
|
"prettier": "npm:wp-prettier@2.0.5",
|
||||||
"puppeteer": "^2.1.1",
|
|
||||||
"stylelint": "12.0.1",
|
"stylelint": "12.0.1",
|
||||||
"stylelint-config-wordpress": "16.0.0",
|
"stylelint-config-wordpress": "16.0.0",
|
||||||
"typescript": "3.9.7",
|
"typescript": "3.9.7",
|
||||||
|
|
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
||||||
Requires at least: 5.3
|
Requires at least: 5.3
|
||||||
Tested up to: 5.5
|
Tested up to: 5.5
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Stable tag: 4.6.1
|
Stable tag: 4.6.2
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
|
|
@ -24,24 +24,14 @@ The simplest way to use the client is directly:
|
||||||
import { HTTPClientFactory } from '@woocommerce/api';
|
import { HTTPClientFactory } from '@woocommerce/api';
|
||||||
|
|
||||||
// You can create an API client using the client factory with pre-configured middleware for convenience.
|
// You can create an API client using the client factory with pre-configured middleware for convenience.
|
||||||
let httpClient = HTTPClientFactory.withBasicAuth(
|
let client = HTTPClientFactory.build( 'https://example.com' )
|
||||||
// The base URL of your REST API.
|
.withBasicAuth( 'username', 'password' )
|
||||||
'https://example.com/wp-json/',
|
.create();
|
||||||
// The username for your WordPress user.
|
|
||||||
'username',
|
|
||||||
// The password for your WordPress user.
|
|
||||||
'password',
|
|
||||||
);
|
|
||||||
|
|
||||||
// You can also create an API client configured for requests using OAuth.
|
// You can also create an API client configured for requests using OAuth.
|
||||||
httpClient = HTTPClientFactory.withOAuth(
|
client = HTTPClientFactory.build( 'https://example.com' )
|
||||||
// The base URL of your REST API.
|
.withOAuth( 'consumer_secret', 'consumer_password' )
|
||||||
'https://example.com/wp-json/',
|
.create();
|
||||||
// The OAuth API Key's consumer secret.
|
|
||||||
'consumer_secret',
|
|
||||||
// The OAuth API Key's consumer password.
|
|
||||||
'consumer_pasword',
|
|
||||||
);
|
|
||||||
|
|
||||||
// You can then use the client to make API requests.
|
// You can then use the client to make API requests.
|
||||||
httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
||||||
|
@ -54,6 +44,7 @@ httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
||||||
}, ( error ) => {
|
}, ( error ) => {
|
||||||
// Handle errors that may have come up.
|
// Handle errors that may have come up.
|
||||||
} );
|
} );
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Repositories
|
### Repositories
|
||||||
|
@ -66,7 +57,9 @@ import { SimpleProduct } from '@woocommerce/api';
|
||||||
|
|
||||||
// Prepare the HTTP client that will be consumed by the repository.
|
// Prepare the HTTP client that will be consumed by the repository.
|
||||||
// This is necessary so that it can make requests to the REST API.
|
// This is necessary so that it can make requests to the REST API.
|
||||||
const httpClient = HTTPClientFactory.withBasicAuth( 'https://example.com/wp-json/','username','password' );
|
const httpClient = HTTPClientFactory.build( 'https://example.com' )
|
||||||
|
.withBasicAuth( 'username', 'password' )
|
||||||
|
.create();
|
||||||
|
|
||||||
const repository = SimpleProduct.restRepository( httpClient );
|
const repository = SimpleProduct.restRepository( httpClient );
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import * as moxios from 'moxios';
|
||||||
|
import { AxiosURLToQueryInterceptor } from '../axios-url-to-query-interceptor';
|
||||||
|
|
||||||
|
describe( 'AxiosURLToQueryInterceptor', () => {
|
||||||
|
let urlToQueryInterceptor: AxiosURLToQueryInterceptor;
|
||||||
|
let axiosInstance: AxiosInstance;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
axiosInstance = axios.create();
|
||||||
|
moxios.install( axiosInstance );
|
||||||
|
urlToQueryInterceptor = new AxiosURLToQueryInterceptor( 'test' );
|
||||||
|
urlToQueryInterceptor.start( axiosInstance );
|
||||||
|
} );
|
||||||
|
|
||||||
|
afterEach( () => {
|
||||||
|
urlToQueryInterceptor.stop( axiosInstance );
|
||||||
|
moxios.uninstall();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should put path in query string', async () => {
|
||||||
|
moxios.stubRequest( 'http://test.test/?test=%2Ftest%2Froute', {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
responseText: JSON.stringify( { test: 'value' } ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const response = await axiosInstance.get( 'http://test.test/test/route' );
|
||||||
|
|
||||||
|
expect( response.status ).toEqual( 200 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -1,4 +1,4 @@
|
||||||
import { buildURL } from '../utils';
|
import { buildURL, buildURLWithParams } from '../utils';
|
||||||
|
|
||||||
describe( 'buildURL', () => {
|
describe( 'buildURL', () => {
|
||||||
it( 'should use base when given no url', () => {
|
it( 'should use base when given no url', () => {
|
||||||
|
@ -15,9 +15,16 @@ describe( 'buildURL', () => {
|
||||||
const url = buildURL( { baseURL: 'http://test.test', url: 'yes/test' } );
|
const url = buildURL( { baseURL: 'http://test.test', url: 'yes/test' } );
|
||||||
expect( url ).toBe( 'http://test.test/yes/test' );
|
expect( url ).toBe( 'http://test.test/yes/test' );
|
||||||
} );
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
it( 'should combine base and url with trailing/leading slashes', () => {
|
describe( 'buildURLWithParams', () => {
|
||||||
const url = buildURL( { baseURL: 'http://test.test/////', url: '////yes/test' } );
|
it( 'should do nothing without query string', () => {
|
||||||
expect( url ).toBe( 'http://test.test/yes/test' );
|
const url = buildURLWithParams( { baseURL: 'http://test.test' } );
|
||||||
|
expect( url ).toBe( 'http://test.test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should append query string', () => {
|
||||||
|
const url = buildURLWithParams( { baseURL: 'http://test.test', params: { test: 'yes' } } );
|
||||||
|
expect( url ).toBe( 'http://test.test?test=yes' );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -2,10 +2,10 @@ import type { AxiosRequestConfig } from 'axios';
|
||||||
import * as createHmac from 'create-hmac';
|
import * as createHmac from 'create-hmac';
|
||||||
import * as OAuth from 'oauth-1.0a';
|
import * as OAuth from 'oauth-1.0a';
|
||||||
import { AxiosInterceptor } from './axios-interceptor';
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
import { buildURL } from './utils';
|
import { buildURLWithParams } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for managing the lifecycle of an authentication interceptor.
|
* An interceptor for adding OAuth 1.0a signatures to HTTP requests.
|
||||||
*/
|
*/
|
||||||
export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private oauth: OAuth;
|
private readonly oauth: OAuth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new interceptor.
|
* Creates a new interceptor.
|
||||||
|
@ -44,7 +44,7 @@ export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
* @return {AxiosRequestConfig} The request with the additional authorization headers.
|
* @return {AxiosRequestConfig} The request with the additional authorization headers.
|
||||||
*/
|
*/
|
||||||
protected handleRequest( request: AxiosRequestConfig ): AxiosRequestConfig {
|
protected handleRequest( request: AxiosRequestConfig ): AxiosRequestConfig {
|
||||||
const url = buildURL( request );
|
const url = buildURLWithParams( request );
|
||||||
if ( url.startsWith( 'https' ) ) {
|
if ( url.startsWith( 'https' ) ) {
|
||||||
request.auth = {
|
request.auth = {
|
||||||
username: this.oauth.consumer.key,
|
username: this.oauth.consumer.key,
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { AxiosResponse } from 'axios';
|
||||||
import { AxiosInterceptor } from './axios-interceptor';
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
import { HTTPResponse } from '../http-client';
|
import { HTTPResponse } from '../http-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interceptor for transforming the responses from axios into a consistent format for package consumers.
|
||||||
|
*/
|
||||||
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
||||||
/**
|
/**
|
||||||
* Transforms the Axios response into our HTTP response.
|
* Transforms the Axios response into our HTTP response.
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import { buildURL } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interceptor for transforming the request's path into a query parameter.
|
||||||
|
*/
|
||||||
|
export class AxiosURLToQueryInterceptor extends AxiosInterceptor {
|
||||||
|
/**
|
||||||
|
* The query parameter we want to assign the path to.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly queryParam: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new interceptor.
|
||||||
|
*
|
||||||
|
* @param {string} queryParam The query parameter we want to assign the path to.
|
||||||
|
*/
|
||||||
|
public constructor( queryParam: string ) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.queryParam = queryParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the outgoing path into a query parameter.
|
||||||
|
*
|
||||||
|
* @param {AxiosRequestConfig} config The axios config.
|
||||||
|
* @return {AxiosRequestConfig} The axios config.
|
||||||
|
*/
|
||||||
|
protected handleRequest( config: AxiosRequestConfig ): AxiosRequestConfig {
|
||||||
|
const url = new URL( buildURL( config ) );
|
||||||
|
|
||||||
|
// Store the path in the query string.
|
||||||
|
if ( config.params instanceof URLSearchParams ) {
|
||||||
|
config.params.set( this.queryParam, url.pathname );
|
||||||
|
} else if ( config.params ) {
|
||||||
|
config.params[ this.queryParam ] = url.pathname;
|
||||||
|
} else {
|
||||||
|
config.params = { [ this.queryParam ]: url.pathname };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the URL without the path now that it's in the query string.
|
||||||
|
url.pathname = '';
|
||||||
|
config.url = url.toString();
|
||||||
|
delete config.baseURL;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import buildFullPath = require( 'axios/lib/core/buildFullPath' );
|
||||||
|
// @ts-ignore
|
||||||
|
import appendParams = require( 'axios/lib/helpers/buildURL' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an Axios request config this function generates the URL that Axios will
|
* Given an Axios request config this function generates the URL that Axios will
|
||||||
* use to make the request.
|
* use to make the request.
|
||||||
|
@ -8,17 +13,16 @@ import { AxiosRequestConfig } from 'axios';
|
||||||
* @return {string} The merged URL.
|
* @return {string} The merged URL.
|
||||||
*/
|
*/
|
||||||
export function buildURL( request: AxiosRequestConfig ): string {
|
export function buildURL( request: AxiosRequestConfig ): string {
|
||||||
const base = request.baseURL || '';
|
return buildFullPath( request.baseURL, request.url );
|
||||||
if ( ! request.url ) {
|
|
||||||
return base;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Axios ignores the base when the URL is absolute.
|
/**
|
||||||
const url = request.url;
|
* Given an Axios request config this function generates the URL that Axios will
|
||||||
if ( ! base || url.match( /^([a-z][a-z\d+\-.]*:)?\/\/[^\/]/i ) ) {
|
* use to make the request with the query parameters included.
|
||||||
return url;
|
*
|
||||||
}
|
* @param {AxiosRequestConfig} request The Axios request we're building the URL for.
|
||||||
|
* @return {string} The merged URL.
|
||||||
// Remove trailing slashes from the base and leading slashes from the URL so we can combine them consistently.
|
*/
|
||||||
return base.replace( /\/+$/, '' ) + '/' + url.replace( /^\/+/, '' );
|
export function buildURLWithParams( request: AxiosRequestConfig ): string {
|
||||||
|
return appendParams( buildURL( request ), request.params, request.paramsSerializer );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,141 @@
|
||||||
import { HTTPClient } from './http-client';
|
import { HTTPClient } from './http-client';
|
||||||
import { AxiosClient, AxiosOAuthInterceptor } from './axios';
|
import { AxiosClient, AxiosOAuthInterceptor } from './axios';
|
||||||
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import { AxiosInterceptor } from './axios/axios-interceptor';
|
||||||
|
import { AxiosURLToQueryInterceptor } from './axios/axios-url-to-query-interceptor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for generating HTTPClient instances with desired configurations.
|
* These types describe the shape of the different auth methods our factory supports.
|
||||||
|
*/
|
||||||
|
type OAuthMethod = {
|
||||||
|
type: 'oauth',
|
||||||
|
key: string,
|
||||||
|
secret: string,
|
||||||
|
};
|
||||||
|
type BasicAuthMethod = {
|
||||||
|
type: 'basic',
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for describing the shape of a client to create using the factory.
|
||||||
|
*/
|
||||||
|
interface BuildParams {
|
||||||
|
wpURL: string,
|
||||||
|
useIndexPermalinks?: boolean,
|
||||||
|
auth?: OAuthMethod | BasicAuthMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for generating an HTTPClient with a desired configuration.
|
||||||
*/
|
*/
|
||||||
export class HTTPClientFactory {
|
export class HTTPClientFactory {
|
||||||
/**
|
/**
|
||||||
* Creates a new client instance prepared for basic auth.
|
* The configuration object describing the client we're trying to create.
|
||||||
*
|
*
|
||||||
* @param {string} apiURL
|
* @private
|
||||||
* @param {string} username
|
|
||||||
* @param {string} password
|
|
||||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
|
||||||
*/
|
*/
|
||||||
public static withBasicAuth( apiURL: string, username: string, password: string ): HTTPClient {
|
private clientConfig: BuildParams;
|
||||||
return new AxiosClient(
|
|
||||||
{
|
private constructor( wpURL: string ) {
|
||||||
baseURL: apiURL,
|
this.clientConfig = { wpURL };
|
||||||
auth: { username, password },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new client instance prepared for oauth.
|
* Creates a new factory that can be used to build clients.
|
||||||
*
|
*
|
||||||
* @param {string} apiURL
|
* @param {string} wpURL The root URL of the WordPress installation we're querying.
|
||||||
* @param {string} consumerKey
|
* @return {HTTPClientFactory} The new factory instance.
|
||||||
* @param {string} consumerSecret
|
|
||||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
|
||||||
*/
|
*/
|
||||||
public static withOAuth( apiURL: string, consumerKey: string, consumerSecret: string ): HTTPClient {
|
public static build( wpURL: string ): HTTPClientFactory {
|
||||||
return new AxiosClient(
|
return new HTTPClientFactory( wpURL );
|
||||||
{ baseURL: apiURL },
|
}
|
||||||
[ new AxiosOAuthInterceptor( consumerKey, consumerSecret ) ],
|
|
||||||
|
/**
|
||||||
|
* Configures the client to utilize OAuth.
|
||||||
|
*
|
||||||
|
* @param {string} key The OAuth consumer key to use.
|
||||||
|
* @param {string} secret The OAuth consumer secret to use.
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withOAuth( key: string, secret: string ): this {
|
||||||
|
this.clientConfig.auth = { type: 'oauth', key, secret };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to utilize basic auth.
|
||||||
|
*
|
||||||
|
* @param {string} username The WordPress username to use.
|
||||||
|
* @param {string} password The password for the WordPress user.
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withBasicAuth( username: string, password: string ): this {
|
||||||
|
this.clientConfig.auth = { type: 'basic', username, password };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to use index permalinks.
|
||||||
|
*
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withIndexPermalinks(): this {
|
||||||
|
this.clientConfig.useIndexPermalinks = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to use query permalinks.
|
||||||
|
*
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withoutIndexPermalinks(): this {
|
||||||
|
this.clientConfig.useIndexPermalinks = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a client instance using the configuration stored within.
|
||||||
|
*
|
||||||
|
* @return {HTTPClient} The created client.
|
||||||
|
*/
|
||||||
|
public create(): HTTPClient {
|
||||||
|
const axiosConfig: AxiosRequestConfig = {};
|
||||||
|
const interceptors: AxiosInterceptor[] = [];
|
||||||
|
|
||||||
|
axiosConfig.baseURL = this.clientConfig.wpURL;
|
||||||
|
if ( ! axiosConfig.baseURL.endsWith( '/' ) ) {
|
||||||
|
axiosConfig.baseURL += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.clientConfig.useIndexPermalinks ) {
|
||||||
|
axiosConfig.baseURL += 'wp-json/';
|
||||||
|
} else {
|
||||||
|
interceptors.push( new AxiosURLToQueryInterceptor( 'rest_route' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.clientConfig.auth ) {
|
||||||
|
switch ( this.clientConfig.auth.type ) {
|
||||||
|
case 'basic':
|
||||||
|
axiosConfig.auth = {
|
||||||
|
username: this.clientConfig.auth.username,
|
||||||
|
password: this.clientConfig.auth.password,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'oauth':
|
||||||
|
interceptors.push(
|
||||||
|
new AxiosOAuthInterceptor(
|
||||||
|
this.clientConfig.auth.key,
|
||||||
|
this.clientConfig.auth.secret,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AxiosClient( axiosConfig, interceptors );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
# WooCommerce Core End to End Test Suite
|
||||||
|
|
||||||
|
This package contains the automated end-to-end tests for WooCommerce.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
- [Pre-requisites](#pre-requisites)
|
||||||
|
- [Setting up core tests](#setting-up-core-tests)
|
||||||
|
- [Test functions](#test-functions)
|
||||||
|
- [Activation and setup](#activation-and-setup)
|
||||||
|
- [Merchant](#merchant)
|
||||||
|
- [Shopper](#shopper)
|
||||||
|
- [Contributing a new test](#contributing-a-new-test)
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
|
||||||
|
### Setting up the test environment
|
||||||
|
|
||||||
|
Follow [E2E setup instructions](https://github.com/woocommerce/woocommerce/blob/master/tests/e2e/README.md).
|
||||||
|
|
||||||
|
### Setting up core tests
|
||||||
|
|
||||||
|
- Create the folder `tests/e2e/specs` in your repository if it does not exist.
|
||||||
|
- To add a core test to your test suite, create a new `.test.js` file within `tests/e2e/specs` . Example code to run all the shopper tests:
|
||||||
|
```js
|
||||||
|
|
||||||
|
const { runShopperTests } = require( '@woocommerce/e2e-core-tests' );
|
||||||
|
|
||||||
|
runShopperTests();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test functions
|
||||||
|
|
||||||
|
The functions to access the core tests are:
|
||||||
|
|
||||||
|
### Activation and setup
|
||||||
|
|
||||||
|
- `runSetupOnboardingTests` - Run all setup and onboarding tests
|
||||||
|
- `runActivationTest` - Merchant can activate WooCommerce
|
||||||
|
- `runOnboardingFlowTest` - Merchant can complete onboarding flow
|
||||||
|
- `runTaskListTest` - Merchant can complete onboarding task list
|
||||||
|
- `runInitialStoreSettingsTest` - Merchant can complete initial settings
|
||||||
|
|
||||||
|
### Merchant
|
||||||
|
|
||||||
|
- `runMerchantTests` - Run all merchant tests
|
||||||
|
- `runCreateCouponTest` - Merchant can create coupon
|
||||||
|
- `runCreateOrderTest` - Merchant can create order
|
||||||
|
- `runAddSimpleProductTest` - Merchant can create a simple product
|
||||||
|
- `runAddVariableProductTest` - Merchant can create a variable product
|
||||||
|
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
|
||||||
|
- `runProductSettingsTest` - Merchant can update product settings
|
||||||
|
- `runTaxSettingsTest` - Merchant can update tax settings
|
||||||
|
|
||||||
|
### Shopper
|
||||||
|
|
||||||
|
- `runShopperTests` - Run all shopper tests
|
||||||
|
- `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
|
||||||
|
|
||||||
|
## Contributing a new test
|
||||||
|
|
||||||
|
- In your branch create a new `example-test-name.test.js` under the `tests/e2e/core-tests/specs` folder.
|
||||||
|
- Jest does not allow its global functions to be accessed outside the jest environment. To allow the test code to be published in a package import any jest global functions used in your test
|
||||||
|
```js
|
||||||
|
const {
|
||||||
|
it,
|
||||||
|
describe,
|
||||||
|
beforeAll,
|
||||||
|
} = require( '@jest/globals' );
|
||||||
|
```
|
||||||
|
- Wrap your test in a function and export it
|
||||||
|
```js
|
||||||
|
const runExampleTestName = () => {
|
||||||
|
describe('Example test', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do some example action', async () => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = runExampleTestName;
|
||||||
|
```
|
||||||
|
- Add your test to `tests/e2e/core-tests/specs/index.js`
|
||||||
|
```js
|
||||||
|
const runExampleTestName = require( './grouping/example-test-name.test' );
|
||||||
|
// ...
|
||||||
|
module.exports = {
|
||||||
|
// ...
|
||||||
|
runExampleTestName,
|
||||||
|
}
|
||||||
|
```
|
|
@ -1,44 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
const {
|
const allSpecs = require( './specs' );
|
||||||
runActivationTest,
|
|
||||||
runOnboardingFlowTest,
|
|
||||||
runTaskListTest,
|
|
||||||
runInitialStoreSettingsTest,
|
|
||||||
runSetupOnboardingTests,
|
|
||||||
runCartPageTest,
|
|
||||||
runCheckoutPageTest,
|
|
||||||
runMyAccountPageTest,
|
|
||||||
runSingleProductPageTest,
|
|
||||||
runShopperTests,
|
|
||||||
runCreateCouponTest,
|
|
||||||
runCreateOrderTest,
|
|
||||||
runAddSimpleProductTest,
|
|
||||||
runAddVariableProductTest,
|
|
||||||
runUpdateGeneralSettingsTest,
|
|
||||||
runProductSettingsTest,
|
|
||||||
runTaxSettingsTest,
|
|
||||||
runMerchantTests,
|
|
||||||
} = require( './specs' );
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = allSpecs;
|
||||||
runActivationTest,
|
|
||||||
runOnboardingFlowTest,
|
|
||||||
runTaskListTest,
|
|
||||||
runInitialStoreSettingsTest,
|
|
||||||
runSetupOnboardingTests,
|
|
||||||
runCartPageTest,
|
|
||||||
runCheckoutPageTest,
|
|
||||||
runMyAccountPageTest,
|
|
||||||
runSingleProductPageTest,
|
|
||||||
runShopperTests,
|
|
||||||
runCreateCouponTest,
|
|
||||||
runCreateOrderTest,
|
|
||||||
runAddSimpleProductTest,
|
|
||||||
runAddVariableProductTest,
|
|
||||||
runUpdateGeneralSettingsTest,
|
|
||||||
runProductSettingsTest,
|
|
||||||
runTaxSettingsTest,
|
|
||||||
runMerchantTests,
|
|
||||||
};
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ const runCreateCouponTest = () => {
|
||||||
// Publish coupon, verify that it was published. Trash coupon, verify that it was trashed.
|
// Publish coupon, verify that it was published. Trash coupon, verify that it was trashed.
|
||||||
await verifyPublishAndTrash(
|
await verifyPublishAndTrash(
|
||||||
'#publish',
|
'#publish',
|
||||||
'#message',
|
'.notice',
|
||||||
'Coupon updated.',
|
'Coupon updated.',
|
||||||
'1 coupon moved to the Trash.'
|
'1 coupon moved to the Trash.'
|
||||||
);
|
);
|
||||||
|
|
|
@ -196,9 +196,8 @@ const runAddVariableProductTest = () => {
|
||||||
// Publish product, verify that it was published. Trash product, verify that it was trashed.
|
// Publish product, verify that it was published. Trash product, verify that it was trashed.
|
||||||
await verifyPublishAndTrash(
|
await verifyPublishAndTrash(
|
||||||
'#publish',
|
'#publish',
|
||||||
'.updated.notice',
|
'.notice',
|
||||||
'Product published.',
|
'Product published.',
|
||||||
'Move to Trash',
|
|
||||||
'1 product moved to the Trash.'
|
'1 product moved to the Trash.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,12 +23,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#0f3ec50",
|
"@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#0f3ec50",
|
||||||
"@jest/test-sequencer": "^25.5.4",
|
"@jest/test-sequencer": "^25.5.4",
|
||||||
"@wordpress/e2e-test-utils": "^4.6.0",
|
"@wordpress/e2e-test-utils": "^4.15.0",
|
||||||
"@wordpress/jest-preset-default": "^6.2.0",
|
"@wordpress/jest-preset-default": "^6.4.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"jest-puppeteer": "^4.4.0",
|
"jest-puppeteer": "^4.4.0"
|
||||||
"puppeteer": "^2.1.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.12.0",
|
"@babel/cli": "7.12.0",
|
||||||
|
@ -36,6 +35,11 @@
|
||||||
"@babel/polyfill": "7.11.5",
|
"@babel/polyfill": "7.11.5",
|
||||||
"@babel/preset-env": "7.12.0",
|
"@babel/preset-env": "7.12.0",
|
||||||
"@wordpress/eslint-plugin": "7.1.0",
|
"@wordpress/eslint-plugin": "7.1.0",
|
||||||
|
"@babel/cli": "^7.12.1",
|
||||||
|
"@babel/core": "^7.12.3",
|
||||||
|
"@babel/polyfill": "^7.12.1",
|
||||||
|
"@babel/preset-env": "^7.12.1",
|
||||||
|
"@wordpress/eslint-plugin": "^4.0.0",
|
||||||
"ndb": "^1.1.5",
|
"ndb": "^1.1.5",
|
||||||
"semver": "^7.3.2"
|
"semver": "^7.3.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { HTTPClientFactory } from '@woocommerce/api';
|
import { HTTPClientFactory } from '@woocommerce/api';
|
||||||
const config = require( 'config' );
|
const config = require( 'config' );
|
||||||
|
|
||||||
const httpClient = HTTPClientFactory.withBasicAuth(
|
const httpClient = HTTPClientFactory.build( config.get( 'url' ) )
|
||||||
config.get( 'url' ) + '/wp-json',
|
.withBasicAuth( config.get( 'users.admin.username' ), config.get( 'users.admin.password' ) )
|
||||||
config.get( 'users.admin.username' ),
|
.create();
|
||||||
config.get( 'users.admin.password' ),
|
|
||||||
);
|
|
||||||
|
|
||||||
import { simpleProductFactory } from './factories/simple-product';
|
import { simpleProductFactory } from './factories/simple-product';
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,8 @@ const verifyPublishAndTrash = async ( button, publishNotice, publishVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trash
|
// Trash
|
||||||
await expect( page ).toClick( 'a', { text: "Move to Trash" } );
|
await page.focus( 'a.submitdelete' );
|
||||||
|
await expect( page ).toClick( 'a.submitdelete' );
|
||||||
await page.waitForSelector( '#message' );
|
await page.waitForSelector( '#message' );
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
|
|
Loading…
Reference in New Issue