Merge branch 'trunk' into add/install-all-pending-updates

This commit is contained in:
Greg 2021-06-18 11:31:42 -06:00 committed by GitHub
commit 74a9e1b0ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 380 additions and 353 deletions

View File

@ -254,16 +254,16 @@
},
{
"name": "rmccue/requests",
"version": "v1.8.0",
"version": "v1.8.1",
"source": {
"type": "git",
"url": "https://github.com/WordPress/Requests.git",
"reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1"
"reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/WordPress/Requests/zipball/afbe4790e4def03581c4a0963a1e8aa01f6030f1",
"reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1",
"url": "https://api.github.com/repos/WordPress/Requests/zipball/82e6936366eac3af4d836c18b9d8c31028fe4cd5",
"reference": "82e6936366eac3af4d836c18b9d8c31028fe4cd5",
"shasum": ""
},
"require": {
@ -306,11 +306,7 @@
"iri",
"sockets"
],
"support": {
"issues": "https://github.com/WordPress/Requests/issues",
"source": "https://github.com/WordPress/Requests/tree/v1.8.0"
},
"time": "2021-04-27T11:05:25+00:00"
"time": "2021-06-04T09:56:25+00:00"
},
{
"name": "symfony/finder",
@ -612,5 +608,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "1.1.0"
}

View File

@ -21,8 +21,8 @@
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.2.0",
"woocommerce/woocommerce-admin": "2.3.1",
"woocommerce/woocommerce-blocks": "5.1.0"
"woocommerce/woocommerce-admin": "2.4.0-rc.2",
"woocommerce/woocommerce-blocks": "5.3.1"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4"

61
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "21b06e63b5f65deec4b635463a10402b",
"content-hash": "6427ca1d82e637782404faeb3b988ec3",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -51,9 +51,6 @@
"GPL-2.0-or-later"
],
"description": "Creates a custom autoloader for a plugin or theme.",
"support": {
"source": "https://github.com/Automattic/jetpack-autoloader/tree/2.10.1"
},
"time": "2021-03-30T15:15:59+00:00"
},
{
@ -85,9 +82,6 @@
"GPL-2.0-or-later"
],
"description": "A wrapper for defining constants in a more testable way.",
"support": {
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
},
"time": "2020-10-28T19:00:31+00:00"
},
{
@ -220,10 +214,6 @@
"zend",
"zikula"
],
"support": {
"issues": "https://github.com/composer/installers/issues",
"source": "https://github.com/composer/installers/tree/v1.11.0"
},
"funding": [
{
"url": "https://packagist.com",
@ -298,10 +288,6 @@
"geolocation",
"maxmind"
],
"support": {
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
},
"time": "2019-12-19T22:59:03+00:00"
},
{
@ -376,10 +362,6 @@
"email",
"pre-processing"
],
"support": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
"time": "2019-12-26T19:37:31+00:00"
},
{
@ -429,10 +411,6 @@
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/master"
},
"time": "2017-02-14T16:28:37+00:00"
},
{
@ -486,9 +464,6 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/css-selector/tree/master"
},
"time": "2017-05-01T15:01:29+00:00"
},
{
@ -532,16 +507,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "2.3.1",
"version": "2.4.0-rc.2",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "f28cf3f027e27a6679e4fa8173d8b6859ec84838"
"reference": "ed72985cd459831c555dff2ff5f75111b6e210c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/f28cf3f027e27a6679e4fa8173d8b6859ec84838",
"reference": "f28cf3f027e27a6679e4fa8173d8b6859ec84838",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/ed72985cd459831c555dff2ff5f75111b6e210c1",
"reference": "ed72985cd459831c555dff2ff5f75111b6e210c1",
"shasum": ""
},
"require": {
@ -576,24 +551,20 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.3.1"
},
"time": "2021-05-24T09:48:40+00:00"
"time": "2021-06-18T05:58:25+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v5.1.0",
"version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "a4f168596f3832e161b26dec636b69293039ee51"
"reference": "28c7c4f9b5cace9098fb2246ff93abe110a26bca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/a4f168596f3832e161b26dec636b69293039ee51",
"reference": "a4f168596f3832e161b26dec636b69293039ee51",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/28c7c4f9b5cace9098fb2246ff93abe110a26bca",
"reference": "28c7c4f9b5cace9098fb2246ff93abe110a26bca",
"shasum": ""
},
"require": {
@ -627,11 +598,7 @@
"gutenberg",
"woocommerce"
],
"support": {
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v5.1.0"
},
"time": "2021-05-10T15:01:42+00:00"
"time": "2021-06-15T09:12:48+00:00"
}
],
"packages-dev": [
@ -679,10 +646,6 @@
"isolation",
"tool"
],
"support": {
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
},
"time": "2020-05-03T08:27:20+00:00"
}
],
@ -698,5 +661,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "1.1.0"
}

View File

