Merge branch 'trunk' into fix/27873
This commit is contained in:
commit
7261813fc9
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* dashboard-setup.scss
|
||||
* Styles for WooCommerce dashboard finish setup widgets
|
||||
* only loaded on the dashboard itself.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Styling begins
|
||||
*/
|
||||
|
||||
.dashboard-widget-finish-setup {
|
||||
|
||||
.progress-wrapper {
|
||||
border: 1px solid #757575;
|
||||
border-radius: 16px;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 8px 2px 8px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.progress-wrapper span {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.description div {
|
||||
margin-top: 11px;
|
||||
float: left;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.description img {
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.circle-progress {
|
||||
margin-top: 1px;
|
||||
margin-left: -3px;
|
||||
|
||||
circle {
|
||||
stroke: #f0f0f0;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
stroke: #949494;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* Abstract payment tokens
|
||||
*
|
||||
* Generic payment tokens functionality which can be extended by idividual types of payment tokens.
|
||||
* Generic payment tokens functionality which can be extended by individual types of payment tokens.
|
||||
*
|
||||
* @class WC_Payment_Token
|
||||
* @package WooCommerce\Abstracts
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin Dashboard - Setup
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Dashboard_Setup Class.
|
||||
*/
|
||||
class WC_Admin_Dashboard_Setup {
|
||||
|
||||
/**
|
||||
* List of tasks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tasks = array(
|
||||
'store_details' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard',
|
||||
),
|
||||
'products' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&task=products',
|
||||
),
|
||||
'woocommerce-payments' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&path=%2Fpayments%2Fconnect',
|
||||
),
|
||||
'payments' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&task=payments',
|
||||
),
|
||||
'tax' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&task=tax',
|
||||
),
|
||||
'shipping' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&task=shipping',
|
||||
),
|
||||
'appearance' => array(
|
||||
'completed' => false,
|
||||
'button_link' => 'admin.php?page=wc-admin&task=appearance',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* # of completed tasks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $completed_tasks_count = 0;
|
||||
|
||||
/**
|
||||
* WC_Admin_Dashboard_Setup constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( $this->should_display_widget() ) {
|
||||
$this->populate_general_tasks();
|
||||
$this->populate_payment_tasks();
|
||||
$this->completed_tasks_count = $this->get_completed_tasks_count();
|
||||
add_meta_box(
|
||||
'wc_admin_dashboard_setup',
|
||||
__( 'WooCommerce Setup', 'woocommerce' ),
|
||||
array( $this, 'render' ),
|
||||
'dashboard',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render meta box output.
|
||||
*/
|
||||
public function render() {
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
wp_enqueue_style( 'wc-dashboard-setup', WC()->plugin_url() . '/assets/css/dashboard-setup.css', array(), $version );
|
||||
|
||||
$task = $this->get_next_task();
|
||||
if ( ! $task ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button_link = $task['button_link'];
|
||||
$completed_tasks_count = $this->completed_tasks_count;
|
||||
$tasks_count = count( $this->tasks );
|
||||
|
||||
// Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2).
|
||||
$progress_percentage = ( $completed_tasks_count / $tasks_count ) * 100;
|
||||
$circle_r = 6.5;
|
||||
$circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) );
|
||||
|
||||
include __DIR__ . '/views/html-admin-dashboard-setup.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate tasks from the database.
|
||||
*/
|
||||
private function populate_general_tasks() {
|
||||
$tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() );
|
||||
foreach ( $tasks as $task ) {
|
||||
if ( isset( $this->tasks[ $task ] ) ) {
|
||||
$this->tasks[ $task ]['completed'] = true;
|
||||
$this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for $tasks
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tasks() {
|
||||
return $this->tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return # of completed tasks
|
||||
*/
|
||||
public function get_completed_tasks_count() {
|
||||
$completed_tasks = array_filter(
|
||||
$this->tasks,
|
||||
function( $task ) {
|
||||
return $task['completed'];
|
||||
}
|
||||
);
|
||||
|
||||
return count( $completed_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next task.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_next_task() {
|
||||
foreach ( $this->get_tasks() as $task ) {
|
||||
if ( false === $task['completed'] ) {
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should display the widget
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_display_widget() {
|
||||
return 'yes' !== get_option( 'woocommerce_task_list_complete' ) && 'yes' !== get_option( 'woocommerce_task_list_hidden' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate payment tasks's visibility and completion
|
||||
*/
|
||||
private function populate_payment_tasks() {
|
||||
$is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
|
||||
$country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0];
|
||||
|
||||
// woocommerce-payments requires its plugin activated and country must be US.
|
||||
if ( ! $is_woo_payment_installed || 'US' !== $country ) {
|
||||
unset( $this->tasks['woocommerce-payments'] );
|
||||
}
|
||||
|
||||
// payments can't be used when woocommerce-payments exists and country is US.
|
||||
if ( $is_woo_payment_installed || 'US' === $country ) {
|
||||
unset( $this->tasks['payments'] );
|
||||
}
|
||||
|
||||
if ( isset( $this->tasks['payments'] ) ) {
|
||||
$gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$enabled_gateways = array_filter(
|
||||
$gateways,
|
||||
function ( $gateway ) {
|
||||
return 'yes' === $gateway->enabled;
|
||||
}
|
||||
);
|
||||
$this->tasks['payments']['completed'] = ! empty( $enabled_gateways );
|
||||
}
|
||||
|
||||
if ( isset( $this->tasks['woocommerce-payments'] ) ) {
|
||||
$wc_pay_is_connected = false;
|
||||
if ( class_exists( '\WC_Payments' ) ) {
|
||||
$wc_payments_gateway = \WC_Payments::get_gateway();
|
||||
$wc_pay_is_connected = method_exists( $wc_payments_gateway, 'is_connected' )
|
||||
? $wc_payments_gateway->is_connected()
|
||||
: false;
|
||||
}
|
||||
$this->tasks['woocommerce-payments']['completed'] = $wc_pay_is_connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Dashboard_Setup();
|
|
@ -24,7 +24,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
|||
*/
|
||||
public function __construct() {
|
||||
// Only hook in admin parts if the user has admin access.
|
||||
if ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) {
|
||||
if ( $this->should_display_widget() ) {
|
||||
// If on network admin, only load the widget that works in that context and skip the rest.
|
||||
if ( is_multisite() && is_network_admin() ) {
|
||||
add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) );
|
||||
|
@ -57,6 +57,17 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
|||
wp_add_dashboard_widget( 'woocommerce_network_orders', __( 'WooCommerce Network Orders', 'woocommerce' ), array( $this, 'network_orders' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should display the widget.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_display_widget() {
|
||||
$has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' );
|
||||
$task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' );
|
||||
return $task_completed_or_hidden && $has_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top seller from DB.
|
||||
*
|
||||
|
|
|
@ -94,6 +94,7 @@ class WC_Admin {
|
|||
switch ( $screen->id ) {
|
||||
case 'dashboard':
|
||||
case 'dashboard-network':
|
||||
include __DIR__ . '/class-wc-admin-dashboard-setup.php';
|
||||
include __DIR__ . '/class-wc-admin-dashboard.php';
|
||||
break;
|
||||
case 'options-permalink':
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin View: Dashboard - Finish Setup
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="dashboard-widget-finish-setup">
|
||||
<span class='progress-wrapper'>
|
||||
<svg class="circle-progress" width="17" height="17" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle r="6.5" cx="10" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="0"></circle>
|
||||
<circle class="bar" r="6.5" cx="190" cy="10" fill="transparent" stroke-dasharray="40.859" stroke-dashoffset="<?php echo esc_attr( $circle_dashoffset ); ?>" transform='rotate(-90 100 100)'></circle>
|
||||
</svg>
|
||||
<span><?php echo esc_html_e( 'Step', 'woocommerce' ); ?> <?php echo esc_html( $completed_tasks_count ); ?> <?php echo esc_html_e( 'of', 'woocommerce' ); ?> <?php echo esc_html( $tasks_count ); ?></span>
|
||||
</span>
|
||||
|
||||
<div class="description">
|
||||
<div>
|
||||
<?php echo esc_html_e( 'You\'re almost there! Once you complete store setup you can start receiving orders.', 'woocommerce' ); ?>
|
||||
<div><a href='<?php echo esc_attr( $button_link ); ?>' class='button button-primary'><?php echo esc_html_e( 'Start selling', 'woocommerce' ); ?></a></div>
|
||||
</div>
|
||||
<img src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/dashboard-widget-setup.png" />
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
|
@ -602,7 +602,12 @@ class WC_Countries {
|
|||
array(
|
||||
'{first_name}' => $args['first_name'],
|
||||
'{last_name}' => $args['last_name'],
|
||||
'{name}' => $args['first_name'] . ' ' . $args['last_name'],
|
||||
'{name}' => sprintf(
|
||||
/* translators: 1: first name 2: last name */
|
||||
_x( '%1$s %2$s', 'full name', 'woocommerce' ),
|
||||
$args['first_name'],
|
||||
$args['last_name']
|
||||
),
|
||||
'{company}' => $args['company'],
|
||||
'{address_1}' => $args['address_1'],
|
||||
'{address_2}' => $args['address_2'],
|
||||
|
@ -612,7 +617,14 @@ class WC_Countries {
|
|||
'{country}' => $full_country,
|
||||
'{first_name_upper}' => wc_strtoupper( $args['first_name'] ),
|
||||
'{last_name_upper}' => wc_strtoupper( $args['last_name'] ),
|
||||
'{name_upper}' => wc_strtoupper( $args['first_name'] . ' ' . $args['last_name'] ),
|
||||
'{name_upper}' => wc_strtoupper(
|
||||
sprintf(
|
||||
/* translators: 1: first name 2: last name */
|
||||
_x( '%1$s %2$s', 'full name', 'woocommerce' ),
|
||||
$args['first_name'],
|
||||
$args['last_name']
|
||||
)
|
||||
),
|
||||
'{company_upper}' => wc_strtoupper( $args['company'] ),
|
||||
'{address_1_upper}' => wc_strtoupper( $args['address_1'] ),
|
||||
'{address_2_upper}' => wc_strtoupper( $args['address_2'] ),
|
||||
|
|
|
@ -907,7 +907,7 @@ class WC_Order extends WC_Abstract_Order {
|
|||
$address = WC()->countries->get_formatted_address( $raw_address );
|
||||
|
||||
/**
|
||||
* Filter orders formatterd billing address.
|
||||
* Filter orders formatted billing address.
|
||||
*
|
||||
* @since 3.8.0
|
||||
* @param string $address Formatted billing address string.
|
||||
|
@ -933,7 +933,7 @@ class WC_Order extends WC_Abstract_Order {
|
|||
}
|
||||
|
||||
/**
|
||||
* Filter orders formatterd shipping address.
|
||||
* Filter orders formatted shipping address.
|
||||
*
|
||||
* @since 3.8.0
|
||||
* @param string $address Formatted billing address string.
|
||||
|
|
|
@ -162,7 +162,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set ID for the rate. This is usually a combination of the method and instance IDs.
|
||||
* Get ID for the rate. This is usually a combination of the method and instance IDs.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return string
|
||||
|
@ -172,7 +172,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set shipping method ID the rate belongs to.
|
||||
* Get shipping method ID the rate belongs to.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return string
|
||||
|
@ -182,7 +182,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set instance ID the rate belongs to.
|
||||
* Get instance ID the rate belongs to.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return int
|
||||
|
@ -192,7 +192,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set rate label.
|
||||
* Get rate label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -201,7 +201,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set rate cost.
|
||||
* Get rate cost.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return string
|
||||
|
@ -211,7 +211,7 @@ class WC_Shipping_Rate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set rate taxes.
|
||||
* Get rate taxes.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return array
|
||||
|
|
|
@ -2298,6 +2298,7 @@ if ( ! function_exists( 'woocommerce_checkout_coupon_form' ) ) {
|
|||
* Output the Coupon form for the checkout.
|
||||
*/
|
||||
function woocommerce_checkout_coupon_form() {
|
||||
if ( is_user_logged_in() || WC()->checkout()->is_registration_enabled() || ! WC()->checkout()->is_registration_required() ) {
|
||||
wc_get_template(
|
||||
'checkout/form-coupon.php',
|
||||
array(
|
||||
|
@ -2306,6 +2307,7 @@ if ( ! function_exists( 'woocommerce_checkout_coupon_form' ) ) {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'woocommerce_products_will_display' ) ) {
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ if ( $total <= 1 ) {
|
|||
'add_args' => false,
|
||||
'current' => max( 1, $current ),
|
||||
'total' => $total,
|
||||
'prev_text' => '←',
|
||||
'next_text' => '→',
|
||||
'prev_text' => is_rtl() ? '→' : '←',
|
||||
'next_text' => is_rtl() ? '←' : '→',
|
||||
'type' => 'list',
|
||||
'end_size' => 3,
|
||||
'mid_size' => 3,
|
||||
|
|
|
@ -51,8 +51,8 @@ if ( ! comments_open() ) {
|
|||
apply_filters(
|
||||
'woocommerce_comment_pagination_args',
|
||||
array(
|
||||
'prev_text' => '←',
|
||||
'next_text' => '→',
|
||||
'prev_text' => is_rtl() ? '→' : '←',
|
||||
'next_text' => is_rtl() ? '←' : '→',
|
||||
'type' => 'list',
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"url": "http://localhost:8084/",
|
||||
"appName": "woocommerce_e2e",
|
||||
"users": {
|
||||
"admin": {
|
||||
"username": "admin",
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
- Merchant Product Search tests
|
||||
- Shopper Single Product tests
|
||||
- Shopper Checkout Apply Coupon
|
||||
- Shopper Shop Browse Search Sort
|
||||
- Merchant Orders Customer Checkout Page
|
||||
- Shopper Cart Apply Coupon
|
||||
- Shopper Variable product info updates on different variations
|
||||
- Merchant order emails flow
|
||||
|
||||
## Fixed
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ The functions to access the core tests are:
|
|||
- `runProductEditDetailsTest` - Merchant can edit an existing product
|
||||
- `runProductSearchTest` - Merchant can search for a product and view it
|
||||
- `runMerchantOrdersCustomerPaymentPage` - Merchant can visit the customer payment page
|
||||
- `runMerchantOrderEmailsTest` - Merchant can receive order emails and resend emails by Order Actions
|
||||
|
||||
### Shopper
|
||||
|
||||
|
@ -68,6 +69,7 @@ The functions to access the core tests are:
|
|||
- `runCheckoutPageTest` - Shopper can complete checkout
|
||||
- `runMyAccountPageTest` - Shopper can access my account page
|
||||
- `runSingleProductPageTest` - Shopper can view single product page in many variations (simple, variable, grouped)
|
||||
- `runProductBrowseSearchSortTest` - Shopper can browse, search & sort products
|
||||
- `runVariableProductUpdateTest` - Shopper can view and update variations on a variable product
|
||||
|
||||
## Contributing a new test
|
||||
|
|
|
@ -74,10 +74,10 @@ const runCouponApiTest = () => {
|
|||
|
||||
it('can delete a coupon', async () => {
|
||||
// Delete the coupon
|
||||
const deletedCoupon = await repository.delete( coupon.id );
|
||||
const status = await repository.delete( coupon.id );
|
||||
|
||||
// If the delete is successful, the response comes back truthy
|
||||
expect( deletedCoupon ).toBeTruthy();
|
||||
expect( status ).toBeTruthy();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -69,6 +69,11 @@ const runExternalProductAPITest = () => {
|
|||
const transformed = await repository.read( product.id );
|
||||
expect( transformed ).toEqual( expect.objectContaining( transformedProperties ) );
|
||||
});
|
||||
|
||||
it('can delete an external product', async () => {
|
||||
const status = repository.delete( product.id );
|
||||
expect( status ).toBeTruthy();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -71,11 +71,16 @@ const runGroupedProductAPITest = () => {
|
|||
expect( response.data ).toEqual( expect.objectContaining( rawProperties ) );
|
||||
});
|
||||
|
||||
it('can retrieve a transformed external product', async () => {
|
||||
it('can retrieve a transformed grouped product', async () => {
|
||||
// Read product via the repository.
|
||||
const transformed = await repository.read( product.id );
|
||||
expect( transformed ).toEqual( expect.objectContaining( baseGroupedProduct ) );
|
||||
});
|
||||
|
||||
it('can delete a grouped product', async () => {
|
||||
const status = repository.delete( product.id );
|
||||
expect( status ).toBeTruthy();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setu
|
|||
const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' );
|
||||
|
||||
// Shopper tests
|
||||
const runProductBrowseSearchSortTest = require( './shopper/front-end-product-browse-search-sort.test' );
|
||||
const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test');
|
||||
const runCartPageTest = require( './shopper/front-end-cart.test' );
|
||||
const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test');
|
||||
|
@ -31,6 +32,7 @@ const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon
|
|||
const runProductEditDetailsTest = require( './merchant/wp-admin-product-edit-details.test' );
|
||||
const runProductSearchTest = require( './merchant/wp-admin-product-search.test' );
|
||||
const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' );
|
||||
const runMerchantOrderEmailsTest = require( './merchant/wp-admin-order-emails.test' );
|
||||
|
||||
// REST API tests
|
||||
const runExternalProductAPITest = require( './api/external-product.test' );
|
||||
|
@ -46,6 +48,7 @@ const runSetupOnboardingTests = () => {
|
|||
};
|
||||
|
||||
const runShopperTests = () => {
|
||||
runProductBrowseSearchSortTest();
|
||||
runCartApplyCouponsTest();
|
||||
runCartPageTest();
|
||||
runCheckoutApplyCouponsTest();
|
||||
|
@ -110,6 +113,8 @@ module.exports = {
|
|||
runProductEditDetailsTest,
|
||||
runProductSearchTest,
|
||||
runMerchantOrdersCustomerPaymentPage,
|
||||
runMerchantOrderEmailsTest,
|
||||
runMerchantTests,
|
||||
runProductBrowseSearchSortTest,
|
||||
runApiTests,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/no-standalone-expect */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
merchant,
|
||||
clickUpdateOrder,
|
||||
createSimpleOrder,
|
||||
selectOrderAction,
|
||||
deleteAllEmailLogs,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
const customerEmail = config.get( 'addresses.customer.billing.email' );
|
||||
const adminEmail = 'admin@woocommercecoree2etestsuite.com';
|
||||
const storeName = 'WooCommerce Core E2E Test Suite';
|
||||
|
||||
let orderId;
|
||||
|
||||
const runMerchantOrderEmailsTest = () => {
|
||||
|
||||
describe('Merchant > Order Action emails received', () => {
|
||||
beforeAll( async () => {
|
||||
await merchant.login();
|
||||
|
||||
// Clear out the existing email logs if any
|
||||
await deleteAllEmailLogs();
|
||||
|
||||
orderId = await createSimpleOrder( 'Processing' );
|
||||
|
||||
await Promise.all( [
|
||||
// Select the billing email address field and add the customer billing email from the config
|
||||
await page.click( 'div.order_data_column:nth-child(2) > h3:nth-child(1) > a:nth-child(1)' ),
|
||||
await expect( page ).toFill( '#_billing_email', customerEmail ),
|
||||
await clickUpdateOrder( 'Order updated.' ),
|
||||
] );
|
||||
} );
|
||||
|
||||
afterEach( async () => {
|
||||
// Clear out any emails after each test
|
||||
await deleteAllEmailLogs();
|
||||
} );
|
||||
|
||||
// New order emails are sent automatically when we create the simple order above, so let's verify we get these emails
|
||||
it('can receive new order email', async () => {
|
||||
await merchant.openEmailLog();
|
||||
await expect( page ).toMatchElement( '.column-receiver', { text: adminEmail } );
|
||||
await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } );
|
||||
} );
|
||||
|
||||
it('can resend new order notification', async () => {
|
||||
await merchant.goToOrder( orderId );
|
||||
await selectOrderAction( 'send_order_details_admin' );
|
||||
|
||||
await merchant.openEmailLog();
|
||||
await expect( page ).toMatchElement( '.column-receiver', { text: adminEmail } );
|
||||
await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } );
|
||||
} );
|
||||
|
||||
it('can email invoice/order details to customer', async () => {
|
||||
await merchant.goToOrder( orderId );
|
||||
await selectOrderAction( 'send_order_details' );
|
||||
|
||||
await merchant.openEmailLog();
|
||||
await expect( page ).toMatchElement( '.column-receiver', { text: customerEmail } );
|
||||
await expect( page ).toMatchElement( '.column-subject', { text: `Invoice for order #${orderId} on ${storeName}` } );
|
||||
} );
|
||||
|
||||
} );
|
||||
}
|
||||
|
||||
module.exports = runMerchantOrderEmailsTest;
|
|
@ -8,7 +8,8 @@ const {
|
|||
createCoupon,
|
||||
createSimpleProduct,
|
||||
uiUnblocked,
|
||||
clearAndFillInput,
|
||||
applyCoupon,
|
||||
removeCoupon,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
|
@ -20,28 +21,6 @@ const {
|
|||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Apply a coupon code to the cart.
|
||||
*
|
||||
* @param couponCode string
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const applyCouponToCart = async ( couponCode ) => {
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove one coupon from the cart.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const removeCouponFromCart = async () => {
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
}
|
||||
const runCartApplyCouponsTest = () => {
|
||||
describe('Cart applying coupons', () => {
|
||||
let couponFixedCart;
|
||||
|
@ -62,42 +41,42 @@ const runCartApplyCouponsTest = () => {
|
|||
});
|
||||
|
||||
it('allows customer to apply fixed cart coupon', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedCart);
|
||||
});
|
||||
|
||||
it('allows customer to apply percentage coupon', async () => {
|
||||
await applyCouponToCart( couponPercentage );
|
||||
await applyCoupon(couponPercentage);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponPercentage);
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed product coupon', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedProduct);
|
||||
});
|
||||
|
||||
it('prevents customer applying same coupon twice', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
// Verify only one discount applied
|
||||
// This is a work around for Puppeteer inconsistently finding 'Coupon code already applied'
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
|
@ -105,7 +84,7 @@ const runCartApplyCouponsTest = () => {
|
|||
});
|
||||
|
||||
it('allows customer to apply multiple coupons', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
|
@ -114,8 +93,8 @@ const runCartApplyCouponsTest = () => {
|
|||
});
|
||||
|
||||
it('restores cart total when coupons are removed', async () => {
|
||||
await removeCouponFromCart();
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedCart);
|
||||
await removeCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,8 @@ const {
|
|||
createCoupon,
|
||||
createSimpleProduct,
|
||||
uiUnblocked,
|
||||
clearAndFillInput,
|
||||
applyCoupon,
|
||||
removeCoupon,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
|
@ -20,30 +21,6 @@ const {
|
|||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Apply a coupon code to the cart.
|
||||
*
|
||||
* @param couponCode string
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const applyCouponToCart = async ( couponCode ) => {
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove one coupon from the cart.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const removeCouponFromCart = async () => {
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
}
|
||||
const runCheckoutApplyCouponsTest = () => {
|
||||
describe('Checkout coupons', () => {
|
||||
let couponFixedCart;
|
||||
|
@ -64,7 +41,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
});
|
||||
|
||||
it('allows customer to apply fixed cart coupon', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Wait for page to expand total calculations to avoid flakyness
|
||||
|
@ -73,31 +50,31 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
// Verify discount applied and order total
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedCart);
|
||||
});
|
||||
|
||||
it('allows customer to apply percentage coupon', async () => {
|
||||
await applyCouponToCart( couponPercentage );
|
||||
await applyCoupon(couponPercentage);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponPercentage);
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed product coupon', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedProduct);
|
||||
});
|
||||
|
||||
it('prevents customer applying same coupon twice', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await applyCoupon(couponFixedCart);
|
||||
// Verify only one discount applied
|
||||
// This is a work around for Puppeteer inconsistently finding 'Coupon code already applied'
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
|
@ -105,14 +82,14 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
});
|
||||
|
||||
it('allows customer to apply multiple coupons', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'});
|
||||
});
|
||||
|
||||
it('restores cart total when coupons are removed', async () => {
|
||||
await removeCouponFromCart();
|
||||
await removeCouponFromCart();
|
||||
await removeCoupon(couponFixedCart);
|
||||
await removeCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createSimpleProductWithCategory,
|
||||
uiUnblocked,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
|
||||
const singleProductPrice2 = config.has('products.simple.price') ? config.get('products.simple.price') : '19.99';
|
||||
const singleProductPrice3 = config.has('products.simple.price') ? config.get('products.simple.price') : '29.99';
|
||||
const clothing = 'Clothing';
|
||||
const audio = 'Audio';
|
||||
const hardware = 'Hardware';
|
||||
const productTitle = 'li.first > a > h2.woocommerce-loop-product__title';
|
||||
|
||||
const runProductBrowseSearchSortTest = () => {
|
||||
describe('Search, browse by categories and sort items in the shop', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
// Create 1st product with Clothing category
|
||||
await createSimpleProductWithCategory(simpleProductName + ' 1', singleProductPrice, clothing);
|
||||
// Create 2nd product with Audio category
|
||||
await createSimpleProductWithCategory(simpleProductName + ' 2', singleProductPrice2, audio);
|
||||
// Create 3rd product with Hardware category
|
||||
await createSimpleProductWithCategory(simpleProductName + ' 3', singleProductPrice3, hardware);
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('should let user search the store', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.searchForProduct(simpleProductName + ' 1');
|
||||
});
|
||||
|
||||
it('should let user browse products by categories', async () => {
|
||||
// Browse through Clothing category link
|
||||
await Promise.all([
|
||||
page.waitForNavigation({waitUntil: 'networkidle0'}),
|
||||
page.click('span.posted_in > a', {text: clothing}),
|
||||
]);
|
||||
await uiUnblocked();
|
||||
|
||||
// Verify Clothing category page
|
||||
await page.waitForSelector(productTitle);
|
||||
await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'});
|
||||
await expect(page).toClick(productTitle, {text: simpleProductName + ' 1'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('h1.entry-title');
|
||||
await expect(page).toMatchElement('h1.entry-title', simpleProductName + ' 1');
|
||||
});
|
||||
|
||||
it('should let user sort the products in the shop', async () => {
|
||||
await shopper.goToShop();
|
||||
|
||||
// Sort by price high to low
|
||||
await page.select('.orderby', 'price-desc');
|
||||
// Verify the first product in sort order
|
||||
await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'});
|
||||
|
||||
// Sort by price low to high
|
||||
await page.select('.orderby', 'price');
|
||||
// Verify the first product in sort order
|
||||
await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 1'});
|
||||
|
||||
// Sort by date of creation, latest to oldest
|
||||
await page.select('.orderby', 'date');
|
||||
// Verify the first product in sort order
|
||||
await expect(page).toMatchElement(productTitle, {text: simpleProductName + ' 3'});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runProductBrowseSearchSortTest;
|
|
@ -8,3 +8,6 @@ wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=pas
|
|||
|
||||
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
|
||||
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
|
||||
|
||||
# install the WP Mail Logging plugin to test emails
|
||||
wp plugin install wp-mail-logging --activate
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
- support for custom container name
|
||||
- Insert a 12 hour delay in using new docker image tags
|
||||
- Package `bin` script `wc-e2e`
|
||||
- WP Mail Log plugin as part of container initialization
|
||||
|
||||
## Fixed
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runProductBrowseSearchSortTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runProductBrowseSearchSortTest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runMerchantOrderEmailsTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runMerchantOrderEmailsTest();
|
|
@ -17,6 +17,13 @@
|
|||
- `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code.
|
||||
- `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element.
|
||||
- `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field
|
||||
- `createSimpleProductWithCategory` component which creates a simple product with categories, containing three parameters for title, price and category name.
|
||||
- `applyCoupon( couponName )` util helper method which applies previously created coupon to cart or checkout
|
||||
- `removeCoupon()` util helper method that removes a single coupon within cart or checkout
|
||||
- `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox
|
||||
- `merchant.openEmailLog()` go to the WP Mail Log page
|
||||
- `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin
|
||||
- `clickUpdateOrder( noticeText, waitForSave )` util helper that clicks the `Update` button on an order
|
||||
|
||||
## Changes
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ describe( 'Cart page', () => {
|
|||
| `openSettings` | | Go to WooCommerce -> Settings |
|
||||
| `runSetupWizard` | | Open the onboarding profiler |
|
||||
| `updateOrderStatus` | `orderId, status` | Update the status of an order |
|
||||
| `openEmailLog` | | Open the WP Mail Log page |
|
||||
|
||||
### Shopper `shopper`
|
||||
|
||||
|
@ -77,6 +78,7 @@ describe( 'Cart page', () => {
|
|||
| `productIsInCheckout` | `productTitle, quantity, total, cartSubtotal` | Verify product is in cart on checkout page |
|
||||
| `removeFromCart` | `productTitle` | Remove a product from the cart on the cart page |
|
||||
| `setCartQuantity` | `productTitle, quantityValue` | Change the quantity of a product on the cart page |
|
||||
| `searchForProduct` | Searching for a product name and landing on its detail page |
|
||||
|
||||
### Page Utilities
|
||||
|
||||
|
@ -101,7 +103,11 @@ describe( 'Cart page', () => {
|
|||
| `clickFilter` | `selector` | Click on a list page filter |
|
||||
| `moveAllItemsToTrash` | | Moves all items in a list view to the Trash |
|
||||
| `verifyAndPublish` | `noticeText` | Verify that an item can be published |
|
||||
| `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside
|
||||
| `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside |
|
||||
| `applyCoupon` | `couponName` | helper method which applies a coupon in cart or checkout |
|
||||
| `removeCoupon` | | helper method that removes a single coupon within cart or checkout |
|
||||
| `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox |
|
||||
| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page |
|
||||
|
||||
### Test Utilities
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { merchant } from './flows';
|
||||
import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick, selectOptionInSelect2 } from './page-utils';
|
||||
import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick, selectOptionInSelect2, setCheckbox } from './page-utils';
|
||||
import factories from './factories';
|
||||
|
||||
const config = require( 'config' );
|
||||
|
@ -184,6 +184,44 @@ const createSimpleProduct = async () => {
|
|||
return product.id;
|
||||
} ;
|
||||
|
||||
/**
|
||||
* Create simple product with categories
|
||||
*
|
||||
* @param productName Product's name which can be changed when writing a test
|
||||
* @param productPrice Product's price which can be changed when writing a test
|
||||
* @param categoryName Product's category which can be changed when writing a test
|
||||
*/
|
||||
const createSimpleProductWithCategory = async ( productName, productPrice, categoryName ) => {
|
||||
// Go to "add product" page
|
||||
await merchant.openNewProduct();
|
||||
|
||||
// Add title and regular price
|
||||
await expect(page).toFill('#title', productName);
|
||||
await expect(page).toClick('#_virtual');
|
||||
await clickTab('General');
|
||||
await expect(page).toFill('#_regular_price', productPrice);
|
||||
|
||||
// Try to select the existing category if present already, otherwise add a new and select it
|
||||
try {
|
||||
const [checkbox] = await page.$x('//label[contains(text(), "'+categoryName+'")]');
|
||||
await checkbox.click();
|
||||
} catch (error) {
|
||||
await expect(page).toClick('#product_cat-add-toggle');
|
||||
await expect(page).toFill('#newproduct_cat', categoryName);
|
||||
await expect(page).toClick('#product_cat-add-submit');
|
||||
}
|
||||
|
||||
// Publish the product
|
||||
await expect(page).toClick('#publish');
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.updated.notice', {text:'Product published.'});
|
||||
|
||||
// Get the product ID
|
||||
const variablePostId = await page.$('#post_ID');
|
||||
let variablePostIdValue = (await(await variablePostId.getProperty('value')).jsonValue());
|
||||
return variablePostIdValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create variable product.
|
||||
*/
|
||||
|
@ -435,6 +473,42 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc
|
|||
return couponCode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Click the Update button on the order details page.
|
||||
*
|
||||
* @param noticeText The text that appears in the notice after updating the order.
|
||||
* @param waitForSave Optionally wait for auto save.
|
||||
*/
|
||||
const clickUpdateOrder = async ( noticeText, waitForSave = false ) => {
|
||||
if ( waitForSave ) {
|
||||
await page.waitFor( 2000 );
|
||||
}
|
||||
|
||||
// PUpdate order
|
||||
await expect( page ).toClick( 'button.save_order' );
|
||||
await page.waitForSelector( '.updated.notice' );
|
||||
|
||||
// Verify
|
||||
await expect( page ).toMatchElement( '.updated.notice', { text: noticeText } );
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all email logs in the WP Mail Logging plugin page.
|
||||
*/
|
||||
const deleteAllEmailLogs = async () => {
|
||||
await merchant.openEmailLog();
|
||||
|
||||
// Make sure we have emails to delete. If we don't, this selector will return null.
|
||||
if ( await page.$( '#bulk-action-selector-top' ) !== null ) {
|
||||
await setCheckbox( '#cb-select-all-1' );
|
||||
await expect( page ).toSelect( '#bulk-action-selector-top', 'Delete' );
|
||||
await Promise.all( [
|
||||
page.click( '#doaction' ),
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
completeOnboardingWizard,
|
||||
createSimpleProduct,
|
||||
|
@ -444,4 +518,7 @@ export {
|
|||
verifyAndPublish,
|
||||
addProductToOrder,
|
||||
createCoupon,
|
||||
createSimpleProductWithCategory,
|
||||
clickUpdateOrder,
|
||||
deleteAllEmailLogs,
|
||||
};
|
||||
|
|
|
@ -169,6 +169,12 @@ const merchant = {
|
|||
await expect( page ).toMatchElement( 'label[for="customer_user"] a[href*=user-edit]', { text: 'Profile' } );
|
||||
}
|
||||
},
|
||||
|
||||
openEmailLog: async () => {
|
||||
await page.goto( `${baseUrl}wp-admin/tools.php?page=wpml_plugin_log`, {
|
||||
waitUntil: 'networkidle0',
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = merchant;
|
||||
|
|
|
@ -137,6 +137,17 @@ const shopper = {
|
|||
await quantityInput.type( quantityValue.toString() );
|
||||
},
|
||||
|
||||
searchForProduct: async ( prouductName ) => {
|
||||
await expect(page).toFill('.search-field', prouductName);
|
||||
await expect(page).toClick('.search-submit');
|
||||
await page.waitForSelector('h2.entry-title');
|
||||
await expect(page).toMatchElement('h2.entry-title', {text: prouductName});
|
||||
await expect(page).toClick('h2.entry-title', {text: prouductName});
|
||||
await page.waitForSelector('h1.entry-title');
|
||||
await expect(page.title()).resolves.toMatch(prouductName);
|
||||
await expect(page).toMatchElement('h1.entry-title', prouductName);
|
||||
},
|
||||
|
||||
/*
|
||||
* My Accounts flows.
|
||||
*/
|
||||
|
|
|
@ -209,6 +209,53 @@ const selectOptionInSelect2 = async ( value, selector = 'input.select2-search__f
|
|||
await page.keyboard.press('Enter');
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply a coupon code within cart or checkout.
|
||||
* Method will try to apply a coupon in the checkout, otherwise will try to apply in the cart.
|
||||
*
|
||||
* @param couponCode string
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const applyCoupon = async ( couponCode ) => {
|
||||
try {
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
} catch (error) {
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove one coupon within cart or checkout.
|
||||
*
|
||||
* @param couponCode Coupon name.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const removeCoupon = async ( couponCode ) => {
|
||||
await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Select and perform an order action in the `Order actions` postbox.
|
||||
*
|
||||
* @param {string} action The action to take on the order.
|
||||
*/
|
||||
const selectOrderAction = async ( action ) => {
|
||||
await page.select( 'select[name=wc_order_action]', action );
|
||||
await Promise.all( [
|
||||
page.click( '.wc-reload' ),
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
}
|
||||
|
||||
export {
|
||||
clearAndFillInput,
|
||||
clickTab,
|
||||
|
@ -225,4 +272,7 @@ export {
|
|||
moveAllItemsToTrash,
|
||||
evalAndClick,
|
||||
selectOptionInSelect2,
|
||||
applyCoupon,
|
||||
removeCoupon,
|
||||
selectOrderAction,
|
||||
};
|
||||
|
|
|
@ -141,7 +141,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test usage limit for guest users uasge limit per user is set.
|
||||
* Test usage limit for guest users usage limit per user is set.
|
||||
*
|
||||
* @throws Exception When unable to create order.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ class Dummy_Widget extends WC_Widget {
|
|||
* Output widget.
|
||||
*
|
||||
* @param mixed $args Arguments.
|
||||
* @param WP_Widget $instance Intance of WP_Widget.
|
||||
* @param WP_Widget $instance Instance of WP_Widget.
|
||||
* @return void
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for the WC_Admin_Dashboard_Setup class.
|
||||
*
|
||||
* @package WooCommerce\Tests\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Admin_Dashboard_Setup_Test
|
||||
*/
|
||||
class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Set up
|
||||
*/
|
||||
public function setUp() {
|
||||
// set default country to US so that 'payments' task does not get added.
|
||||
// we want to remove payment tasks as they depend on installation & activation.
|
||||
update_option( 'woocommerce_default_country', 'US' );
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes widget class and return the class.
|
||||
*
|
||||
* @return WC_Admin_Dashboard_Setup
|
||||
*/
|
||||
public function get_widget() {
|
||||
return include __DIR__ . '/../../../../includes/admin/class-wc-admin-dashboard-setup.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return widget output (HTML).
|
||||
*
|
||||
* @return string Render widget HTML
|
||||
*/
|
||||
public function get_widget_output() {
|
||||
update_option( 'woocommerce_task_list_hidden', 'no' );
|
||||
|
||||
ob_start();
|
||||
$this->get_widget()->render();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget does not get rendered when woocommerce_task_list_hidden or woocommerce_task_list_hidden
|
||||
* is true.
|
||||
*
|
||||
* @dataProvider should_display_widget_data_provider
|
||||
*
|
||||
* @param array $options a set of options.
|
||||
*/
|
||||
public function test_widget_does_not_get_rendered( array $options ) {
|
||||
global $wp_meta_boxes;
|
||||
|
||||
foreach ( $options as $name => $value ) {
|
||||
update_option( $name, $value );
|
||||
}
|
||||
|
||||
$this->get_widget();
|
||||
$this->assertNull( $wp_meta_boxes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given both woocommerce_task_list_hidden and woocommerce_task_list_complete are false
|
||||
* Then the widget should be added to the $wp_meta_boxes
|
||||
*/
|
||||
public function test_widget_gets_rendered_when_both_options_are_false() {
|
||||
global $wp_meta_boxes;
|
||||
update_option( 'woocommerce_task_list_complete', false );
|
||||
update_option( 'woocommerce_task_list_hidden', false );
|
||||
|
||||
$this->get_widget();
|
||||
$this->assertArrayHasKey( 'wc_admin_dashboard_setup', $wp_meta_boxes['dashboard']['normal']['high'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the widget output when 0 task has been completed.
|
||||
*/
|
||||
public function test_initial_widget_output() {
|
||||
$html = $this->get_widget_output();
|
||||
|
||||
$required_strings = array(
|
||||
'Step 0 of 5',
|
||||
'You're almost there! Once you complete store setup you can start receiving orders.',
|
||||
'Start selling',
|
||||
'admin.php\?page=wc-admin&path=%2Fsetup-wizard',
|
||||
);
|
||||
|
||||
foreach ( $required_strings as $required_string ) {
|
||||
$this->assertRegexp( "/${required_string}/", $html );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests completed task count as it completes one by one
|
||||
*/
|
||||
public function test_widget_renders_completed_task_count() {
|
||||
$completed_tasks = array();
|
||||
$tasks = $this->get_widget()->get_tasks();
|
||||
$tasks_count = count( $tasks );
|
||||
foreach ( $tasks as $key => $task ) {
|
||||
array_push( $completed_tasks, $key );
|
||||
update_option( 'woocommerce_task_list_tracked_completed_tasks', $completed_tasks );
|
||||
$completed_tasks_count = count( $completed_tasks );
|
||||
// When all tasks are completed, assert that the widget output is empty.
|
||||
// As widget won't be rendered when tasks are completed.
|
||||
if ( $completed_tasks_count === $tasks_count ) {
|
||||
$this->assertEmpty( $this->get_widget_output() );
|
||||
} else {
|
||||
$this->assertRegexp( "/Step ${completed_tasks_count} of 5/", $this->get_widget_output() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides dataset that controls output of `should_display_widget`
|
||||
*/
|
||||
public function should_display_widget_data_provider() {
|
||||
return array(
|
||||
array(
|
||||
array(
|
||||
'woocommerce_task_list_complete' => 'yes',
|
||||
'woocommerce_task_list_hidden' => 'no',
|
||||
),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'woocommerce_task_list_complete' => 'no',
|
||||
'woocommerce_task_list_hidden' => 'yes',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -85,7 +85,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which reduces stock to another status which also reduces stock.
|
||||
* Test inventory count after order status transitions which reduces stock to another status which also reduces stock.
|
||||
* Stock should have reduced once already, and should not reduce again.
|
||||
*/
|
||||
public function test_status_transition_stock_reduce_to_stock_reduce() {
|
||||
|
@ -97,7 +97,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which reduces stock to another status which restores stock.
|
||||
* Test inventory count after order status transitions which reduces stock to another status which restores stock.
|
||||
* Should should have already reduced once, and will increase again after transitioning.
|
||||
*/
|
||||
public function test_status_transition_stock_reduce_to_stock_restore() {
|
||||
|
@ -109,7 +109,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which reduces stock to another status which don't affect inventory.
|
||||
* Test inventory count after order status transitions which reduces stock to another status which don't affect inventory.
|
||||
* Stock should have already reduced, and will not change on transitioning.
|
||||
*/
|
||||
public function test_status_transition_stock_reduce_to_stock_no_effect() {
|
||||
|
@ -133,7 +133,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which restores stock to another status which also restores stock.
|
||||
* Test inventory count after order status transitions which restores stock to another status which also restores stock.
|
||||
* Stock should not have reduced, and will remain the same even after transition (i.e. should not be restocked again).
|
||||
*/
|
||||
public function test_status_transition_stock_restore_to_stock_restore() {
|
||||
|
@ -145,7 +145,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which restores stock to another status which don't affect inventory.
|
||||
* Test inventory count after order status transitions which restores stock to another status which don't affect inventory.
|
||||
* Stock should not have reduced, and will remain the same even after transition.
|
||||
*/
|
||||
public function test_status_transition_stock_restore_to_stock_no_effect() {
|
||||
|
@ -157,7 +157,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which don't affect inventory stock to another status which reduces stock.
|
||||
* Test inventory count after order status transitions which don't affect inventory stock to another status which reduces stock.
|
||||
* Stock would not have been affected, but will reduce after transition.
|
||||
*/
|
||||
public function test_status_transition_stock_no_effect_to_stock_reduce() {
|
||||
|
@ -169,7 +169,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which don't affect inventory stock to another status which restores stock.
|
||||
* Test inventory count after order status transitions which don't affect inventory stock to another status which restores stock.
|
||||
* Stock would not have been affected, and will not be restored after transition (since it was not reduced to begin with).
|
||||
*/
|
||||
public function test_status_transition_stock_no_effect_to_stock_restore() {
|
||||
|
@ -181,7 +181,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test inventory count after order status transtions which don't affect inventory stock to another status which also don't affect inventory.
|
||||
* Test inventory count after order status transitions which don't affect inventory stock to another status which also don't affect inventory.
|
||||
* Stock levels will not change before or after the transition.
|
||||
*/
|
||||
public function test_status_transition_stock_no_effect_to_stock_no_effect() {
|
||||
|
|
Loading…
Reference in New Issue