From 7b7c86ed2b3c6c522fb4209894266d34a4c53fe2 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 12:22:47 -0800 Subject: [PATCH 1/8] Add filter to change allowed blocks in Cart and Checkout blocks --- .../cart-checkout-shared/editor-utils.ts | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/assets/js/blocks/cart-checkout-shared/editor-utils.ts b/assets/js/blocks/cart-checkout-shared/editor-utils.ts index 32b9d44eb98..0f7530b5e43 100644 --- a/assets/js/blocks/cart-checkout-shared/editor-utils.ts +++ b/assets/js/blocks/cart-checkout-shared/editor-utils.ts @@ -2,6 +2,9 @@ * External dependencies */ import { getBlockTypes } from '@wordpress/blocks'; +import { applyCheckoutFilter } from '@woocommerce/blocks-checkout'; +import { CART_STORE_KEY } from '@woocommerce/block-data'; +import { select } from '@wordpress/data'; // List of core block types to allow in inner block areas. const coreBlockTypes = [ 'core/paragraph', 'core/image', 'core/separator' ]; @@ -9,11 +12,35 @@ const coreBlockTypes = [ 'core/paragraph', 'core/image', 'core/separator' ]; /** * Gets a list of allowed blocks types under a specific parent block type. */ -export const getAllowedBlocks = ( block: string ): string[] => [ - ...getBlockTypes() - .filter( ( blockType ) => - ( blockType?.parent || [] ).includes( block ) - ) - .map( ( { name } ) => name ), - ...coreBlockTypes, -]; +export const getAllowedBlocks = ( block: string ): string[] => { + const additionalBlockTypes = applyCheckoutFilter( { + filterName: 'allowedBlockTypes', + defaultValue: [], + extensions: select( CART_STORE_KEY ).getCartData().extensions, + arg: { block }, + validation: ( value ) => { + if ( + Array.isArray( value ) && + value.every( ( item ) => typeof item === 'string' ) + ) { + return true; + } + throw new Error( + 'allowedBlockTypes filters must return an array of strings.' + ); + }, + } ); + + // Convert to set here so that we remove duplicated block types. + return Array.from( + new Set( [ + ...getBlockTypes() + .filter( ( blockType ) => + ( blockType?.parent || [] ).includes( block ) + ) + .map( ( { name } ) => name ), + ...coreBlockTypes, + ...additionalBlockTypes, + ] ) + ); +}; From 6f1ee64673861f4b9cf816f9c5f44f88b50f5c81 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 12:46:36 -0800 Subject: [PATCH 2/8] Add documentation for allowedBlockTypes --- .../checkout-block/available-filters.md | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/third-party-developers/extensibility/checkout-block/available-filters.md b/docs/third-party-developers/extensibility/checkout-block/available-filters.md index 33fc5fc791b..a2d94fc4177 100644 --- a/docs/third-party-developers/extensibility/checkout-block/available-filters.md +++ b/docs/third-party-developers/extensibility/checkout-block/available-filters.md @@ -9,11 +9,14 @@ - [Proceed to Checkout Button Label](#proceed-to-checkout-button-label) - [Proceed to Checkout Button Link](#proceed-to-checkout-button-link) - [Place Order Button Label](#place-order-button-label) +- [Allowed block types](#allowed-block-types) - [Examples](#examples) - - [Changing the wording and the link on the "Proceed to Checkout" button when a specific item is in the Cart](#changing-the-wording-and-the-link-on-the--proceed-to-checkout--button-when-a-specific-item-is-in-the-cart) + - [Changing the wording and the link on the "Proceed to Checkout" button when a specific item is in the Cart](#changing-the-wording-and-the-link-on-the-proceed-to-checkout-button-when-a-specific-item-is-in-the-cart) + - [Allowing blocks in specific areas in the Cart and Checkout blocks](#allowing-blocks-in-specific-areas-in-the-cart-and-checkout-blocks) - [Changing the wording of the Totals label in the Mini Cart, Cart and Checkout](#changing-the-wording-of-the-totals-label-in-the-mini-cart-cart-and-checkout) - [Changing the format of the item's single price](#changing-the-format-of-the-items-single-price) - [Change the name of a coupon](#change-the-name-of-a-coupon) + - [Prevent a snackbar notice from appearing for coupons](#prevent-a-snackbar-notice-from-appearing-for-coupons) - [Hide the "Remove item" link on a cart item](#hide-the-remove-item-link-on-a-cart-item) - [Change the label of the Place Order button](#change-the-label-of-the-place-order-button) - [Troubleshooting](#troubleshooting) @@ -98,7 +101,7 @@ CartCoupon { The Cart block contains a button which is labelled 'Proceed to Checkout' by default. It can be changed using the following filter. | Filter name | Description | Return type | -|--------------------------------|-----------------------------------------------------| ----------- | +| ------------------------------ | --------------------------------------------------- | ----------- | | `proceedToCheckoutButtonLabel` | The wanted label of the Proceed to Checkout button. | `string` | ## Proceed to Checkout Button Link @@ -106,7 +109,7 @@ The Cart block contains a button which is labelled 'Proceed to Checkout' by defa The Cart block contains a button which is labelled 'Proceed to Checkout' and links to the Checkout page by default, but can be changed using the following filter. This filter has the current cart passed to it in the third parameter. | Filter name | Description | Return type | -|-------------------------------|-------------------------------------------------------------| ----------- | +| ----------------------------- | ----------------------------------------------------------- | ----------- | | `proceedToCheckoutButtonLink` | The URL that the Proceed to Checkout button should link to. | `string` | ## Place Order Button Label @@ -117,6 +120,18 @@ The Checkout block contains a button which is labelled 'Place Order' by default, | ----------------------- | ------------------------------------------- | ----------- | | `placeOrderButtonLabel` | The wanted label of the Place Order button. | `string` | +## Allowed block types + +The Cart and Checkout blocks are made up of inner blocks. These inner blocks areas allow certain block types to be added as children. By default, only `core/paragraph`, `core/image`, and `core/separator` are available to add. + +By using the `allowedBlockTypes` filter it is possible to add or remove items from this array to control what the editor can insert into an inner block. + +This filter is called once for each inner block area, so it is possible to be very granular when determining what blocks can be added where. See the [Allowing blocks in specific areas in the Cart and Checkout blocks.](#allowing-blocks-in-specific-areas-in-the-cart-and-checkout-blocks) example for more information. + +| Filter name | Description | Return type | +| ------------------- | -------------------------------------- | ----------- | +| `allowedBlockTypes` | The new array of allowwed block types. | `string[]` | + ## Examples ### Changing the wording and the link on the "Proceed to Checkout" button when a specific item is in the Cart @@ -154,9 +169,38 @@ registerCheckoutFilters( 'sunglasses-store-extension', { } ); ``` -| Before | After | -|-------------------------------------------------------------------------------------------------------------------------------------------| ----- | -| image | image | +| Before | After | +| ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| image | image | + +### Allowing blocks in specific areas in the Cart and Checkout blocks + +Let's suppose we want to allow the editor to add some blocks in specific places in the Cart and Checkout blocks. + +1. Allow `core/table` to be inserted in the Shipping Address block in the Checkout. +2. Allow `core/quote` to be inserted in every block area in the Cart and Checkout blocks. +3. Remove the ability to add the `core/separator` block to any inner block in the Cart and Checkout blocks. + +In our extension we could register a filter satisfy all three of these conditions like so: + +```tsx +registerCheckoutFilters( 'newsletter-plugin', { + allowedBlockTypes: ( value, extensions, { block } ) => { + // Remove the ability to add `core/separator` + value = value.filter( ( blockName ) => blockName !== 'core/separator' ); + + // Add core/quote to any inner block area. + value.push( 'core/quote' ); + + // If the block we're checking is `woocommerce/checkout-shipping-address-block then allow `core/table`. + if ( block === 'woocommerce/checkout-shipping-address-block' ) { + value.push( 'core/table' ); + } + + return value; + }, +} ); +``` ### Changing the wording of the Totals label in the Mini Cart, Cart and Checkout @@ -324,4 +368,3 @@ The error will also be shown in your console. 🐞 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/third-party-developers/extensibility/checkout-block/available-filters.md) - From 63c729d54da0b4ffdae5d72b43855f96123ab9ff Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 17:01:52 -0800 Subject: [PATCH 3/8] Add test to check the filters work for the Checkout block --- tests/e2e/specs/backend/checkout.test.js | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/e2e/specs/backend/checkout.test.js b/tests/e2e/specs/backend/checkout.test.js index cf661d8e429..edeb42e79ac 100644 --- a/tests/e2e/specs/backend/checkout.test.js +++ b/tests/e2e/specs/backend/checkout.test.js @@ -5,6 +5,7 @@ import { openDocumentSettingsSidebar, switchUserToAdmin, openGlobalBlockInserter, + insertBlock, } from '@wordpress/e2e-test-utils'; import { findLabelWithText, @@ -52,6 +53,72 @@ describe( `${ block.name } Block`, () => { expect( button ).toHaveLength( 0 ); } ); + it( 'inner blocks can be added/removed by filters', async () => { + // Begin by removing the block. + await selectBlockByName( block.slug ); + const options = await page.$x( + '//div[@class="block-editor-block-toolbar"]//button[@aria-label="Options"]' + ); + await options[ 0 ].click(); + const removeButton = await page.$x( + '//button[contains(., "Remove Checkout")]' + ); + await removeButton[ 0 ].click(); + // Expect block to have been removed. + await expect( page ).not.toMatchElement( block.class ); + + // Register a checkout filter to allow `core/table` block in the Checkout block's inner blocks. + await page.evaluate( + "wc.blocksCheckout.registerCheckoutFilters( 'woo-test-namespace'," + + '{ allowedBlockTypes: ( value, extensions, { block } ) => {' + + " value.push('core/table');" + + " if ( block === 'woocommerce/checkout-shipping-address-block' ) {" + + " value.push( 'core/audio' );" + + ' }' + + ' return value;' + + '}' + + '}' + + ');' + ); + + await insertBlock( block.name ); + + // Select the shipping address block and try to insert a block. Check the Table block is available. + await selectBlockByName( + 'woocommerce/checkout-shipping-address-block' + ); + const addBlockButton = await page.waitForXPath( + '//div[@data-type="woocommerce/checkout-shipping-address-block"]//button[@aria-label="Add block"]' + ); + expect( addBlockButton ).not.toBeNull(); + await addBlockButton.click(); + const tableButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Table")]' + ); + const audioButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Audio")]' + ); + expect( tableButton ).not.toBeNull(); + expect( audioButton ).not.toBeNull(); + + // Now check the contact information block and expect only the Table block to be available there. + await selectBlockByName( + 'woocommerce/checkout-contact-information-block' + ); + const contactInformationAddBlockButton = await page.waitForXPath( + '//div[@data-type="woocommerce/checkout-contact-information-block"]//button[@aria-label="Add block"]' + ); + await contactInformationAddBlockButton.click(); + const contactInformationTableButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Table")]' + ); + const contactInformationAudioButton = await page.$x( + '//*[@role="option" and contains(., "Audio")]' + ); + expect( contactInformationTableButton ).not.toBeNull(); + expect( contactInformationAudioButton ).toHaveLength( 0 ); + } ); + it( 'renders without crashing', async () => { await expect( page ).toRenderBlock( block ); } ); From 6f98b4b7bc77d70ff8174925dae45d6d13edb502 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 20:04:54 -0800 Subject: [PATCH 4/8] Rename filter to additionalCartCheckoutInnerBlockTypes --- assets/js/blocks/cart-checkout-shared/editor-utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/blocks/cart-checkout-shared/editor-utils.ts b/assets/js/blocks/cart-checkout-shared/editor-utils.ts index 0f7530b5e43..aa72240f82a 100644 --- a/assets/js/blocks/cart-checkout-shared/editor-utils.ts +++ b/assets/js/blocks/cart-checkout-shared/editor-utils.ts @@ -13,8 +13,8 @@ const coreBlockTypes = [ 'core/paragraph', 'core/image', 'core/separator' ]; * Gets a list of allowed blocks types under a specific parent block type. */ export const getAllowedBlocks = ( block: string ): string[] => { - const additionalBlockTypes = applyCheckoutFilter( { - filterName: 'allowedBlockTypes', + const additionalCartCheckoutInnerBlockTypes = applyCheckoutFilter( { + filterName: 'additionalCartCheckoutInnerBlockTypes', defaultValue: [], extensions: select( CART_STORE_KEY ).getCartData().extensions, arg: { block }, @@ -40,7 +40,7 @@ export const getAllowedBlocks = ( block: string ): string[] => { ) .map( ( { name } ) => name ), ...coreBlockTypes, - ...additionalBlockTypes, + ...additionalCartCheckoutInnerBlockTypes, ] ) ); }; From 070d77055a5483319790b3d0d9943ff8929df1f8 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 20:05:06 -0800 Subject: [PATCH 5/8] Update docs to reflect new name --- .../checkout-block/available-filters.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/third-party-developers/extensibility/checkout-block/available-filters.md b/docs/third-party-developers/extensibility/checkout-block/available-filters.md index a2d94fc4177..a2b661c7b3f 100644 --- a/docs/third-party-developers/extensibility/checkout-block/available-filters.md +++ b/docs/third-party-developers/extensibility/checkout-block/available-filters.md @@ -9,7 +9,7 @@ - [Proceed to Checkout Button Label](#proceed-to-checkout-button-label) - [Proceed to Checkout Button Link](#proceed-to-checkout-button-link) - [Place Order Button Label](#place-order-button-label) -- [Allowed block types](#allowed-block-types) +- [Additional Cart and Checkout inner block types](#additional-cart-checkout-inner-block-types) - [Examples](#examples) - [Changing the wording and the link on the "Proceed to Checkout" button when a specific item is in the Cart](#changing-the-wording-and-the-link-on-the-proceed-to-checkout-button-when-a-specific-item-is-in-the-cart) - [Allowing blocks in specific areas in the Cart and Checkout blocks](#allowing-blocks-in-specific-areas-in-the-cart-and-checkout-blocks) @@ -120,17 +120,19 @@ The Checkout block contains a button which is labelled 'Place Order' by default, | ----------------------- | ------------------------------------------- | ----------- | | `placeOrderButtonLabel` | The wanted label of the Place Order button. | `string` | -## Allowed block types +## Additional Cart Checkout inner block types The Cart and Checkout blocks are made up of inner blocks. These inner blocks areas allow certain block types to be added as children. By default, only `core/paragraph`, `core/image`, and `core/separator` are available to add. -By using the `allowedBlockTypes` filter it is possible to add or remove items from this array to control what the editor can insert into an inner block. +By using the `additionalCartCheckoutInnerBlockTypes` filter it is possible to add items to this array to control what the editor can insert into an inner block. This filter is called once for each inner block area, so it is possible to be very granular when determining what blocks can be added where. See the [Allowing blocks in specific areas in the Cart and Checkout blocks.](#allowing-blocks-in-specific-areas-in-the-cart-and-checkout-blocks) example for more information. -| Filter name | Description | Return type | -| ------------------- | -------------------------------------- | ----------- | -| `allowedBlockTypes` | The new array of allowwed block types. | `string[]` | +| Filter name | Description | Return type | +| --------------------------------------- | ---------------------------------------- | ------------- | +| `allowedBlockTypes` | The new array of allowwed block types. | `string[]` | +| ------------------- | ---------------------------------------- | ------------- | +| `additionalCartCheckoutInnerBlockTypes` | The new array of allowwed block types. | `string[]` | ## Examples @@ -179,9 +181,8 @@ Let's suppose we want to allow the editor to add some blocks in specific places 1. Allow `core/table` to be inserted in the Shipping Address block in the Checkout. 2. Allow `core/quote` to be inserted in every block area in the Cart and Checkout blocks. -3. Remove the ability to add the `core/separator` block to any inner block in the Cart and Checkout blocks. -In our extension we could register a filter satisfy all three of these conditions like so: +In our extension we could register a filter satisfy both of these conditions like so: ```tsx registerCheckoutFilters( 'newsletter-plugin', { From 24a78ac52ed9681b4e38563a063f3e9554a1d5c5 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 20:12:49 -0800 Subject: [PATCH 6/8] Add tests for additionalCartCheckoutInnerBlockTypes in Cart block --- tests/e2e/specs/backend/cart.test.js | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/e2e/specs/backend/cart.test.js b/tests/e2e/specs/backend/cart.test.js index 91223c4b411..5e177dd4f99 100644 --- a/tests/e2e/specs/backend/cart.test.js +++ b/tests/e2e/specs/backend/cart.test.js @@ -7,6 +7,7 @@ import { switchUserToAdmin, searchForBlock, openGlobalBlockInserter, + insertBlock, } from '@wordpress/e2e-test-utils'; import { findLabelWithText, @@ -67,6 +68,70 @@ describe( `${ block.name } Block`, () => { expect( button ).toHaveLength( 0 ); } ); + it( 'inner blocks can be added/removed by filters', async () => { + // Begin by removing the block. + await selectBlockByName( block.slug ); + const options = await page.$x( + '//div[@class="block-editor-block-toolbar"]//button[@aria-label="Options"]' + ); + await options[ 0 ].click(); + const removeButton = await page.$x( + '//button[contains(., "Remove Cart")]' + ); + await removeButton[ 0 ].click(); + // Expect block to have been removed. + await expect( page ).not.toMatchElement( block.class ); + + // Register a checkout filter to allow `core/table` block in the Checkout block's inner blocks, add + // core/audio into the woocommerce/cart-order-summary-block and remove core/paragraph from all Cart inner + // blocks. + await page.evaluate( + "wc.blocksCheckout.registerCheckoutFilters( 'woo-test-namespace'," + + '{ additionalCartCheckoutInnerBlockTypes: ( value, extensions, { block } ) => {' + + " value.push('core/table');" + + " if ( block === 'woocommerce/cart-order-summary-block' ) {" + + " value.push( 'core/audio' );" + + ' }' + + ' return value;' + + '}' + + '}' + + ');' + ); + + await insertBlock( block.name ); + + // Select the shipping address block and try to insert a block. Check the Table block is available. + await selectBlockByName( 'woocommerce/cart-order-summary-block' ); + await page.waitForTimeout( 1000 ); + const addBlockButton = await page.waitForXPath( + '//div[@data-type="woocommerce/cart-order-summary-block"]//button[@aria-label="Add block"]' + ); + await addBlockButton.click(); + const tableButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Table")]' + ); + const audioButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Audio")]' + ); + expect( tableButton ).not.toBeNull(); + expect( audioButton ).not.toBeNull(); + + // // Now check the filled cart block and expect only the Table block to be available there. + await selectBlockByName( 'woocommerce/filled-cart-block' ); + const filledCartAddBlockButton = await page.waitForXPath( + '//div[@data-type="woocommerce/filled-cart-block"]//button[@aria-label="Add block"]' + ); + await filledCartAddBlockButton.click(); + const filledCartTableButton = await page.waitForXPath( + '//*[@role="option" and contains(., "Table")]' + ); + const filledCartAudioButton = await page.$x( + '//*[@role="option" and contains(., "Audio")]' + ); + expect( filledCartTableButton ).not.toBeNull(); + expect( filledCartAudioButton ).toHaveLength( 0 ); + } ); + it( 'renders without crashing', async () => { await expect( page ).toRenderBlock( block ); await expect( page ).toRenderBlock( filledCartBlock ); From 2c85b8caa10d1bd1e94524480f5f25b5d3a5d979 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 20:13:10 -0800 Subject: [PATCH 7/8] Update Checkout test to reflect new filter name --- tests/e2e/specs/backend/checkout.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/backend/checkout.test.js b/tests/e2e/specs/backend/checkout.test.js index edeb42e79ac..8d30fc18881 100644 --- a/tests/e2e/specs/backend/checkout.test.js +++ b/tests/e2e/specs/backend/checkout.test.js @@ -70,7 +70,7 @@ describe( `${ block.name } Block`, () => { // Register a checkout filter to allow `core/table` block in the Checkout block's inner blocks. await page.evaluate( "wc.blocksCheckout.registerCheckoutFilters( 'woo-test-namespace'," + - '{ allowedBlockTypes: ( value, extensions, { block } ) => {' + + '{ additionalCartCheckoutInnerBlockTypes: ( value, extensions, { block } ) => {' + " value.push('core/table');" + " if ( block === 'woocommerce/checkout-shipping-address-block' ) {" + " value.push( 'core/audio' );" + From d463f1bf6fff4e7355989d8a60696f7382722cd4 Mon Sep 17 00:00:00 2001 From: Thomas Roberts Date: Mon, 6 Mar 2023 20:22:40 -0800 Subject: [PATCH 8/8] Update docs TOC to add example link --- .../extensibility/checkout-block/available-filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/third-party-developers/extensibility/checkout-block/available-filters.md b/docs/third-party-developers/extensibility/checkout-block/available-filters.md index a2b661c7b3f..d615b46bcdd 100644 --- a/docs/third-party-developers/extensibility/checkout-block/available-filters.md +++ b/docs/third-party-developers/extensibility/checkout-block/available-filters.md @@ -9,7 +9,7 @@ - [Proceed to Checkout Button Label](#proceed-to-checkout-button-label) - [Proceed to Checkout Button Link](#proceed-to-checkout-button-link) - [Place Order Button Label](#place-order-button-label) -- [Additional Cart and Checkout inner block types](#additional-cart-checkout-inner-block-types) +- [Additional Cart Checkout inner block types](#additional-cart-checkout-inner-block-types) - [Examples](#examples) - [Changing the wording and the link on the "Proceed to Checkout" button when a specific item is in the Cart](#changing-the-wording-and-the-link-on-the-proceed-to-checkout-button-when-a-specific-item-is-in-the-cart) - [Allowing blocks in specific areas in the Cart and Checkout blocks](#allowing-blocks-in-specific-areas-in-the-cart-and-checkout-blocks)