@ -566,6 +566,8 @@ class WC_Product_CSV_Importer_Controller {
/* translators: %d: Attribute number */
__( 'Attribute %d default', 'woocommerce' ) => 'attributes:default',
/* translators: %d: Download number */
__( 'Download %d ID', 'woocommerce' ) => 'downloads:id',
/* translators: %d: Download number */
__( 'Download %d name', 'woocommerce' ) => 'downloads:name',
/* translators: %d: Download number */
__( 'Download %d URL', 'woocommerce' ) => 'downloads:url',
@ -721,6 +723,7 @@ class WC_Product_CSV_Importer_Controller {
'downloads' => array(
'name' => __( 'Downloads', 'woocommerce' ),
'options' => array(
'downloads:id' . $index => __( 'Download ID', 'woocommerce' ),
'downloads:name' . $index => __( 'Download name', 'woocommerce' ),
'downloads:url' . $index => __( 'Download URL', 'woocommerce' ),
'download_limit' => __( 'Download limit', 'woocommerce' ),

View File

@ -102,6 +102,7 @@ function wc_importer_default_special_english_mappings( $mappings ) {
'Attribute %d visible' => 'attributes:visible',
'Attribute %d global' => 'attributes:taxonomy',
'Attribute %d default' => 'attributes:default',
'Download %d ID' => 'downloads:id',
'Download %d name' => 'downloads:name',
'Download %d URL' => 'downloads:url',
'Meta: %s' => 'meta:',

View File

@ -145,6 +145,7 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
$actions['mark_processing'] = __( 'Change status to processing', 'woocommerce' );
$actions['mark_on-hold'] = __( 'Change status to on-hold', 'woocommerce' );
$actions['mark_completed'] = __( 'Change status to completed', 'woocommerce' );
$actions['mark_cancelled'] = __( 'Change status to cancelled', 'woocommerce' );
if ( wc_string_to_bool( get_option( 'woocommerce_allow_bulk_remove_personal_data', 'no' ) ) ) {
$actions['remove_personal_data'] = __( 'Remove personal data', 'woocommerce' );

View File

@ -310,6 +310,7 @@ class WC_Install {
self::create_files();
self::maybe_create_pages();
self::maybe_set_activation_transients();
self::set_paypal_standard_load_eligibility();
self::update_wc_version();
self::maybe_update_db_version();
@ -1635,6 +1636,18 @@ CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
ob_end_clean();
}
}
/**
* Sets whether PayPal Standard will be loaded on install.
*
* @since 5.5.0
*/
private static function set_paypal_standard_load_eligibility() {
// Initiating the payment gateways sets the flag.
if ( class_exists( 'WC_Gateway_Paypal' ) ) {
( new WC_Gateway_Paypal() )->should_load();
}
}
}
WC_Install::init();

View File

@ -78,9 +78,12 @@ class WC_Payment_Gateways {
'WC_Gateway_BACS',
'WC_Gateway_Cheque',
'WC_Gateway_COD',
'WC_Gateway_Paypal',
);
if ( $this->should_load_paypal_standard() ) {
$load_gateways[] = 'WC_Gateway_Paypal';
}
// Filter.
$load_gateways = apply_filters( 'woocommerce_payment_gateways', $load_gateways );
@ -219,4 +222,15 @@ class WC_Payment_Gateways {
update_option( 'woocommerce_gateway_order', $order );
}
/**
* Determines if PayPal Standard should be loaded.
*
* @since 5.5.0
* @return bool Whether PayPal Standard should be loaded or not.
*/
protected function should_load_paypal_standard() {
$paypal = new WC_Gateway_Paypal();
return $paypal->should_load();
}
}

View File

@ -601,10 +601,13 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
if ( $downloads ) {
$i = 1;
foreach ( $downloads as $download ) {
/* translators: %s: download number */
$this->column_names[ 'downloads:id' . $i ] = sprintf( __( 'Download %d ID', 'woocommerce' ), $i );
/* translators: %s: download number */
$this->column_names[ 'downloads:name' . $i ] = sprintf( __( 'Download %d name', 'woocommerce' ), $i );
/* translators: %s: download number */
$this->column_names[ 'downloads:url' . $i ] = sprintf( __( 'Download %d URL', 'woocommerce' ), $i );
$row[ 'downloads:id' . $i ] = $download->get_id();
$row[ 'downloads:name' . $i ] = $download->get_name();
$row[ 'downloads:url' . $i ] = $download->get_file();
$i++;

View File

@ -473,4 +473,42 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
return $text;
}
/**
* Determines whether PayPal Standard should be loaded or not.
*
* By default PayPal Standard isn't loaded on new installs or on existing sites which haven't set up the gateway.
*
* @since 5.5.0
*
* @return bool Whether PayPal Standard should be loaded.
*/
public function should_load() {
$option_key = '_should_load';
$should_load = $this->get_option( $option_key );
if ( '' === $should_load ) {
// New installs without PayPal Standard enabled don't load it.
if ( 'no' === $this->enabled && WC_Install::is_new_install() ) {
$should_load = false;
} else {
$should_load = true;
}
$this->update_option( $option_key, wc_bool_to_string( $should_load ) );
} else {
$should_load = wc_string_to_bool( $should_load );
}
/**
* Allow third-parties to filter whether PayPal Standard should be loaded or not.
*
* @since 5.5.0
*
* @param bool $should_load Whether PayPal Standard should be loaded.
* @param WC_Gateway_Paypal $this The WC_Gateway_Paypal instance.
*/
return apply_filters( 'woocommerce_should_load_paypal_standard', $should_load, $this );
}
}

View File

@ -894,6 +894,12 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'downloads:id' ) ) {
if ( ! empty( $value ) ) {
$downloads[ str_replace( 'downloads:id', '', $key ) ]['id'] = $value;
}
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'downloads:name' ) ) {
if ( ! empty( $value ) ) {
$downloads[ str_replace( 'downloads:name', '', $key ) ]['name'] = $value;
@ -935,8 +941,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
$data['downloads'][] = array(
'name' => $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['url'] ),
'file' => $file['url'],
'download_id' => isset( $file['id'] ) ? $file['id'] : null,
'name' => $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['url'] ),
'file' => $file['url'],
);
}
}

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interfaces
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interfaces
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Coupon Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Customer Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Customer Download Data Store Interface.
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Customer Download Log Data Store Interface.
*

View File

@ -6,10 +6,6 @@
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Importer_Interface class.
*/

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* WC Log Handler Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* WC Logger Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Item Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Item Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Item Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Order Refund Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Payment Token Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Product Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Product Variable Data Store Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* WC Queue Interface
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Shipping Zone Data Store Interface.
*

View File

@ -6,10 +6,6 @@
* @package WooCommerce\Interface
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WooCommerce Webhook data store interface.
*/

View File

@ -69,14 +69,6 @@ const runCheckoutPageTest = () => {
// Verify that settings have been saved
await verifyCheckboxIsSet('#woocommerce_cod_enabled');
// Enable PayPal payment method
await merchant.openSettings('checkout', 'paypal');
await setCheckbox('#woocommerce_paypal_enabled');
await settingsPageSaveChanges();
// Verify that settings have been saved
await verifyCheckboxIsSet('#woocommerce_paypal_enabled');
await merchant.logout();
});
@ -93,7 +85,6 @@ const runCheckoutPageTest = () => {
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `2`, twoProductPrice, twoProductPrice);
await expect(page).toClick('.wc_payment_method label', {text: 'PayPal'});
await expect(page).toClick('.wc_payment_method label', {text: 'Direct bank transfer'});
await expect(page).toClick('.wc_payment_method label', {text: 'Cash on delivery'});
});

View File

@ -4,18 +4,37 @@
*/
const {
shopper,
merchant,
createSimpleProduct,
createVariableProduct,
createGroupedProduct,
uiUnblocked
} = require( '@woocommerce/e2e-utils' );
let simplePostIdValue;
let variablePostIdValue;
let groupedPostIdValue;
const config = require( 'config' );
// Variables for simple product
const simpleProductName = config.get( 'products.simple.name' );
let simplePostIdValue;
// Variables for variable product
const defaultVariableProduct = config.get( 'products.variable' );
let variableProductId;
// Variables for grouped product
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const simple1 = {
name: simpleProductName + ' 1',
regularPrice: simpleProductPrice
};
const simple2 = {
name: simpleProductName + ' 2',
regularPrice: simpleProductPrice
};
const groupedProduct = {
name: 'Grouped Product',
groupedProducts: [simple1, simple2]
};
let groupedPostIdValue;
const runSingleProductPageTest = () => {
describe('Single Product Page', () => {
@ -43,30 +62,34 @@ const runSingleProductPageTest = () => {
});
});
describe.skip('Variable Product Page', () => {
describe('Variable Product Page', () => {
beforeAll(async () => {
await merchant.login();
variablePostIdValue = await createVariableProduct();
await merchant.logout();
variableProductId = await createVariableProduct();
});
it('should be able to add variation products to the cart', async () => {
// Add a product with one set of variations to cart
await shopper.goToProduct(variablePostIdValue);
await expect(page).toSelect('#attr-1', 'val1');
await expect(page).toSelect('#attr-2', 'val1');
await expect(page).toSelect('#attr-3', 'val1');
await shopper.goToProduct(variableProductId);
for (const attr of defaultVariableProduct.attributes) {
const { name, options } = attr;
const selectElem = `#${name.toLowerCase()}`;
const value = options[0];
await expect(page).toSelect(selectElem, value);
}
await shopper.addToCart();
await expect(page).toMatchElement('.woocommerce-message', {text: 'has been added to your cart.'});
// Verify cart contents
await shopper.goToCart();
await shopper.productIsInCart('Variable Product with Three Variations');
await shopper.productIsInCart(defaultVariableProduct.name);
});
it('should be able to remove variation products from the cart', async () => {
// Remove items from cart
await shopper.removeFromCart('Variable Product with Three Variations');
await shopper.removeFromCart(defaultVariableProduct.name);
await uiUnblocked();
await expect(page).toMatchElement('.cart-empty', {text: 'Your cart is currently empty.'});
});
@ -74,9 +97,7 @@ const runSingleProductPageTest = () => {
describe('Grouped Product Page', () => {
beforeAll(async () => {
await merchant.login();
groupedPostIdValue = await createGroupedProduct();
await merchant.logout();
groupedPostIdValue = await createGroupedProduct(groupedProduct);
});
it('should be able to add grouped products to the cart', async () => {
@ -93,7 +114,7 @@ const runSingleProductPageTest = () => {
await quantityFields[1].type('5');
await shopper.addToCart();
await expect(page).toMatchElement('.woocommerce-message',
{text: '“'+simpleProductName+' 1” and “'+simpleProductName+' 2” have been added to your cart.'});
{text: '“'+simpleProductName+' 1” and “'+simpleProductName+' 2” have been added to your cart.'});
// Verify cart contents
await shopper.goToCart();

View File

@ -4,73 +4,103 @@
*/
const {
shopper,
merchant,
createVariableProduct,
} = require( '@woocommerce/e2e-utils' );
const config = require('config');
let variablePostIdValue;
const cartDialogMessage = 'Please select some product options before adding this product to your cart.';
const attributes = config.get( 'products.variable.attributes' )
const runVariableProductUpdateTest = () => {
describe('Shopper > Update variable product',() => {
beforeAll(async () => {
await merchant.login();
variablePostIdValue = await createVariableProduct();
await merchant.logout();
});
it('shopper can change variable attributes to the same value', async () => {
await shopper.goToProduct(variablePostIdValue);
await expect(page).toSelect('#attr-1', 'val1');
await expect(page).toSelect('#attr-2', 'val1');
await expect(page).toSelect('#attr-3', 'val1');
await expect(page).toMatchElement('.woocommerce-variation-price', { text: '9.99' });
for (const a of attributes) {
const { name, options } = a;
const attrHTMLId = `#${name.toLowerCase()}`;
await expect(page).toSelect(attrHTMLId, options[0]);
}
await expect(page).toMatchElement('.woocommerce-variation-price', {
text: '9.99'
});
});
it('shopper can change attributes to combination with dimensions and weight', async () => {
await shopper.goToProduct(variablePostIdValue);
await expect(page).toSelect('#attr-1', 'val1');
await expect(page).toSelect('#attr-2', 'val2');
await expect(page).toSelect('#attr-3', 'val1');
await expect(page).toSelect(
`#${attributes[0].name.toLowerCase()}`,
attributes[0].options[0]
);
await expect(page).toSelect(
`#${attributes[1].name.toLowerCase()}`,
attributes[1].options[1]
);
await expect(page).toSelect(
`#${attributes[2].name.toLowerCase()}`,
attributes[2].options[0]
);
await expect(page).toMatchElement('.woocommerce-variation-price', { text: '20.00' });
await expect(page).toMatchElement('.woocommerce-variation-availability', { text: 'Out of stock' });
await expect(page).toMatchElement('.woocommerce-product-attributes-item--weight', { text: '200 kg' });
await expect(page).toMatchElement('.woocommerce-product-attributes-item--dimensions', { text: '10 × 20 × 15 cm' });
});
it('shopper can change variable product attributes to variation with a different price', async () => {
await shopper.goToProduct(variablePostIdValue);
await expect(page).toSelect('#attr-1', 'val1');
await expect(page).toSelect('#attr-2', 'val1');
await expect(page).toSelect('#attr-3', 'val2');
await expect(page).toSelect(
`#${attributes[0].name.toLowerCase()}`,
attributes[0].options[0]
);
await expect(page).toSelect(
`#${attributes[1].name.toLowerCase()}`,
attributes[1].options[0]
);
await expect(page).toSelect(
`#${attributes[2].name.toLowerCase()}`,
attributes[2].options[1]
);
await expect(page).toMatchElement('.woocommerce-variation-price', { text: '11.99' });
});
it('shopper can reset variations', async () => {
await shopper.goToProduct(variablePostIdValue);
await expect(page).toSelect('#attr-1', 'val1');
await expect(page).toSelect('#attr-2', 'val2');
await expect(page).toSelect('#attr-3', 'val1');
await expect(page).toSelect(
`#${attributes[0].name.toLowerCase()}`,
attributes[0].options[0]
);
await expect(page).toSelect(
`#${attributes[1].name.toLowerCase()}`,
attributes[1].options[1]
);
await expect(page).toSelect(
`#${attributes[2].name.toLowerCase()}`,
attributes[2].options[0]
);
await expect(page).toClick('.reset_variations');
// Verify the reset by attempting to add the product to the cart
const couponDialog = await expect(page).toDisplayDialog(async () => {
await expect(page).toClick('.single_add_to_cart_button');
});
await expect(page).toClick('.single_add_to_cart_button');
});
expect(couponDialog.message()).toMatch(cartDialogMessage);
// Accept the dialog
await couponDialog.accept();
});
});
};
module.exports = runVariableProductUpdateTest;

View File

@ -1,5 +1,8 @@
# Unreleased
## Added
- Factories for variable product, variation, and grouped product
- Added new constant for WordPress update page `WP_ADMIN_WP_UPDATES`
- Added new merchant flow for `openWordPressUpdatesPage()`
- Added new merchant flows:

View File

@ -7,10 +7,8 @@
*/
import { merchant, IS_RETEST_MODE } from './flows';
import {
clickTab,
uiUnblocked,
verifyCheckboxIsUnset,
selectOptionInSelect2,
setCheckbox,
unsetCheckbox,
evalAndClick,
@ -24,6 +22,8 @@ const client = factories.api.withDefaultPermalinks;
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const defaultVariableProduct = config.get('products.variable');
const defaultGroupedProduct = config.get('products.grouped');
/**
* Verify and publish
@ -135,6 +135,9 @@ const completeOnboardingWizard = async () => {
// Business Details section
// Temporarily add delay to reduce test flakiness
await page.waitFor( 2000 );
// Query for the <SelectControl>s
const selectControls = await page.$$( '.woocommerce-select-control' );
expect( selectControls ).toHaveLength( 2 );
@ -220,172 +223,104 @@ const createSimpleProductWithCategory = async ( productName, productPrice, categ
/**
* Create variable product.
* Also, create variations for all attributes.
*
* @param varProduct Defaults to the variable product object in `default.json`
* @returns the ID of the created variable product
*/
const createVariableProduct = async () => {
const createVariableProduct = async (varProduct = defaultVariableProduct) => {
const { attributes } = varProduct;
const { id } = await factories.products.variable.create(varProduct); // create the variable product
const variations = [];
const buffer = []; // accumulated attributes while looping
const aIdx = 0; // attributes[] index
// We need to remove any listeners on the `dialog` event otherwise we can't catch the dialogs below
page.removeAllListeners('dialog');
// Create variation for all attributes
const createVariation = (aIdx) => {
const { name, options } = attributes[aIdx];
const isLastAttribute = aIdx === attributes.length - 1;
// Go to "add product" page
await merchant.openNewProduct();
// Add each attribute value to the buffer.
options.forEach((opt) => {
buffer.push({
name: name,
option: opt
});
// Make sure we're on the add product page
await expect( page.title() ).resolves.toMatch( 'Add new product' );
if (isLastAttribute) {
// If this is the last attribute, it means the variation is now complete.
// Save whatever's been accumulated in the buffer to the `variations[]` array.
variations.push({
attributes: [...buffer]
});
} else {
// Otherwise, move to the next attribute first
// before proceeding to the next value in this attribute.
createVariation(aIdx + 1);
}
// Set product data
await expect( page ).toFill( '#title', 'Variable Product with Three Variations' );
await expect( page ).toSelect( '#product-type', 'Variable product' );
buffer.pop();
});
};
createVariation(aIdx);
// Create attributes for variations
await clickTab( 'Attributes' );
await expect( page ).toSelect( 'select[name="attribute_taxonomy"]', 'Custom product attribute' );
// Set some properties of 1st variation
variations[0].regularPrice = '9.99';
variations[0].virtual = true;
for ( let i = 0; i < 3; i++ ) {
await expect( page ).toClick( 'button.add_attribute', { text: 'Add' } );
// Wait for attribute form to load
await uiUnblocked();
// Set some properties of 2nd variation
variations[1].regularPrice = '11.99';
variations[1].virtual = true;
await page.focus( `input[name="attribute_names[${ i }]"]` );
await expect( page ).toFill( `input[name="attribute_names[${ i }]"]`, 'attr #' + ( i + 1 ) );
await expect( page ).toFill( `textarea[name="attribute_values[${ i }]"]`, 'val1 | val2' );
await expect( page ).toClick( `input[name="attribute_variation[${ i }]"]` );
// Set some properties of 3rd variation
variations[2].regularPrice = '20';
variations[2].weight = '200';
variations[2].dimensions = {
length: '10',
width: '20',
height: '15'
};
variations[2].manage_stock = true;
// Use API to create each variation
for (const v of variations) {
await factories.products.variation.create({
productId: id,
variation: v
});
}
await expect( page ).toClick( 'button', { text: 'Save attributes' } );
// Wait for attribute form to save (triggers 2 UI blocks)
await uiUnblocked();
await page.waitFor( 1000 );
await uiUnblocked();
// Create variations from attributes
await clickTab( 'Variations' );
await page.waitForSelector( 'select.variation_actions:not([disabled])' );
await page.focus( 'select.variation_actions' );
await expect( page ).toSelect( 'select.variation_actions', 'Create variations from all attributes' );
const firstDialog = await expect( page ).toDisplayDialog( async () => {
// Using this technique since toClick() isn't working.
// See: https://github.com/GoogleChrome/puppeteer/issues/1805#issuecomment-464802876
page.$eval( 'a.do_variation_action', elem => elem.click() );
} );
expect( firstDialog.message() ).toMatch( 'Are you sure you want to link all variations?' );
const secondDialog = await expect( page ).toDisplayDialog( async () => {
await firstDialog.accept();
} );
expect( secondDialog.message() ).toMatch( '8 variations added' );
await secondDialog.dismiss();
// Set some variation data
await uiUnblocked();
await uiUnblocked();
await page.waitForSelector( '.woocommerce_variation .handlediv' );
// Verify that variations were created
await Promise.all( [
expect( page ).toMatchElement( 'select[name="attribute_attr-1[0]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[0]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[0]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[1]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[1]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[1]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[2]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[2]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[2]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[3]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[3]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[3]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[4]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[4]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[4]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[5]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[5]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[5]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[6]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[6]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[6]"]', { text: 'val1' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-1[7]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-2[7]"]', { text: 'val2' } ),
expect( page ).toMatchElement( 'select[name="attribute_attr-3[7]"]', { text: 'val2' } ),
] );
await expect( page ).toClick( '.woocommerce_variation:nth-of-type(2) .handlediv' );
await page.waitFor( 2000 );
await page.focus( 'input[name="variable_is_virtual[0]"]' );
await expect( page ).toClick( 'input[name="variable_is_virtual[0]"]' );
await expect( page ).toFill( 'input[name="variable_regular_price[0]"]', '9.99' );
await expect( page ).toClick( '.woocommerce_variation:nth-of-type(3) .handlediv' );
await page.waitFor( 2000 );
await page.focus( 'input[name="variable_is_virtual[1]"]' );
await expect( page ).toClick( 'input[name="variable_is_virtual[1]"]' );
await expect( page ).toFill( 'input[name="variable_regular_price[1]"]', '11.99' );
await expect( page ).toClick( '.woocommerce_variation:nth-of-type(4) .handlediv' );
await page.waitFor( 2000 );
await page.focus( 'input[name="variable_manage_stock[2]"]' );
await expect( page ).toClick( 'input[name="variable_manage_stock[2]"]' );
await expect( page ).toFill( 'input[name="variable_regular_price[2]"]', '20' );
await expect( page ).toFill( 'input[name="variable_weight[2]"]', '200' );
await expect( page ).toFill( 'input[name="variable_length[2]"]', '10' );
await expect( page ).toFill( 'input[name="variable_width[2]"]', '20' );
await expect( page ).toFill( 'input[name="variable_height[2]"]', '15' );
await page.focus( 'button.save-variation-changes' );
await expect( page ).toClick( 'button.save-variation-changes', { text: 'Save changes' } );
await verifyAndPublish( 'Product published.' );
const variablePostId = await page.$( '#post_ID' );
let variablePostIdValue = ( await ( await variablePostId.getProperty( 'value' ) ).jsonValue() );
return variablePostIdValue;
return id;
};
/**
* Create grouped product.
*
* @param groupedProduct Defaults to the grouped product object in `default.json`
* @returns ID of the grouped product
*/
const createGroupedProduct = async () => {
// Create two products to be linked in a grouped product after
await factories.products.simple.create( {
name: simpleProductName + ' 1',
regularPrice: simpleProductPrice
} );
await factories.products.simple.create( {
name: simpleProductName + ' 2',
regularPrice: simpleProductPrice
} );
const createGroupedProduct = async (groupedProduct = defaultGroupedProduct) => {
const { name, groupedProducts } = groupedProduct;
const simpleProductIds = [];
let groupedProductRequest;
// Go to "add product" page
await merchant.openNewProduct();
// Using the api, create simple products to be grouped
for (const simpleProduct of groupedProducts) {
const { id } = await factories.products.simple.create(simpleProduct);
simpleProductIds.push(id);
}
// Make sure we're on the add product page
await expect( page.title() ).resolves.toMatch( 'Add new product' );
// Using the api, create the grouped product
groupedProductRequest = {
name: name,
groupedProducts: simpleProductIds
};
const { id } = await factories.products.grouped.create(
groupedProductRequest
);
// Set product data and save the product
await expect( page ).toFill( '#title', 'Grouped Product' );
await expect( page ).toSelect( '#product-type', 'Grouped product' );
await clickTab( 'Linked Products' );
await selectOptionInSelect2( simpleProductName + ' 1' );
await selectOptionInSelect2( simpleProductName + ' 2' );
await verifyAndPublish();
// Get product ID
const groupedPostId = await page.$( '#post_ID' );
let groupedPostIdValue = ( await ( await groupedPostId.getProperty( 'value' ) ).jsonValue() );
return groupedPostIdValue;
}
return id;
};
/**
* Create a basic order with the provided order status.

View File

@ -1,6 +1,9 @@
import { HTTPClientFactory } from '@woocommerce/api';
const config = require( 'config' );
import { simpleProductFactory } from './factories/simple-product';
import { variableProductFactory } from './factories/variable-product';
import { variationFactory } from './factories/variation';
import { groupedProductFactory } from './factories/grouped-product';
const apiUrl = config.get( 'url' );
const adminUsername = config.get( 'users.admin.username' );
@ -20,6 +23,9 @@ const factories = {
},
products: {
simple: simpleProductFactory( withDefaultPermalinks ),
variable: variableProductFactory( withDefaultPermalinks ),
variation: variationFactory( withDefaultPermalinks ),
grouped: groupedProductFactory( withDefaultPermalinks )
},
};

View File

@ -0,0 +1,26 @@
import { GroupedProduct } from '@woocommerce/api';
import { Factory } from 'fishery';
/**
* Creates a new factory for creating variable products.
* This does not include creating product variations.
* Instead, use `variationFactory()` for that.
*
* @param {HTTPClient} httpClient The HTTP client we will give the repository.
* @return {AsyncFactory} The factory for creating models.
*/
export function groupedProductFactory(httpClient) {
const repository = GroupedProduct.restRepository(httpClient);
return Factory.define(({ params, onCreate }) => {
onCreate((model) => {
return repository.create(model);
});
return {
name: params.name,
type: 'grouped',
groupedProducts: params.groupedProducts
};
});
}

View File

@ -0,0 +1,27 @@
import { VariableProduct } from '@woocommerce/api';
import { Factory } from 'fishery';
/**
* Creates a new factory for creating variable products.
* This does not include creating product variations.
* Instead, use `variationFactory()` for that.
*
* @param {HTTPClient} httpClient The HTTP client we will give the repository.
* @return {AsyncFactory} The factory for creating models.
*/
export function variableProductFactory(httpClient) {
const repository = VariableProduct.restRepository(httpClient);
return Factory.define(({ params, onCreate }) => {
onCreate((model) => {
return repository.create(model);
});
return {
name: params.name,
type: 'variable',
defaultAttributes: params.defaultAttributes,
attributes: params.attributes
};
});
}

View File

@ -0,0 +1,22 @@
import { ProductVariation } from '@woocommerce/api';
import { Factory } from 'fishery';
/**
* Creates a new factory for creating a product variation.
*
* @param {HTTPClient} httpClient The HTTP client we will give the repository.
* @return {AsyncFactory} The factory for creating models.
*/
export function variationFactory(httpClient) {
const repository = ProductVariation.restRepository(httpClient);
return Factory.define(({ params, onCreate }) => {
const { productId, variation } = params;
onCreate((model) => {
return repository.create(productId, model);
});
return variation;
});
}

View File

@ -58,6 +58,9 @@ class WC_Unit_Tests_Bootstrap {
// load test function so tests_add_filter() is available.
require_once $this->wp_tests_dir . '/includes/functions.php';
// Always load PayPal Standard for unit tests.
tests_add_filter( 'woocommerce_should_load_paypal_standard', '__return_true' );
// load WC.
tests_add_filter( 'muplugins_loaded', array( $this, 'load_wc' ) );

View File

@ -84,6 +84,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'Attribute 2 value(s)' => 'attributes:value2',
'Attribute 1 default' => 'attributes:default1',
'Attribute 2 default' => 'attributes:default2',
'Download 1 ID' => 'downloads:id1',
'Download 1 name' => 'downloads:name1',
'Download 1 URL' => 'downloads:url1',
);
@ -238,6 +239,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'',
'',
'',
'',
),
array(
'simple, downloadable, virtual',
@ -283,6 +285,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'180-Gram',
'',
'',
'4ff604c2-97bd-4869-938b-7798ba6648ab',
'Album flac',
'http://woo.dev/albums/album.flac',
),
@ -393,8 +396,9 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
),
'downloads' => array(
array(
'name' => 'Album flac',
'file' => 'http://woo.dev/albums/album.flac',
'name' => 'Album flac',
'file' => 'http://woo.dev/albums/album.flac',
'download_id' => '4ff604c2-97bd-4869-938b-7798ba6648ab',
),
),
'menu_order' => 1,

View File

@ -1,8 +1,8 @@
Type,SKU,Name,Published,Is featured?,Visibility in catalog,Short description,Description,Date sale price starts,Date sale price ends,Tax status,Tax class,In stock?,Stock,Backorders allowed?,Sold individually?,Weight (kg),Length (cm),Width (cm),Height (cm),Allow customer reviews?,Purchase note,Sale price,Regular price,Categories,Tags,Shipping class,Images,Download limit,Download expiry days,Parent,Upsells,Cross-sells,Grouped products,External URL,BUTTON TEXT,Position,Attribute 1 Name,Attribute 1 Value(s),Attribute 2 name,Attribute 2 value(s),Attribute 1 default,Attribute 2 default,Download 1 name,Download 1 URL
simple,WOOLOGO,Woo Logo,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",2017-01-01,2030-01-01 0:00:00,taxable,standard,1,5,notify,1,1,1,20,40,1,Lorem ipsum dolor sit amet.,18,20,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg",,,,WOOALBUM,WOOALBUM,,,,0,Color,Red,,,,,,
"simple, downloadable, virtual",WOOALBUM,Woo Album #1,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,1,Lorem ipsum dolor sit amet.,,5,"Music > Albums, Music",Woo,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg",10,90,,WOOLOGO,WOOLOGO,,,,1,Label,WooCommerce,Vinyl,180-Gram,,,Album flac,http://woo.dev/albums/album.flac
external,,WooCommerce Product CSV Suite,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,0,Lorem ipsum dolor sit amet.,,199,Software,WooCommerce,,,,,,,,,https://woocommerce.com/products/product-csv-import-suite/,Buy on WooCommerce.com,2,,,,,,,,
variable,WOOIDEA,Ship Your Idea,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,,,,,,,,,1,Lorem ipsum dolor sit amet.,,,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg",,,,,,,,,3,Color,"Black, Green",Size,"M, L",Green,L,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,6,0,,1,2,25,55,,,,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg,,,WOOIDEA,,,,,,1,Color,Black,Size,M,,,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,10,1,,1,2,25,55,,,17.99,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg,,,WOOIDEA,,,,,,2,Color,Green,Size,L,,,,
grouped,,Best Woo Products,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,1,,,,,,,,,,,,"Clothing, Clothing > T-shirts, Music > Albums, Music",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg",,,,,,"WOOLOGO, WOOALBUM",,,4,,,,,,,,
Type,SKU,Name,Published,Is featured?,Visibility in catalog,Short description,Description,Date sale price starts,Date sale price ends,Tax status,Tax class,In stock?,Stock,Backorders allowed?,Sold individually?,Weight (kg),Length (cm),Width (cm),Height (cm),Allow customer reviews?,Purchase note,Sale price,Regular price,Categories,Tags,Shipping class,Images,Download limit,Download expiry days,Parent,Upsells,Cross-sells,Grouped products,External URL,BUTTON TEXT,Position,Attribute 1 Name,Attribute 1 Value(s),Attribute 2 name,Attribute 2 value(s),Attribute 1 default,Attribute 2 default,Download 1 ID,Download 1 name,Download 1 URL
simple,WOOLOGO,Woo Logo,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",2017-01-01,2030-01-01 0:00:00,taxable,standard,1,5,notify,1,1,1,20,40,1,Lorem ipsum dolor sit amet.,18,20,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg",,,,WOOALBUM,WOOALBUM,,,,0,Color,Red,,,,,,,
"simple, downloadable, virtual",WOOALBUM,Woo Album #1,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,1,Lorem ipsum dolor sit amet.,,5,"Music > Albums, Music",Woo,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg",10,90,,WOOLOGO,WOOLOGO,,,,1,Label,WooCommerce,Vinyl,180-Gram,,,4ff604c2-97bd-4869-938b-7798ba6648ab,Album flac,http://woo.dev/albums/album.flac
external,,WooCommerce Product CSV Suite,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,0,Lorem ipsum dolor sit amet.,,199,Software,WooCommerce,,,,,,,,,https://woocommerce.com/products/product-csv-import-suite/,Buy on WooCommerce.com,2,,,,,,,,,
variable,WOOIDEA,Ship Your Idea,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,,,,,,,,,1,Lorem ipsum dolor sit amet.,,,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg",,,,,,,,,3,Color,"Black, Green",Size,"M, L",Green,L,,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,6,0,,1,2,25,55,,,,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg,,,WOOIDEA,,,,,,1,Color,Black,Size,M,,,,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,10,1,,1,2,25,55,,,17.99,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg,,,WOOIDEA,,,,,,2,Color,Green,Size,L,,,,,
grouped,,Best Woo Products,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,1,,,,,,,,,,,,"Clothing, Clothing > T-shirts, Music > Albums, Music",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg",,,,,,"WOOLOGO, WOOALBUM",,,4,,,,,,,,,

1 Type SKU Name Published Is featured? Visibility in catalog Short description Description Date sale price starts Date sale price ends Tax status Tax class In stock? Stock Backorders allowed? Sold individually? Weight (kg) Length (cm) Width (cm) Height (cm) Allow customer reviews? Purchase note Sale price Regular price Categories Tags Shipping class Images Download limit Download expiry days Parent Upsells Cross-sells Grouped products External URL BUTTON TEXT Position Attribute 1 Name Attribute 1 Value(s) Attribute 2 name Attribute 2 value(s) Attribute 1 default Attribute 2 default Download 1 ID Download 1 name Download 1 URL
2 simple WOOLOGO Woo Logo 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 2017-01-01 2030-01-01 0:00:00 taxable standard 1 5 notify 1 1 1 20 40 1 Lorem ipsum dolor sit amet. 18 20 Clothing, Clothing > T-shirts http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg WOOALBUM WOOALBUM 0 Color Red
3 simple, downloadable, virtual WOOALBUM Woo Album #1 1 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 1 Lorem ipsum dolor sit amet. 5 Music > Albums, Music Woo http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg 10 90 WOOLOGO WOOLOGO 1 Label WooCommerce Vinyl 180-Gram 4ff604c2-97bd-4869-938b-7798ba6648ab Album flac http://woo.dev/albums/album.flac
4 external WooCommerce Product CSV Suite 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 0 Lorem ipsum dolor sit amet. 199 Software WooCommerce https://woocommerce.com/products/product-csv-import-suite/ Buy on WooCommerce.com 2
5 variable WOOIDEA Ship Your Idea 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 1 Lorem ipsum dolor sit amet. Clothing, Clothing > T-shirts http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg 3 Color Black, Green Size M, L Green L
6 variation 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 6 0 1 2 25 55 20 http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg WOOIDEA 1 Color Black Size M
7 variation 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 10 1 1 2 25 55 17.99 20 http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg WOOIDEA 2 Color Green Size L
8 grouped Best Woo Products 1 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 1 Clothing, Clothing > T-shirts, Music > Albums, Music http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg WOOLOGO, WOOALBUM 4