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, };