diff --git a/.github/workflows/build-release-zip-file.yml b/.github/workflows/build-release-zip-file.yml
new file mode 100644
index 00000000000..2f6dd819e20
--- /dev/null
+++ b/.github/workflows/build-release-zip-file.yml
@@ -0,0 +1,30 @@
+name: Build release zip file
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: 'By default the zip file is generated from the branch the workflow runs from, but you can specify an explicit reference to use instead here (e.g. refs/tags/tag_name). The resulting file will be available as an artifact on the workflow run.'
+ required: false
+ default: ''
+jobs:
+ build:
+ name: Build release zip file
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ ref: ${{ github.event.inputs.ref || github.ref }}
+ - name: Build the zip file
+ id: build
+ uses: woocommerce/action-build@v2
+ - name: Unzip the file (prevents double zip problem)
+ run: unzip ${{ steps.build.outputs.zip_path }} -d zipfile
+ - name: Upload the zip file as an artifact
+ uses: actions/upload-artifact@v2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ name: woocommerce
+ path: zipfile
+ retention-days: 7
diff --git a/.github/workflows/pull-request-post-merge-processing.yml b/.github/workflows/pull-request-post-merge-processing.yml
new file mode 100644
index 00000000000..1347ed5e5e1
--- /dev/null
+++ b/.github/workflows/pull-request-post-merge-processing.yml
@@ -0,0 +1,32 @@
+name: "Pull request post-merge processing"
+on:
+ pull_request:
+ types: [closed]
+
+jobs:
+ assign-milestone:
+ name: "Assign milestone to merged pull request"
+ if: github.event.pull_request.merged == true && ! github.event.pull_request.milestone
+ runs-on: ubuntu-latest
+ steps:
+ - name: "Get the milestone changer script"
+ run: |
+ curl \
+ --silent \
+ --fail \
+ --header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \
+ --header 'User-Agent: GitHub action to set the milestone for a pull request' \
+ --header 'Accept: application/vnd.github.v3.raw' \
+ --remote-name \
+ --location $GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/assign-milestone-to-merged-pr.php
+ env:
+ GITHUB_API_URL: ${{ env.GITHUB_API_URL }}
+ - name: "Install PHP"
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '7.4'
+ - name: "Run the milestone changer script"
+ run: php assign-milestone-to-merged-pr.php
+ env:
+ PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scripts/assign-milestone-to-merged-pr.php b/.github/workflows/scripts/assign-milestone-to-merged-pr.php
new file mode 100644
index 00000000000..0258813e209
--- /dev/null
+++ b/.github/workflows/scripts/assign-milestone-to-merged-pr.php
@@ -0,0 +1,144 @@
+ "$keyword { $body }" );
+ $context = stream_context_create(
+ array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'header' => array(
+ 'Accept: application/json',
+ 'Content-Type: application/json',
+ 'User-Agent: GitHub action to set the milestone for a pull request',
+ 'Authorization: bearer ' . $github_token,
+ ),
+ 'content' => json_encode( $data ),
+ ),
+ )
+ );
+
+ $result = file_get_contents( $graphql_api_url, false, $context );
+ return json_decode( $result, true );
+}
+
+// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions
diff --git a/changelog.txt b/changelog.txt
index 4cfdac0fc66..4b636018665 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,11 @@
== Changelog ==
+= 5.2.2 2021-04-15 =
+
+**WooCommerce**
+
+* Fix - Can't grant permission for download from order details page. #29691
+
= 5.2.1 2021-04-14 =
**WooCommerce**
diff --git a/composer.json b/composer.json
index da6ef18a12f..44863a80836 100644
--- a/composer.json
+++ b/composer.json
@@ -21,8 +21,8 @@
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.1.6",
- "woocommerce/woocommerce-admin": "2.1.5",
- "woocommerce/woocommerce-blocks": "4.7.2"
+ "woocommerce/woocommerce-admin": "2.2.1",
+ "woocommerce/woocommerce-blocks": "4.9.1"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4"
diff --git a/composer.lock b/composer.lock
index 874d83c7834..e4fe95fe063 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": "aaad3b20adf49ba997d4be94865087c6",
+ "content-hash": "11158f53934e897bd84be190a184ec87",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@@ -51,9 +51,6 @@
"GPL-2.0-or-later"
],
"description": "Creates a custom autoloader for a plugin or theme.",
- "support": {
- "source": "https://github.com/Automattic/jetpack-autoloader/tree/2.10.1"
- },
"time": "2021-03-30T15:15:59+00:00"
},
{
@@ -85,9 +82,6 @@
"GPL-2.0-or-later"
],
"description": "A wrapper for defining constants in a more testable way.",
- "support": {
- "source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
- },
"time": "2020-10-28T19:00:31+00:00"
},
{
@@ -218,10 +212,6 @@
"zend",
"zikula"
],
- "support": {
- "issues": "https://github.com/composer/installers/issues",
- "source": "https://github.com/composer/installers/tree/v1.10.0"
- },
"funding": [
{
"url": "https://packagist.com",
@@ -296,10 +286,6 @@
"geolocation",
"maxmind"
],
- "support": {
- "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
- "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
- },
"time": "2019-12-19T22:59:03+00:00"
},
{
@@ -374,10 +360,6 @@
"email",
"pre-processing"
],
- "support": {
- "issues": "https://github.com/MyIntervals/emogrifier/issues",
- "source": "https://github.com/MyIntervals/emogrifier"
- },
"time": "2019-12-26T19:37:31+00:00"
},
{
@@ -427,10 +409,6 @@
"container-interop",
"psr"
],
- "support": {
- "issues": "https://github.com/php-fig/container/issues",
- "source": "https://github.com/php-fig/container/tree/master"
- },
"time": "2017-02-14T16:28:37+00:00"
},
{
@@ -484,9 +462,6 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "support": {
- "source": "https://github.com/symfony/css-selector/tree/master"
- },
"time": "2017-05-01T15:01:29+00:00"
},
{
@@ -522,24 +497,20 @@
],
"description": "Action Scheduler for WordPress and WooCommerce",
"homepage": "https://actionscheduler.org/",
- "support": {
- "issues": "https://github.com/woocommerce/action-scheduler/issues",
- "source": "https://github.com/woocommerce/action-scheduler/tree/master"
- },
"time": "2020-05-12T16:22:33+00:00"
},
{
"name": "woocommerce/woocommerce-admin",
- "version": "2.1.5",
+ "version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
- "reference": "71c233d8463f551430edbe1877e51d19a4bb2df6"
+ "reference": "78cc9c5ef7de5be5bd0f9208483e5ae97422be9a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/71c233d8463f551430edbe1877e51d19a4bb2df6",
- "reference": "71c233d8463f551430edbe1877e51d19a4bb2df6",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/78cc9c5ef7de5be5bd0f9208483e5ae97422be9a",
+ "reference": "78cc9c5ef7de5be5bd0f9208483e5ae97422be9a",
"shasum": ""
},
"require": {
@@ -571,24 +542,20 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"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.1.5"
- },
- "time": "2021-04-02T16:48:38+00:00"
+ "time": "2021-04-02T19:30:03+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
- "version": "v4.7.2",
+ "version": "v4.9.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
- "reference": "942e58553b1a299ad04842e7f0d7465d9e029ac3"
+ "reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/942e58553b1a299ad04842e7f0d7465d9e029ac3",
- "reference": "942e58553b1a299ad04842e7f0d7465d9e029ac3",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce",
+ "reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce",
"shasum": ""
},
"require": {
@@ -624,9 +591,9 @@
],
"support": {
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
- "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.7.2"
+ "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.9.1"
},
- "time": "2021-04-13T16:06:16+00:00"
+ "time": "2021-04-13T16:11:16+00:00"
}
],
"packages-dev": [
@@ -674,10 +641,6 @@
"isolation",
"tool"
],
- "support": {
- "issues": "https://github.com/bamarni/composer-bin-plugin/issues",
- "source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
- },
"time": "2020-05-03T08:27:20+00:00"
}
],
@@ -693,5 +656,5 @@
"platform-overrides": {
"php": "7.0"
},
- "plugin-api-version": "2.0.0"
+ "plugin-api-version": "1.1.0"
}
diff --git a/includes/admin/class-wc-admin-dashboard-setup.php b/includes/admin/class-wc-admin-dashboard-setup.php
index 580bdc00b2e..4360241b37f 100644
--- a/includes/admin/class-wc-admin-dashboard-setup.php
+++ b/includes/admin/class-wc-admin-dashboard-setup.php
@@ -172,7 +172,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
*/
private function populate_payment_tasks() {
$is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
- $country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0];
+ $country = explode( ':', get_option( 'woocommerce_default_country', 'US:CA' ) )[0];
// woocommerce-payments requires its plugin activated and country must be US.
if ( ! $is_woo_payment_installed || 'US' !== $country ) {
diff --git a/includes/admin/class-wc-admin-post-types.php b/includes/admin/class-wc-admin-post-types.php
index e2d145206a1..2eaef024a14 100644
--- a/includes/admin/class-wc-admin-post-types.php
+++ b/includes/admin/class-wc-admin-post-types.php
@@ -430,7 +430,10 @@ class WC_Admin_Post_Types {
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$product->set_manage_stock( $manage_stock );
- $product->set_backorders( $backorders );
+
+ if ( 'external' !== $product->get_type() ) {
+ $product->set_backorders( $backorders );
+ }
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
@@ -550,7 +553,10 @@ class WC_Admin_Post_Types {
$stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
$product->set_manage_stock( $manage_stock );
- $product->set_backorders( $backorders );
+
+ if ( 'external' !== $product->get_type() ) {
+ $product->set_backorders( $backorders );
+ }
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
$change_stock = absint( $request_data['change_stock'] );
diff --git a/includes/admin/class-wc-admin-setup-wizard.php b/includes/admin/class-wc-admin-setup-wizard.php
index 14c11081d8c..255776aa251 100644
--- a/includes/admin/class-wc-admin-setup-wizard.php
+++ b/includes/admin/class-wc-admin-setup-wizard.php
@@ -481,7 +481,7 @@ class WC_Admin_Setup_Wizard {
$state = WC()->countries->get_base_state();
$country = WC()->countries->get_base_country();
$postcode = WC()->countries->get_base_postcode();
- $currency = get_option( 'woocommerce_currency', 'GBP' );
+ $currency = get_option( 'woocommerce_currency', 'USD' );
$product_type = get_option( 'woocommerce_product_type', 'both' );
$sell_in_person = get_option( 'woocommerce_sell_in_person', 'none_selected' );
diff --git a/includes/admin/class-wc-admin-taxonomies.php b/includes/admin/class-wc-admin-taxonomies.php
index 82651dbc65d..fd6d297dfa1 100644
--- a/includes/admin/class-wc-admin-taxonomies.php
+++ b/includes/admin/class-wc-admin-taxonomies.php
@@ -11,6 +11,8 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
+use Automattic\WooCommerce\Internal\AssignDefaultCategory;
+
/**
* WC_Admin_Taxonomies class.
*/
@@ -49,6 +51,12 @@ class WC_Admin_Taxonomies {
// Category/term ordering.
add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
+ add_action(
+ 'delete_product_cat',
+ function() {
+ wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
+ }
+ );
// Add form.
add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
@@ -91,7 +99,7 @@ class WC_Admin_Taxonomies {
* @param string $taxonomy Taxonomy slug.
*/
public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) {
- if ( 'product_cat' != $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
+ if ( 'product_cat' !== $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
return;
}
diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php
index f5b1f75a0fd..0543e42631e 100644
--- a/includes/admin/settings/class-wc-settings-emails.php
+++ b/includes/admin/settings/class-wc-settings-emails.php
@@ -46,12 +46,19 @@ class WC_Settings_Emails extends WC_Settings_Page {
* @return array
*/
public function get_settings() {
+ $desc_help_text = sprintf(
+ /* translators: %1$s: Link to WP Mail Logging plugin, %2$s: Link to Email FAQ support page. */
+ __( 'To ensure your store’s notifications arrive in your and your customers’ inboxes, we recommend connecting your email address to your domain and setting up a dedicated SMTP server. If something doesn’t seem to be sending correctly, install the WP Mail Logging Plugin or check the Email FAQ page.', 'woocommerce' ),
+ 'https://wordpress.org/plugins/wp-mail-logging/',
+ 'https://docs.woocommerce.com/document/email-faq'
+ );
$settings = apply_filters(
'woocommerce_email_settings',
array(
array(
'title' => __( 'Email notifications', 'woocommerce' ),
- 'desc' => __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.', 'woocommerce' ),
+ /* translators: %s: help description with link to WP Mail logging and support page. */
+ 'desc' => sprintf( __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.
%s', 'woocommerce' ), $desc_help_text ),
'type' => 'title',
'id' => 'email_notification_settings',
),
diff --git a/includes/admin/settings/class-wc-settings-general.php b/includes/admin/settings/class-wc-settings-general.php
index 8535dd7f956..1476c428c0c 100644
--- a/includes/admin/settings/class-wc-settings-general.php
+++ b/includes/admin/settings/class-wc-settings-general.php
@@ -81,7 +81,7 @@ class WC_Settings_General extends WC_Settings_Page {
'title' => __( 'Country / State', 'woocommerce' ),
'desc' => __( 'The country and state or province, if any, in which your business is located.', 'woocommerce' ),
'id' => 'woocommerce_default_country',
- 'default' => 'GB',
+ 'default' => 'US:CA',
'type' => 'single_select_country',
'desc_tip' => true,
),
@@ -229,7 +229,7 @@ class WC_Settings_General extends WC_Settings_Page {
'title' => __( 'Currency', 'woocommerce' ),
'desc' => __( 'This controls what currency prices are listed at in the catalog and which currency gateways will take payments in.', 'woocommerce' ),
'id' => 'woocommerce_currency',
- 'default' => 'GBP',
+ 'default' => 'USD',
'type' => 'select',
'class' => 'wc-enhanced-select',
'desc_tip' => true,
diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php
index be2df455f7d..8c74c11fd5b 100644
--- a/includes/class-woocommerce.php
+++ b/includes/class-woocommerce.php
@@ -9,6 +9,7 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
+use Automattic\WooCommerce\Internal\AssignDefaultCategory;
use Automattic\WooCommerce\Proxies\LegacyProxy;
/**
@@ -207,6 +208,7 @@ final class WooCommerce {
// These classes set up hooks on instantiation.
wc_get_container()->get( DownloadPermissionsAdjuster::class );
+ wc_get_container()->get( AssignDefaultCategory::class );
}
/**
diff --git a/includes/customizer/class-wc-shop-customizer.php b/includes/customizer/class-wc-shop-customizer.php
index 2d633188612..f17cac7dfea 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\Internal\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',
@@ -769,7 +780,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/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php
index f434ef60b00..40423decb89 100644
--- a/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php
+++ b/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php
@@ -10,6 +10,8 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
+use Automattic\WooCommerce\Internal\AssignDefaultCategory;
+
/**
* Terms controller class.
*/
@@ -563,6 +565,9 @@ abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
+ // Schedule action to assign default category.
+ wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
+
/**
* Fires after a single term is deleted via the REST API.
*
diff --git a/includes/theme-support/class-wc-twenty-eleven.php b/includes/theme-support/class-wc-twenty-eleven.php
index fb06a7a84dc..db598f78907 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\Internal\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..a3a194c7579 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\Internal\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..b8f5cf09a6b 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\Internal\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 47c1aff660c..5419ade5b0a 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\Internal\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,
@@ -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/theme-support/class-wc-twenty-seventeen.php b/includes/theme-support/class-wc-twenty-seventeen.php
index 2093d2200e7..661121063a5 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\Internal\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..f78fbb634f5 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\Internal\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..4988aea16a4 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\Internal\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..527fa176ee0 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\Internal\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..3815ac4c26d 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\Internal\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-one.php b/includes/theme-support/class-wc-twenty-twenty-one.php
index 6b568ae0e2d..7707c05ecd5 100644
--- a/includes/theme-support/class-wc-twenty-twenty-one.php
+++ b/includes/theme-support/class-wc-twenty-twenty-one.php
@@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Internal\ThemeSupport;
defined( 'ABSPATH' ) || exit;
@@ -37,8 +38,7 @@ class WC_Twenty_Twenty_One {
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/theme-support/class-wc-twenty-twenty.php b/includes/theme-support/class-wc-twenty-twenty.php
index 47296f639ab..862376f9eda 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\Internal\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 6564d77f698..63ff71103cc 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -9,6 +9,7 @@
*/
use Automattic\Jetpack\Constants;
+use Automattic\WooCommerce\Internal\ThemeSupport;
use Automattic\WooCommerce\Utilities\NumberUtil;
if ( ! defined( 'ABSPATH' ) ) {
@@ -390,7 +391,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,
@@ -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 );
}
/**
@@ -1270,7 +1240,7 @@ function wc_format_country_state_string( $country_string ) {
* @return array
*/
function wc_get_base_location() {
- $default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
+ $default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country', 'US:CA' ) );
return wc_format_country_state_string( $default );
}
@@ -1286,7 +1256,7 @@ function wc_get_base_location() {
*/
function wc_get_customer_default_location() {
$set_default_location_to = get_option( 'woocommerce_default_customer_address', 'base' );
- $default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', '' );
+ $default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', 'US:CA' );
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', $default_location ) );
// Geolocation takes priority if used and if geolocation is possible.
@@ -2269,9 +2239,9 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
}
- $new_version = wc_clean( $plugin->new_version );
- $plugin_updates = new WC_Plugin_Updates();
- $version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
+ $new_version = wc_clean( $plugin->new_version );
+ $plugin_updates = new WC_Plugin_Updates();
+ $version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
if ( ! is_string( $version_type ) ) {
$version_type = 'none';
}
diff --git a/includes/wc-stock-functions.php b/includes/wc-stock-functions.php
index 70b268836d5..f8fc3d557ff 100644
--- a/includes/wc-stock-functions.php
+++ b/includes/wc-stock-functions.php
@@ -403,7 +403,7 @@ function wc_get_low_stock_amount( WC_Product $product ) {
$low_stock_amount = $product->get_low_stock_amount();
if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) {
- $product = wc_get_product( $product->get_parent_id() );
+ $product = wc_get_product( $product->get_parent_id() );
$low_stock_amount = $product->get_low_stock_amount();
}
@@ -411,5 +411,5 @@ function wc_get_low_stock_amount( WC_Product $product ) {
$low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 );
}
- return $low_stock_amount;
+ return (int) $low_stock_amount;
}
diff --git a/includes/wc-term-functions.php b/includes/wc-term-functions.php
index c6fd42ab966..163a1e476c8 100644
--- a/includes/wc-term-functions.php
+++ b/includes/wc-term-functions.php
@@ -486,7 +486,8 @@ function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_
$term_query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ',', array_map( 'absint', $terms_to_count ) ) . ' ) ) AS include_join ON include_join.object_id = p.ID';
// Get the count.
- $count = $wpdb->get_var( implode( ' ', $term_query ) ); // WPCS: unprepared SQL ok.
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ $count = $wpdb->get_var( implode( ' ', $term_query ) );
// Update the count.
update_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );
diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php
index 439095d5482..afefd264c5a 100644
--- a/includes/wc-update-functions.php
+++ b/includes/wc-update-functions.php
@@ -10,6 +10,8 @@
defined( 'ABSPATH' ) || exit;
+use Automattic\WooCommerce\Internal\AssignDefaultCategory;
+
/**
* Update file paths for 2.0
*
@@ -1567,31 +1569,12 @@ function wc_update_330_webhooks() {
* Assign default cat to all products with no cats.
*/
function wc_update_330_set_default_product_cat() {
- global $wpdb;
-
- $default_category = get_option( 'default_product_cat', 0 );
-
- if ( $default_category ) {
- $wpdb->query(
- $wpdb->prepare(
- "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
- SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
- LEFT JOIN
- (
- SELECT object_id FROM {$wpdb->term_relationships} term_relationships
- LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
- WHERE term_taxonomy.taxonomy = 'product_cat'
- ) AS tax_query
- ON posts.ID = tax_query.object_id
- WHERE posts.post_type = 'product'
- AND tax_query.object_id IS NULL",
- $default_category
- )
- );
- wp_cache_flush();
- delete_transient( 'wc_term_counts' );
- wp_update_term_count_now( array( $default_category ), 'product_cat' );
- }
+ /*
+ * When a product category is deleted, we need to check
+ * if the product has no categories assigned. Then assign
+ * it a default category.
+ */
+ wc_get_container()->get( AssignDefaultCategory::class )->maybe_assign_default_product_cat();
}
/**
diff --git a/package-lock.json b/package-lock.json
index 2ec65648a08..9358ea03b15 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "woocommerce",
- "version": "5.1.0",
+ "version": "5.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/readme.txt b/readme.txt
index 76ce7b54471..6c7b73bf3ba 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.5
Tested up to: 5.7
Requires PHP: 7.0
-Stable tag: 5.2.1
+Stable tag: 5.2.2
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -160,6 +160,12 @@ WooCommerce comes with some sample data you can use to see how products look; im
== Changelog ==
+= 5.2.2 2021-04-15 =
+
+**WooCommerce**
+
+* Fix - Can't grant permission for download from order details page. #29691
+
= 5.2.1 2021-04-14 =
**WooCommerce**
diff --git a/src/Container.php b/src/Container.php
index 517239afeb7..a9291a6de5a 100644
--- a/src/Container.php
+++ b/src/Container.php
@@ -7,12 +7,14 @@ namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
+use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
+use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ThemeManagementServiceProvider;
/**
* 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
@@ -34,7 +36,9 @@ final class Container implements \Psr\Container\ContainerInterface {
*/
private $service_providers = array(
ProxiesServiceProvider::class,
+ ThemeManagementServiceProvider::class,
DownloadPermissionsAdjusterServiceProvider::class,
+ AssignDefaultCategoryServiceProvider::class,
);
/**
diff --git a/src/Internal/AssignDefaultCategory.php b/src/Internal/AssignDefaultCategory.php
new file mode 100644
index 00000000000..34968e974ee
--- /dev/null
+++ b/src/Internal/AssignDefaultCategory.php
@@ -0,0 +1,73 @@
+queue()->schedule_single(
+ time(),
+ 'wc_schedule_update_product_default_cat',
+ array(),
+ 'wc_update_product_default_cat'
+ );
+ }
+
+ /**
+ * Assigns default product category for products
+ * that have no categories.
+ *
+ * @return void
+ */
+ public function maybe_assign_default_product_cat() {
+ global $wpdb;
+
+ $default_category = get_option( 'default_product_cat', 0 );
+
+ if ( $default_category ) {
+ $wpdb->query(
+ $wpdb->prepare(
+ "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
+ SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
+ LEFT JOIN
+ (
+ SELECT object_id FROM {$wpdb->term_relationships} term_relationships
+ LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
+ WHERE term_taxonomy.taxonomy = 'product_cat'
+ ) AS tax_query
+ ON posts.ID = tax_query.object_id
+ WHERE posts.post_type = 'product'
+ AND tax_query.object_id IS NULL",
+ $default_category
+ )
+ );
+ wp_cache_flush();
+ delete_transient( 'wc_term_counts' );
+ wp_update_term_count_now( array( $default_category ), 'product_cat' );
+ }
+ }
+}
diff --git a/src/Internal/DependencyManagement/ServiceProviders/AssignDefaultCategoryServiceProvider.php b/src/Internal/DependencyManagement/ServiceProviders/AssignDefaultCategoryServiceProvider.php
new file mode 100644
index 00000000000..cba9e812aab
--- /dev/null
+++ b/src/Internal/DependencyManagement/ServiceProviders/AssignDefaultCategoryServiceProvider.php
@@ -0,0 +1,31 @@
+share( AssignDefaultCategory::class );
+ }
+}
diff --git a/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php b/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php
new file mode 100644
index 00000000000..7216b04271b
--- /dev/null
+++ b/src/Internal/DependencyManagement/ServiceProviders/ThemeManagementServiceProvider.php
@@ -0,0 +1,31 @@
+share_with_auto_arguments( ThemeSupport::class );
+ }
+}
diff --git a/src/Internal/ThemeSupport.php b/src/Internal/ThemeSupport.php
new file mode 100644
index 00000000000..46e627eb137
--- /dev/null
+++ b/src/Internal/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/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md
index 789e7afc3fb..7dc328942d6 100644
--- a/tests/e2e/core-tests/CHANGELOG.md
+++ b/tests/e2e/core-tests/CHANGELOG.md
@@ -3,6 +3,7 @@
## Added
- Support for re-running setup and shopper tests
+- Shopper Order Email Receiving
## Fixed
@@ -14,6 +15,7 @@
- Shopper Checkout Login Account
- Shopper My Account Create Account
+- Shopper Cart Calculate Shipping
- Shopper Cart Redirection
## Fixed
diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md
index ef2cd6a45be..fc3956293a6 100644
--- a/tests/e2e/core-tests/README.md
+++ b/tests/e2e/core-tests/README.md
@@ -85,7 +85,9 @@ The functions to access the core tests are:
- `runCheckoutCreateAccountTest` - Shopper can create an account during checkout
- `runCheckoutLoginAccountTest` - Shopper can login to an account during checkout
- `runMyAccountCreateAccountTest` - Shopper can create an account via my account page
+ - `runCartCalculateShippingTest` - Shopper can calculate shipping in the cart
- `runCartRedirectionTest` - Shopper is redirected to the cart page after adding to cart
+ - `runOrderEmailReceivingTest` - Shopper can receive an email for his order
### REST API
diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js
index 8ffcff1742c..4380bb79d4f 100644
--- a/tests/e2e/core-tests/specs/index.js
+++ b/tests/e2e/core-tests/specs/index.js
@@ -21,7 +21,9 @@ const runSingleProductPageTest = require( './shopper/front-end-single-product.te
const runVariableProductUpdateTest = require( './shopper/front-end-variable-product-updates.test' );
const runCheckoutCreateAccountTest = require( './shopper/front-end-checkout-create-account.test' );
const runCheckoutLoginAccountTest = require( './shopper/front-end-checkout-login-account.test' );
+const runCartCalculateShippingTest = require( './shopper/front-end-cart-calculate-shipping.test' );
const runCartRedirectionTest = require( './shopper/front-end-cart-redirection.test' );
+const runOrderEmailReceivingTest = require( './shopper/front-end-order-email-receiving.test' );
// Merchant tests
const runAddNewShippingZoneTest = require ( './merchant/wp-admin-settings-shipping-zones.test' );
@@ -70,7 +72,9 @@ const runShopperTests = () => {
runVariableProductUpdateTest();
runCheckoutCreateAccountTest();
runCheckoutLoginAccountTest();
+ runCartCalculateShippingTest();
runCartRedirectionTest();
+ runOrderEmailReceivingTest();
};
const runMerchantTests = () => {
@@ -144,8 +148,10 @@ module.exports = {
runAddShippingClassesTest,
runAnalyticsPageLoadsTest,
runCheckoutCreateAccountTest,
- runImportProductsTest,
+ runImportProductsTest,
runCheckoutLoginAccountTest,
+ runCartCalculateShippingTest,
runCartRedirectionTest,
runMyAccountCreateAccountTest,
+ runOrderEmailReceivingTest,
};
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 e7da3ffe377..c6bae31d94f 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,10 +10,18 @@ const {
addShippingZoneAndMethod,
clearAndFillInput,
selectOptionInSelect2,
- evalAndClick,
- uiUnblocked,
+ deleteAllShippingZones,
} = require( '@woocommerce/e2e-utils' );
+/**
+ * External dependencies
+ */
+const {
+ it,
+ describe,
+ beforeAll,
+} = require( '@jest/globals' );
+
const config = require( 'config' );
const simpleProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
const simpleProductName = config.get( 'products.simple.name' );
@@ -28,27 +36,7 @@ const runAddNewShippingZoneTest = () => {
beforeAll(async () => {
await merchant.login();
await createSimpleProduct();
- await merchant.openSettings('shipping');
-
- // Delete existing shipping zones.
- try {
- let zone = await page.$( '.wc-shipping-zone-delete' );
- if ( zone ) {
- // WP action links aren't clickable because they are hidden with a left=-9999 style.
- await page.evaluate(() => {
- document.querySelector('.wc-shipping-zone-name .row-actions')
- .style
- .left = '0';
- });
- while ( zone ) {
- await evalAndClick( '.wc-shipping-zone-delete' );
- await uiUnblocked();
- zone = await page.$( '.wc-shipping-zone-delete' );
- }
- }
- } catch (error) {
- // Prevent an error here causing the test to fail.
- }
+ await deleteAllShippingZones();
});
it('add shipping zone for San Francisco with free Local pickup', async () => {
diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-calculate-shipping.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-calculate-shipping.test.js
new file mode 100644
index 00000000000..7bdccb60e00
--- /dev/null
+++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-calculate-shipping.test.js
@@ -0,0 +1,136 @@
+/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
+
+/**
+ * Internal dependencies
+ */
+const {
+ shopper,
+ merchant,
+ createSimpleProduct,
+ addShippingZoneAndMethod,
+ clearAndFillInput,
+ uiUnblocked,
+ selectOptionInSelect2,
+} = require( '@woocommerce/e2e-utils' );
+
+/**
+ * External dependencies
+ */
+const {
+ it,
+ describe,
+ beforeAll,
+} = require( '@jest/globals' );
+
+const config = require( 'config' );
+const firstProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
+const secondProductPrice = '4.99';
+const fourProductPrice = firstProductPrice * 4;
+var twoProductsPrice = (+firstProductPrice) + (+secondProductPrice);
+var firstProductPriceWithFlatRate = (+firstProductPrice) + (+5);
+var fourProductPriceWithFlatRate = (+fourProductPrice) + (+5);
+var twoProductsPriceWithFlatRate = (+twoProductsPrice) + (+5);
+const firstProductName = 'First Product';
+const secondProductName = 'Second Product';
+const shippingZoneNameDE = 'Germany Free Shipping';
+const shippingCountryDE = 'country:DE';
+const shippingZoneNameFR = 'France Flat Local';
+const shippingCountryFR = 'country:FR';
+
+const runCartCalculateShippingTest = () => {
+ describe('Cart Calculate Shipping', () => {
+ beforeAll(async () => {
+ await merchant.login();
+ await createSimpleProduct(firstProductName);
+ await createSimpleProduct(secondProductName, secondProductPrice);
+ await merchant.openNewShipping();
+
+ // Add a new shipping zone Germany with Free shipping
+ await addShippingZoneAndMethod(shippingZoneNameDE, shippingCountryDE, ' ', 'free_shipping');
+
+ // Add a new shipping zone for France with Flat rate & Local pickup
+ await addShippingZoneAndMethod(shippingZoneNameFR, shippingCountryFR, ' ', 'flat_rate');
+ await page.waitFor(1000); // to avoid flakiness in headless
+ await page.click('a.wc-shipping-zone-method-settings', {text: 'Flat rate'});
+ await clearAndFillInput('#woocommerce_flat_rate_cost', '5');
+ await page.click('.wc-backbone-modal-main button#btn-ok');
+ // Add additional method Local pickup for the same location
+ await page.waitFor(1000); // to avoid flakiness in headless
+ await page.click('button.wc-shipping-zone-add-method', {text:'Add shipping method'});
+ await page.waitForSelector('.wc-shipping-zone-method-selector');
+ await page.select('select[name="add_method_id"]', 'local_pickup');
+ await page.click('button#btn-ok');
+ await page.waitForSelector('#zone_locations');
+ await merchant.logout();
+ await shopper.emptyCart();
+ });
+
+ it('allows customer to calculate Free Shipping if in Germany', async () => {
+ await shopper.goToShop();
+ await shopper.addToCartFromShopPage(firstProductName);
+ await shopper.goToCart();
+
+ // Set shipping country to Germany
+ await expect(page).toClick('a.shipping-calculator-button');
+ await expect(page).toClick('#select2-calc_shipping_country-container');
+ await selectOptionInSelect2('Germany');
+ await expect(page).toClick('button[name="calc_shipping"]');
+
+ // Verify shipping 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: `$${firstProductPrice}`});
+ });
+
+ it('allows customer to calculate Flat rate and Local pickup if in France', async () => {
+ await page.reload();
+
+ // Set shipping country to France
+ await expect(page).toClick('a.shipping-calculator-button');
+ await expect(page).toClick('#select2-calc_shipping_country-container');
+ await selectOptionInSelect2('France');
+ await expect(page).toClick('button[name="calc_shipping"]');
+
+ // Verify shipping costs
+ await page.waitForSelector('.order-total');
+ await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
+ await expect(page).toMatchElement('.order-total .amount', {text: `$${firstProductPriceWithFlatRate}`});
+ });
+
+ it('should show correct total cart price after updating quantity', async () => {
+ await shopper.setCartQuantity(firstProductName, 4);
+ await expect(page).toClick('button', {text: 'Update cart'});
+ await uiUnblocked();
+ await expect(page).toMatchElement('.order-total .amount', {text: `$${fourProductPriceWithFlatRate}`});
+ });
+
+ it('should show correct total cart price with 2 products and flat rate', async () => {
+ await shopper.goToShop();
+ await shopper.addToCartFromShopPage(secondProductName);
+ await shopper.goToCart();
+
+ await shopper.setCartQuantity(firstProductName, 1);
+ await expect(page).toClick('button', {text: 'Update cart'});
+ await uiUnblocked();
+ await page.waitForSelector('.order-total');
+ await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
+ await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPriceWithFlatRate}`});
+ });
+
+ it('should show correct total cart price with 2 products without flat rate', async () => {
+ await page.reload();
+
+ // Set shipping country to Spain
+ await expect(page).toClick('a.shipping-calculator-button');
+ await expect(page).toClick('#select2-calc_shipping_country-container');
+ await selectOptionInSelect2('Spain');
+ await expect(page).toClick('button[name="calc_shipping"]');
+
+ // Verify shipping costs
+ await page.waitForSelector('.order-total');
+ await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPrice}`});
+ });
+ });
+};
+
+module.exports = runCartCalculateShippingTest;
diff --git a/tests/e2e/core-tests/specs/shopper/front-end-order-email-receiving.test.js b/tests/e2e/core-tests/specs/shopper/front-end-order-email-receiving.test.js
new file mode 100644
index 00000000000..da466cae6de
--- /dev/null
+++ b/tests/e2e/core-tests/specs/shopper/front-end-order-email-receiving.test.js
@@ -0,0 +1,59 @@
+/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
+/**
+ * Internal dependencies
+ */
+ const {
+ shopper,
+ merchant,
+ createSimpleProduct,
+ uiUnblocked,
+ deleteAllEmailLogs,
+} = require( '@woocommerce/e2e-utils' );
+
+let simplePostIdValue;
+let orderId;
+const config = require( 'config' );
+const simpleProductName = config.get( 'products.simple.name' );
+const customerEmail = config.get( 'addresses.customer.billing.email' );
+const storeName = 'WooCommerce Core E2E Test Suite';
+
+/**
+ * External dependencies
+ */
+const {
+ it,
+ describe,
+ beforeAll,
+} = require( '@jest/globals' );
+
+const runOrderEmailReceivingTest = () => {
+ describe('Shopper Order Email Receiving', () => {
+ beforeAll(async () => {
+ await merchant.login();
+ await deleteAllEmailLogs();
+ simplePostIdValue = await createSimpleProduct();
+ await merchant.logout();
+ });
+
+ it('should receive order email after purchasing an item', async () => {
+ await shopper.login();
+
+ // Go to the shop and purchase an item
+ await shopper.goToProduct(simplePostIdValue);
+ await shopper.addToCart(simpleProductName);
+ await shopper.goToCheckout();
+ await uiUnblocked();
+ await shopper.placeOrder();
+ // Get order ID from the order received html element on the page
+ orderId = await page.$$eval(".woocommerce-order-overview__order strong", elements => elements.map(item => item.textContent));
+
+ // Verify the new order email has been received
+ await merchant.login();
+ await merchant.openEmailLog();
+ await expect( page ).toMatchElement( '.column-receiver', { text: customerEmail } );
+ await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } );
+ });
+ });
+};
+
+module.exports = runOrderEmailReceivingTest;
diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md
index fa331747ea3..23718b7cd8d 100644
--- a/tests/e2e/env/CHANGELOG.md
+++ b/tests/e2e/env/CHANGELOG.md
@@ -2,6 +2,7 @@
## Added
+- `takeScreenshotFor` utility function to take screenshots within tests
- `toBeInRange` expect numeric range matcher
# 0.2.1
diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md
index 3856d5d6dfe..c79723412d2 100644
--- a/tests/e2e/env/README.md
+++ b/tests/e2e/env/README.md
@@ -79,6 +79,12 @@ The test sequencer provides a screenshot function for test failures. To enable s
WC_E2E_SCREENSHOTS=1 npx wc-e2e test:e2e
```
+To take adhoc in test screenshots use
+
+```js
+await takeScreenshotFor( 'name of current step' );
+```
+
Screenshots will be saved to `tests/e2e/screenshots`. This folder is cleared at the beginning of each test run.
### Jest Puppeteer Config
diff --git a/tests/e2e/env/package-lock.json b/tests/e2e/env/package-lock.json
index 0b7e8c5a069..2766327b895 100644
--- a/tests/e2e/env/package-lock.json
+++ b/tests/e2e/env/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@woocommerce/e2e-environment",
- "version": "0.2.0",
+ "version": "0.2.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/tests/e2e/env/src/setup/jest.failure.js b/tests/e2e/env/src/setup/jest.failure.js
index 31a906a10ee..7eb0947a784 100644
--- a/tests/e2e/env/src/setup/jest.failure.js
+++ b/tests/e2e/env/src/setup/jest.failure.js
@@ -4,10 +4,8 @@ import {
sendFailedTestMessageToSlack,
} from '../slack';
-const path = require( 'path' );
-const mkdirp = require( 'mkdirp' );
import { bind } from 'jest-each';
-const { getAppRoot } = require( '../../utils' );
+const { takeScreenshotFor } = require( '../../utils' );
/**
* Override the test case method so we can take screenshots of assertion failures.
@@ -88,22 +86,11 @@ const screenshotTest = async ( testName, callback ) => {
try {
await callback();
} catch ( e ) {
- const testTitle = 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 );
+ const { title, filePath } = await takeScreenshotFor( testName );
+ await sendFailedTestMessageToSlack( title );
+ if ( filePath ) {
+ await sendFailedTestScreenshotToSlack( filePath );
+ }
throw ( e );
}
diff --git a/tests/e2e/env/utils/index.js b/tests/e2e/env/utils/index.js
index ce89ae395f7..bb6331f46bb 100644
--- a/tests/e2e/env/utils/index.js
+++ b/tests/e2e/env/utils/index.js
@@ -1,6 +1,7 @@
const getAppRoot = require( './app-root' );
const { getAppName, getAppBase } = require( './app-name' );
const { getTestConfig, getAdminConfig } = require( './test-config' );
+const takeScreenshotFor = require( './take-screenshot' );
module.exports = {
getAppBase,
@@ -8,4 +9,5 @@ module.exports = {
getAppName,
getTestConfig,
getAdminConfig,
+ takeScreenshotFor,
};
diff --git a/tests/e2e/env/utils/take-screenshot.js b/tests/e2e/env/utils/take-screenshot.js
new file mode 100644
index 00000000000..634da95dfab
--- /dev/null
+++ b/tests/e2e/env/utils/take-screenshot.js
@@ -0,0 +1,37 @@
+const path = require( 'path' );
+const mkdirp = require( 'mkdirp' );
+const getAppRoot = require( './app-root' );
+
+/**
+ * Take a screenshot if browser context exists.
+ * @param message
+ * @returns {Promise<{filePath: string, title: string}|{filePath: *, title: *}>}
+ */
+const takeScreenshotFor = async ( message ) => {
+ const title = message.replace( /\.$/, '' );
+ const appPath = getAppRoot();
+ const savePath = path.resolve( appPath, 'tests/e2e/screenshots' );
+ const filePath = path.join(
+ savePath,
+ `${ title }.png`.replace( /[^a-z0-9.-]+/gi, '-' )
+ );
+
+ mkdirp.sync( savePath );
+ try {
+ await page.screenshot({
+ path: filePath,
+ fullPage: true,
+ });
+ } catch ( error ) {
+ return {
+ title: 'no screenshot',
+ filePath: '',
+ };
+ }
+ return {
+ title,
+ filePath,
+ };
+};
+
+module.exports = takeScreenshotFor;
diff --git a/tests/e2e/specs/front-end/test-cart-calculate-shipping.js b/tests/e2e/specs/front-end/test-cart-calculate-shipping.js
new file mode 100644
index 00000000000..6ee84f7b8f2
--- /dev/null
+++ b/tests/e2e/specs/front-end/test-cart-calculate-shipping.js
@@ -0,0 +1,6 @@
+/*
+ * Internal dependencies
+ */
+const { runCartCalculateShippingTest } = require( '@woocommerce/e2e-core-tests' );
+
+runCartCalculateShippingTest();
diff --git a/tests/e2e/specs/front-end/test-order-email-receiving.js b/tests/e2e/specs/front-end/test-order-email-receiving.js
new file mode 100644
index 00000000000..a85e21d0c22
--- /dev/null
+++ b/tests/e2e/specs/front-end/test-order-email-receiving.js
@@ -0,0 +1,6 @@
+/*
+ * Internal dependencies
+ */
+const { runOrderEmailReceivingTest } = require( '@woocommerce/e2e-core-tests' );
+
+runOrderEmailReceivingTest();
diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md
index e1b9055658c..4068597eb60 100644
--- a/tests/e2e/utils/CHANGELOG.md
+++ b/tests/e2e/utils/CHANGELOG.md
@@ -3,6 +3,7 @@
## Added
- `emptyCart()` Shopper flow helper that empties the cart
+- `deleteAllShippingZones` Delete all the existing shipping zones
- constants
- `WP_ADMIN_POST_TYPE`
- `WP_ADMIN_NEW_POST_TYPE`
diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md
index 8119b274fdf..b7fdb4ec48a 100644
--- a/tests/e2e/utils/README.md
+++ b/tests/e2e/utils/README.md
@@ -180,6 +180,7 @@ This package provides support for enabling retries in tests:
| `removeCoupon` | | helper method that removes a single coupon within cart or checkout |
| `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox |
| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page |
+| `deleteAllShippingZones` | | Delete all the existing shipping zones |
### Test Utilities
diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js
index 73bda6b7060..309be76beaf 100644
--- a/tests/e2e/utils/src/components.js
+++ b/tests/e2e/utils/src/components.js
@@ -13,6 +13,7 @@ import {
selectOptionInSelect2,
setCheckbox,
unsetCheckbox,
+ evalAndClick,
clearAndFillInput,
} from './page-utils';
import factories from './factories';
@@ -540,6 +541,33 @@ const deleteAllEmailLogs = async () => {
}
};
+/**
+ * Delete all the existing shipping zones.
+ */
+const deleteAllShippingZones = async () => {
+ await merchant.openSettings('shipping');
+
+ // Delete existing shipping zones.
+ try {
+ let zone = await page.$( '.wc-shipping-zone-delete' );
+ if ( zone ) {
+ // WP action links aren't clickable because they are hidden with a left=-9999 style.
+ await page.evaluate(() => {
+ document.querySelector('.wc-shipping-zone-name .row-actions')
+ .style
+ .left = '0';
+ });
+ while ( zone ) {
+ await evalAndClick( '.wc-shipping-zone-delete' );
+ await uiUnblocked();
+ zone = await page.$( '.wc-shipping-zone-delete' );
+ };
+ };
+ } catch (error) {
+ // Prevent an error here causing the test to fail.
+ };
+};
+
export {
completeOnboardingWizard,
createSimpleProduct,
@@ -553,4 +581,5 @@ export {
createSimpleProductWithCategory,
clickUpdateOrder,
deleteAllEmailLogs,
+ deleteAllShippingZones,
};
diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js
index 4bf9024989e..7a5f5e7d181 100644
--- a/tests/e2e/utils/src/page-utils.js
+++ b/tests/e2e/utils/src/page-utils.js
@@ -9,7 +9,7 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils';
* @param {string} selector
* @param {string} value
*/
-const clearAndFillInput = async ( selector, value ) => {
+export const clearAndFillInput = async ( selector, value ) => {
await page.waitForSelector( selector );
await page.focus( selector );
await pressKeyWithModifier( 'primary', 'a' );
@@ -21,14 +21,14 @@ const clearAndFillInput = async ( selector, value ) => {
*
* @param {string} tabName Tab label
*/
-const clickTab = async ( tabName ) => {
+export const clickTab = async ( tabName ) => {
await expect( page ).toClick( '.wc-tabs > li > a', { text: tabName } );
};
/**
* Save changes on a WooCommerce settings page.
*/
-const settingsPageSaveChanges = async () => {
+export const settingsPageSaveChanges = async () => {
await page.focus( 'button.woocommerce-save-button' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
@@ -39,7 +39,7 @@ const settingsPageSaveChanges = async () => {
/**
* Save changes on Permalink settings page.
*/
-const permalinkSettingsPageSaveChanges = async () => {
+export const permalinkSettingsPageSaveChanges = async () => {
await page.focus( '.wp-core-ui .button-primary' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
@@ -52,7 +52,7 @@ const permalinkSettingsPageSaveChanges = async () => {
*
* @param {string} selector
*/
-const setCheckbox = async( selector ) => {
+export const setCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
@@ -66,7 +66,7 @@ const setCheckbox = async( selector ) => {
*
* @param {string} selector
*/
-const unsetCheckbox = async( selector ) => {
+export const unsetCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
@@ -78,7 +78,7 @@ const unsetCheckbox = async( selector ) => {
/**
* Wait for UI blocking to end.
*/
-const uiUnblocked = async () => {
+export const uiUnblocked = async () => {
await page.waitForFunction( () => ! Boolean( document.querySelector( '.blockUI' ) ) );
};
@@ -90,7 +90,7 @@ const uiUnblocked = async () => {
* @param {string} publishVerification
* @param {string} trashVerification
*/
-const verifyPublishAndTrash = async ( button, publishNotice, publishVerification, trashVerification ) => {
+export const verifyPublishAndTrash = async ( button, publishNotice, publishVerification, trashVerification ) => {
// Wait for auto save
await page.waitFor( 2000 );
@@ -124,7 +124,7 @@ const verifyPublishAndTrash = async ( button, publishNotice, publishVerification
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
-const verifyCheckboxIsSet = async( selector ) => {
+export const verifyCheckboxIsSet = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
@@ -136,7 +136,7 @@ const verifyCheckboxIsSet = async( selector ) => {
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
-const verifyCheckboxIsUnset = async( selector ) => {
+export const verifyCheckboxIsUnset = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
@@ -149,7 +149,7 @@ const verifyCheckboxIsUnset = async( selector ) => {
* @param {string} selector Selector of the input field that needs to be verified.
* @param {string} value Value of the input field that needs to be verified.
*/
-const verifyValueOfInputField = async( selector, value ) => {
+export const verifyValueOfInputField = async( selector, value ) => {
await page.focus( selector );
const field = await page.$( selector );
const fieldValue = ( await ( await field.getProperty( 'value' ) ).jsonValue() );
@@ -161,7 +161,7 @@ const verifyValueOfInputField = async( selector, value ) => {
*
* @param {string} selector Selector of the filter link to be clicked.
*/
-const clickFilter = async ( selector ) => {
+export const clickFilter = async ( selector ) => {
await page.waitForSelector( selector );
await page.focus( selector );
await Promise.all( [
@@ -175,7 +175,7 @@ const clickFilter = async ( selector ) => {
*
* If there's more than 20 items, it moves all 20 items on the current page.
*/
-const moveAllItemsToTrash = async () => {
+export const moveAllItemsToTrash = async () => {
await setCheckbox( '#cb-select-all-1' );
await expect( page ).toSelect( '#bulk-action-selector-top', 'Move to Trash' );
await Promise.all( [
@@ -191,7 +191,7 @@ const moveAllItemsToTrash = async () => {
*
* @param {string} selector Selector of the filter link to be clicked.
*/
-const evalAndClick = async ( selector ) => {
+export const evalAndClick = async ( selector ) => {
// We use this when `expect(page).toClick()` is unable to find the element
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
page.$eval( selector, elem => elem.click() );
@@ -203,7 +203,7 @@ const evalAndClick = async ( selector ) => {
* @param {string} value Value of what to be selected
* @param {string} selector Selector of the select2 search field
*/
-const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__field' ) => {
+export const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__field' ) => {
await page.waitForSelector(selector);
await page.click(selector);
await page.type(selector, value);
@@ -218,7 +218,7 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f
* @param {string} orderId Order ID
* @param {string} customerName Customer's full name attached to order ID.
*/
-const searchForOrder = async (value, orderId, customerName) => {
+export 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');
@@ -234,8 +234,12 @@ const searchForOrder = async (value, orderId, customerName) => {
* @param couponCode string
* @returns {Promise}
*/
-const applyCoupon = async ( couponCode ) => {
+export const applyCoupon = async ( couponCode ) => {
try {
+ await Promise.all([
+ page.reload(),
+ page.waitForNavigation( { waitUntil: 'networkidle0' } ),
+ ]);
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await clearAndFillInput('#coupon_code', couponCode);
@@ -254,7 +258,11 @@ const applyCoupon = async ( couponCode ) => {
* @param couponCode Coupon name.
* @returns {Promise}
*/
-const removeCoupon = async ( couponCode ) => {
+export const removeCoupon = async ( couponCode ) => {
+ await Promise.all([
+ page.reload(),
+ page.waitForNavigation( { waitUntil: 'networkidle0' } ),
+ ]);
await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'});
await uiUnblocked();
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
@@ -266,32 +274,10 @@ const removeCoupon = async ( couponCode ) => {
*
* @param {string} action The action to take on the order.
*/
-const selectOrderAction = async ( action ) => {
+export const selectOrderAction = async ( action ) => {
await page.select( 'select[name=wc_order_action]', action );
await Promise.all( [
page.click( '.wc-reload' ),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
] );
}
-
-export {
- clearAndFillInput,
- clickTab,
- settingsPageSaveChanges,
- permalinkSettingsPageSaveChanges,
- setCheckbox,
- unsetCheckbox,
- uiUnblocked,
- verifyPublishAndTrash,
- verifyCheckboxIsSet,
- verifyCheckboxIsUnset,
- verifyValueOfInputField,
- clickFilter,
- moveAllItemsToTrash,
- evalAndClick,
- selectOptionInSelect2,
- searchForOrder,
- applyCoupon,
- removeCoupon,
- selectOrderAction,
-};
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/legacy/framework/class-wc-unit-test-case.php b/tests/legacy/framework/class-wc-unit-test-case.php
index 74289f2b413..c3255481fef 100644
--- a/tests/legacy/framework/class-wc-unit-test-case.php
+++ b/tests/legacy/framework/class-wc-unit-test-case.php
@@ -7,6 +7,7 @@
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
+use PHPUnit\Framework\Constraint\IsType;
/**
* WC Unit Test Case.
@@ -246,4 +247,16 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
public function register_legacy_proxy_class_mocks( array $mocks ) {
wc_get_container()->get( LegacyProxy::class )->register_class_mocks( $mocks );
}
+
+ /**
+ * Asserts that a variable is of type int.
+ * TODO: After upgrading to PHPUnit 8 or newer, remove this method and replace calls with PHPUnit's built-in 'assertIsInt'.
+ *
+ * @param mixed $actual The value to check.
+ * @param mixed $message Error message to use if the assertion fails.
+ * @return bool mixed True if the value is of integer type, false otherwise.
+ */
+ public static function assertIsInteger( $actual, $message = '' ) {
+ return self::assertInternalType( 'int', $actual, $message );
+ }
}
diff --git a/tests/legacy/framework/helpers/class-wc-helper-customer.php b/tests/legacy/framework/helpers/class-wc-helper-customer.php
index fe6b9ba644d..c814ac104d7 100644
--- a/tests/legacy/framework/helpers/class-wc-helper-customer.php
+++ b/tests/legacy/framework/helpers/class-wc-helper-customer.php
@@ -17,15 +17,15 @@ class WC_Helper_Customer {
'id' => 0,
'date_modified' => null,
'country' => 'US',
- 'state' => 'PA',
- 'postcode' => '19123',
- 'city' => 'Philadelphia',
+ 'state' => 'CA',
+ 'postcode' => '94110',
+ 'city' => 'San Francisco',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
- 'shipping_state' => 'PA',
- 'shipping_postcode' => '19123',
- 'shipping_city' => 'Philadelphia',
+ 'shipping_state' => 'CA',
+ 'shipping_postcode' => '94110',
+ 'shipping_city' => 'San Francisco',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
@@ -46,15 +46,15 @@ class WC_Helper_Customer {
$customer = new WC_Customer();
$customer->set_billing_country( 'US' );
$customer->set_first_name( 'Justin' );
- $customer->set_billing_state( 'PA' );
- $customer->set_billing_postcode( '19123' );
- $customer->set_billing_city( 'Philadelphia' );
+ $customer->set_billing_state( 'CA' );
+ $customer->set_billing_postcode( '94110' );
+ $customer->set_billing_city( 'San Francisco' );
$customer->set_billing_address( '123 South Street' );
$customer->set_billing_address_2( 'Apt 1' );
$customer->set_shipping_country( 'US' );
- $customer->set_shipping_state( 'PA' );
- $customer->set_shipping_postcode( '19123' );
- $customer->set_shipping_city( 'Philadelphia' );
+ $customer->set_shipping_state( 'CA' );
+ $customer->set_shipping_postcode( '94110' );
+ $customer->set_shipping_city( 'San Francisco' );
$customer->set_shipping_address( '123 South Street' );
$customer->set_shipping_address_2( 'Apt 1' );
$customer->set_username( $username );
@@ -70,7 +70,7 @@ class WC_Helper_Customer {
* @return array
*/
public static function get_expected_store_location() {
- return array( 'GB', '', '', '' );
+ return array( 'US', 'CA', '', '' );
}
/**
diff --git a/tests/legacy/unit-tests/account/functions.php b/tests/legacy/unit-tests/account/functions.php
index b9b5756b1c6..ca57ba3bf5e 100644
--- a/tests/legacy/unit-tests/account/functions.php
+++ b/tests/legacy/unit-tests/account/functions.php
@@ -179,7 +179,7 @@ class WC_Tests_Account_Functions extends WC_Unit_Test_Case {
public function test_wc_get_account_formatted_address() {
$customer = WC_Helper_Customer::create_customer();
- $this->assertEquals( '123 South Street
Apt 1
Philadelphia, PA 19123
United States (US)', wc_get_account_formatted_address( 'billing', $customer->get_id() ) );
+ $this->assertEquals( '123 South Street
Apt 1
San Francisco, CA 94110', wc_get_account_formatted_address( 'billing', $customer->get_id() ) );
$customer->delete( true );
}
diff --git a/tests/legacy/unit-tests/countries/countries.php b/tests/legacy/unit-tests/countries/countries.php
index 73f41fbd55c..d2e6c0e76d4 100644
--- a/tests/legacy/unit-tests/countries/countries.php
+++ b/tests/legacy/unit-tests/countries/countries.php
@@ -5,6 +5,8 @@
* @package WooCommerce\Tests\Countries
*/
+// phpcs:disable WordPress.Files.FileName
+
/**
* WC_Countries tests.
*/
@@ -178,7 +180,7 @@ class WC_Tests_Countries extends WC_Unit_Test_Case {
update_option( 'woocommerce_default_country', 'NO' );
$this->assertEquals( 'VAT', $countries->tax_or_vat() );
- update_option( 'woocommerce_default_country', 'US' );
+ update_option( 'woocommerce_default_country', 'US:CA' );
$this->assertEquals( 'Tax', $countries->tax_or_vat() );
}
diff --git a/tests/legacy/unit-tests/customer/crud.php b/tests/legacy/unit-tests/customer/crud.php
index 41c801d6538..2b070bbc309 100644
--- a/tests/legacy/unit-tests/customer/crud.php
+++ b/tests/legacy/unit-tests/customer/crud.php
@@ -324,16 +324,16 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
update_option( 'woocommerce_tax_based_on', 'shipping' );
$taxable = $customer->get_taxable_address();
$this->assertEquals( 'US', $taxable[0] );
- $this->assertEquals( 'PA', $taxable[1] );
+ $this->assertEquals( 'CA', $taxable[1] );
$this->assertEquals( '11111', $taxable[2] );
$this->assertEquals( 'Test', $taxable[3] );
update_option( 'woocommerce_tax_based_on', 'billing' );
$taxable = $customer->get_taxable_address();
$this->assertEquals( 'US', $taxable[0] );
- $this->assertEquals( 'PA', $taxable[1] );
- $this->assertEquals( '19123', $taxable[2] );
- $this->assertEquals( 'Philadelphia', $taxable[3] );
+ $this->assertEquals( 'CA', $taxable[1] );
+ $this->assertEquals( '94110', $taxable[2] );
+ $this->assertEquals( 'San Francisco', $taxable[3] );
update_option( 'woocommerce_tax_based_on', 'base' );
$taxable = $customer->get_taxable_address();
@@ -431,7 +431,7 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
*/
public function test_customer_is_customer_outside_base() {
$customer = WC_Helper_Customer::create_customer();
- $this->assertTrue( $customer->is_customer_outside_base() );
+ $this->assertFalse( $customer->is_customer_outside_base() );
update_option( 'woocommerce_tax_based_on', 'base' );
$customer->set_billing_address_to_base();
$this->assertFalse( $customer->is_customer_outside_base() );
@@ -444,9 +444,9 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
public function test_customer_sessions() {
$session = WC_Helper_Customer::create_mock_customer(); // set into session....
- $this->assertEquals( '19123', $session->get_billing_postcode() );
+ $this->assertEquals( '94110', $session->get_billing_postcode() );
$this->assertEquals( '123 South Street', $session->get_billing_address() );
- $this->assertEquals( 'Philadelphia', $session->get_billing_city() );
+ $this->assertEquals( 'San Francisco', $session->get_billing_city() );
$session->set_billing_address( '124 South Street' );
$session->save();
diff --git a/tests/legacy/unit-tests/customer/customer.php b/tests/legacy/unit-tests/customer/customer.php
index 05b093e102d..de73d7ccdaa 100644
--- a/tests/legacy/unit-tests/customer/customer.php
+++ b/tests/legacy/unit-tests/customer/customer.php
@@ -83,7 +83,7 @@ class WC_Tests_Customer extends WC_Unit_Test_Case {
// Customer is going with the Free Shipping option, and the store calculates tax based on the customer's billing address.
WC_Helper_Customer::set_chosen_shipping_methods( array( 'free_shipping' ) );
WC_Helper_Customer::set_tax_based_on( 'billing' );
- $this->assertEquals( $customer->is_customer_outside_base(), true );
+ $this->assertEquals( $customer->is_customer_outside_base(), false );
// Customer is going with the Free Shipping option, and the store calculates tax based on the store base location.
WC_Helper_Customer::set_chosen_shipping_methods( array( 'free_shipping' ) );
diff --git a/tests/legacy/unit-tests/formatting/functions.php b/tests/legacy/unit-tests/formatting/functions.php
index 533f5ed1a5a..ae237dacf6e 100644
--- a/tests/legacy/unit-tests/formatting/functions.php
+++ b/tests/legacy/unit-tests/formatting/functions.php
@@ -593,32 +593,32 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_price() {
// Common prices.
- $this->assertEquals( '£1.00', wc_price( 1 ) );
- $this->assertEquals( '£1.10', wc_price( 1.1 ) );
- $this->assertEquals( '£1.17', wc_price( 1.17 ) );
- $this->assertEquals( '£1,111.17', wc_price( 1111.17 ) );
- $this->assertEquals( '£0.00', wc_price( 0 ) );
+ $this->assertEquals( '$1.00', wc_price( 1 ) );
+ $this->assertEquals( '$1.10', wc_price( 1.1 ) );
+ $this->assertEquals( '$1.17', wc_price( 1.17 ) );
+ $this->assertEquals( '$1,111.17', wc_price( 1111.17 ) );
+ $this->assertEquals( '$0.00', wc_price( 0 ) );
// Different currency.
- $this->assertEquals( '$1,111.17', wc_price( 1111.17, array( 'currency' => 'USD' ) ) );
+ $this->assertEquals( '£1,111.17', wc_price( 1111.17, array( 'currency' => 'GBP' ) ) );
// Negative price.
- $this->assertEquals( '-£1.17', wc_price( -1.17 ) );
+ $this->assertEquals( '-$1.17', wc_price( -1.17 ) );
// Bogus prices.
- $this->assertEquals( '£0.00', wc_price( null ) );
- $this->assertEquals( '£0.00', wc_price( 'Q' ) );
- $this->assertEquals( '£0.00', wc_price( 'ಠ_ಠ' ) );
+ $this->assertEquals( '$0.00', wc_price( null ) );
+ $this->assertEquals( '$0.00', wc_price( 'Q' ) );
+ $this->assertEquals( '$0.00', wc_price( 'ಠ_ಠ' ) );
// Trim zeros.
add_filter( 'woocommerce_price_trim_zeros', '__return_true' );
- $this->assertEquals( '£1', wc_price( 1.00 ) );
+ $this->assertEquals( '$1', wc_price( 1.00 ) );
remove_filter( 'woocommerce_price_trim_zeros', '__return_true' );
// Ex tax label.
$calc_taxes = get_option( 'woocommerce_calc_taxes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
- $this->assertEquals( '£1,111.17 (ex. VAT)', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
+ $this->assertEquals( '$1,111.17 (ex. tax)', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
update_option( 'woocommerce_calc_taxes', $calc_taxes );
}
@@ -926,7 +926,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_sale_price() {
- $this->assertEquals( '£10.00 £5.00', wc_format_sale_price( '10', '5' ) );
+ $this->assertEquals( '$10.00 $5.00', wc_format_sale_price( '10', '5' ) );
}
/**
@@ -935,7 +935,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_price_range() {
- $this->assertEquals( '£10.00 – £5.00', wc_format_price_range( '10', '5' ) );
+ $this->assertEquals( '$10.00 – $5.00', wc_format_price_range( '10', '5' ) );
}
/**
diff --git a/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php b/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
index 190f4257c19..2444987f2ae 100644
--- a/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
+++ b/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
@@ -1245,7 +1245,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$order->set_billing_country( 'US' );
$order->set_billing_city( 'Portland' );
$order->set_billing_postcode( '97266' );
- $this->assertEquals( '123 Test St.
Portland, 97266
United States (US)', $order->get_formatted_billing_address( 'none' ) );
+ $this->assertEquals( '123 Test St.
Portland, 97266', $order->get_formatted_billing_address( 'none' ) );
$this->assertTrue( $order->has_billing_address() );
$this->assertFalse( $order->has_shipping_address() );
@@ -1265,7 +1265,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$order->set_shipping_country( 'US' );
$order->set_shipping_city( 'Portland' );
$order->set_shipping_postcode( '97266' );
- $this->assertEquals( '123 Test St.
Portland, 97266
United States (US)', $order->get_formatted_shipping_address( 'none' ) );
+ $this->assertEquals( '123 Test St.
Portland, 97266', $order->get_formatted_shipping_address( 'none' ) );
$this->assertFalse( $order->has_billing_address() );
$this->assertTrue( $order->has_shipping_address() );
@@ -1498,7 +1498,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object->set_billing_state( 'Boulder' );
$object->set_billing_postcode( '00001' );
$object->set_billing_country( 'US' );
- $this->assertEquals( 'Fred Flintstone
Bedrock Ltd.
34 Stonepants avenue
Rockville
Bedrock, BOULDER 00001
United States (US)', $object->get_formatted_billing_address() );
+ $this->assertEquals( 'Fred Flintstone
Bedrock Ltd.
34 Stonepants avenue
Rockville
Bedrock, BOULDER 00001', $object->get_formatted_billing_address() );
}
/**
@@ -1515,7 +1515,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object->set_shipping_state( 'Boulder' );
$object->set_shipping_postcode( '00001' );
$object->set_shipping_country( 'US' );
- $this->assertEquals( 'Barney Rubble
Bedrock Ltd.
34 Stonepants avenue
Rockville
Bedrock, BOULDER 00001
United States (US)', $object->get_formatted_shipping_address() );
+ $this->assertEquals( 'Barney Rubble
Bedrock Ltd.
34 Stonepants avenue
Rockville
Bedrock, BOULDER 00001', $object->get_formatted_shipping_address() );
}
/**
diff --git a/tests/legacy/unit-tests/privacy/export.php b/tests/legacy/unit-tests/privacy/export.php
index 0d8db0ee39e..68d8b7fe85a 100644
--- a/tests/legacy/unit-tests/privacy/export.php
+++ b/tests/legacy/unit-tests/privacy/export.php
@@ -86,15 +86,15 @@ class WC_Test_Privacy_Export extends WC_Unit_Test_Case {
),
array(
'name' => 'Billing City',
- 'value' => 'Philadelphia',
+ 'value' => 'San Francisco',
),
array(
'name' => 'Billing Postal/Zip Code',
- 'value' => '19123',
+ 'value' => '94110',
),
array(
'name' => 'Billing State',
- 'value' => 'PA',
+ 'value' => 'CA',
),
array(
'name' => 'Billing Country / Region',
@@ -114,15 +114,15 @@ class WC_Test_Privacy_Export extends WC_Unit_Test_Case {
),
array(
'name' => 'Shipping City',
- 'value' => 'Philadelphia',
+ 'value' => 'San Francisco',
),
array(
'name' => 'Shipping Postal/Zip Code',
- 'value' => '19123',
+ 'value' => '94110',
),
array(
'name' => 'Shipping State',
- 'value' => 'PA',
+ 'value' => 'CA',
),
array(
'name' => 'Shipping Country / Region',
diff --git a/tests/legacy/unit-tests/product/data.php b/tests/legacy/unit-tests/product/data.php
index b3beb1f9f2a..09c5f78ea57 100644
--- a/tests/legacy/unit-tests/product/data.php
+++ b/tests/legacy/unit-tests/product/data.php
@@ -259,15 +259,15 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product = wc_get_product( $product1_id );
$this->assertEquals( $product1_id, $product->get_id() );
- $this->assertEquals( '£10.00 £7.00', $product->get_price_html() );
+ $this->assertEquals( '$10.00 $7.00', $product->get_price_html() );
$product = wc_get_product( $product2_id );
$this->assertEquals( $product2_id, $product->get_id() );
- $this->assertEquals( '£20.00 £16.00', $product->get_price_html() );
+ $this->assertEquals( '$20.00 $16.00', $product->get_price_html() );
$product = wc_get_product( $product3_id );
$this->assertEquals( $product3_id, $product->get_id() );
- $this->assertEquals( '£50.00', $product->get_price_html() );
+ $this->assertEquals( '$50.00', $product->get_price_html() );
}
/**
diff --git a/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php b/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php
index 5751cf31f32..2dfcbdbb4e6 100644
--- a/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php
+++ b/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php
@@ -26,15 +26,15 @@ class CustomerHelper {
'id' => 0,
'date_modified' => null,
'country' => 'US',
- 'state' => 'PA',
- 'postcode' => '19123',
- 'city' => 'Philadelphia',
+ 'state' => 'CA',
+ 'postcode' => '94110',
+ 'city' => 'San Francisco',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
- 'shipping_state' => 'PA',
- 'shipping_postcode' => '19123',
- 'shipping_city' => 'Philadelphia',
+ 'shipping_state' => 'CA',
+ 'shipping_postcode' => '94110',
+ 'shipping_city' => 'San Francisco',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
@@ -55,15 +55,15 @@ class CustomerHelper {
$customer = new WC_Customer();
$customer->set_billing_country( 'US' );
$customer->set_first_name( 'Justin' );
- $customer->set_billing_state( 'PA' );
- $customer->set_billing_postcode( '19123' );
- $customer->set_billing_city( 'Philadelphia' );
+ $customer->set_billing_state( 'CA' );
+ $customer->set_billing_postcode( '94110' );
+ $customer->set_billing_city( 'San Francisco' );
$customer->set_billing_address( '123 South Street' );
$customer->set_billing_address_2( 'Apt 1' );
$customer->set_shipping_country( 'US' );
- $customer->set_shipping_state( 'PA' );
- $customer->set_shipping_postcode( '19123' );
- $customer->set_shipping_city( 'Philadelphia' );
+ $customer->set_shipping_state( 'CA' );
+ $customer->set_shipping_postcode( '94110' );
+ $customer->set_shipping_city( 'San Francisco' );
$customer->set_shipping_address( '123 South Street' );
$customer->set_shipping_address_2( 'Apt 1' );
$customer->set_username( $username );
@@ -79,7 +79,7 @@ class CustomerHelper {
* @return array
*/
public static function get_expected_store_location() {
- return array( 'GB', '', '', '' );
+ return array( 'US', 'CA', '', '' );
}
/**
diff --git a/tests/legacy/unit-tests/rest-api/Tests/Version2/customers.php b/tests/legacy/unit-tests/rest-api/Tests/Version2/customers.php
index f9a4d5acca1..cebf6cdad9c 100644
--- a/tests/legacy/unit-tests/rest-api/Tests/Version2/customers.php
+++ b/tests/legacy/unit-tests/rest-api/Tests/Version2/customers.php
@@ -70,9 +70,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@@ -83,9 +83,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@@ -315,9 +315,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@@ -328,9 +328,9 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
diff --git a/tests/legacy/unit-tests/rest-api/Tests/Version3/customers.php b/tests/legacy/unit-tests/rest-api/Tests/Version3/customers.php
index 8d4508bade4..15e02ae4c9b 100644
--- a/tests/legacy/unit-tests/rest-api/Tests/Version3/customers.php
+++ b/tests/legacy/unit-tests/rest-api/Tests/Version3/customers.php
@@ -77,9 +77,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@@ -90,9 +90,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@@ -147,9 +147,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@@ -160,9 +160,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
@@ -387,9 +387,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
'email' => '',
'phone' => '',
@@ -400,9 +400,9 @@ class Customers extends WC_REST_Unit_Test_Case {
'company' => '',
'address_1' => '123 South Street',
'address_2' => 'Apt 1',
- 'city' => 'Philadelphia',
- 'state' => 'PA',
- 'postcode' => '19123',
+ 'city' => 'San Francisco',
+ 'state' => 'CA',
+ 'postcode' => '94110',
'country' => 'US',
),
'is_paying_customer' => false,
diff --git a/tests/legacy/unit-tests/tax/tax.php b/tests/legacy/unit-tests/tax/tax.php
index 9269f766526..8862aa848cd 100644
--- a/tests/legacy/unit-tests/tax/tax.php
+++ b/tests/legacy/unit-tests/tax/tax.php
@@ -117,8 +117,8 @@ class WC_Tests_Tax extends WC_Unit_Test_Case {
*/
public function test_get_base_tax_rates() {
$tax_rate = array(
- 'tax_rate_country' => 'GB',
- 'tax_rate_state' => '',
+ 'tax_rate_country' => 'US',
+ 'tax_rate_state' => 'CA',
'tax_rate' => '20.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '1',
diff --git a/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php b/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php
index a782089e51b..6f2c17b7d5e 100644
--- a/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php
@@ -30,7 +30,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
*/
public function test_get_woocommerce_currency() {
- $this->assertEquals( 'GBP', get_woocommerce_currency() );
+ $this->assertEquals( 'USD', get_woocommerce_currency() );
}
/**
@@ -222,10 +222,10 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_get_woocommerce_currency_symbol() {
// Default currency.
- $this->assertEquals( '£', get_woocommerce_currency_symbol() );
+ $this->assertEquals( '$', get_woocommerce_currency_symbol() );
// Given specific currency.
- $this->assertEquals( '$', get_woocommerce_currency_symbol( 'USD' ) );
+ $this->assertEquals( '£', get_woocommerce_currency_symbol( 'GBP' ) );
// Each case.
foreach ( array_keys( get_woocommerce_currencies() ) as $currency_code ) {
@@ -348,8 +348,8 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_wc_get_base_location() {
$default = wc_get_base_location();
- $this->assertEquals( 'GB', $default['country'] );
- $this->assertEquals( '', $default['state'] );
+ $this->assertEquals( 'US', $default['country'] );
+ $this->assertEquals( 'CA', $default['state'] );
}
/**
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..0dc8057ccc9
--- /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/includes/wc-stock-functions-tests.php b/tests/php/includes/wc-stock-functions-tests.php
index 368eab017cf..4e04dc29d29 100644
--- a/tests/php/includes/wc-stock-functions-tests.php
+++ b/tests/php/includes/wc-stock-functions-tests.php
@@ -192,6 +192,17 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
}
}
+ /**
+ * Assert that a value is equal to another one and is of integer type.
+ *
+ * @param mixed $expected The value $actual must be equal to.
+ * @param mixed $actual The value to check for equality to $expected and for type.
+ */
+ private function assertIsIntAndEquals( $expected, $actual ) {
+ $this->assertEquals( $expected, $actual );
+ self::assertIsInteger( $actual );
+ }
+
/**
* Test wc_get_low_stock_amount with a simple product which has low stock amount set.
*/
@@ -200,7 +211,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Simple product, set low stock amount.
$product = WC_Helper_Product::create_simple_product(
@@ -212,7 +223,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
)
);
- $this->assertEquals( $product_low_stock_amount, wc_get_low_stock_amount( $product ) );
+ $this->assertIsIntAndEquals( $product_low_stock_amount, wc_get_low_stock_amount( $product ) );
}
/**
@@ -222,18 +233,18 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Simple product, don't set low stock amount.
$product = WC_Helper_Product::create_simple_product(
true,
array(
- 'manage_stock' => true,
- 'stock_quantity' => 10,
+ 'manage_stock' => true,
+ 'stock_quantity' => 10,
)
);
- $this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $product ) );
+ $this->assertIsIntAndEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $product ) );
}
/**
@@ -245,7 +256,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$variation_low_stock_amount = 7;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Parent low stock amount NOT set.
$variable_product = WC_Helper_Product::create_variation_product();
@@ -254,22 +265,22 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
- $var1 = $variations[0];
+ $var1 = $variations[0];
$var1->set_manage_stock( true );
$var1->set_low_stock_amount( $variation_low_stock_amount );
$var1->save();
- $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
// Even after turning on manage stock on the parent, but with no value.
$variable_product->set_manage_stock( true );
$variable_product->save();
- $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
// Ans also after turning the manage stock off again on the parent.
$variable_product->set_manage_stock( false );
$variable_product->save();
- $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@@ -282,7 +293,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$variation_low_stock_amount = 7;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@@ -292,12 +303,12 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
- $var1 = $variations[0];
+ $var1 = $variations[0];
$var1->set_manage_stock( true );
$var1->set_low_stock_amount( $variation_low_stock_amount );
$var1->save();
- $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@@ -309,7 +320,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$parent_low_stock_amount = 5;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@@ -319,9 +330,9 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Don't set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
- $var1 = $variations[0];
+ $var1 = $variations[0];
- $this->assertEquals( $parent_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $parent_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
/**
@@ -332,7 +343,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
$site_wide_low_stock_amount = 3;
// Set the store-wide default.
- update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount );
+ update_option( 'woocommerce_notify_low_stock_amount', strval( $site_wide_low_stock_amount ) );
// Set the parent low stock amount.
$variable_product = WC_Helper_Product::create_variation_product();
@@ -340,10 +351,10 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
// Don't set the variation low stock amount.
$variations = $variable_product->get_available_variations( 'objects' );
- $var1 = $variations[0];
+ $var1 = $variations[0];
$var1->set_manage_stock( false );
- $this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
+ $this->assertIsIntAndEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
}
}
diff --git a/tests/php/src/Internal/AssignDefaultCategoryTest.php b/tests/php/src/Internal/AssignDefaultCategoryTest.php
new file mode 100644
index 00000000000..1b638b56e19
--- /dev/null
+++ b/tests/php/src/Internal/AssignDefaultCategoryTest.php
@@ -0,0 +1,65 @@
+sut = new AssignDefaultCategory();
+ $product1 = ProductHelper::create_simple_product();
+ $product2 = ProductHelper::create_simple_product();
+ $product3 = ProductHelper::create_simple_product();
+ $default_category = (int) get_option( 'default_product_cat', 0 );
+
+ $products = array( $product1, $product2, $product3 );
+
+ // Remove all categories from products.
+ foreach ( $products as $product ) {
+ $result = $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM {$wpdb->term_relationships} WHERE object_id = %d AND term_taxonomy_id = %d",
+ $product->get_id(),
+ $default_category
+ )
+ );
+ }
+
+ // Ensure all categories are removed from products.
+ foreach ( $products as $product ) {
+ $cats = wp_get_post_terms( $product->get_id(), 'product_cat' );
+
+ $this->assertEmpty( $cats );
+ }
+
+ // Add in default category.
+ $this->sut->maybe_assign_default_product_cat();
+
+ // Ensure default category are now assigned to products.
+ foreach ( $products as $product ) {
+ $cats = wp_list_pluck( wp_get_post_terms( $product->get_id(), 'product_cat' ), 'term_id' );
+
+ $this->assertContains( $default_category, $cats );
+ }
+ }
+}
diff --git a/tests/php/src/ThemeSupport/ThemeSupportTest.php b/tests/php/src/ThemeSupport/ThemeSupportTest.php
new file mode 100644
index 00000000000..0add81a3415
--- /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 );
+ }
+}
diff --git a/woocommerce.php b/woocommerce.php
index 89d55866da5..1fb1516fb43 100644
--- a/woocommerce.php
+++ b/woocommerce.php
@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
- * Version: 5.3.0-dev
+ * Version: 5.4.0-dev
* Author: Automattic
* Author URI: https://woocommerce.com
* Text Domain: woocommerce