diff --git a/.github/workflows/php-js-e2e-tests.yml b/.github/workflows/php-js-e2e-tests.yml index 3000500b053..584a6222840 100644 --- a/.github/workflows/php-js-e2e-tests.yml +++ b/.github/workflows/php-js-e2e-tests.yml @@ -66,7 +66,6 @@ jobs: - name: E2E Tests (WP latest with Gutenberg plugin) env: WOOCOMMERCE_BLOCKS_PHASE: 3 - GUTENBERG_EDITOR_CONTEXT: 'gutenberg' run: | node ./bin/wp-env-with-gutenberg.js npm run wp-env start diff --git a/assets/js/blocks/checkout/address-wrapper/index.tsx b/assets/js/blocks/checkout/address-wrapper/index.tsx index b6182fa0141..bd9f5349c79 100644 --- a/assets/js/blocks/checkout/address-wrapper/index.tsx +++ b/assets/js/blocks/checkout/address-wrapper/index.tsx @@ -1,12 +1,17 @@ /** * External dependencies */ -import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import classnames from 'classnames'; + /** * Internal dependencies */ import './style.scss'; +/** + * Wrapper for address fields which handles the edit/preview transition. Form fields are always rendered so that + * validation can occur. + */ export const AddressWrapper = ( { isEditing = false, addressCard, @@ -16,25 +21,22 @@ export const AddressWrapper = ( { addressCard: () => JSX.Element; addressForm: () => JSX.Element; } ): JSX.Element | null => { + const wrapperClasses = classnames( + 'wc-block-components-address-address-wrapper', + { + 'is-editing': isEditing, + } + ); + return ( - - { ! isEditing && ( - - { addressCard() } - - ) } - { isEditing && ( - - { addressForm() } - - ) } - +
+
+ { addressCard() } +
+
+ { addressForm() } +
+
); }; diff --git a/assets/js/blocks/checkout/address-wrapper/style.scss b/assets/js/blocks/checkout/address-wrapper/style.scss index 6215de67970..3f18307a240 100644 --- a/assets/js/blocks/checkout/address-wrapper/style.scss +++ b/assets/js/blocks/checkout/address-wrapper/style.scss @@ -1,25 +1,32 @@ -.address-fade-transition-wrapper { +.wc-block-components-address-address-wrapper { position: relative; -} -.address-fade-transition-enter { - opacity: 0; -} -.address-fade-transition-enter-active { - opacity: 1; - transition: opacity 300ms ease-in; -} -.address-fade-transition-enter-done { - opacity: 1; -} -.address-fade-transition-exit { - opacity: 1; - position: absolute; - top: 0; -} -.address-fade-transition-exit-active { - opacity: 0; - transition: opacity 300ms ease-out; -} -.address-fade-transition-done { - opacity: 0; + + .wc-block-components-address-card-wrapper, + .wc-block-components-address-form-wrapper { + transition: all 300ms ease-in-out; + width: 100%; + } + + &.is-editing { + .wc-block-components-address-form-wrapper { + opacity: 1; + } + .wc-block-components-address-card-wrapper { + opacity: 0; + visibility: hidden; + position: absolute; + top: 0; + } + } + + &:not(.is-editing) { + .wc-block-components-address-form-wrapper { + opacity: 0; + visibility: hidden; + height: 0; + } + .wc-block-components-address-card-wrapper { + opacity: 1; + } + } } diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx index b1f1dcdc2f4..ae722ab9228 100644 --- a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx +++ b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx @@ -80,10 +80,6 @@ const Block = ( { const noticeContext = useBillingAsShipping ? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ] : [ noticeContexts.BILLING_ADDRESS ]; - const hasAddress = !! ( - billingAddress.address_1 && - ( billingAddress.first_name || billingAddress.last_name ) - ); return ( <> @@ -93,7 +89,6 @@ const Block = ( { addressFieldsConfig={ addressFieldsConfig } showPhoneField={ showPhoneField } requirePhoneField={ requirePhoneField } - hasAddress={ hasAddress } forceEditing={ forceEditing } /> diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx index 45bc172b382..e4dafcf40a1 100644 --- a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx +++ b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { useState, useCallback } from '@wordpress/element'; +import { useState, useCallback, useEffect } from '@wordpress/element'; import { AddressForm } from '@woocommerce/base-components/cart-checkout'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import type { @@ -9,6 +9,8 @@ import type { AddressField, AddressFields, } from '@woocommerce/settings'; +import { useSelect } from '@wordpress/data'; +import { VALIDATION_STORE_KEY } from '@woocommerce/block-data'; /** * Internal dependencies @@ -21,13 +23,11 @@ const CustomerAddress = ( { addressFieldsConfig, showPhoneField, requirePhoneField, - hasAddress, forceEditing = false, }: { addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >; showPhoneField: boolean; requirePhoneField: boolean; - hasAddress: boolean; forceEditing?: boolean; } ) => { const { @@ -40,8 +40,34 @@ const CustomerAddress = ( { useBillingAsShipping, } = useCheckoutAddress(); const { dispatchCheckoutEvent } = useStoreEvents(); - + const hasAddress = !! ( + billingAddress.address_1 && + ( billingAddress.first_name || billingAddress.last_name ) + ); const [ editing, setEditing ] = useState( ! hasAddress || forceEditing ); + + // Forces editing state if store has errors. + const { hasValidationErrors, invalidProps } = useSelect( ( select ) => { + const store = select( VALIDATION_STORE_KEY ); + return { + hasValidationErrors: store.hasValidationErrors(), + invalidProps: Object.keys( billingAddress ) + .filter( ( key ) => { + return ( + store.getValidationError( 'billing_' + key ) !== + undefined + ); + } ) + .filter( Boolean ), + }; + } ); + + useEffect( () => { + if ( invalidProps.length > 0 && editing === false ) { + setEditing( true ); + } + }, [ editing, hasValidationErrors, invalidProps.length ] ); + const addressFieldKeys = Object.keys( defaultAddressFields ) as ( keyof AddressFields )[]; diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/block.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/block.tsx index 077133c5c23..3b8dc12871f 100644 --- a/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/block.tsx +++ b/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/block.tsx @@ -104,7 +104,6 @@ const Block = ( { addressFieldsConfig={ addressFieldsConfig } showPhoneField={ showPhoneField } requirePhoneField={ requirePhoneField } - hasAddress={ hasAddress } /> { hasAddress && ( diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx index 72431db75fa..f041c54d369 100644 --- a/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx +++ b/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { useState, useCallback } from '@wordpress/element'; +import { useState, useCallback, useEffect } from '@wordpress/element'; import { AddressForm } from '@woocommerce/base-components/cart-checkout'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import type { @@ -9,6 +9,8 @@ import type { AddressField, AddressFields, } from '@woocommerce/settings'; +import { useSelect } from '@wordpress/data'; +import { VALIDATION_STORE_KEY } from '@woocommerce/block-data'; /** * Internal dependencies @@ -21,12 +23,10 @@ const CustomerAddress = ( { addressFieldsConfig, showPhoneField, requirePhoneField, - hasAddress, }: { addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >; showPhoneField: boolean; requirePhoneField: boolean; - hasAddress: boolean; } ) => { const { defaultAddressFields, @@ -37,8 +37,34 @@ const CustomerAddress = ( { useShippingAsBilling, } = useCheckoutAddress(); const { dispatchCheckoutEvent } = useStoreEvents(); - + const hasAddress = !! ( + shippingAddress.address_1 && + ( shippingAddress.first_name || shippingAddress.last_name ) + ); const [ editing, setEditing ] = useState( ! hasAddress ); + + // Forces editing state if store has errors. + const { hasValidationErrors, invalidProps } = useSelect( ( select ) => { + const store = select( VALIDATION_STORE_KEY ); + return { + hasValidationErrors: store.hasValidationErrors(), + invalidProps: Object.keys( shippingAddress ) + .filter( ( key ) => { + return ( + store.getValidationError( 'shipping_' + key ) !== + undefined + ); + } ) + .filter( Boolean ), + }; + } ); + + useEffect( () => { + if ( invalidProps.length > 0 && editing === false ) { + setEditing( true ); + } + }, [ editing, hasValidationErrors, invalidProps.length ] ); + const addressFieldKeys = Object.keys( defaultAddressFields ) as ( keyof AddressFields )[]; diff --git a/composer.json b/composer.json index 5625974a903..1578091b5f2 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://woocommerce.com/", "type": "wordpress-plugin", - "version": "11.3.0-dev", + "version": "11.3.0", "keywords": [ "gutenberg", "woocommerce", diff --git a/docs/contributors/javascript-testing.md b/docs/contributors/javascript-testing.md index 0268c7684d8..79377844e1f 100644 --- a/docs/contributors/javascript-testing.md +++ b/docs/contributors/javascript-testing.md @@ -107,7 +107,7 @@ You will need to stop `wp-env` and start it again. In some cases, you will also Currently, we only run e2e tests with the most recent version of WordPress. We also have the infrastructure in place to run e2e tests with the most recent version of WordPress with Gutenberg installed, but [it's currently disabled](https://github.com/woocommerce/woocommerce-blocks/blob/07605450ffa4e460543980b7011b3bf8a8e82ff4/.github/workflows/php-js-e2e-tests.yml#L10). -When preparing for a new version of WordPress, it's a good practice to search for conditions in our tests that check for specific WP versions (with the variable `WP_VERSION`) or that check whether Gutenberg is installed (with the variable `GUTENBERG_EDITOR_CONTEXT`). +When preparing for a new version of WordPress, it's a good practice to search for conditions in our tests that check for specific WP versions (with the variable `WP_VERSION`). @@ -118,4 +118,3 @@ When preparing for a new version of WordPress, it's a good practice to search fo 🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/contributors/javascript-testing.md) - diff --git a/docs/internal-developers/testing/releases/1130.md b/docs/internal-developers/testing/releases/1130.md new file mode 100644 index 00000000000..98874bf3f61 --- /dev/null +++ b/docs/internal-developers/testing/releases/1130.md @@ -0,0 +1,296 @@ +# Testing notes and ZIP for release 11.3.0 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-blocks/files/12862192/woocommerce-gutenberg-products-block.zip) + + +## WooCommerce Core + +### Condensed Address Form Implementation [11167](https://github.com/woocommerce/woocommerce-blocks/pull/11167) + +#### As a logged out guest customer + +1. Add something to cart and then go to checkout +2. Notice the address form is shown +3. Fill out address. Wait for totals to update. +4. Refresh the page. Condensed address component should be visible. + +#### As a logged in user who has checked out before (has an address) + +1. Repeat above tests. Condensed address component should be visible upon entry to checkout. +2. Click "edit" on the condensed shipping address. Form should be shown instead. +3. Uncheck "use shipping for billing". Billing address form should be shown. +4. Change some item of billing address data. +5. Refresh the page. +6. Condensed billing address should be shown. + +![Screenshot 2023-10-06 at 17 15 21](https://github.com/woocommerce/woocommerce-blocks/assets/90977/06621207-9ab6-4e75-8b04-e2c8e532971e) + +### Store Customization > Update aspect ratio, icons and default text for the Social: Follow us on social media pattern [11161](https://github.com/woocommerce/woocommerce-blocks/pull/11161) + +1. Create a new post +2. Insert the **Social: Follow us on social media** pattern +3. Make sure it is correctly rendered both on the editor side and on the front end. The images should all be with a square format. +4. Make sure the icons have the color set as primary. + +| Before | After | +| ------ | ----- | +| Screenshot 2023-10-06 at 10 39 06 | Screenshot 2023-10-06 at 10 50 19 | + +### Add horizontal padding to the Featured Category Triple pattern [11160](https://github.com/woocommerce/woocommerce-blocks/pull/11160) + +1. Create a new page or post. +2. Insert the `Featured Category Triple` pattern. +3. Check if there's a padding left and right of the pattern, both in the editor and the front end. + +### Remove placeholder and pagination [11145](https://github.com/woocommerce/woocommerce-blocks/pull/11145) + +1. Create a new page or post. +2. Insert the `Product Collection 3 Columns`, `Product Collection 4 Columns`, `Product Collection 5 Columns`, and `Product Gallery` patterns. +3. Check they don't have the pagination or the no results blocks. +4. Check the image's aspect ratio is portrait (height > width). + +| Before | After | +| ------ | ----- | +| before | after | + +### Store Customization > Homepage Template 1: Review layout and spacing in between patterns [11144](https://github.com/woocommerce/woocommerce-blocks/pull/11144) + +1. Create a new page or post and insert all the patterns shown in the screenshot below 👇 +- `Minimal 5-column products row` corresponds to `Product Collection 5 Columns` +2. Make sure the design is the same and the spacing between patterns matches. + +Screenshot 2023-10-02 at 17 39 07 + +### Store Customization > Replace patterns based on the Products (Beta) block with an equivalent based on the Product Collection block [11134](https://github.com/woocommerce/woocommerce-blocks/pull/11134) + +| Old Pattern | Replacement | +| ------ | ----- | +| 3-Column Product Row Screenshot 2023-10-04 at 17 12 47 | Product Collection 3 Columns Screenshot 2023-10-04 at 17 13 37 | +| 1:1 Image 4-Column Product Row Screenshot 2023-10-04 at 17 14 25 | Product Collection 4 Columns Screenshot 2023-10-04 at 17 14 59 | +| Minimal 5-Column Product Row Screenshot 2023-10-04 at 17 15 54 | Product Collection 5 Columns Screenshot 2023-10-04 at 17 16 32 | +| Featured Products 5-Item Grid Screenshot 2023-10-04 at 17 17 40 | Product Collection: Featured Products 5 Columns Screenshot 2023-10-04 at 17 18 14 | + +1. Create a new post +2. Insert the following patterns: Product Collection 3 Columns, Product Collection 4 Columns, Product Collection 5 Columns, Product Collection: Featured Products 5 Columns +3. Make sure all of them are using the Product Collection block for rendering in the editor: + +Screenshot 2023-10-04 at 17 23 05 + +4. Save and head over to the front end: make sure things are working as expected and the designs match the ones displayed on the screenshots in this PR. + +### Store Customization > Homepage Template 3: Review layout and spacing in between patterns [11131](https://github.com/woocommerce/woocommerce-blocks/pull/11131) + +1. Create a new page or post and insert all the patterns shown in the screenshot below 👇 (`Featured Products 5-Item Grid` corresponds to `Product Collection: Featured Products 5 Columns`) +2. Make sure the design is the same and the spacing between patterns matches. + +Screenshot 2023-10-02 at 17 39 07 + +### Store Customization > Add default image to Just Arrived pattern [11130](https://github.com/woocommerce/woocommerce-blocks/pull/11130) + +1. Create a new post and add the pattern "Just Arrived Full Hero" +2. Check that it looks like the below screenshots on both desktop and mobile viewports both in the editor and on the frontend. + +| Desktop | Mobile | +| ------ | ----- | +| ![Screenshot 2023-10-04 at 14 21 14](https://github.com/woocommerce/woocommerce-blocks/assets/8639742/321440db-4be3-4691-af48-427edbe60488) | ![Screenshot 2023-10-04 at 14 21 28](https://github.com/woocommerce/woocommerce-blocks/assets/8639742/90b55b88-e7da-4555-b25e-480ad4474a22) | + +### Store Customization > Homepage Template 2: Review layout and spacing in between patterns [11129](https://github.com/woocommerce/woocommerce-blocks/pull/11129) + +1. Create a new page or post and insert all the patterns shown in the screenshot below 👇 +- `1:1 image 4-column products row` corresponds to `Product Collection 4 Columns`. +- `Minimal 5-column products row` corresponds to `Product Collection 5 Columns`. +2. Make sure the design is the same and the spacing between patterns matches. + +Screenshot 2023-10-02 at 17 36 50 + +### Add data-price button attribute containing product price [11117](https://github.com/woocommerce/woocommerce-blocks/pull/11117) + +1. Add a page with a Products (Beta), Product by Category or Products by Attribute block on it. +2. Install Pinterest Tag Helper extension into your browser (I use Chrome for that matter). +3. Navigate to the page with the added block and click Add to Cart button. +4. See an AddToCart event inside Pinterest Tag Helper has no value attribute value (it has it set to null, instead of a product price). + +![240404349-7dbb2c93-2fd4-4820-ac59-87e3cee11075](https://github.com/woocommerce/woocommerce-blocks/assets/9010963/f42032c7-a971-4f52-a6ff-d3336e9e1182) + +Expected value is not `null` + +All_Products_–_WordPress_Pinterest + +### Store Customization > Wireframe and adjust width for the Just Arrived Full Hero pattern [11115](https://github.com/woocommerce/woocommerce-blocks/pull/11115) + +1. Create a new post +2. Insert the **Just Arrived Full Hero** pattern +3. Add an image to it and save +4. Make sure the styles match what is on the screenshots of this PR +5. Save the post and check the front end: make sure things are working as expected as well + +| Desktop | Mobile | +| ------ | ----- | +| Screenshot 2023-10-03 at 17 58 13 | Screenshot 2023-10-03 at 18 00 10 | + +### Remove opinionated styles from the Hero Product 3 Split pattern [11110](https://github.com/woocommerce/woocommerce-blocks/pull/11110) + +1. Insert the `Hero Product 3 Split` pattern into your page. +2. Go to Site Editor > Styles and select various color palettes. +3. Ensure the patterns style changes color based on the selected preferences. +4. Test in various block themes to ensure it works as expected. + +| Before | After | +| ------ | ----- | +| Screenshot 2023-10-03 at 15 29 20 | Screenshot 2023-10-03 at 15 27 59 | + +### Store Customization > Add the Featured Category Cover Image pattern [11109](https://github.com/woocommerce/woocommerce-blocks/pull/11109) + +1. Create a new post +2. Insert the **Featured Category Cover Image** pattern +3. Make sure the styles match what is on the screenshots of this PR +4. Save the post and check the front end: make sure things are working as expected as well + +| Desktop | Mobile | +| ------ | ----- | +| Screenshot 2023-10-03 at 15 39 56 | Screenshot 2023-10-03 at 15 39 45 | + +### Add fee ID/key to parent cart and checkout block [11054](https://github.com/woocommerce/woocommerce-blocks/pull/11054) + +#### Testing notes for the development team + +1. add a fee to you cart, for example by using this code in your functions.php +``` +add_action( 'woocommerce_cart_calculate_fees', function( $cart ) { + $cart->add_fee( 'Fee 0', 5, true ); + $cart->add_fee( 'Fee 2', 10, true ); + $cart->add_fee( 'Fee 3', 50, true ); +}); + +``` +2. go to your cart or checkout block +3. check the CSS classes of the fee elements with your browser developer tool + +![screen](https://github.com/woocommerce/woocommerce-blocks/assets/2648926/e89c27de-6306-4dcf-84cc-ab0a7a297988) + +### Update the sidebar notice we show for incompatible extensions [10877](https://github.com/woocommerce/woocommerce-blocks/pull/10877) + +#### Testing notes for the development team + +##### Test updated text + +1. Install and activate the [Coinbase Commerce extension](https://wordpress.org/plugins/coinbase-commerce/). +2. Install and activate the [Paystack WooCommerce Payment Gateway extension](https://wordpress.org/plugins/woo-paystack/). You should check `Enable Paystack` in its settings. +3. Create a test page and add the Checkout block to it. +4. Open the settings sidebar, if not already open. +5. Verify that the incompatibility notice lists the `Coinbase` and `Paystack` extensions: +Screenshot 2023-09-22 at 19 57 49 + +> **Note** +> If the compatibility notice does not appear, then open the inspector, go to `Application » Local Storage`, select your local site, remove the entry `wc-wc-blocks_dismissed_incompatible_payment_gateways_notices`, and refresh the page: +Screenshot 2023-09-22 at 19 58 40 + +##### Test display of incompatible extensions + +1. Install and activate the following three helper plugins: + - [helper-plugin-1.zip](https://github.com/woocommerce/woocommerce-blocks/files/12701036/helper-plugin-1.zip) + - [helper-plugin-2.zip](https://github.com/woocommerce/woocommerce-blocks/files/12701039/helper-plugin-2.zip) + - [helper-plugin-3.zip](https://github.com/woocommerce/woocommerce-blocks/files/12701041/helper-plugin-3.zip) +2. Create a test page and add the Checkout block to it. +4. Open the settings sidebar, if not already open. +5. Verify that the incompatibility notice lists the `A → Incompatible Extension`, `Coinbase`, `N → Incompatible Extension +`, `Paystack` and `Z → Incompatible Extension` extensions: +Screenshot 2023-09-22 at 20 12 20 + +##### Test closing of sidebar notice + +1. Ensure that you executed the previous test case. +2. Dismiss the incompatibility sidebar notice by clicking on the X in the upper-right corner. +3. Refresh the page and verify that the incompatibility sidebar notice remains hidden. + + + + + + +
Before: +

+Screenshot 2023-09-22 at 20 23 22 +
After: +

+Screenshot 2023-09-22 at 20 12 20 +
+ +### Fix: Store Notices block breaks page editors [11165](https://github.com/woocommerce/woocommerce-blocks/pull/11165) + +1. Edit a page. +2. Add the Store Notices Block. +3. Try Saving the page. +4. See no error. The page is saved successfully. + +| Before | After | +| ------ | ----- | +| image | image | + +### Store Customization > Ensure the Just Arrived Full Hero pattern can have an AI selected image assigned to it [11159](https://github.com/woocommerce/woocommerce-blocks/pull/11159) + +1. Access the JN install with the credentials provided here > p1696409554974299-slack-C053716F2H2 +2. Create a new post +3. Insert the **Just Arrived Full Hero** pattern +4. Make sure it is correctly rendered both on the editor side and on the front end. + +PS: the background image doesn't necessarily have to match what is displayed in the screenshot below; those are dynamically changed depending on the business description. + +| Before | After | +| ------ | ----- | +| Screenshot 2023-10-06 at 09 55 46 | Screenshot 2023-10-06 at 09 55 12 | + +### Store Customization > Fix the Testimonials 3 Columns pattern [11158](https://github.com/woocommerce/woocommerce-blocks/pull/11158) + +#### Testing notes for the development team + +1. Create a new post +2. Insert the Testimonials 3 Columns pattern +3. Make sure it works as expected both on the editor side and on the front end +4. Check your PHP error log and confirm you don't see any errors related to this pattern in particular. + +| Before | After | +| ------ | ----- | +| Screenshot 2023-10-06 at 08 45 52 | Screenshot 2023-10-06 at 08 46 02 | + +### Pattern: fetch product id on the JS side [11138](https://github.com/woocommerce/woocommerce-blocks/pull/11138) + +1. Open the post editor. +2. Add the `Product hero` and `Product details listing` patterns. +3. Ensure that a product is visible. + +### Force 100% width for combobox in checkout block [11133](https://github.com/woocommerce/woocommerce-blocks/pull/11133) + +1. Install and activate the Twenty Twenty-Three theme. +2. Install and activate the Gutenberg plugin. +3. Create a test page and add the Checkout block. +4. Edit the page and verify that the Country/Region and State fields are full width. + +![Screenshot 2023-10-04 at 15 14 24](https://github.com/woocommerce/woocommerce-blocks/assets/90977/f42f91c8-d235-40ba-8571-1d0714958ae4) + +### Icon should use current color on checkout error [11127](https://github.com/woocommerce/woocommerce-blocks/pull/11127) + +1. With TT3 go to Appearance > Editor, edit one template and open the styles sidebar. Select a dark style variation, like Pilgrimage. +2. In the frontend, add a product to your cart and go to the Checkout page with the Checkout block. +3. In the admin, mark that product as out of stock. +4. Reload the Checkout page. +5. Icon color should match text. + +| Before | After | +| ------ | ----- | +| ![Screenshot 2023-10-04 at 12 31 18](https://github.com/woocommerce/woocommerce-blocks/assets/90977/63fd3b2d-31c3-4666-aa2b-c5620204ca6f) | ![Screenshot 2023-10-04 at 12 36 12](https://github.com/woocommerce/woocommerce-blocks/assets/90977/874a66cc-4ab7-4434-8884-b96ea041784d) | + +### Single Product block: Redirect to the cart page after successful addition setting isn't respected [11151](https://github.com/woocommerce/woocommerce-blocks/pull/11151) + +#### Test 1: Redirect shopper to the cart page after successful addition when setting is enabled +1. Enable redirects to the cart page with the setting under WooCommerce > Settings > Products > General > Add to cart behaviour > Check - Redirect to the cart page after successful addition +2. Create a product and a page with an FSE theme. +3. Add the product to the page with the Single Product block. +4. View the page and click "Add to cart". Make sure the shopper is redirected to the Cart page and the product was correctly added to the cart + +#### Test 2: Keep shopper at the same page when setting is disabled +1. Enable redirects to the cart page with the setting under WooCommerce > Settings > Products > General > Add to cart behaviour > Check - Redirect to the cart page after successful addition +2. Create a product and a page with an FSE theme. +3. Add the product to the page with the Single Product block. +4. View the page and click "Add to cart". Make sure the shopper is NOT redirected to the Cart page; +5. Visit the Cart page and make sure the product was correctly added to the cart. diff --git a/docs/internal-developers/testing/releases/README.md b/docs/internal-developers/testing/releases/README.md index 7c3ebc4a80c..32c39ffc1fd 100644 --- a/docs/internal-developers/testing/releases/README.md +++ b/docs/internal-developers/testing/releases/README.md @@ -179,6 +179,7 @@ Every release includes specific testing instructions for new features and bug fi - [11.1.1](./1111.md) - [11.1.2](./1112.md) - [11.2.0](./1120.md) +- [11.3.0](./1130.md) diff --git a/package-lock.json b/package-lock.json index 284fa18b008..4686cddd26d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@woocommerce/block-library", - "version": "11.3.0-dev", + "version": "11.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@woocommerce/block-library", - "version": "11.3.0-dev", + "version": "11.3.0", "hasInstallScript": true, "license": "GPL-3.0+", "dependencies": { diff --git a/package.json b/package.json index 5e39e3878d1..500599f279a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "11.3.0-dev", + "version": "11.3.0", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index 8e71bfe1638..2859bf7d6d8 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks Requires at least: 6.3 Tested up to: 6.3 Requires PHP: 7.3 -Stable tag: 11.1.1 +Stable tag: 11.3.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -81,6 +81,38 @@ Release and roadmap notes available on the [WooCommerce Developers Blog](https:/ == Changelog == += 11.3.0 - 2023-10-09 = + +#### Enhancements + +- Introduced condensed address components on checkout for customers with an existing address. ([11167](https://github.com/woocommerce/woocommerce-blocks/pull/11167)) +- Update aspect ratio, icons and default text for the Social: Follow us on social media pattern. ([11161](https://github.com/woocommerce/woocommerce-blocks/pull/11161)) +- Add horizontal padding to the Featured Category Triple pattern. ([11160](https://github.com/woocommerce/woocommerce-blocks/pull/11160)) +- Remove the "no results" placeholder and pagination and set aspect ratio on the Product Collection 3 Columns, Product Collection 4 Columns, Product Collection 5 Columns, and Product Gallery patterns. ([11145](https://github.com/woocommerce/woocommerce-blocks/pull/11145)) +- Add group and padding to the Product Collection 3 Columns, Featured Category Triple and the Social: Follow us on social media patterns. ([11144](https://github.com/woocommerce/woocommerce-blocks/pull/11144)) +- New Product Collection Patterns: add the new Product Collection 3 Columns, Product Collection 4 Columns, Product Collection 5 Columns and Product Collection: Featured Products 5 Columns patterns. ([11134](https://github.com/woocommerce/woocommerce-blocks/pull/11134)) +- Add titles and padding to the Product Collection: Featured Products 5 Columns and the Product Gallery patterns. ([11131](https://github.com/woocommerce/woocommerce-blocks/pull/11131)) +- Add default image and fixed height to the Just Arrived Full Hero pattern. ([11130](https://github.com/woocommerce/woocommerce-blocks/pull/11130)) +- Add titles and padding to the Product Collection 4 Columns, Product Collection 5 Columns, and Testimonials 3 Columns patterns. ([11129](https://github.com/woocommerce/woocommerce-blocks/pull/11129)) +- Add Add to Cart button's product price data attribute. ([11117](https://github.com/woocommerce/woocommerce-blocks/pull/11117)) +- Just Arrived Full Hero pattern: wireframe the content and adjust the pattern width. ([11115](https://github.com/woocommerce/woocommerce-blocks/pull/11115)) +- Remove opinionated styles from the Hero Product 3 Split pattern. ([11110](https://github.com/woocommerce/woocommerce-blocks/pull/11110)) +- Add the Featured Category Cover Image pattern. ([11109](https://github.com/woocommerce/woocommerce-blocks/pull/11109)) +- Add fee ID to parent cart and checkout block. ([11054](https://github.com/woocommerce/woocommerce-blocks/pull/11054)) +- Update: Adjust text of incompatibility sidebar notice and show extensions, that explicitly declared incompatibility with the Cart and Checkout blocks. ([10877](https://github.com/woocommerce/woocommerce-blocks/pull/10877)) + +#### Bug Fixes + +- Fix Store Notices block breaks page editors. ([11165](https://github.com/woocommerce/woocommerce-blocks/pull/11165)) +- Ensure the Just Arrived Full Hero pattern can have an AI-selected images assigned to it and add a background dim. ([11159](https://github.com/woocommerce/woocommerce-blocks/pull/11159)) +- Testimonials 3 Columns pattern > Update the width and fix the PHP warnings that could be triggered if the content saved within the wc_blocks_patterns_content option didn't match the updated patterns dictionary. ([11158](https://github.com/woocommerce/woocommerce-blocks/pull/11158)) +- Pattern: Fetch product ID with JS to prevent unnecesary queries on every page load. ([11138](https://github.com/woocommerce/woocommerce-blocks/pull/11138)) +- Fix checkout state/country field width in the site editor. ([11133](https://github.com/woocommerce/woocommerce-blocks/pull/11133)) +- Fixed PHP notice that would appear if an API endpoint failed to load. ([11128](https://github.com/woocommerce/woocommerce-blocks/pull/11128)) +- Made error icon on checkout match text color. ([11127](https://github.com/woocommerce/woocommerce-blocks/pull/11127)) +- Fix a PHP error that was occurring when the WooCommerce Product Add-ons or the WooCommerce Product Bundles plugins were enabled. ([11082](https://github.com/woocommerce/woocommerce-blocks/pull/11082)) +- Resolved an issue where the Single Product block did not respect the WooCommerce setting for redirecting to the cart page after successful addition. ([11151](https://github.com/woocommerce/woocommerce-blocks/pull/11151)) + = 11.2.0 - 2023-09-25 = #### Enhancements diff --git a/src/BlockTypes/AbstractProductGrid.php b/src/BlockTypes/AbstractProductGrid.php index 6196cb50880..a7460767493 100644 --- a/src/BlockTypes/AbstractProductGrid.php +++ b/src/BlockTypes/AbstractProductGrid.php @@ -649,6 +649,7 @@ protected function get_add_to_cart( $product ) { 'data-quantity' => '1', 'data-product_id' => $product->get_id(), 'data-product_sku' => $product->get_sku(), + 'data-price' => wc_get_price_to_display( $product ), 'rel' => 'nofollow', 'class' => 'wp-block-button__link ' . ( function_exists( 'wc_wp_theme_get_element_class_name' ) ? wc_wp_theme_get_element_class_name( 'button' ) : '' ) . ' add_to_cart_button', ); diff --git a/src/BlockTypes/AddToCartForm.php b/src/BlockTypes/AddToCartForm.php index 853a037607a..0d5b0782796 100644 --- a/src/BlockTypes/AddToCartForm.php +++ b/src/BlockTypes/AddToCartForm.php @@ -162,6 +162,11 @@ public function add_to_cart_message_html_filter( $message ) { public function add_to_cart_redirect_filter( $url ) { // phpcs:ignore if ( isset( $_POST['is-descendent-of-single-product-block'] ) && 'true' == $_POST['is-descendent-of-single-product-block'] ) { + + if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) { + return wc_get_cart_url(); + } + return wp_validate_redirect( wp_get_referer(), $url ); } diff --git a/src/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php b/src/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php index 4514a557c78..abde6624cde 100644 --- a/src/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php +++ b/src/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php @@ -151,7 +151,7 @@ protected function email_verification_required( $order ) { * the 'order received' or 'order pay' pages. * * @see \WC_Shortcode_Checkout::order_received() - * @since $VID:$ + * @since 11.3.0 * @param int $grace_period Time in seconds after an order is placed before email verification may be required. * @param \WC_Order $order The order for which this grace period is being assessed. * @param string $context Indicates the context in which we might verify the email address. Typically 'order-pay' or 'order-received'. @@ -195,7 +195,7 @@ protected function email_verification_required( $order ) { * before we show information such as the order summary, or order payment page. * * @see \WC_Shortcode_Checkout::order_received() - * @since $VID:$ + * @since 11.3.0 * @param bool $email_verification_required If email verification is required. * @param WC_Order $order The relevant order. * @param string $context The context under which we are performing this check. diff --git a/src/Package.php b/src/Package.php index 64890635db1..af1ab5de1ac 100644 --- a/src/Package.php +++ b/src/Package.php @@ -109,7 +109,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '11.3.0-dev'; + $version = '11.3.0'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/tests/e2e-jest/specs/backend/product-query.test.ts b/tests/e2e-jest/specs/backend/product-query.test.ts index e7231eb1bf6..669a3f66839 100644 --- a/tests/e2e-jest/specs/backend/product-query.test.ts +++ b/tests/e2e-jest/specs/backend/product-query.test.ts @@ -14,8 +14,6 @@ import { visitBlockPage } from '@woocommerce/blocks-test-utils'; */ import { insertBlockDontWaitForInsertClose, - GUTENBERG_EDITOR_CONTEXT, - describeOrSkip, openSettingsSidebar, } from '../../utils'; @@ -25,48 +23,46 @@ const block = { class: '.wp-block-query', }; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - `${ block.name } Block`, - () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( `${ block.name } Block`, () => { + beforeAll( async () => { + await switchUserToAdmin(); + await visitBlockPage( `${ block.name } Block` ); + } ); - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); + it( 'can be inserted more than once', async () => { + await insertBlockDontWaitForInsertClose( block.name ); + expect( await getAllBlocks() ).toHaveLength( 2 ); + } ); - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); + it( 'renders without crashing', async () => { + await expect( page ).toRenderBlock( block ); + } ); - /** - * We changed the “Show only products on sale” from a top-level toggle - * setting to a product filter, but tests for them haven't been updated - * yet. We will fix these tests in a follow-up PR. - */ - it.skip( 'Editor preview shows only on sale products after enabling `Show only products on sale`', async () => { - await visitBlockPage( `${ block.name } Block` ); - const canvasEl = canvas(); - await openSettingsSidebar(); - await openListView(); - await page.click( - '.block-editor-list-view-block__contents-container a.components-button' - ); - const [ onSaleToggle ] = await page.$x( - '//label[text()="Show only products on sale"]' - ); - await onSaleToggle.click(); - await canvasEl.waitForSelector( `${ block.class } > p` ); - await canvasEl.waitForSelector( - `${ block.class } > ul.wp-block-post-template` - ); - const products = await canvasEl.$$( - `${ block.class } ul.wp-block-post-template > li.block-editor-block-preview__live-content` - ); - expect( products ).toHaveLength( 1 ); - } ); - } -); + /** + * We changed the “Show only products on sale” from a top-level toggle + * setting to a product filter, but tests for them haven't been updated + * yet. We will fix these tests in a follow-up PR. + */ + it.skip( 'Editor preview shows only on sale products after enabling `Show only products on sale`', async () => { + await visitBlockPage( `${ block.name } Block` ); + const canvasEl = canvas(); + await openSettingsSidebar(); + await openListView(); + await page.click( + '.block-editor-list-view-block__contents-container a.components-button' + ); + const [ onSaleToggle ] = await page.$x( + '//label[text()="Show only products on sale"]' + ); + await onSaleToggle.click(); + await canvasEl.waitForSelector( `${ block.class } > p` ); + await canvasEl.waitForSelector( + `${ block.class } > ul.wp-block-post-template` + ); + const products = await canvasEl.$$( + `${ block.class } ul.wp-block-post-template > li.block-editor-block-preview__live-content` + ); + expect( products ).toHaveLength( 1 ); + } ); +} ); diff --git a/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts b/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts index f7325217d41..bc606f59b59 100644 --- a/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts +++ b/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts @@ -16,7 +16,6 @@ import { setCheckbox } from '@woocommerce/e2e-utils'; /** * Internal dependencies */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../../utils'; import { block, SELECTORS, @@ -28,129 +27,127 @@ import { selectToken, } from './common'; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - `${ block.name } > Advanced Filters`, - () => { - let $productFiltersPanel: ElementHandle< Node >; - const defaultCount = getFixtureProductsData().length; - const saleCount = getFixtureProductsData( 'sale_price' ).length; - const outOfStockCount = getFixtureProductsData( 'stock_status' ).filter( - ( status: string ) => status === 'outofstock' - ).length; - - beforeEach( async () => { - /** - * Reset the block page before each test to ensure the block is - * inserted in a known state. This is also needed to ensure each - * test can be run individually. - */ - await resetProductQueryBlockPage(); - await ensureSidebarOpened(); - await selectBlockByName( block.slug ); - $productFiltersPanel = await findToolsPanelWithTitle( - 'Advanced Filters' - ); - } ); +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( `${ block.name } > Advanced Filters`, () => { + let $productFiltersPanel: ElementHandle< Node >; + const defaultCount = getFixtureProductsData().length; + const saleCount = getFixtureProductsData( 'sale_price' ).length; + const outOfStockCount = getFixtureProductsData( 'stock_status' ).filter( + ( status: string ) => status === 'outofstock' + ).length; + beforeEach( async () => { /** - * Reset the content of Product Query Block page after this test suite - * to avoid breaking other tests. + * Reset the block page before each test to ensure the block is + * inserted in a known state. This is also needed to ensure each + * test can be run individually. */ - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); + await resetProductQueryBlockPage(); + await ensureSidebarOpened(); + await selectBlockByName( block.slug ); + $productFiltersPanel = await findToolsPanelWithTitle( + 'Advanced Filters' + ); + } ); - it( 'Editor preview shows all products by default', async () => { - expect( await getPreviewProducts() ).toHaveLength( defaultCount ); - } ); + /** + * Reset the content of Product Query Block page after this test suite + * to avoid breaking other tests. + */ + afterAll( async () => { + await resetProductQueryBlockPage(); + } ); - it( 'On the front end, blocks shows all products by default', async () => { - expect( await getPreviewProducts() ).toHaveLength( defaultCount ); - } ); + it( 'Editor preview shows all products by default', async () => { + expect( await getPreviewProducts() ).toHaveLength( defaultCount ); + } ); - describe( 'Sale Status', () => { - it( 'Sale status is disabled by default', async () => { - await expect( $productFiltersPanel ).not.toMatch( - 'Show only products on sale' - ); - } ); + it( 'On the front end, blocks shows all products by default', async () => { + expect( await getPreviewProducts() ).toHaveLength( defaultCount ); + } ); - it( 'Can add and remove Sale Status filter', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await expect( $productFiltersPanel ).toMatch( - 'Show only products on sale' - ); - await toggleAdvancedFilter( 'Sale status' ); - await expect( $productFiltersPanel ).not.toMatch( - 'Show only products on sale' - ); - } ); + describe( 'Sale Status', () => { + it( 'Sale status is disabled by default', async () => { + await expect( $productFiltersPanel ).not.toMatch( + 'Show only products on sale' + ); + } ); + + it( 'Can add and remove Sale Status filter', async () => { + await toggleAdvancedFilter( 'Sale status' ); + await expect( $productFiltersPanel ).toMatch( + 'Show only products on sale' + ); + await toggleAdvancedFilter( 'Sale status' ); + await expect( $productFiltersPanel ).not.toMatch( + 'Show only products on sale' + ); + } ); - it( 'Enable Sale Status > Editor preview shows only on sale products', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await setCheckbox( - await getToggleIdByLabel( 'Show only products on sale' ) - ); - expect( await getPreviewProducts() ).toHaveLength( saleCount ); - } ); + it( 'Enable Sale Status > Editor preview shows only on sale products', async () => { + await toggleAdvancedFilter( 'Sale status' ); + await setCheckbox( + await getToggleIdByLabel( 'Show only products on sale' ) + ); + expect( await getPreviewProducts() ).toHaveLength( saleCount ); + } ); - it( 'Enable Sale Status > On the front end, block shows only on sale products', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await setCheckbox( - await getToggleIdByLabel( 'Show only products on sale' ) - ); - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( await getFrontEndProducts() ).toHaveLength( saleCount ); - } ); + it( 'Enable Sale Status > On the front end, block shows only on sale products', async () => { + await toggleAdvancedFilter( 'Sale status' ); + await setCheckbox( + await getToggleIdByLabel( 'Show only products on sale' ) + ); + await canvas().waitForSelector( SELECTORS.productsGrid ); + await saveOrPublish(); + await shopper.block.goToBlockPage( block.name ); + expect( await getFrontEndProducts() ).toHaveLength( saleCount ); } ); + } ); - describe( 'Stock Status', () => { - it( 'Stock status is enabled by default', async () => { - await expect( $productFiltersPanel ).toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - } ); + describe( 'Stock Status', () => { + it( 'Stock status is enabled by default', async () => { + await expect( $productFiltersPanel ).toMatchElement( + SELECTORS.formTokenField.label, + { text: 'Stock status' } + ); + } ); - it( 'Can add and remove Stock Status filter', async () => { - await toggleAdvancedFilter( 'Stock status' ); - await expect( $productFiltersPanel ).not.toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - await toggleAdvancedFilter( 'Stock status' ); - await expect( $productFiltersPanel ).toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - } ); + it( 'Can add and remove Stock Status filter', async () => { + await toggleAdvancedFilter( 'Stock status' ); + await expect( $productFiltersPanel ).not.toMatchElement( + SELECTORS.formTokenField.label, + { text: 'Stock status' } + ); + await toggleAdvancedFilter( 'Stock status' ); + await expect( $productFiltersPanel ).toMatchElement( + SELECTORS.formTokenField.label, + { text: 'Stock status' } + ); + } ); - it( 'All statuses are enabled by default', async () => { - await expect( $productFiltersPanel ).toMatch( 'In stock' ); - await expect( $productFiltersPanel ).toMatch( 'Out of stock' ); - await expect( $productFiltersPanel ).toMatch( 'On backorder' ); - } ); + it( 'All statuses are enabled by default', async () => { + await expect( $productFiltersPanel ).toMatch( 'In stock' ); + await expect( $productFiltersPanel ).toMatch( 'Out of stock' ); + await expect( $productFiltersPanel ).toMatch( 'On backorder' ); + } ); - it( 'Set Stock status to Out of stock > Editor preview shows only out-of-stock products', async () => { - await clearSelectedTokens( $productFiltersPanel ); - await selectToken( 'Stock status', 'Out of stock' ); - expect( await getPreviewProducts() ).toHaveLength( - outOfStockCount - ); - } ); + it( 'Set Stock status to Out of stock > Editor preview shows only out-of-stock products', async () => { + await clearSelectedTokens( $productFiltersPanel ); + await selectToken( 'Stock status', 'Out of stock' ); + expect( await getPreviewProducts() ).toHaveLength( + outOfStockCount + ); + } ); - it( 'Set Stock status to Out of stock > On the front end, block shows only out-of-stock products', async () => { - await clearSelectedTokens( $productFiltersPanel ); - await selectToken( 'Stock status', 'Out of stock' ); - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( await getFrontEndProducts() ).toHaveLength( - outOfStockCount - ); - } ); + it( 'Set Stock status to Out of stock > On the front end, block shows only out-of-stock products', async () => { + await clearSelectedTokens( $productFiltersPanel ); + await selectToken( 'Stock status', 'Out of stock' ); + await canvas().waitForSelector( SELECTORS.productsGrid ); + await saveOrPublish(); + await shopper.block.goToBlockPage( block.name ); + expect( await getFrontEndProducts() ).toHaveLength( + outOfStockCount + ); } ); - } -); + } ); +} ); diff --git a/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts b/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts index d40af39c231..16390ea5787 100644 --- a/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts +++ b/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts @@ -12,7 +12,6 @@ import { /** * Internal dependencies */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../../utils'; import { block, SELECTORS, @@ -21,90 +20,75 @@ import { getEditorProductElementNodesCount, } from './common'; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - `${ block.name } > Atomic blocks`, - () => { - beforeEach( async () => { - await resetProductQueryBlockPage(); - } ); - - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( `${ block.name } > Atomic blocks`, () => { + beforeEach( async () => { + await resetProductQueryBlockPage(); + } ); - it( 'Can add the Add to Cart Button block and render it on the front end', async () => { - await page.waitForSelector( SELECTORS.productButton ); - await expect( canvas() ).toMatchElement( SELECTORS.productButton, { - text: 'Add to cart', - } ); - await insertInnerBlock( - 'Add to Cart Button', - 'core/post-template' - ); - expect( - await getEditorProductElementNodesCount( - SELECTORS.productButton - ) - ).toEqual( 2 ); + afterAll( async () => { + await resetProductQueryBlockPage(); + } ); - await shopper.block.goToBlockPage( block.name ); - await page.waitForSelector( SELECTORS.productButton ); - await expect( page ).toClick( 'button', { - text: 'Add to cart', - } ); - await shopper.block.goToCart(); - await page.waitForSelector( '.wc-block-cart-items__row' ); - expect( - await getProductElementNodesCount( SELECTORS.cartItemRow ) - ).toEqual( 1 ); + it( 'Can add the Add to Cart Button block and render it on the front end', async () => { + await page.waitForSelector( SELECTORS.productButton ); + await expect( canvas() ).toMatchElement( SELECTORS.productButton, { + text: 'Add to cart', } ); + await insertInnerBlock( 'Add to Cart Button', 'core/post-template' ); + expect( + await getEditorProductElementNodesCount( SELECTORS.productButton ) + ).toEqual( 2 ); - it( 'Can add the Product Image block', async () => { - await page.waitForSelector( SELECTORS.productImage ); - await insertInnerBlock( 'Product Image', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( - SELECTORS.productImage - ) - ).toEqual( 2 ); + await shopper.block.goToBlockPage( block.name ); + await page.waitForSelector( SELECTORS.productButton ); + await expect( page ).toClick( 'button', { + text: 'Add to cart', } ); + await shopper.block.goToCart(); + await page.waitForSelector( '.wc-block-cart-items__row' ); + expect( + await getProductElementNodesCount( SELECTORS.cartItemRow ) + ).toEqual( 1 ); + } ); - it( 'Can add the Product Price block and render it on the front end', async () => { - const fixturePrices = getFixtureProductsData( 'regular_price' ); - const testPrice = - fixturePrices[ - Math.floor( Math.random() * fixturePrices.length ) - ]; - await page.waitForSelector( SELECTORS.productPrice ); - await expect( canvas() ).toMatchElement( SELECTORS.productPrice, { - text: testPrice, - } ); - await insertInnerBlock( 'Product Price', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( - SELECTORS.productPrice - ) - ).toEqual( 2 ); + it( 'Can add the Product Image block', async () => { + await page.waitForSelector( SELECTORS.productImage ); + await insertInnerBlock( 'Product Image', 'core/post-template' ); + expect( + await getEditorProductElementNodesCount( SELECTORS.productImage ) + ).toEqual( 2 ); + } ); - await shopper.block.goToBlockPage( block.name ); - await page.waitForSelector( SELECTORS.productPrice ); - await expect( page ).toMatchElement( SELECTORS.productPrice, { - text: testPrice, - } ); + it( 'Can add the Product Price block and render it on the front end', async () => { + const fixturePrices = getFixtureProductsData( 'regular_price' ); + const testPrice = + fixturePrices[ Math.floor( Math.random() * fixturePrices.length ) ]; + await page.waitForSelector( SELECTORS.productPrice ); + await expect( canvas() ).toMatchElement( SELECTORS.productPrice, { + text: testPrice, } ); + await insertInnerBlock( 'Product Price', 'core/post-template' ); + expect( + await getEditorProductElementNodesCount( SELECTORS.productPrice ) + ).toEqual( 2 ); - it( 'Can add the Product Ratings block and render it on the front end', async () => { - await insertInnerBlock( 'Product Rating', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( - SELECTORS.productRating - ) - ).toEqual( 1 ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( - await getProductElementNodesCount( SELECTORS.productRating ) - ).toEqual( getFixtureProductsData().length ); + await shopper.block.goToBlockPage( block.name ); + await page.waitForSelector( SELECTORS.productPrice ); + await expect( page ).toMatchElement( SELECTORS.productPrice, { + text: testPrice, } ); - } -); + } ); + + it( 'Can add the Product Ratings block and render it on the front end', async () => { + await insertInnerBlock( 'Product Rating', 'core/post-template' ); + expect( + await getEditorProductElementNodesCount( SELECTORS.productRating ) + ).toEqual( 1 ); + await saveOrPublish(); + await shopper.block.goToBlockPage( block.name ); + expect( + await getProductElementNodesCount( SELECTORS.productRating ) + ).toEqual( getFixtureProductsData().length ); + } ); +} ); diff --git a/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts b/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts index 86bb8241716..98e4de7d624 100644 --- a/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts +++ b/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts @@ -16,7 +16,6 @@ import { /** * Internal dependencies */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../../utils'; import { block, SELECTORS, @@ -80,79 +79,77 @@ const selectPopularFilter = async ( filter: string ) => { } }; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - 'Product Query > Popular Filters', - () => { - let $popularFiltersPanel: ElementHandle< Node >; - beforeEach( async () => { - /** - * Reset the block page before each test to ensure the block is - * inserted in a known state. This is also needed to ensure each - * test can be run individually. - */ - await resetProductQueryBlockPage(); - $popularFiltersPanel = await getPopularFilterPanel(); - } ); - +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( 'Product Query > Popular Filters', () => { + let $popularFiltersPanel: ElementHandle< Node >; + beforeEach( async () => { /** - * Reset the content of Product Query Block page after this test suite - * to avoid breaking other tests. + * Reset the block page before each test to ensure the block is + * inserted in a known state. This is also needed to ensure each + * test can be run individually. */ - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); + await resetProductQueryBlockPage(); + $popularFiltersPanel = await getPopularFilterPanel(); + } ); - it( 'Popular Filters is expanded by default', async () => { - await expect( $popularFiltersPanel ).toMatch( - 'Arrange products by popular pre-sets.' - ); - } ); + /** + * Reset the content of Product Query Block page after this test suite + * to avoid breaking other tests. + */ + afterAll( async () => { + await resetProductQueryBlockPage(); + } ); - it( 'Sorted by title is the default preset', async () => { - const currentFilter = await getCurrentPopularFilter( - $popularFiltersPanel - ); - expect( currentFilter ).toEqual( 'Sorted by title' ); - } ); + it( 'Popular Filters is expanded by default', async () => { + await expect( $popularFiltersPanel ).toMatch( + 'Arrange products by popular pre-sets.' + ); + } ); - describe.each( [ - { - filter: 'Sorted by title', - shortcode: '[products orderby="title" order="ASC" limit="9"]', - }, - { - filter: 'Newest', - shortcode: '[products orderby="date" order="DESC" limit="9"]', - }, - /** - * The following tests are commented out because they are flaky - * due to the lack of orders and reviews in the test environment. - * - * @see https://github.com/woocommerce/woocommerce-blocks/issues/8116 - */ - // { - // filter: 'Best Selling', - // shortcode: '[products best_selling="true" limit="9"]', - // }, - // { - // filter: 'Top Rated', - // shortcode: '[products top_rated="true" limit="9"]', - // }, - ] )( '$filter', ( { filter, shortcode } ) => { - beforeEach( async () => { - await selectPopularFilter( filter ); - } ); - it( 'Editor preview and block frontend display the same products', async () => { - const { previewProducts, frontEndProducts } = - await setupEditorFrontendComparison(); - expect( frontEndProducts ).toEqual( previewProducts ); - } ); + it( 'Sorted by title is the default preset', async () => { + const currentFilter = await getCurrentPopularFilter( + $popularFiltersPanel + ); + expect( currentFilter ).toEqual( 'Sorted by title' ); + } ); + + describe.each( [ + { + filter: 'Sorted by title', + shortcode: '[products orderby="title" order="ASC" limit="9"]', + }, + { + filter: 'Newest', + shortcode: '[products orderby="date" order="DESC" limit="9"]', + }, + /** + * The following tests are commented out because they are flaky + * due to the lack of orders and reviews in the test environment. + * + * @see https://github.com/woocommerce/woocommerce-blocks/issues/8116 + */ + // { + // filter: 'Best Selling', + // shortcode: '[products best_selling="true" limit="9"]', + // }, + // { + // filter: 'Top Rated', + // shortcode: '[products top_rated="true" limit="9"]', + // }, + ] )( '$filter', ( { filter, shortcode } ) => { + beforeEach( async () => { + await selectPopularFilter( filter ); + } ); + it( 'Editor preview and block frontend display the same products', async () => { + const { previewProducts, frontEndProducts } = + await setupEditorFrontendComparison(); + expect( frontEndProducts ).toEqual( previewProducts ); + } ); - it( 'Products are displayed in the correct order', async () => { - const { productQueryProducts, shortcodeProducts } = - await setupProductQueryShortcodeComparison( shortcode ); - expect( productQueryProducts ).toEqual( shortcodeProducts ); - } ); + it( 'Products are displayed in the correct order', async () => { + const { productQueryProducts, shortcodeProducts } = + await setupProductQueryShortcodeComparison( shortcode ); + expect( productQueryProducts ).toEqual( shortcodeProducts ); } ); - } -); + } ); +} ); diff --git a/tests/e2e-jest/specs/backend/product-search-legacy.test.ts b/tests/e2e-jest/specs/backend/product-search-legacy.test.ts index 51a8c29197f..9609e79047a 100644 --- a/tests/e2e-jest/specs/backend/product-search-legacy.test.ts +++ b/tests/e2e-jest/specs/backend/product-search-legacy.test.ts @@ -4,38 +4,31 @@ import { switchUserToAdmin } from '@wordpress/e2e-test-utils'; import { visitBlockPage } from '@woocommerce/blocks-test-utils'; -/** - * Internal dependencies - */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../utils'; +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( 'Product Search Legacy Block', () => { + beforeAll( async () => { + await switchUserToAdmin(); + await visitBlockPage( 'Product Search Legacy Block' ); + } ); -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - 'Product Search Legacy Block', - () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( 'Product Search Legacy Block' ); - } ); + it( 'render the upgrade prompt', async () => { + await expect( page ).toMatch( + 'This version of the Product Search block is outdated. Upgrade to continue using.' + ); + await expect( page ).toMatch( 'Upgrade Block' ); + } ); - it( 'render the upgrade prompt', async () => { - await expect( page ).toMatch( - 'This version of the Product Search block is outdated. Upgrade to continue using.' - ); - await expect( page ).toMatch( 'Upgrade Block' ); - } ); + it( 'clicking the upgrade button convert the legacy block to core/search variation', async () => { + await page.click( '.block-editor-warning__action button' ); - it( 'clicking the upgrade button convert the legacy block to core/search variation', async () => { - await page.click( '.block-editor-warning__action button' ); + await expect( page ).toMatchElement( '.wp-block-search' ); - await expect( page ).toMatchElement( '.wp-block-search' ); - - await expect( page ).toMatchElement( '.wp-block-search__label', { - text: 'Search', - } ); - - await expect( page ).toMatchElement( - '.wp-block-search__input[value="Search products…"]' - ); + await expect( page ).toMatchElement( '.wp-block-search__label', { + text: 'Search', } ); - } -); + + await expect( page ).toMatchElement( + '.wp-block-search__input[value="Search products…"]' + ); + } ); +} ); diff --git a/tests/e2e-jest/specs/backend/product-search.test.ts b/tests/e2e-jest/specs/backend/product-search.test.ts index fba265b85b3..2f081cdb839 100644 --- a/tests/e2e-jest/specs/backend/product-search.test.ts +++ b/tests/e2e-jest/specs/backend/product-search.test.ts @@ -7,40 +7,33 @@ import { insertBlock, } from '@wordpress/e2e-test-utils'; -/** - * Internal dependencies - */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../utils'; - const block = { name: 'Product Search', slug: 'core/search', class: '.wp-block-search', }; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - `${ block.name } Block`, - () => { - it( 'inserting Product Search block renders the core/search variation', async () => { - await switchUserToAdmin(); +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( `${ block.name } Block`, () => { + it( 'inserting Product Search block renders the core/search variation', async () => { + await switchUserToAdmin(); - await createNewPost( { - postType: 'page', - } ); - - await insertBlock( block.name ); + await createNewPost( { + postType: 'page', + } ); - await page.waitForSelector( block.class ); + await insertBlock( block.name ); - await expect( page ).toRenderBlock( block ); + await page.waitForSelector( block.class ); - await expect( page ).toMatchElement( '.wp-block-search__label', { - text: 'Search', - } ); + await expect( page ).toRenderBlock( block ); - await expect( page ).toMatchElement( - '.wp-block-search__input[value="Search products…"]' - ); + await expect( page ).toMatchElement( '.wp-block-search__label', { + text: 'Search', } ); - } -); + + await expect( page ).toMatchElement( + '.wp-block-search__input[value="Search products…"]' + ); + } ); +} ); diff --git a/tests/e2e-jest/specs/shopper/product-search.test.ts b/tests/e2e-jest/specs/shopper/product-search.test.ts index 88fba01fe6b..8d83902e226 100644 --- a/tests/e2e-jest/specs/shopper/product-search.test.ts +++ b/tests/e2e-jest/specs/shopper/product-search.test.ts @@ -1,43 +1,40 @@ /** * Internal dependencies */ -import { GUTENBERG_EDITOR_CONTEXT, describeOrSkip } from '../../utils'; import { shopper } from '../../../utils'; import { getTextContent } from '../../page-utils'; -describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - `Shopper → Product Search`, - () => { - beforeEach( async () => { - await shopper.block.goToBlockPage( 'Product Search' ); - await page.waitForSelector( '.wp-block-search' ); - } ); - - it( 'should render product variation', async () => { - const [ postType ] = await getTextContent( - '.wp-block-search input[name="post_type"]' - ); - await expect( postType ).toBe( 'product' ); - } ); - - it( 'should be able to search for products', async () => { - await page.type( '.wp-block-search input[name="s"]', 'Stick' ); - - await Promise.all( [ - page.waitForNavigation(), - page.keyboard.press( 'Enter' ), - ] ); - - const products = await page.$$( 'ul.products.columns-3 > li' ); - - expect( products ).toHaveLength( 2 ); - - const productTitles = await getTextContent( - 'ul.products.columns-3 .woocommerce-loop-product__title' - ); - - expect( productTitles ).toContain( '32GB USB Stick' ); - expect( productTitles ).toContain( '128GB USB Stick' ); - } ); - } -); +// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. +describe.skip( `Shopper → Product Search`, () => { + beforeEach( async () => { + await shopper.block.goToBlockPage( 'Product Search' ); + await page.waitForSelector( '.wp-block-search' ); + } ); + + it( 'should render product variation', async () => { + const [ postType ] = await getTextContent( + '.wp-block-search input[name="post_type"]' + ); + await expect( postType ).toBe( 'product' ); + } ); + + it( 'should be able to search for products', async () => { + await page.type( '.wp-block-search input[name="s"]', 'Stick' ); + + await Promise.all( [ + page.waitForNavigation(), + page.keyboard.press( 'Enter' ), + ] ); + + const products = await page.$$( 'ul.products.columns-3 > li' ); + + expect( products ).toHaveLength( 2 ); + + const productTitles = await getTextContent( + 'ul.products.columns-3 .woocommerce-loop-product__title' + ); + + expect( productTitles ).toContain( '32GB USB Stick' ); + expect( productTitles ).toContain( '128GB USB Stick' ); + } ); +} ); diff --git a/tests/e2e-jest/utils.js b/tests/e2e-jest/utils.js index cc36c9b1525..b36c77ac1d5 100644 --- a/tests/e2e-jest/utils.js +++ b/tests/e2e-jest/utils.js @@ -42,8 +42,6 @@ export const adminPassword = config.get( 'users.admin.password' ); export const client = HTTPClientFactory.build( BASE_URL ) .withBasicAuth( adminUsername, adminPassword ) .create(); -export const GUTENBERG_EDITOR_CONTEXT = - process.env.GUTENBERG_EDITOR_CONTEXT || 'core'; export const DEFAULT_TIMEOUT = 30000; export const SHOP_CHECKOUT_BLOCK_PAGE = BASE_URL + 'checkout-block/'; @@ -447,14 +445,6 @@ export const waitForAllProductsBlockLoaded = async () => { ); }; -/** - * Execute or skip the test suite base on the provided condition. - * - * @param {boolean} condition Condition to execute test suite. - */ -export const describeOrSkip = ( condition ) => - condition ? describe : describe.skip; - /** * Get all blocks in the document that match a certain slug. * diff --git a/tests/e2e/tests/checkout/checkout.page.ts b/tests/e2e/tests/checkout/checkout.page.ts index eaa337e9719..52990872a41 100644 --- a/tests/e2e/tests/checkout/checkout.page.ts +++ b/tests/e2e/tests/checkout/checkout.page.ts @@ -126,34 +126,22 @@ export class CheckoutPage { } async editBillingDetails() { - const editButton = await this.page - .locator( - '.wc-block-checkout__billing-fields .wc-block-components-address-card__edit' - ) - .isVisible(); + const editButton = this.page.locator( + '.wc-block-checkout__billing-fields .wc-block-components-address-card__edit' + ); - if ( editButton ) { - await this.page - .locator( - '.wc-block-checkout__billing-fields .wc-block-components-address-card__edit' - ) - .click(); + if ( await editButton.isVisible() ) { + await editButton.click(); } } async editShippingDetails() { - const editButton = await this.page - .locator( - '.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit' - ) - .isVisible(); + const editButton = this.page.locator( + '.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit' + ); - if ( editButton ) { - await this.page - .locator( - '.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit' - ) - .click(); + if ( await editButton.isVisible() ) { + await editButton.click(); } } diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index c91e86e8a23..4414cedafed 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,14 +3,14 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 11.3.0-dev + * Version: 11.3.0 * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block * Requires at least: 6.3 * Requires PHP: 7.3 - * WC requires at least: 7.9 - * WC tested up to: 8.0 + * WC requires at least: 8.0 + * WC tested up to: 8.1 * * @package WooCommerce\Blocks * @internal This file is only used when running as a feature plugin.