diff --git a/.travis.yml b/.travis.yml
index 216fb609ea5..4503220a027 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,24 +1,24 @@
version: ~> 1.0
+# Specifies that Travis should create builds for master and release branches and also tags.
+branches:
+ only:
+ - master
+ - /^\d+\.\d+(\.\d+)?(-\S*)?$/
+ - /^release\//
+
language: php
+os:
+ - linux
dist: xenial
-cache:
- directories:
- - $HOME/.composer/cache
-
-# Since Xenial services are not started by default, we need to instruct it below to start.
-services:
- - mysql
- - docker
-
# Test main supported versions of PHP against latest WP.
php:
- - 7.0
- - 7.1
- - 7.2
- - 7.3
- - 7.4
+ - "7.0"
+ - "7.1"
+ - "7.2"
+ - "7.3"
+ - "7.4"
env:
- WP_VERSION=latest WP_MULTISITE=0
@@ -35,30 +35,48 @@ jobs:
- npm install
- composer install --no-dev
script:
- - npm run build:assets
- - npm run docker:up
- - npm run test:e2e
+ - travis_retry npm run build:assets
+ - travis_retry npm run docker:up
+ - travis_retry npm run test:e2e
after_script:
- npm run docker:down
- name: "WP Nightly"
- php: 7.4
+ php: "7.4"
env: WP_VERSION=nightly WP_MULTISITE=0
- name: "WP Latest - 1"
- php: 7.2
+ php: "7.2"
env: WP_VERSION=5.4 WP_MULTISITE=0
- name: "WP Latest - 2"
- php: 7.2
+ php: "7.2"
env: WP_VERSION=5.3 WP_MULTISITE=0
- name: "Code Standards"
- php: 7.4
+ php: "7.4"
env: WP_VERSION=latest WP_MULTISITE=0 RUN_PHPCS=1
- name: "Code Coverage"
- php: 7.4
+ php: "7.4"
env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
allow_failures:
- - php: 7.4
+ - php: "7.4"
env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
+# Git clone depth
+# By default Travis CI clones repositories to a depth of 50 commits. Using a depth of 1 makes this step a bit faster.
+git:
+ depth: 1
+
+# Since Xenial services are not started by default, we need to instruct it below to start.
+services:
+ - mysql
+ - docker
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+# Composer 2.0.7 introduced a change that broke the jetpack autoloader in PHP 7.0 - 7.3.
+before_install:
+ - composer self-update 2.0.6
+
install:
- export PATH="$HOME/.composer/vendor/bin:$PATH"
- |
@@ -81,20 +99,3 @@ script:
after_script:
- bash tests/bin/travis.sh after
-
-# Specifies that Travis should create builds for master and release branches and also tags.
-branches:
- only:
- - master
- - /^\d+\.\d+(\.\d+)?(-\S*)?$/
- - /^release\//
-
-# Composer 2.0.7 introduced a change that broke the jetpack autoloader in PHP 7.0 - 7.3.
-before_install:
- - composer self-update 2.0.6
-
-# Git clone depth
-# By default Travis CI clones repositories to a depth of 50 commits. Using a depth of 1 makes this step a bit faster.
-git:
- depth: 1
-
diff --git a/assets/css/admin.scss b/assets/css/admin.scss
index 1b6fbdbe9ae..0a1edc7c487 100644
--- a/assets/css/admin.scss
+++ b/assets/css/admin.scss
@@ -360,7 +360,7 @@
}
.addons-button-solid {
- background-color:#674399;
+ background-color: #674399;
color: #fff;
}
@@ -3169,6 +3169,13 @@ table.wc_input_table {
}
}
+table.wc_tax_rates {
+
+ td.country {
+ position: relative;
+ }
+}
+
table.wc_gateways,
table.wc_emails,
table.wc_shipping {
diff --git a/includes/admin/reports/class-wc-report-customer-list.php b/includes/admin/reports/class-wc-report-customer-list.php
index e223570b7b9..4beeb951df9 100644
--- a/includes/admin/reports/class-wc-report-customer-list.php
+++ b/includes/admin/reports/class-wc-report-customer-list.php
@@ -62,6 +62,7 @@ class WC_Report_Customer_List extends WP_List_Table {
delete_user_meta( $user_id, '_money_spent' );
delete_user_meta( $user_id, '_order_count' );
+ delete_user_meta( $user_id, '_last_order' );
/* translators: User display name */
echo '
' . sprintf( esc_html__( 'Refreshed stats for %s', 'woocommerce' ), esc_html( $user->display_name ) ) . '
';
}
diff --git a/includes/class-wc-post-data.php b/includes/class-wc-post-data.php
index 39b89ba29b9..e09d8597190 100644
--- a/includes/class-wc-post-data.php
+++ b/includes/class-wc-post-data.php
@@ -406,8 +406,9 @@ class WC_Post_Data {
$customer->save();
}
- // Delete order count meta.
+ // Delete order count and last order meta.
delete_user_meta( $customer_id, '_order_count' );
+ delete_user_meta( $customer_id, '_last_order' );
}
// Clean up items.
diff --git a/includes/data-stores/class-wc-customer-data-store.php b/includes/data-stores/class-wc-customer-data-store.php
index 65da3bba514..30694d86a12 100644
--- a/includes/data-stores/class-wc-customer-data-store.php
+++ b/includes/data-stores/class-wc-customer-data-store.php
@@ -64,6 +64,7 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
'syntax_highlighting',
'_order_count',
'_money_spent',
+ '_last_order',
'_woocommerce_tracks_anon_id',
);
@@ -325,21 +326,30 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
* @return WC_Order|false
*/
public function get_last_order( &$customer ) {
- global $wpdb;
-
- $last_order = $wpdb->get_var(
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
- "SELECT posts.ID
- FROM $wpdb->posts AS posts
- LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
- WHERE meta.meta_key = '_customer_user'
- AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
- AND posts.post_type = 'shop_order'
- AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )
- ORDER BY posts.ID DESC"
- // phpcs:enable
+ $last_order = apply_filters(
+ 'woocommerce_customer_get_last_order',
+ get_user_meta( $customer->get_id(), '_last_order', true ),
+ $customer
);
+ if ( '' === $last_order ) {
+ global $wpdb;
+
+ $last_order = $wpdb->get_var(
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
+ "SELECT posts.ID
+ FROM $wpdb->posts AS posts
+ LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
+ WHERE meta.meta_key = '_customer_user'
+ AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
+ AND posts.post_type = 'shop_order'
+ AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )
+ ORDER BY posts.ID DESC"
+ // phpcs:enable
+ );
+ update_user_meta( $customer->get_id(), '_last_order', $last_order );
+ }
+
if ( ! $last_order ) {
return false;
}
@@ -355,7 +365,11 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
* @return integer
*/
public function get_order_count( &$customer ) {
- $count = get_user_meta( $customer->get_id(), '_order_count', true );
+ $count = apply_filters(
+ 'woocommerce_customer_get_order_count',
+ get_user_meta( $customer->get_id(), '_order_count', true ),
+ $customer
+ );
if ( '' === $count ) {
global $wpdb;
diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php
index 9e4158266fd..2cb6cf3a2f6 100644
--- a/includes/wc-order-functions.php
+++ b/includes/wc-order-functions.php
@@ -456,11 +456,12 @@ function wc_delete_shop_order_transients( $order = 0 ) {
delete_transient( $transient );
}
- // Clear money spent for user associated with order.
+ // Clear customer's order related caches.
if ( is_a( $order, 'WC_Order' ) ) {
$order_id = $order->get_id();
delete_user_meta( $order->get_customer_id(), '_money_spent' );
delete_user_meta( $order->get_customer_id(), '_order_count' );
+ delete_user_meta( $order->get_customer_id(), '_last_order' );
} else {
$order_id = 0;
}
diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php
index 132f5d6acd0..2afa3e0e7bd 100644
--- a/includes/wc-update-functions.php
+++ b/includes/wc-update-functions.php
@@ -701,6 +701,7 @@ function wc_update_230_options() {
// _money_spent and _order_count may be out of sync - clear them
delete_metadata( 'user', 0, '_money_spent', '', true );
delete_metadata( 'user', 0, '_order_count', '', true );
+ delete_metadata( 'user', 0, '_last_order', '', true );
// To prevent taxes being hidden when using a default 'no address' in a store with tax inc prices, set the woocommerce_default_customer_address to use the store base address by default.
if ( '' === get_option( 'woocommerce_default_customer_address', false ) && wc_prices_include_tax() ) {
diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php
index ae59b7e8b91..0c30c4f1aeb 100644
--- a/includes/wc-user-functions.php
+++ b/includes/wc-user-functions.php
@@ -272,6 +272,7 @@ function wc_update_new_customer_past_orders( $customer_id ) {
update_user_meta( $customer_id, 'paying_customer', 1 );
update_user_meta( $customer_id, '_order_count', '' );
update_user_meta( $customer_id, '_money_spent', '' );
+ delete_user_meta( $customer_id, '_last_order' );
}
return $linked;
@@ -897,7 +898,7 @@ function wc_update_user_last_active( $user_id ) {
if ( ! $user_id ) {
return;
}
- update_user_meta( $user_id, 'wc_last_active', (string) strtotime( date( 'Y-m-d', time() ) ) );
+ update_user_meta( $user_id, 'wc_last_active', (string) strtotime( gmdate( 'Y-m-d', time() ) ) );
}
/**
diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md
index f6ca2257bd8..87db6cb988f 100644
--- a/tests/e2e/core-tests/CHANGELOG.md
+++ b/tests/e2e/core-tests/CHANGELOG.md
@@ -5,6 +5,7 @@
- View Single Order test
- Update Order Status test
- Update Order Details test
+- Merchant Order Status Filter tests
## Fixed
diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md
index 8107b6420f7..c90af7e4bf5 100644
--- a/tests/e2e/core-tests/README.md
+++ b/tests/e2e/core-tests/README.md
@@ -52,6 +52,7 @@ The functions to access the core tests are:
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
- `runProductSettingsTest` - Merchant can update product settings
- `runTaxSettingsTest` - Merchant can update tax settings
+- `runOrderStatusFilterTest` - Merchant can filter orders by order status
### Shopper
diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js
index a0d915867a7..b404e8bf19d 100644
--- a/tests/e2e/core-tests/specs/index.js
+++ b/tests/e2e/core-tests/specs/index.js
@@ -2,19 +2,26 @@
/*
* Internal dependencies
*/
+
+// Setup and onboarding tests
const runActivationTest = require( './activate-and-setup/activate.test' );
const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setup/onboarding-tasklist.test' );
const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' );
+
+// Shopper tests
const runCartPageTest = require( './shopper/front-end-cart.test' );
const runCheckoutPageTest = require( './shopper/front-end-checkout.test' );
const runMyAccountPageTest = require( './shopper/front-end-my-account.test' );
const runSingleProductPageTest = require( './shopper/front-end-single-product.test' );
+
+// Merchant tests
const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' );
const runCreateOrderTest = require( './merchant/wp-admin-order-new.test' );
const { runAddSimpleProductTest, runAddVariableProductTest } = require( './merchant/wp-admin-product-new.test' );
const runUpdateGeneralSettingsTest = require( './merchant/wp-admin-settings-general.test' );
const runProductSettingsTest = require( './merchant/wp-admin-settings-product.test' );
const runTaxSettingsTest = require( './merchant/wp-admin-settings-tax.test' );
+const runOrderStatusFiltersTest = require( './merchant/wp-admin-order-status-filters.test' );
const runSetupOnboardingTests = () => {
runActivationTest();
@@ -38,6 +45,7 @@ const runMerchantTests = () => {
runUpdateGeneralSettingsTest();
runProductSettingsTest();
runTaxSettingsTest();
+ runOrderStatusFiltersTest();
}
module.exports = {
@@ -58,5 +66,6 @@ module.exports = {
runUpdateGeneralSettingsTest,
runProductSettingsTest,
runTaxSettingsTest,
+ runOrderStatusFiltersTest,
runMerchantTests,
};
diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-status-filters.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-status-filters.test.js
new file mode 100644
index 00000000000..5cf69225996
--- /dev/null
+++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-status-filters.test.js
@@ -0,0 +1,182 @@
+/* eslint-disable jest/no-export, jest/no-disabled-tests */
+/**
+ * Internal dependencies
+ */
+const {
+ StoreOwnerFlow,
+ createSimpleOrder,
+ clickFilter,
+ moveAllItemsToTrash,
+} = require( '@woocommerce/e2e-utils' );
+
+const statusColumnTextSelector = 'mark.order-status > span';
+
+// Define order statuses to filter against
+const orderStatus = {
+ pending: {
+ name: 'wc-pending',
+ description: { text: 'Pending payment' },
+ },
+ processing: {
+ name: 'wc-processing',
+ description: { text: 'Processing' },
+ },
+ onHold: {
+ name: 'wc-on-hold',
+ description: { text: 'On hold' },
+ },
+ completed: {
+ name: 'wc-completed',
+ description: { text: 'Completed' },
+ },
+ cancelled: {
+ name: 'wc-cancelled',
+ description: { text: 'Cancelled' },
+ },
+ refunded: {
+ name: 'wc-refunded',
+ description: { text: 'Refunded' },
+ },
+ failed: {
+ name: 'wc-failed',
+ description: { text: 'Failed' },
+ }
+};
+
+const runOrderStatusFiltersTest = () => {
+ describe('WooCommerce Orders > Filter Orders by Status', () => {
+ beforeAll(async () => {
+ // First, let's login
+ await StoreOwnerFlow.login();
+
+ // Next, let's create some orders we can filter against
+ await createSimpleOrder(orderStatus.pending.description.text);
+ await createSimpleOrder(orderStatus.processing.description.text);
+ await createSimpleOrder(orderStatus.onHold.description.text);
+ await createSimpleOrder(orderStatus.completed.description.text);
+ await createSimpleOrder(orderStatus.cancelled.description.text);
+ await createSimpleOrder(orderStatus.refunded.description.text);
+ await createSimpleOrder(orderStatus.failed.description.text);
+ }, 40000);
+
+ afterAll( async () => {
+ // Make sure we're on the all orders view and cleanup the orders we created
+ await StoreOwnerFlow.openAllOrdersView();
+ await moveAllItemsToTrash();
+ });
+
+ it('should filter by Pending payment', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.pending.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by Processing', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.processing.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by On hold', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.onHold.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by Completed', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.completed.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by Cancelled', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.cancelled.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by Refunded', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.refunded.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ it('should filter by Failed', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ await clickFilter('.' + orderStatus.failed.name);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+
+ // Verify other statuses don't show
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).not.toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ });
+
+ it('should filter by All', async () => {
+ await StoreOwnerFlow.openAllOrdersView();
+ // Make sure all the order statuses that were created show in this list
+ await clickFilter('.all');
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.pending.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.processing.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.completed.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.cancelled.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.refunded.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.onHold.description);
+ await expect(page).toMatchElement(statusColumnTextSelector, orderStatus.failed.description);
+ });
+
+ });
+};
+
+module.exports = runOrderStatusFiltersTest;
diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md
index b6a38c5bdc5..d1f5c9be9c1 100644
--- a/tests/e2e/env/CHANGELOG.md
+++ b/tests/e2e/env/CHANGELOG.md
@@ -1,5 +1,9 @@
# Unreleased
+## Added
+
+- Insert a 12 hour delay in using new docker image tags
+
## Fixed
- Remove redundant `puppeteer` dependency
diff --git a/tests/e2e/env/bin/get-latest-docker-tag.js b/tests/e2e/env/bin/get-latest-docker-tag.js
index 1e3814e6c89..23adf48317a 100755
--- a/tests/e2e/env/bin/get-latest-docker-tag.js
+++ b/tests/e2e/env/bin/get-latest-docker-tag.js
@@ -36,12 +36,22 @@ async function fetchLatestTagFromPage( image, nameSearch, page ) {
if ( ! data.count ) {
reject( "No image '" + image + '" found' );
} else {
+ // Implement a 12 hour delay on pulling newly released docker tags.
+ const delayMilliseconds = 12 * 3600 * 1000;
+ const currentTime = Date.now();
let latestTag = null;
+ let lastUpdated = null;
for ( let tag of data.results ) {
tag.semver = tag.name.match( /^\d+\.\d+(.\d+)*$/ );
if ( ! tag.semver ) {
continue;
}
+
+ lastUpdated = Date.parse( tag.last_updated );
+ if ( currentTime - lastUpdated < delayMilliseconds ) {
+ continue;
+ }
+
tag.semver = semver.coerce( tag.semver[0] );
if ( ! latestTag || semver.gt( tag.semver, latestTag.semver ) ) {
latestTag = tag;
diff --git a/tests/e2e/specs/wp-admin/test-order-status-filters.js b/tests/e2e/specs/wp-admin/test-order-status-filters.js
new file mode 100644
index 00000000000..5de9b3c2a50
--- /dev/null
+++ b/tests/e2e/specs/wp-admin/test-order-status-filters.js
@@ -0,0 +1,6 @@
+/*
+ * Internal dependencies
+ */
+const { runOrderStatusFiltersTest } = require( '@woocommerce/e2e-core-tests' );
+
+runOrderStatusFiltersTest();
diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md
index d0544feb3c6..c3502850770 100644
--- a/tests/e2e/utils/CHANGELOG.md
+++ b/tests/e2e/utils/CHANGELOG.md
@@ -1,15 +1,18 @@
# Unreleased
-## Added
-
-- `openOrder` for opening existing orders
-- `getValueOfInputField` to get value of input field
-- split `verifyPublishAndTrash` into separate functions.
-
## Fixed
- Missing `config` package dependency
+## Added
+
+- `clickFilter()` util helper method that clicks on a list page filter
+- `moveAllItemsToTrash()` util helper method that checks every item in a list page and moves them to the trash
+- `createSimpleOrder( status )` component which accepts an order status string and creates a basic order with that status
+- `openOrder` for opening existing orders
+- `getValueOfInputField` to get value of input field
+- split `verifyPublishAndTrash` into separate functions.
+
# 0.1.1
- Initial/beta release
diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md
index bcd841f2af3..6a31fde72dc 100644
--- a/tests/e2e/utils/README.md
+++ b/tests/e2e/utils/README.md
@@ -91,6 +91,8 @@ describe( 'Cart page', () => {
| `verifyCheckboxIsSet` | `selector` | Verify that a checkbox is checked |
| `verifyCheckboxIsUnset` | `selector` | Verify that a checkbox is unchecked |
| `verifyValueOfInputField` | `selector, value` | Verify an input contains the passed value |
+| `clickFilter` | `selector` | Click on a list page filter |
+| `moveAllItemsToTrash` | | Moves all items in a list view to the Trash |
|----------|------------|-------------|
### Test Utilities
diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js
index 5df1b4425b8..4e82df359ae 100644
--- a/tests/e2e/utils/src/components.js
+++ b/tests/e2e/utils/src/components.js
@@ -220,7 +220,7 @@ const createVariableProduct = async () => {
// Go to "add product" page
await StoreOwnerFlow.openNewProduct();
- // Make sure we're on the add order page
+ // Make sure we're on the add product page
await expect( page.title() ).resolves.toMatch( 'Add new product' );
// Set product data
@@ -344,9 +344,36 @@ const createVariableProduct = async () => {
return variablePostIdValue;
};
+/**
+ * Create a basic order with the provided order status.
+ *
+ * @param orderStatus Status of the new order. Defaults to `Pending payment`.
+ */
+const createSimpleOrder = async ( orderStatus = 'Pending payment' ) => {
+ // Go to 'Add new order' page
+ await StoreOwnerFlow.openNewOrder();
+
+ // Make sure we're on the add order page
+ await expect( page.title() ).resolves.toMatch( 'Add new order' );
+
+ // Set order status
+ await expect( page ).toSelect( '#order_status', orderStatus );
+
+ // Wait for auto save
+ await page.waitFor( 2000 );
+
+ // Create the order
+ await expect( page ).toClick( 'button.save_order' );
+ await page.waitForSelector( '#message' );
+
+ // Verify
+ await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
+};
+
export {
completeOnboardingWizard,
createSimpleProduct,
createVariableProduct,
+ createSimpleOrder,
verifyAndPublish,
};
diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js
index 8a96cac2bb5..2a53a71567b 100644
--- a/tests/e2e/utils/src/page-utils.js
+++ b/tests/e2e/utils/src/page-utils.js
@@ -182,6 +182,34 @@ const verifyValueOfInputField = async( selector, value ) => {
await expect( fieldValue ).toBe( value );
};
+/**
+ * Clicks on a filter on a list page, such as WooCommerce > Orders or Posts > All Posts.
+ *
+ * @param {string} selector Selector of the filter link to be clicked.
+ */
+const clickFilter = async( selector ) => {
+ await page.waitForSelector( selector );
+ await page.focus( selector );
+ await Promise.all( [
+ page.click( `.subsubsub > ${selector} > a` ),
+ page.waitForNavigation( { waitUntil: 'networkidle0' } ),
+ ] );
+};
+
+/**
+ * Moves all items in a list view to the trash.
+ *
+ * If there's more than 20 items, it moves all 20 items on the current page.
+ */
+const moveAllItemsToTrash = async() => {
+ await setCheckbox( '#cb-select-all-1' );
+ await expect( page ).toSelect( '#bulk-action-selector-top', 'Move to Trash' );
+ await Promise.all( [
+ page.click( '#doaction' ),
+ page.waitForNavigation( { waitUntil: 'networkidle0' } ),
+ ] );
+};
+
export {
clearAndFillInput,
clickTab,
@@ -197,4 +225,6 @@ export {
verifyCheckboxIsSet,
verifyCheckboxIsUnset,
verifyValueOfInputField,
+ clickFilter,
+ moveAllItemsToTrash,
};