Merge branch 'trunk' into update/wccom-11343
This commit is contained in:
commit
9aa7fd9dd7
|
@ -49,3 +49,53 @@ jobs:
|
|||
npx wc-e2e test:e2e ./tests/e2e/specs/smoke-tests/update-woocommerce.js
|
||||
npx wc-e2e test:e2e
|
||||
npx wc-api-tests test api
|
||||
test-wp-version:
|
||||
name: Smoke test on L-${{ matrix.wp }} WordPress version
|
||||
runs-on: ubuntu-18.04
|
||||
strategy:
|
||||
matrix:
|
||||
wp: [ '1', '2' ]
|
||||
steps:
|
||||
|
||||
- name: Create dirs.
|
||||
run: |
|
||||
mkdir -p code/woocommerce
|
||||
mkdir -p package/woocommerce
|
||||
mkdir -p tmp/woocommerce
|
||||
mkdir -p node_modules
|
||||
|
||||
- name: Checkout code.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: package/woocommerce
|
||||
|
||||
- name: Run npm install.
|
||||
working-directory: package/woocommerce
|
||||
run: npm install
|
||||
|
||||
- name: Load docker images and start containers.
|
||||
working-directory: package/woocommerce
|
||||
env:
|
||||
LATEST_WP_VERSION_MINUS: ${{ matrix.wp }}
|
||||
run: npx wc-e2e docker:up
|
||||
|
||||
- name: Move current directory to code. We will install zip file in this dir later.
|
||||
run: mv ./package/woocommerce/* ./code/woocommerce
|
||||
|
||||
- name: Download WooCommerce release zip
|
||||
working-directory: tmp
|
||||
run: |
|
||||
ASSET_ID=$(jq ".release.assets[0].id" $GITHUB_EVENT_PATH)
|
||||
|
||||
curl https://api.github.com/repos/woocommerce/woocommerce/releases/assets/${ASSET_ID} -LJOH 'Accept: application/octet-stream'
|
||||
|
||||
unzip woocommerce.zip -d woocommerce
|
||||
mv woocommerce/* ../package/woocommerce/
|
||||
|
||||
- name: Run tests command.
|
||||
working-directory: code/woocommerce
|
||||
env:
|
||||
WC_E2E_SCREENSHOTS: 1
|
||||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
||||
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
|
||||
run: npx wc-e2e test:e2e
|
||||
|
|
|
@ -162,16 +162,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/mime-type-detection",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/mime-type-detection.git",
|
||||
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3"
|
||||
"reference": "b38b25d7b372e9fddb00335400467b223349fd7e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
|
||||
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
|
||||
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b38b25d7b372e9fddb00335400467b223349fd7e",
|
||||
"reference": "b38b25d7b372e9fddb00335400467b223349fd7e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -202,7 +202,7 @@
|
|||
"description": "Mime-type detection for Flysystem",
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
|
||||
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0"
|
||||
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -214,7 +214,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-01-18T20:58:21+00:00"
|
||||
"time": "2021-09-25T08:23:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
@ -1153,5 +1153,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.3"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
|
|
@ -251,16 +251,16 @@
|
|||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625"
|
||||
"reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
|
||||
"reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f268ca40d54617c6e06757f83f699775c9b3ff2e",
|
||||
"reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -303,7 +303,7 @@
|
|||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2021-04-09T00:54:41+00:00"
|
||||
"time": "2021-10-11T04:00:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-sniffs",
|
||||
|
@ -411,5 +411,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
|
|
@ -1697,5 +1697,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,78 @@
|
|||
== Changelog ==
|
||||
|
||||
= 5.8.0 2021-10-12 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Add - `modified_before` and `modified_after` filtering parameters to REST API for products, orders and coupons. #30585
|
||||
* Add - `woocommerce_quantity_input_min_admin` and `woocommerce_quantity_input_step_admin` filters. #30705
|
||||
* Dev - Action Scheduler updated to 3.3.0. #30719
|
||||
* Dev - Add order argument to `woocommerce_order_actions` filter. #30475
|
||||
* Fix - During product quick edit, the featured setting is sometimes not shown correctly as checked. #30639
|
||||
* Fix - Offsets not calculated correctly sometimes on select2 dropdowns causing usability issues. #30690
|
||||
* Fix - Select2 dropdown search input not getting focus when select2 dropdown element gets focused. #30626
|
||||
* Tweak - Add individual item remove notices based on the context of the line item in the order. #30650
|
||||
* Tweak - Change the shop page summary which was not relevant to the public. #30573
|
||||
* Tweak - Deleted unneeded double spaces in text strings. #30487
|
||||
* Tweak - Open Browse all extensions link in a new tab. #30640
|
||||
|
||||
** WooCommerce Admin - 2.7.1 & 2.7.2**
|
||||
|
||||
* Fix - Fix analytics crashing on daylight saving. #7763
|
||||
* Fix - Allow super admins all capabilities within WooCommerce Admin. #7489
|
||||
* Fix - Fix end date for last periods. #6584
|
||||
* Fix - Fix up onboarding profiler not working when opted out of tracking. #7490
|
||||
* Fix - Making Business Details sticky in onboarding wizard. #7426
|
||||
* Fix - Missing RTL for onboarding styles. #7531
|
||||
* Fix - Skip scheduling action if Action Scheduler tables have not been set up. #7521
|
||||
* Fix - Update country region typeahead for better autofill support. #7497
|
||||
* Fix - Use installable extensions for local state versus free extensions. #7585
|
||||
* Fix - Fix fatal error and unrelated results in analytics. #7682
|
||||
* Fix - Harden the reports directory. #7691
|
||||
* Fix - Update task-item logic to only display content when expanded is true. #7611
|
||||
* Add - Show Pinterest in installed marketing extensions (if installed). #7417
|
||||
* Add - Added MailchimpScheduler that runs daily to subscribe store_email in the profile data. #7579
|
||||
* Add - Added shipping plugin recommendations to settings page. #7446
|
||||
* Add - Adding endpoint to snooze onboarding task. #7539
|
||||
* Add - Adding undo snooze task endpoint. #7560
|
||||
* Add - Add task dismissal endpoints. #7538
|
||||
* Update - Add HK and SG countries to WC Pay intl support. #7558
|
||||
* Update - Create task list REST API endpoint. #7512
|
||||
* Update - Deleted OnboardingEmailMarketing note class. #7595
|
||||
* Update - Removes the use of the depreciated woocommerce_shared_settings hook. #7480
|
||||
* Update - Removes non WooCommerce Admin specific settings from the `wc_admin` namespace in the `wc/data` settings store (ex: countries). #7480
|
||||
* Update - Updating eway logo in payment suggestions defaults. #7562
|
||||
* Update - Update marketing task completion logic. #7586
|
||||
* Dev - Add email address field to OBW. #7552
|
||||
* Tweak - Add navigation items for the Marketplace menu. #7529
|
||||
* Tweak - Change all analytics strings and labels to sentence case. #6501
|
||||
* Tweak - Delete unneeded double spaces in text strings. #7502
|
||||
* Tweak - Remove the preloaded onboarding options. #7338
|
||||
* Tweak - Update analytics card header text styles. #6506
|
||||
* Enhancement - Align Table fields with the fallback on isNumeric. #7431
|
||||
|
||||
**WooCommerce Blocks - 5.7.1 & 5.8.0 & 5.9.0 & 5.9.1**
|
||||
|
||||
* Add - Extensibility point for extensions to filter payment methods. #4668
|
||||
* Add - "Filter Products by Stock" block. #4145
|
||||
* Add - Introduced the `__experimental_woocommerce_blocks_checkout_update_order_from_request` hook to the Checkout Store API. #4610.
|
||||
* Fix - Add label element to `<BlockTitle>` component. #4585
|
||||
* Fix - Disable Cart, Checkout, All Products & filters blocks from the widgets screen.
|
||||
* Fix - Infinite recursion when removing an attribute filter from the Active filters block. #4816
|
||||
* Fix - Prevent Product Category List from displaying incorrectly when used on the shop page. #4587
|
||||
* Fix - Product Search block displaying incorrectly. #4740
|
||||
* Tweak - Add Extensibility info to Store API readme. #4605
|
||||
* Tweak - Update documentation for the snackbarNoticeVisibility filter. #4508
|
||||
* Tweak - Add documentation for `extensionCartUpdate` method - this allows extensions to update the client-side cart after it has been modified on the server. #4377
|
||||
|
||||
**Action Scheduler 3.3.0**
|
||||
|
||||
* Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645
|
||||
* Dev - Now supports queries that use multiple statuses. #649
|
||||
* Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723
|
||||
* Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519
|
||||
* Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645
|
||||
|
||||
= 5.7.1 2021-09-23 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.3.0",
|
||||
"woocommerce/woocommerce-admin": "2.7.1-rc.1",
|
||||
"woocommerce/woocommerce-admin": "2.7.2",
|
||||
"woocommerce/woocommerce-blocks": "5.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -533,16 +533,16 @@
|
|||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-admin",
|
||||
"version": "2.7.1-rc.1",
|
||||
"version": "2.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "3e873f3a3733ef81fc3352a21dd152840550fffe"
|
||||
"reference": "9ce54862556815e74cfe2476fae0eb30fcfd65d6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/3e873f3a3733ef81fc3352a21dd152840550fffe",
|
||||
"reference": "3e873f3a3733ef81fc3352a21dd152840550fffe",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/9ce54862556815e74cfe2476fae0eb30fcfd65d6",
|
||||
"reference": "9ce54862556815e74cfe2476fae0eb30fcfd65d6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -597,9 +597,9 @@
|
|||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.7.1-rc.1"
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.7.2"
|
||||
},
|
||||
"time": "2021-09-23T22:13:54+00:00"
|
||||
"time": "2021-10-11T21:11:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
@ -2386,16 +2386,16 @@
|
|||
},
|
||||
{
|
||||
"name": "yoast/phpunit-polyfills",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Yoast/PHPUnit-Polyfills.git",
|
||||
"reference": "f014fb21c2b0038fd329515d59025af42fb98715"
|
||||
"reference": "1a582ab1d91e86aa450340c4d35631a85314ff9f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/f014fb21c2b0038fd329515d59025af42fb98715",
|
||||
"reference": "f014fb21c2b0038fd329515d59025af42fb98715",
|
||||
"url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a582ab1d91e86aa450340c4d35631a85314ff9f",
|
||||
"reference": "1a582ab1d91e86aa450340c4d35631a85314ff9f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2403,9 +2403,7 @@
|
|||
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-console-highlighter": "^0.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.0",
|
||||
"yoast/yoastcs": "^2.1.0"
|
||||
"yoast/yoastcs": "^2.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -2445,7 +2443,7 @@
|
|||
"issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues",
|
||||
"source": "https://github.com/Yoast/PHPUnit-Polyfills"
|
||||
},
|
||||
"time": "2021-08-09T16:28:08+00:00"
|
||||
"time": "2021-10-03T08:40:26+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications as PromotionRuleEngine;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
|
@ -81,10 +82,10 @@ class WC_Admin_Addons {
|
|||
* @param string $term Search terms.
|
||||
* @param string $country Store country.
|
||||
*
|
||||
* @return array of extensions
|
||||
* @return object of extensions and promotions.
|
||||
*/
|
||||
public static function get_extension_data( $category, $term, $country ) {
|
||||
$parameters = self::build_parameter_string( $category, $term, $country );
|
||||
$parameters = self::build_parameter_string( $category, $term, $country );
|
||||
|
||||
$headers = array();
|
||||
$auth = WC_Helper_Options::get( 'auth' );
|
||||
|
@ -99,7 +100,7 @@ class WC_Admin_Addons {
|
|||
);
|
||||
|
||||
if ( ! is_wp_error( $raw_extensions ) ) {
|
||||
$addons = json_decode( wp_remote_retrieve_body( $raw_extensions ) )->products;
|
||||
$addons = json_decode( wp_remote_retrieve_body( $raw_extensions ) );
|
||||
}
|
||||
return $addons;
|
||||
}
|
||||
|
@ -522,6 +523,37 @@ class WC_Admin_Addons {
|
|||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the HTML for the promotion block.
|
||||
*
|
||||
* @param array $promotion Array of promotion block data.
|
||||
* @return void
|
||||
*/
|
||||
public static function output_search_promotion_block( array $promotion ) {
|
||||
?>
|
||||
<div class="addons-wcs-banner-block">
|
||||
<div class="addons-wcs-banner-block-image">
|
||||
<img
|
||||
class="addons-img"
|
||||
src="<?php echo esc_url( $promotion['image'] ); ?>"
|
||||
alt="<?php echo esc_attr( $promotion['image_alt'] ); ?>"
|
||||
/>
|
||||
</div>
|
||||
<div class="addons-wcs-banner-block-content">
|
||||
<h1><?php echo esc_html( $promotion['title'] ); ?></h1>
|
||||
<p><?php echo esc_html( $promotion['description'] ); ?></p>
|
||||
<?php
|
||||
if ( ! empty( $promotion['actions'] ) ) {
|
||||
foreach ( $promotion['actions'] as $action ) {
|
||||
self::output_promotion_action( $action );
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the output of a full-width block.
|
||||
*
|
||||
|
@ -563,7 +595,6 @@ class WC_Admin_Addons {
|
|||
</div>
|
||||
<div class="addons-promotion-block-buttons">
|
||||
<?php
|
||||
|
||||
if ( $section['button_1'] ) {
|
||||
self::output_button(
|
||||
$section['button_1_href'],
|
||||
|
@ -581,7 +612,6 @@ class WC_Admin_Addons {
|
|||
$section['plugin']
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -680,6 +710,26 @@ class WC_Admin_Addons {
|
|||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Output HTML for a promotion action.
|
||||
*
|
||||
* @param array $action Array of action properties.
|
||||
* @return void
|
||||
*/
|
||||
public static function output_promotion_action( array $action ) {
|
||||
if ( empty( $action ) ) {
|
||||
return;
|
||||
}
|
||||
$style = ( ! empty( $action['primary'] ) && $action['primary'] ) ? 'addons-button-solid' : 'addons-button-outline-purple';
|
||||
?>
|
||||
<a
|
||||
class="addons-button <?php echo esc_attr( $style ); ?>"
|
||||
href="<?php echo esc_url( $action['url'] ); ?>">
|
||||
<?php echo esc_html( $action['label'] ); ?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles output of the addons page in admin.
|
||||
|
@ -710,13 +760,33 @@ class WC_Admin_Addons {
|
|||
$sections = self::get_sections();
|
||||
$theme = wp_get_theme();
|
||||
$current_section = isset( $_GET['section'] ) ? $section : '_featured';
|
||||
$promotions = array();
|
||||
$addons = array();
|
||||
|
||||
if ( '_featured' !== $current_section ) {
|
||||
$category = $section ? $section : null;
|
||||
$term = $search ? $search : null;
|
||||
$country = WC()->countries->get_base_country();
|
||||
$addons = self::get_extension_data( $category, $term, $country );
|
||||
$category = $section ? $section : null;
|
||||
$term = $search ? $search : null;
|
||||
$country = WC()->countries->get_base_country();
|
||||
$extension_data = self::get_extension_data( $category, $term, $country );
|
||||
$addons = $extension_data->products;
|
||||
$promotions = ! empty( $extension_data->promotions ) ? $extension_data->promotions : array();
|
||||
}
|
||||
|
||||
// We need Automattic\WooCommerce\Admin\RemoteInboxNotifications for the next part, if not remove all promotions.
|
||||
if ( ! WC()->is_wc_admin_active() ) {
|
||||
$promotions = array();
|
||||
}
|
||||
// Check for existence of promotions and evaluate out if we should show them.
|
||||
if ( ! empty( $promotions ) ) {
|
||||
foreach ( $promotions as $promo_id => $promotion ) {
|
||||
$evaluator = new PromotionRuleEngine\RuleEvaluator();
|
||||
$passed = $evaluator->evaluate( $promotion->rules );
|
||||
if ( ! $passed ) {
|
||||
unset( $promotions[ $promo_id ] );
|
||||
}
|
||||
}
|
||||
// Transform promotions to the correct format ready for output.
|
||||
$promotions = self::format_promotions( $promotions );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -811,4 +881,73 @@ class WC_Admin_Addons {
|
|||
|
||||
return " $admin_body_class woocommerce-page-wc-marketplace ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an action object and return the URL based on properties of the action.
|
||||
*
|
||||
* @param object $action Action object.
|
||||
* @return string URL.
|
||||
*/
|
||||
public static function get_action_url( $action ): string {
|
||||
if ( ! isset( $action->url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( isset( $action->url_is_admin_query ) && $action->url_is_admin_query ) {
|
||||
return wc_admin_url( $action->url );
|
||||
}
|
||||
|
||||
if ( isset( $action->url_is_admin_nonce_query ) && $action->url_is_admin_nonce_query ) {
|
||||
if ( empty( $action->nonce ) ) {
|
||||
return '';
|
||||
}
|
||||
return wp_nonce_url(
|
||||
admin_url( $action->url ),
|
||||
$action->nonce
|
||||
);
|
||||
}
|
||||
|
||||
return $action->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the promotion data ready for display, ie fetch locales and actions.
|
||||
*
|
||||
* @param array $promotions Array of promotoin objects.
|
||||
* @return array Array of formatted promotions ready for output.
|
||||
*/
|
||||
public static function format_promotions( array $promotions ): array {
|
||||
$formatted_promotions = array();
|
||||
foreach ( $promotions as $promotion ) {
|
||||
// Get the matching locale or fall back to en-US.
|
||||
$locale = PromotionRuleEngine\SpecRunner::get_locale( $promotion->locales );
|
||||
if ( null === $locale ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$promotion_actions = array();
|
||||
if ( ! empty( $promotion->actions ) ) {
|
||||
foreach ( $promotion->actions as $action ) {
|
||||
$action_locale = PromotionRuleEngine\SpecRunner::get_action_locale( $action->locales );
|
||||
$url = self::get_action_url( $action );
|
||||
|
||||
$promotion_actions[] = array(
|
||||
'name' => $action->name,
|
||||
'label' => $action_locale->label,
|
||||
'url' => $url,
|
||||
'primary' => isset( $action->is_primary ) ? $action->is_primary : false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$formatted_promotions[] = array(
|
||||
'title' => $locale->title,
|
||||
'description' => $locale->description,
|
||||
'image' => ( 'http' === substr( $locale->image, 0, 4 ) ) ? $locale->image : WC()->plugin_url() . $locale->image,
|
||||
'image_alt' => $locale->image_alt,
|
||||
'actions' => $promotion_actions,
|
||||
);
|
||||
}
|
||||
return $formatted_promotions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
* @package WooCommerce\Admin
|
||||
* @var string $view
|
||||
* @var object $addons
|
||||
* @var object $promotions
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications as PromotionRuleEngine;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
@ -62,7 +65,12 @@ $current_section_name = __( 'Browse Categories', 'woocommerce' );
|
|||
|
||||
<div class="wrap">
|
||||
<div class="marketplace-content-wrapper">
|
||||
<?php if ( ! empty( $search ) ) : ?>
|
||||
<?php if ( count( $addons ) == 0 ) : ?>
|
||||
<h1 class="search-form-title">
|
||||
<?php esc_html_e( 'Sorry, could not find anything. Try searching again using a different term.', 'woocommerce' ); ?></p>
|
||||
</h1>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $search ) && count( $addons ) > 0 ) : ?>
|
||||
<h1 class="search-form-title">
|
||||
<?php // translators: search keyword. ?>
|
||||
<?php printf( esc_html__( 'Search results for "%s"', 'woocommerce' ), esc_html( sanitize_text_field( wp_unslash( $search ) ) ) ); ?>
|
||||
|
@ -77,16 +85,13 @@ $current_section_name = __( 'Browse Categories', 'woocommerce' );
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( '_featured' !== $current_section && $addons ) : ?>
|
||||
<?php if ( 'shipping_methods' === $current_section ) : ?>
|
||||
<div class="addons-shipping-methods">
|
||||
<?php WC_Admin_Addons::output_wcs_banner_block(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( 'payment-gateways' === $current_section ) : ?>
|
||||
<div class="addons-shipping-methods">
|
||||
<?php WC_Admin_Addons::output_wcpay_banner_block(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
if ( ! empty( $promotions ) && WC()->is_wc_admin_active() ) {
|
||||
foreach ( $promotions as $promotion ) {
|
||||
WC_Admin_Addons::output_search_promotion_block( $promotion );
|
||||
}
|
||||
}
|
||||
?>
|
||||
<ul class="products">
|
||||
<?php foreach ( $addons as $addon ) : ?>
|
||||
<?php
|
||||
|
|
|
@ -125,7 +125,6 @@ class WC_AJAX {
|
|||
'get_order_details',
|
||||
'add_attribute',
|
||||
'add_new_attribute',
|
||||
'remove_variation',
|
||||
'remove_variations',
|
||||
'save_attributes',
|
||||
'add_variation',
|
||||
|
|
|
@ -397,7 +397,16 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
protected function get_price_hash( &$product, $for_display = false ) {
|
||||
global $wp_filter;
|
||||
|
||||
$price_hash = $for_display && wc_tax_enabled() ? array( get_option( 'woocommerce_tax_display_shop', 'excl' ), WC_Tax::get_rates() ) : array( false );
|
||||
$price_hash = array( false );
|
||||
|
||||
if ( $for_display && wc_tax_enabled() ) {
|
||||
$price_hash = array(
|
||||
get_option( 'woocommerce_tax_display_shop', 'excl' ),
|
||||
WC_Tax::get_rates(),
|
||||
empty( WC()->customer ) ? false : WC()->customer->is_vat_exempt(),
|
||||
);
|
||||
}
|
||||
|
||||
$filter_names = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
|
||||
|
||||
foreach ( $filter_names as $filter_name ) {
|
||||
|
|
|
@ -1140,7 +1140,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
|
|||
),
|
||||
'email' => array(
|
||||
'description' => __( 'Email address.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'email',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
|
|
|
@ -915,6 +915,12 @@ add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts
|
|||
function wc_cancel_unpaid_orders() {
|
||||
$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
|
||||
|
||||
// Re-schedule the event before cancelling orders
|
||||
// this way in case of a DB timeout or (plugin) crash the event is always scheduled for retry.
|
||||
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
|
||||
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $held_duration ) );
|
||||
wp_schedule_single_event( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
|
||||
|
||||
if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) {
|
||||
return;
|
||||
}
|
||||
|
@ -931,9 +937,6 @@ function wc_cancel_unpaid_orders() {
|
|||
}
|
||||
}
|
||||
}
|
||||
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
|
||||
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $held_duration ) );
|
||||
wp_schedule_single_event( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
|
||||
}
|
||||
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
|||
Requires at least: 5.6
|
||||
Tested up to: 5.8
|
||||
Requires PHP: 7.0
|
||||
Stable tag: 5.7.1
|
||||
Stable tag: 5.8.0
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,9 @@
|
|||
const { ordersApi } = require('./orders');
|
||||
const { couponsApi } = require('./coupons');
|
||||
const { productsApi } = require('./products');
|
||||
|
||||
module.exports = {
|
||||
ordersApi,
|
||||
couponsApi,
|
||||
productsApi,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { getRequest, postRequest, putRequest, deleteRequest } = require('../utils/request');
|
||||
|
||||
/**
|
||||
* WooCommerce Products endpoints.
|
||||
*
|
||||
* https://woocommerce.github.io/woocommerce-rest-api-docs/#products
|
||||
*/
|
||||
const productsApi = {
|
||||
name: 'Products',
|
||||
create: {
|
||||
name: 'Create a product',
|
||||
method: 'POST',
|
||||
path: 'products',
|
||||
responseCode: 201,
|
||||
product: async ( productDetails ) => postRequest( 'products', productDetails ),
|
||||
},
|
||||
retrieve: {
|
||||
name: 'Retrieve a product',
|
||||
method: 'GET',
|
||||
path: 'products/<id>',
|
||||
responseCode: 200,
|
||||
product: async ( productId ) => getRequest( `products/${productId}` ),
|
||||
},
|
||||
listAll: {
|
||||
name: 'List all products',
|
||||
method: 'GET',
|
||||
path: 'products',
|
||||
responseCode: 200,
|
||||
products: async ( productsQuery = {} ) => getRequest( 'products', productsQuery ),
|
||||
},
|
||||
update: {
|
||||
name: 'Update a product',
|
||||
method: 'PUT',
|
||||
path: 'products/<id>',
|
||||
responseCode: 200,
|
||||
product: async ( productId, productDetails ) => putRequest( `products/${productId}`, productDetails ),
|
||||
},
|
||||
delete: {
|
||||
name: 'Delete a product',
|
||||
method: 'DELETE',
|
||||
path: 'products/<id>',
|
||||
responseCode: 200,
|
||||
payload: {
|
||||
force: false
|
||||
},
|
||||
product: async ( productId, deletePermanently ) => deleteRequest( `products/${productId}`, deletePermanently ),
|
||||
},
|
||||
batch: {
|
||||
name: 'Batch update products',
|
||||
method: 'POST',
|
||||
path: 'products/batch',
|
||||
responseCode: 200,
|
||||
products: async ( batchUpdatePayload ) => postRequest( `products/batch`, batchUpdatePayload ),
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
productsApi,
|
||||
};
|
|
@ -0,0 +1,773 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { createSampleData, deleteSampleData } = require( '../../data/products' );
|
||||
const { productsApi } = require('../../endpoints/products');
|
||||
|
||||
/**
|
||||
* Tests for the WooCommerce Products API.
|
||||
*
|
||||
* @group api
|
||||
* @group products
|
||||
*
|
||||
*/
|
||||
describe( 'Products API tests', () => {
|
||||
|
||||
const PRODUCTS_COUNT = 20;
|
||||
let sampleData;
|
||||
|
||||
beforeAll( async () => {
|
||||
sampleData = await createSampleData();
|
||||
}, 10000 );
|
||||
|
||||
afterAll( async () => {
|
||||
await deleteSampleData( sampleData );
|
||||
}, 10000 );
|
||||
|
||||
describe( 'List all products', () => {
|
||||
|
||||
it( 'defaults', async () => {
|
||||
const result = await productsApi.listAll.products();
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.headers['x-wp-total'] ).toEqual( PRODUCTS_COUNT.toString() );
|
||||
expect( result.headers['x-wp-totalpages'] ).toEqual( '2' );
|
||||
} );
|
||||
|
||||
it( 'pagination', async () => {
|
||||
const pageSize = 6;
|
||||
const page1 = await productsApi.listAll.products( {
|
||||
per_page: pageSize,
|
||||
} );
|
||||
const page2 = await productsApi.listAll.products( {
|
||||
per_page: pageSize,
|
||||
page: 2,
|
||||
} );
|
||||
expect( page1.statusCode ).toEqual( 200 );
|
||||
expect( page2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify total page count.
|
||||
expect( page1.headers['x-wp-total'] ).toEqual( PRODUCTS_COUNT.toString() );
|
||||
expect( page1.headers['x-wp-totalpages'] ).toEqual( '4' );
|
||||
|
||||
// Verify we get pageSize'd arrays.
|
||||
expect( Array.isArray( page1.body ) ).toBe( true );
|
||||
expect( Array.isArray( page2.body ) ).toBe( true );
|
||||
expect( page1.body ).toHaveLength( pageSize );
|
||||
expect( page2.body ).toHaveLength( pageSize );
|
||||
|
||||
// Ensure all of the product IDs are unique (no page overlap).
|
||||
const allProductIds = page1.body.concat( page2.body ).reduce( ( acc, product ) => {
|
||||
acc[ product.id ] = 1;
|
||||
return acc;
|
||||
}, {} );
|
||||
expect( Object.keys( allProductIds ) ).toHaveLength( pageSize * 2 );
|
||||
|
||||
// Verify that offset takes precedent over page number.
|
||||
const page2Offset = await productsApi.listAll.products( {
|
||||
per_page: pageSize,
|
||||
page: 2,
|
||||
offset: pageSize + 1,
|
||||
} );
|
||||
// The offset pushes the result set 1 product past the start of page 2.
|
||||
expect( page2Offset.body ).toEqual(
|
||||
expect.not.arrayContaining( [
|
||||
expect.objectContaining( { id: page2.body[0].id } )
|
||||
] )
|
||||
);
|
||||
expect( page2Offset.body[0].id ).toEqual( page2.body[1].id );
|
||||
|
||||
// Verify the last page only has 2 products as we expect.
|
||||
const lastPage = await productsApi.listAll.products( {
|
||||
per_page: pageSize,
|
||||
page: 4,
|
||||
} );
|
||||
expect( Array.isArray( lastPage.body ) ).toBe( true );
|
||||
expect( lastPage.body ).toHaveLength( 2 );
|
||||
|
||||
// Verify a page outside the total page count is empty.
|
||||
const page6 = await productsApi.listAll.products( {
|
||||
per_page: pageSize,
|
||||
page: 6,
|
||||
} );
|
||||
expect( Array.isArray( page6.body ) ).toBe( true );
|
||||
expect( page6.body ).toHaveLength( 0 );
|
||||
} );
|
||||
|
||||
it( 'search', async () => {
|
||||
// Match in the short description.
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
search: 'external'
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].name ).toBe( 'WordPress Pennant' );
|
||||
|
||||
// Match in the product name.
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
search: 'pocket'
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 1 );
|
||||
expect( result2.body[0].name ).toBe( 'Hoodie with Pocket' );
|
||||
} );
|
||||
|
||||
it( 'inclusion / exclusion', async () => {
|
||||
const allProducts = await productsApi.listAll.products( {
|
||||
per_page: 20,
|
||||
} );
|
||||
expect( allProducts.statusCode ).toEqual( 200 );
|
||||
const allProductIds = allProducts.body.map( product => product.id );
|
||||
expect( allProductIds ).toHaveLength( PRODUCTS_COUNT );
|
||||
|
||||
const productsToFilter = [
|
||||
allProductIds[2],
|
||||
allProductIds[4],
|
||||
allProductIds[7],
|
||||
allProductIds[13],
|
||||
];
|
||||
|
||||
const included = await productsApi.listAll.products( {
|
||||
per_page: 20,
|
||||
include: productsToFilter.join( ',' ),
|
||||
} );
|
||||
expect( included.statusCode ).toEqual( 200 );
|
||||
expect( included.body ).toHaveLength( productsToFilter.length );
|
||||
expect( included.body ).toEqual(
|
||||
expect.arrayContaining(
|
||||
productsToFilter.map( id => expect.objectContaining( { id } ) )
|
||||
)
|
||||
);
|
||||
|
||||
const excluded = await productsApi.listAll.products( {
|
||||
per_page: 20,
|
||||
exclude: productsToFilter.join( ',' ),
|
||||
} );
|
||||
expect( excluded.statusCode ).toEqual( 200 );
|
||||
expect( excluded.body ).toHaveLength( PRODUCTS_COUNT - productsToFilter.length );
|
||||
expect( excluded.body ).toEqual(
|
||||
expect.not.arrayContaining(
|
||||
productsToFilter.map( id => expect.objectContaining( { id } ) )
|
||||
)
|
||||
);
|
||||
|
||||
} );
|
||||
|
||||
it( 'slug', async () => {
|
||||
// Match by slug.
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
slug: 't-shirt-with-logo'
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].slug ).toBe( 't-shirt-with-logo' );
|
||||
|
||||
// No matches
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
slug: 'no-product-with-this-slug'
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 0 );
|
||||
} );
|
||||
|
||||
it( 'sku', async () => {
|
||||
// Match by SKU.
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
sku: 'woo-sunglasses'
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].sku ).toBe( 'woo-sunglasses' );
|
||||
|
||||
// No matches
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
sku: 'no-product-with-this-sku'
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 0 );
|
||||
} );
|
||||
|
||||
it( 'type', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
type: 'simple'
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.headers['x-wp-total'] ).toEqual( '16' );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
type: 'external'
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 1 );
|
||||
expect( result2.body[0].name ).toBe( 'WordPress Pennant' );
|
||||
|
||||
const result3 = await productsApi.listAll.products( {
|
||||
type: 'variable'
|
||||
} );
|
||||
expect( result3.statusCode ).toEqual( 200 );
|
||||
expect( result3.body ).toHaveLength( 2 );
|
||||
|
||||
const result4 = await productsApi.listAll.products( {
|
||||
type: 'grouped'
|
||||
} );
|
||||
expect( result4.statusCode ).toEqual( 200 );
|
||||
expect( result4.body ).toHaveLength( 1 );
|
||||
expect( result4.body[0].name ).toBe( 'Logo Collection' );
|
||||
} );
|
||||
|
||||
it( 'featured', async () => {
|
||||
const featured = [
|
||||
expect.objectContaining( { name: 'Hoodie with Zipper' } ),
|
||||
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
|
||||
expect.objectContaining( { name: 'Sunglasses' } ),
|
||||
expect.objectContaining( { name: 'Cap' } ),
|
||||
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
|
||||
];
|
||||
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
featured: true,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( featured.length );
|
||||
expect( result1.body ).toEqual( expect.arrayContaining( featured ) );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
featured: false,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toEqual( expect.not.arrayContaining( featured ) );
|
||||
} );
|
||||
|
||||
it( 'categories', async () => {
|
||||
const accessory = [
|
||||
expect.objectContaining( { name: 'Beanie' } ),
|
||||
]
|
||||
const hoodies = [
|
||||
expect.objectContaining( { name: 'Hoodie with Zipper' } ),
|
||||
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
|
||||
expect.objectContaining( { name: 'Hoodie with Logo' } ),
|
||||
expect.objectContaining( { name: 'Hoodie' } ),
|
||||
];
|
||||
|
||||
// Verify that subcategories are included.
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
per_page: 20,
|
||||
category: sampleData.categories.clothing.id,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toEqual( expect.arrayContaining( accessory ) );
|
||||
expect( result1.body ).toEqual( expect.arrayContaining( hoodies ) );
|
||||
|
||||
// Verify sibling categories are not.
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
category: sampleData.categories.hoodies.id,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toEqual( expect.not.arrayContaining( accessory ) );
|
||||
expect( result2.body ).toEqual( expect.arrayContaining( hoodies ) );
|
||||
} );
|
||||
|
||||
it( 'on sale', async () => {
|
||||
const onSale = [
|
||||
expect.objectContaining( { name: 'Beanie with Logo' } ),
|
||||
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
|
||||
expect.objectContaining( { name: 'Single' } ),
|
||||
expect.objectContaining( { name: 'Cap' } ),
|
||||
expect.objectContaining( { name: 'Belt' } ),
|
||||
expect.objectContaining( { name: 'Beanie' } ),
|
||||
expect.objectContaining( { name: 'Hoodie' } ),
|
||||
];
|
||||
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
on_sale: true,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( onSale.length );
|
||||
expect( result1.body ).toEqual( expect.arrayContaining( onSale ) );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
on_sale: false,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toEqual( expect.not.arrayContaining( onSale ) );
|
||||
} );
|
||||
|
||||
it( 'price', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
min_price: 21,
|
||||
max_price: 28,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].name ).toBe( 'Long Sleeve Tee' );
|
||||
expect( result1.body[0].price ).toBe( '25' );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
max_price: 5,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 1 );
|
||||
expect( result2.body[0].name ).toBe( 'Single' );
|
||||
expect( result2.body[0].price ).toBe( '2' );
|
||||
|
||||
const result3 = await productsApi.listAll.products( {
|
||||
min_price: 5,
|
||||
order: 'asc',
|
||||
orderby: 'price',
|
||||
} );
|
||||
expect( result3.statusCode ).toEqual( 200 );
|
||||
expect( result3.body ).toEqual(
|
||||
expect.not.arrayContaining( [
|
||||
expect.objectContaining( { name: 'Single' } )
|
||||
] )
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'before / after', async () => {
|
||||
const before = [
|
||||
expect.objectContaining( { name: 'Album' } ),
|
||||
expect.objectContaining( { name: 'Single' } ),
|
||||
expect.objectContaining( { name: 'T-Shirt with Logo' } ),
|
||||
expect.objectContaining( { name: 'Beanie with Logo' } ),
|
||||
];
|
||||
const after = [
|
||||
expect.objectContaining( { name: 'Hoodie' } ),
|
||||
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
|
||||
expect.objectContaining( { name: 'Parent Product' } ),
|
||||
expect.objectContaining( { name: 'Child Product' } ),
|
||||
];
|
||||
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
before: '2021-09-05T15:50:19',
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( before.length );
|
||||
expect( result1.body ).toEqual( expect.arrayContaining( before ) );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
after: '2021-09-18T15:50:18',
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toEqual( expect.not.arrayContaining( before ) );
|
||||
expect( result2.body ).toHaveLength( after.length );
|
||||
expect( result2.body ).toEqual( expect.arrayContaining( after ) );
|
||||
} );
|
||||
|
||||
it( 'attributes', async () => {
|
||||
const red = sampleData.attributes.colors.find( term => term.name === 'Red' );
|
||||
|
||||
const redProducts = [
|
||||
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
|
||||
expect.objectContaining( { name: 'Hoodie' } ),
|
||||
expect.objectContaining( { name: 'Beanie' } ),
|
||||
expect.objectContaining( { name: 'Beanie with Logo' } ),
|
||||
];
|
||||
|
||||
const result = await productsApi.listAll.products( {
|
||||
attribute: 'pa_color',
|
||||
attribute_term: red.id,
|
||||
} );
|
||||
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.body ).toHaveLength( redProducts.length );
|
||||
expect( result.body ).toEqual( expect.arrayContaining( redProducts ) );
|
||||
} );
|
||||
|
||||
it( 'status', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
status: 'pending'
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].name ).toBe( 'Polo' );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
status: 'draft'
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( 0 );
|
||||
} );
|
||||
|
||||
it( 'shipping class', async () => {
|
||||
const result = await productsApi.listAll.products( {
|
||||
shipping_class: sampleData.shippingClasses.freight.id,
|
||||
} );
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.body ).toHaveLength( 1 );
|
||||
expect( result.body[0].name ).toBe( 'Long Sleeve Tee' );
|
||||
} );
|
||||
|
||||
it( 'tax class', async () => {
|
||||
const result = await productsApi.listAll.products( {
|
||||
tax_class: 'reduced-rate',
|
||||
} );
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.body ).toHaveLength( 1 );
|
||||
expect( result.body[0].name ).toBe( 'Sunglasses' );
|
||||
} );
|
||||
|
||||
it( 'stock status', async () => {
|
||||
const result = await productsApi.listAll.products( {
|
||||
stock_status: 'onbackorder',
|
||||
} );
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.body ).toHaveLength( 1 );
|
||||
expect( result.body[0].name ).toBe( 'T-Shirt' );
|
||||
} );
|
||||
|
||||
it( 'tags', async () => {
|
||||
const coolProducts = [
|
||||
expect.objectContaining( { name: 'Sunglasses' } ),
|
||||
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
|
||||
expect.objectContaining( { name: 'Beanie' } ),
|
||||
];
|
||||
|
||||
const result = await productsApi.listAll.products( {
|
||||
tag: sampleData.tags.cool.id,
|
||||
} );
|
||||
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
expect( result.body ).toHaveLength( coolProducts.length );
|
||||
expect( result.body ).toEqual( expect.arrayContaining( coolProducts ) );
|
||||
} );
|
||||
|
||||
it( 'parent', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
parent: sampleData.hierarchicalProducts.parent.id,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( 1 );
|
||||
expect( result1.body[0].name ).toBe( 'Child Product' );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
parent_exclude: sampleData.hierarchicalProducts.parent.id,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toEqual( expect.not.arrayContaining( [
|
||||
expect.objectContaining( { name: 'Child Product' } ),
|
||||
] ) );
|
||||
} );
|
||||
|
||||
describe( 'orderby', () => {
|
||||
const productNamesAsc = [
|
||||
'Album',
|
||||
'Beanie',
|
||||
'Beanie with Logo',
|
||||
'Belt',
|
||||
'Cap',
|
||||
'Child Product',
|
||||
'Hoodie',
|
||||
'Hoodie with Logo',
|
||||
'Hoodie with Pocket',
|
||||
'Hoodie with Zipper',
|
||||
'Logo Collection',
|
||||
'Long Sleeve Tee',
|
||||
'Parent Product',
|
||||
'Polo',
|
||||
'Single',
|
||||
'Sunglasses',
|
||||
'T-Shirt',
|
||||
'T-Shirt with Logo',
|
||||
'V-Neck T-Shirt',
|
||||
'WordPress Pennant',
|
||||
];
|
||||
const productNamesDesc = [ ...productNamesAsc ].reverse();
|
||||
const productNamesByRatingAsc = [
|
||||
'Sunglasses',
|
||||
'Cap',
|
||||
'T-Shirt',
|
||||
];
|
||||
const productNamesByRatingDesc = [ ...productNamesByRatingAsc ].reverse();
|
||||
const productNamesByPopularityDesc = [
|
||||
'Beanie with Logo',
|
||||
'Single',
|
||||
'T-Shirt',
|
||||
];
|
||||
const productNamesByPopularityAsc = [ ...productNamesByPopularityDesc ].reverse();
|
||||
|
||||
it( 'default', async () => {
|
||||
// Default = date desc.
|
||||
const result = await productsApi.listAll.products();
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all dates are in descending order.
|
||||
let lastDate = Date.now();
|
||||
result.body.forEach( ( { date_created_gmt } ) => {
|
||||
const created = Date.parse( date_created_gmt + '.000Z' );
|
||||
expect( lastDate ).toBeGreaterThan( created );
|
||||
lastDate = created;
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'date', async () => {
|
||||
const result = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'date',
|
||||
} );
|
||||
expect( result.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all dates are in ascending order.
|
||||
let lastDate = 0;
|
||||
result.body.forEach( ( { date_created_gmt } ) => {
|
||||
const created = Date.parse( date_created_gmt + '.000Z' );
|
||||
expect( created ).toBeGreaterThan( lastDate );
|
||||
lastDate = created;
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'id', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'id',
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
let lastId = 0;
|
||||
result1.body.forEach( ( { id } ) => {
|
||||
expect( id ).toBeGreaterThan( lastId );
|
||||
lastId = id;
|
||||
} );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'id',
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
lastId = Number.MAX_SAFE_INTEGER;
|
||||
result2.body.forEach( ( { id } ) => {
|
||||
expect( lastId ).toBeGreaterThan( id );
|
||||
lastId = id;
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'title', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'title',
|
||||
per_page: productNamesAsc.length,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
result1.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesAsc[ idx ] );
|
||||
} );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'title',
|
||||
per_page: productNamesDesc.length,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
result2.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesDesc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
// This case will remain skipped until orderby slug is fixed.
|
||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
||||
it.skip( 'slug', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'slug',
|
||||
per_page: productNamesAsc.length,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
result1.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesAsc[ idx ] );
|
||||
} );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'slug',
|
||||
per_page: productNamesDesc.length,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
result2.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesDesc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'price', async () => {
|
||||
const productNamesMinPriceAsc = [
|
||||
'Parent Product',
|
||||
'Child Product',
|
||||
'Single',
|
||||
'WordPress Pennant',
|
||||
'Album',
|
||||
'V-Neck T-Shirt',
|
||||
'Cap',
|
||||
'Beanie with Logo',
|
||||
'T-Shirt with Logo',
|
||||
'Beanie',
|
||||
'T-Shirt',
|
||||
'Logo Collection',
|
||||
'Polo',
|
||||
'Long Sleeve Tee',
|
||||
'Hoodie with Pocket',
|
||||
'Hoodie',
|
||||
'Hoodie with Zipper',
|
||||
'Hoodie with Logo',
|
||||
'Belt',
|
||||
'Sunglasses',
|
||||
];
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'price',
|
||||
per_page: productNamesMinPriceAsc.length
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( productNamesMinPriceAsc.length );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
// The query uses the min price calculated in the product meta lookup table,
|
||||
// so we can't just check the price property of the response.
|
||||
result1.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesMinPriceAsc[ idx ] );
|
||||
} );
|
||||
|
||||
const productNamesMaxPriceDesc = [
|
||||
'Sunglasses',
|
||||
'Belt',
|
||||
'Hoodie',
|
||||
'Logo Collection',
|
||||
'Hoodie with Logo',
|
||||
'Hoodie with Zipper',
|
||||
'Hoodie with Pocket',
|
||||
'Long Sleeve Tee',
|
||||
'V-Neck T-Shirt',
|
||||
'Polo',
|
||||
'T-Shirt',
|
||||
'Beanie',
|
||||
'T-Shirt with Logo',
|
||||
'Beanie with Logo',
|
||||
'Cap',
|
||||
'Album',
|
||||
'WordPress Pennant',
|
||||
'Single',
|
||||
'Child Product',
|
||||
'Parent Product',
|
||||
];
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'price',
|
||||
per_page: productNamesMaxPriceDesc.length
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( productNamesMaxPriceDesc.length );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
// The query uses the max price calculated in the product meta lookup table,
|
||||
// so we can't just check the price property of the response.
|
||||
result2.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesMaxPriceDesc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
// This case will remain skipped until orderby include is fixed.
|
||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
||||
it.skip( 'include', async () => {
|
||||
const includeIds = [
|
||||
sampleData.groupedProducts[ 0 ].id,
|
||||
sampleData.simpleProducts[ 3 ].id,
|
||||
sampleData.hierarchicalProducts.parent.id,
|
||||
];
|
||||
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'include',
|
||||
include: includeIds.join( ',' ),
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
expect( result1.body ).toHaveLength( includeIds.length );
|
||||
|
||||
// Verify all results are in proper order.
|
||||
result1.body.forEach( ( { id }, idx ) => {
|
||||
expect( id ).toBe( includeIds[ idx ] );
|
||||
} );
|
||||
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'include',
|
||||
include: includeIds.join( ',' ),
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
expect( result2.body ).toHaveLength( includeIds.length );
|
||||
|
||||
// Verify all results are in proper order.
|
||||
result2.body.forEach( ( { id }, idx ) => {
|
||||
expect( id ).toBe( includeIds[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'rating (desc)', async () => {
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'rating',
|
||||
per_page: productNamesByRatingDesc.length,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
result2.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesByRatingDesc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
// This case will remain skipped until ratings can be sorted ascending.
|
||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
||||
it.skip( 'rating (asc)', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'rating',
|
||||
per_page: productNamesByRatingAsc.length,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
result1.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesByRatingAsc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'popularity (desc)', async () => {
|
||||
const result2 = await productsApi.listAll.products( {
|
||||
order: 'desc',
|
||||
orderby: 'popularity',
|
||||
per_page: productNamesByPopularityDesc.length,
|
||||
} );
|
||||
expect( result2.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in descending order.
|
||||
result2.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesByPopularityDesc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
|
||||
// This case will remain skipped until popularity can be sorted ascending.
|
||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
||||
it.skip( 'popularity (asc)', async () => {
|
||||
const result1 = await productsApi.listAll.products( {
|
||||
order: 'asc',
|
||||
orderby: 'popularity',
|
||||
per_page: productNamesByPopularityAsc.length,
|
||||
} );
|
||||
expect( result1.statusCode ).toEqual( 200 );
|
||||
|
||||
// Verify all results are in ascending order.
|
||||
result1.body.forEach( ( { name }, idx ) => {
|
||||
expect( name ).toBe( productNamesByPopularityAsc[ idx ] );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -765,6 +765,26 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"axios": "^0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
|
@ -988,11 +1008,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"version": "0.21.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz",
|
||||
"integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
|
@ -1492,6 +1512,7 @@
|
|||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
|
@ -1889,12 +1910,9 @@
|
|||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
"version": "1.14.4",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
|
||||
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g=="
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
|
@ -3249,7 +3267,8 @@
|
|||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
|
@ -4494,9 +4513,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"tmpl": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
|
||||
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.19.2",
|
||||
"axios": "0.21.2",
|
||||
"create-hmac": "1.1.7",
|
||||
"oauth-1.0a": "2.2.6"
|
||||
},
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Added `LATEST_WP_VERSION_MINUS` that allows setting a number to subtract from the current WordPress version for the WordPress Docker image.
|
||||
|
||||
# 0.2.3
|
||||
|
||||
## Added
|
||||
|
|
|
@ -102,6 +102,18 @@ This value will override the default Jest timeout as well as pass the timeout to
|
|||
|
||||
For a list of the methods that the above timeout affects, please see the Puppeteer documentation for [`page.setDefaultTimeout()`](https://pptr.dev/#?product=Puppeteer&version=v10.2.0&show=api-pagesetdefaulttimeouttimeout) and [`page.setDefaultNavigationTimeout`](https://pptr.dev/#?product=Puppeteer&version=v10.2.0&show=api-pagesetdefaultnavigationtimeouttimeout) for more information.
|
||||
|
||||
### Test Against Previous WordPress Versions
|
||||
|
||||
You can use the `LATEST_WP_VERSION_MINUS` flag to determine how many versions back from the current WordPress version to use in the Docker environment. This is calculated from the current WordPress version minus the set value. For example, if `LATEST_WP_VERSION_MINUS` is set to 1, it will calculate the current WordPress version minus one, and use that for the WordPress Docker container.
|
||||
|
||||
For example, you could run the following command:
|
||||
|
||||
```bash
|
||||
LATEST_WP_VERSION_MINUS=2 npx wc-e2e docker:up
|
||||
```
|
||||
|
||||
In this example, if the current WordPress version is 6.0, this will go two versions back and use the WordPress 5.8 Docker image for the tests.
|
||||
|
||||
### Jest Puppeteer Config
|
||||
|
||||
The test sequencer uses the following default Puppeteer configuration:
|
||||
|
|
|
@ -13,6 +13,10 @@ if [[ $1 ]]; then
|
|||
export WORDPRESS_VERSION="5.8.0"
|
||||
fi
|
||||
|
||||
if [[ $LATEST_WP_VERSION_MINUS ]]; then
|
||||
export WORDPRESS_VERSION=$(./bin/get-previous-version.js $WORDPRESS_VERSION $LATEST_WP_VERSION_MINUS 2> /dev/null)
|
||||
fi
|
||||
|
||||
if ! [[ $TRAVIS_PHP_VERSION =~ ^[0-9]+\.[0-9]+ ]]; then
|
||||
TRAVIS_PHP_VERSION=$(./bin/get-latest-docker-tag.js php 7 2> /dev/null)
|
||||
fi
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const https = require( 'https' );
|
||||
const semver = require( 'semver' );
|
||||
const getLatestMinusVersion = require( './get-previous-version' );
|
||||
|
||||
/**
|
||||
* Fetches the latest tag from a page using the Docker HTTP api.
|
||||
|
@ -107,6 +108,10 @@ function findLatestVersion( image, nameSearch ) {
|
|||
return fetchLatestTagFromPage( image, nameSearch, ++page ).then( paginationFn );
|
||||
}
|
||||
|
||||
if ( image === 'wordpress' && process.env.LATEST_WP_VERSION_MINUS ) {
|
||||
return getLatestMinusVersion( latestVersion.toString(), process.env.LATEST_WP_VERSION_MINUS );
|
||||
}
|
||||
|
||||
return latestVersion.toString();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {latestVersion} latestVersion
|
||||
* @param {minus} minus
|
||||
* @returns {String} the minused version.
|
||||
*/
|
||||
function getLatestMinusVersion( latestVersion, minus ) {
|
||||
// Convert the 1 or 2 to a decimal we can use for the logic below.
|
||||
let minusAmount = minus / 10;
|
||||
|
||||
// Check if we only have a major / minor (e.g. x.x) to append a patch version
|
||||
if ( latestVersion.match( /\./g ).length < 2 ) {
|
||||
latestVersion = latestVersion.concat( '.0' )
|
||||
}
|
||||
|
||||
const baseVersion = latestVersion.replace( /.[^\.]$/, '' );
|
||||
|
||||
// Calculate the version we need and return.
|
||||
console.info( String( baseVersion - minusAmount ) );
|
||||
process.exit( 0 );
|
||||
}
|
||||
|
||||
const latestVersion = process.argv[2];
|
||||
const minus = process.argv[3];
|
||||
if ( ! latestVersion || ! minus ) {
|
||||
console.error( 'Usage: get-previous-version.js <latestVersion> <minus>' );
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
getLatestMinusVersion( latestVersion, minus );
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class WC_Product_Variable_Data_Store_CPT_Test
|
||||
*/
|
||||
class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Helper filter to force prices inclusice of tax.
|
||||
*/
|
||||
public function __return_incl() {
|
||||
return 'incl';
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Variation price cache accounts for Customer VAT exemption.
|
||||
*/
|
||||
public function test_variation_price_cache_vat_exempt() {
|
||||
// Set store to include tax in price display.
|
||||
add_filter( 'wc_tax_enabled', '__return_true' );
|
||||
add_filter( 'woocommerce_prices_include_tax', '__return_true' );
|
||||
add_filter( 'pre_option_woocommerce_tax_display_shop', array( $this, '__return_incl' ) );
|
||||
add_filter( 'pre_option_woocommerce_tax_display_cart', array( $this, '__return_incl' ) );
|
||||
|
||||
// Create tax rate.
|
||||
$tax_id = WC_Tax::_insert_tax_rate(
|
||||
array(
|
||||
'tax_rate_country' => '',
|
||||
'tax_rate_state' => '',
|
||||
'tax_rate' => '10.0000',
|
||||
'tax_rate_name' => 'VAT',
|
||||
'tax_rate_priority' => '1',
|
||||
'tax_rate_compound' => '0',
|
||||
'tax_rate_shipping' => '1',
|
||||
'tax_rate_order' => '1',
|
||||
'tax_rate_class' => '',
|
||||
)
|
||||
);
|
||||
|
||||
// Create our variable product.
|
||||
$product = WC_Helper_Product::create_variation_product();
|
||||
|
||||
// Verify that a VAT exempt customer gets prices with tax removed.
|
||||
WC()->customer->set_is_vat_exempt( true );
|
||||
|
||||
$prices_no_tax = array( '9.09', '13.64', '14.55', '15.45', '16.36', '17.27' );
|
||||
$variation_prices = $product->get_variation_prices( true );
|
||||
|
||||
$this->assertEquals( $prices_no_tax, array_values( $variation_prices['price'] ) );
|
||||
|
||||
// Verify that a normal customer gets prices with tax included.
|
||||
// This indirectly proves that the customer's VAT exemption influences the cache key.
|
||||
WC()->customer->set_is_vat_exempt( false );
|
||||
|
||||
$prices_with_tax = array( '10.00', '15.00', '16.00', '17.00', '18.00', '19.00' );
|
||||
$variation_prices = $product->get_variation_prices( true );
|
||||
|
||||
$this->assertEquals( $prices_with_tax, array_values( $variation_prices['price'] ) );
|
||||
|
||||
// Clean up.
|
||||
WC_Tax::_delete_tax_rate( $tax_id );
|
||||
|
||||
remove_filter( 'wc_tax_enabled', '__return_true' );
|
||||
remove_filter( 'woocommerce_prices_include_tax', '__return_true' );
|
||||
remove_filter( 'pre_option_woocommerce_tax_display_shop', array( $this, '__return_incl' ) );
|
||||
remove_filter( 'pre_option_woocommerce_tax_display_cart', array( $this, '__return_incl' ) );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue