diff --git a/.travis.yml b/.travis.yml index 10acbba2fe2..1b804ddae43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,3 +89,6 @@ branches: - master - /^\d+\.\d+(\.\d+)?(-\S*)?$/ - /^release\// + +before_install: + - composer self-update --1 diff --git a/README.md b/README.md index 91334b368fa..d0ced2c5c07 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Latest Stable Version WordPress.org downloads WordPress.org rating -Build Status +Build Status Scrutinizer Code Quality codecov

diff --git a/bin/composer/phpunit/composer.lock b/bin/composer/phpunit/composer.lock index 6c547721de9..85fb5cd406b 100644 --- a/bin/composer/phpunit/composer.lock +++ b/bin/composer/phpunit/composer.lock @@ -61,6 +61,20 @@ "constructor", "instantiate" ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], "time": "2020-05-29T17:27:14+00:00" }, { @@ -109,6 +123,12 @@ "object", "object graph" ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], "time": "2020-06-29T13:22:24+00:00" }, { @@ -1387,6 +1407,20 @@ "polyfill", "portable" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-10-23T14:02:19+00:00" }, { @@ -1488,5 +1522,6 @@ "platform-dev": [], "platform-overrides": { "php": "7.1" - } + }, + "plugin-api-version": "1.1.0" } diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php index d91e18d7007..aeda900bba1 100644 --- a/includes/class-wc-form-handler.php +++ b/includes/class-wc-form-handler.php @@ -885,10 +885,12 @@ class WC_Form_Handler { * @return bool success or not */ private static function add_to_cart_handler_variable( $product_id ) { - $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $variations = array(); + $product = wc_get_product( $product_id ); + foreach ( $_REQUEST as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 'attribute_' !== substr( $key, 0, 10 ) ) { continue; @@ -899,7 +901,22 @@ class WC_Form_Handler { $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations ); - if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) { + if ( ! $passed_validation ) { + return false; + } + + // Prevent parent variable product from being added to cart. + if ( empty( $variation_id ) && $product->is_type( 'variable' ) ) { + $url = get_permalink( $product_id ); + $product_name = $product->get_name(); + + /* translators: %1$s: Product link, %2$s: Product title, %3$s: Product name. */ + wc_add_notice( sprintf( __( 'Please choose product options by visiting %3$s.', 'woocommerce' ), esc_url( $url ), esc_html( $product_name ), esc_html( $product_name ) ), 'error' ); + + return false; + } + + if ( false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) { wc_add_to_cart_message( array( $product_id => $quantity ), true ); return true; } diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md index b8866c73091..32b786ec751 100644 --- a/tests/e2e/env/README.md +++ b/tests/e2e/env/README.md @@ -88,6 +88,7 @@ The test sequencer uses the following default Puppeteer configuration: launch: { ...jestPuppeteerConfig.launch, // @automattic/puppeteer-utils ignoreHTTPSErrors: true, + headless: false, args: [ '--window-size=1920,1080', '--user-agent=chrome' ], devtools: true, defaultViewport: { @@ -116,31 +117,13 @@ module.exports = puppeteerConfig; Jest provides setup and teardown functions similar to PHPUnit. The default setup and teardown is in [`tests/e2e/env/src/setup/jest.setup.js`](src/setup/jest.setup.js). Additional setup and teardown functions can be added to [`tests/e2e/config/jest.setup.js`](../config/jest.setup.js) -### Webpack Config - -The E2E environment provides a `@woocommerce/e2e-utils` alias for easy use of the WooCommerce E2E test helpers. - -```js -const { webpackAlias: coreE2EAlias } = require( '@woocommerce/e2e-environment' ); - -module.exports = { - .... - resolve: { - alias: { - ...coreE2EAlias, - .... - }, - }, -}; -``` - ### Container Setup Depending on the project and testing scenario, the built in testing environment container might not be the best solution for testing. This could be local testing where there is already a testing container, a repository that isn't a plugin or theme and there are multiple folders mapped into the container, or similar. The `e2e-environment` container supports using either the built in container or an external container. See the the appropriate readme for details: -- [Built In Container](./builtin.md) -- [External Container](./external.md) +- [Built In Container](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/env/builtin.md) +- [External Container](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/env/external.md) ## Additional information -Refer to [`tests/e2e/specs`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/specs) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e) for general information on e2e tests. +Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e) for general information on e2e tests. diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 9ea08949678..bcd841f2af3 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -32,4 +32,67 @@ describe( 'Cart page', () => { await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } ); } ); } ); -~~~ \ No newline at end of file +~~~ + +## Test Function + +### Merchant `StoreOwnerFlow` + +| Function | Description | +|----------|-------------| +| `login` | Log in as merchant | +| `logout` | log out of merchant account | +| `openAllOrdersView` | Go to the orders listing | +| `openDashboard` | Go to the WordPress dashboard | +| `openNewCoupon` | Go to the new coupon editor | +| `openNewOrder` | Go to the new order editor | +| `openNewProduct` | Go to the new product editor | +| `openPermalinkSettings` | Go to Settings -> Permalinks | +| `openPlugins` | Go to the Plugins screen | +| `openSettings` | Go to WooCommerce -> Settings | +| `runSetupWizard` | Open the onboarding profiler | +|----------|-------------| + +### Shopper `CustomerFlow` + +| Function | Parameters | Description | +|----------|------------|-------------| +| `addToCart` | | Add an item to the cart from a single product page | +| `addToCartFromShopPage` | `productTitle` | Add an item to the cart from a single product page | +| `fillBillingDetails` | `customerBillingDetails` | Fill billing fields in checkout form using configured address | +| `fillShippingDetails` | `customerShippingDetails` | Fill shipping fields in checkout form using configured address | +| `goToAddresses` | | Go to My Account -> Address Details | +| `goToAccountDetails` | | Go to My Account -> Details | +| `goToCart` | | Go to the cart page | +| `goToCheckout` | | Go to the checkout page | +| `goToShop` | | Go to the shop page | +| `goToProduct` | `productId` | Go to a single product in the shop | +| `goToOrders` | | Go to My Account -> Orders | +| `goToDownloads` | | Go to My Account -> Downloads | +| `login` | | Log in as the shopper | +| `placeOrder` | | Place an order from the checkout 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 | +|----------|------------|-------------| + +### Page Utilities + +| Function | Parameters | Description | +|----------|------------|-------------| +| `clearAndFillInput` | `selector, value` | Replace the contents of an input with the passed value | +| `clickTab` | `tabName` | Click on a WooCommerce -> Settings tab | +| `settingsPageSaveChanges` | | Save the current WooCommerce settings page | +| `permalinkSettingsPageSaveChanges` | | Save the current Permalink settings | +| `setCheckbox` | `selector` | Check a checkbox | +| `unsetCheckbox` | `selector` | Uncheck a checkbox | +| `uiUnblocked` | | Wait until the page is unblocked | +| `verifyPublishAndTrash` | `button, publishNotice, publishVerification, trashVerification` | Verify that an item can be published and trashed | +| `verifyCheckboxIsSet` | `selector` | Verify that a checkbox is checked | +| `verifyCheckboxIsUnset` | `selector` | Verify that a checkbox is unchecked | +| `verifyValueOfInputField` | `selector, value` | Verify an input contains the passed value | +|----------|------------|-------------| + +### Test Utilities + +As of version 0.1.2, all test utilities from [`@wordpress/e2e-test-utils`](https://www.npmjs.com/package/@wordpress/e2e-test-utils) are available through this package. diff --git a/tests/e2e/utils/src/flows.js b/tests/e2e/utils/src/flows.js index 50b0b8d5790..94b1340f548 100644 --- a/tests/e2e/utils/src/flows.js +++ b/tests/e2e/utils/src/flows.js @@ -125,7 +125,6 @@ const CustomerFlow = { } ); }, - goToShop: async () => { await page.goto(SHOP_PAGE, { waitUntil: 'networkidle0', diff --git a/tests/e2e/utils/src/index.js b/tests/e2e/utils/src/index.js index f6f914713f9..69c9067f6d3 100644 --- a/tests/e2e/utils/src/index.js +++ b/tests/e2e/utils/src/index.js @@ -1,44 +1,11 @@ -import { CustomerFlow, StoreOwnerFlow } from './flows'; +/* + * External dependencies + */ +export * from '@wordpress/e2e-test-utils'; +/* + * Internal dependencies + */ +export * from './flows'; +export * from './components'; +export * from './page-utils'; -import { - completeOnboardingWizard, - completeOldSetupWizard, - createSimpleProduct, - createVariableProduct, - verifyAndPublish, -} from './components'; - -import { - clearAndFillInput, - clickTab, - settingsPageSaveChanges, - permalinkSettingsPageSaveChanges, - setCheckbox, - unsetCheckbox, - uiUnblocked, - verifyPublishAndTrash, - verifyCheckboxIsSet, - verifyCheckboxIsUnset, - verifyValueOfInputField, -} from './page-utils'; - -module.exports = { - CustomerFlow, - StoreOwnerFlow, - completeOnboardingWizard, - completeOldSetupWizard, - createSimpleProduct, - createVariableProduct, - verifyAndPublish, - clearAndFillInput, - clickTab, - settingsPageSaveChanges, - permalinkSettingsPageSaveChanges, - setCheckbox, - unsetCheckbox, - uiUnblocked, - verifyPublishAndTrash, - verifyCheckboxIsSet, - verifyCheckboxIsUnset, - verifyValueOfInputField -} diff --git a/tests/php/includes/class-wc-cart-test.php b/tests/php/includes/class-wc-cart-test.php index c4b31632c03..0a2445b0af7 100644 --- a/tests/php/includes/class-wc-cart-test.php +++ b/tests/php/includes/class-wc-cart-test.php @@ -132,4 +132,26 @@ class WC_Cart_Test extends \WC_Unit_Test_Case { WC()->cart->get_customer()->set_shipping_state( '' ); WC()->cart->get_customer()->set_shipping_postcode( '' ); } + + /** + * Test adding a variable product without selecting variations. + * + * @see WC_Form_Handler::add_to_cart_action() + */ + public function test_form_handler_add_to_cart_action_with_parent_variable_product() { + $this->tearDown(); + + $product = WC_Helper_Product::create_variation_product(); + $product_id = $product->get_id(); + $url = get_permalink( $product_id ); + $_REQUEST['add-to-cart'] = $product_id; + + WC_Form_Handler::add_to_cart_action(); + + $notices = WC()->session->get( 'wc_notices', array() ); + + $this->assertArrayHasKey( 'error', $notices ); + $this->assertCount( 1, $notices['error'] ); + $this->assertRegExp( '/Please choose product options by visiting/', $notices['error'][0]['notice'] ); + } }