Implement the Block Hooks API to automatically inject the Mini-Cart block (https://github.com/woocommerce/woocommerce-blocks/pull/11745)
* Change the default for Mini Cart block The Block Hooks API currently doesn’t allow for setting the default state of the block injected into content so this ensures the mini-cart block has a better default state for injection. The current default (displaying total value in cart) takes up more width increasing the risk of poor layout. * Utilize Block Hooks to automatically inject mini-cart block. * include experimental prefix on filters * Fix filter name. * remove experimental prefix. On thinking about this, I don’t think these need to be experimental. They are intentionally provided as escape hatches for hosts/themes that want to opt-in/out so we’ll have to support them when this is shipped (at least until its no longer needed!) * fix variable name! * fix unit tests because of new default * remove another incorrect text expectation Defaults for the block affect this expectation. * fix E2E tests * Mini Cart Block: improve E2E test * fix: improve check for the Product Collection block --------- Co-authored-by: Luigi Teschio <gigitux@gmail.com>
This commit is contained in:
parent
5b0e74383b
commit
795f008952
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
"hasHiddenPrice": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"default": true
|
||||
},
|
||||
"cartAndCheckoutRenderStyle": {
|
||||
"type": "string",
|
||||
|
|
|
@ -58,7 +58,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
|
|||
contents = '',
|
||||
miniCartIcon,
|
||||
addToCartBehaviour = 'none',
|
||||
hasHiddenPrice = false,
|
||||
hasHiddenPrice = true,
|
||||
priceColor = defaultColorItem,
|
||||
iconColor = defaultColorItem,
|
||||
productCountColor = defaultColorItem,
|
||||
|
|
|
@ -202,7 +202,7 @@ describe( 'Testing Mini-Cart', () => {
|
|||
|
||||
it( 'renders cart price if "Hide Cart Price" setting is not enabled', async () => {
|
||||
mockEmptyCart();
|
||||
render( <MiniCartBlock /> );
|
||||
render( <MiniCartBlock hasHiddenPrice={ false } /> );
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
|
||||
await waitFor( () =>
|
||||
|
@ -212,9 +212,7 @@ describe( 'Testing Mini-Cart', () => {
|
|||
|
||||
it( 'does not render cart price if "Hide Cart Price" setting is enabled', async () => {
|
||||
mockEmptyCart();
|
||||
const { container } = render(
|
||||
<MiniCartBlock hasHiddenPrice={ true } />
|
||||
);
|
||||
const { container } = render( <MiniCartBlock /> );
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
|
||||
await waitFor( () =>
|
||||
|
|
|
@ -72,6 +72,7 @@ class MiniCart extends AbstractBlock {
|
|||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_empty_cart_message_block_pattern' ) );
|
||||
add_action( 'hooked_block_types', array( $this, 'register_auto_insert' ), 10, 4 );
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 2 );
|
||||
}
|
||||
|
||||
|
@ -577,6 +578,104 @@ class MiniCart extends AbstractBlock {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for `hooked_block_types` to auto-inject the mini-cart block into headers after navigation.
|
||||
*
|
||||
* @param array $hooked_blocks An array of block slugs hooked into a given context.
|
||||
* @param string $position Position of the block insertion point.
|
||||
* @param string $anchor_block The block acting as the anchor for the inserted block.
|
||||
* @param \WP_Block_Template|array $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return array An array of block slugs hooked into a given context.
|
||||
*/
|
||||
public function register_auto_insert( $hooked_blocks, $position, $anchor_block, $context ) {
|
||||
// Cache for active theme.
|
||||
static $active_theme_name = null;
|
||||
if ( is_null( $active_theme_name ) ) {
|
||||
$active_theme_name = wp_get_theme()->get( 'Name' );
|
||||
}
|
||||
/**
|
||||
* A list of pattern slugs to exclude from auto-insert (useful when
|
||||
* there are patterns that have a very specific location for the block)
|
||||
*
|
||||
* @since $VID:$
|
||||
*/
|
||||
$pattern_exclude_list = apply_filters( 'woocommerce_blocks_mini_cart_auto_insert_pattern_exclude_list', [] );
|
||||
|
||||
/**
|
||||
* A list of theme slugs to execute this with. This is a temporary
|
||||
* measure until improvements to the Block Hooks API allow for exposing
|
||||
* to all block themes.
|
||||
*
|
||||
* @since $VID:$
|
||||
*/
|
||||
$theme_include_list = apply_filters( 'woocommerce_blocks_mini_cart_auto_insert_theme_include_list', [ 'Twenty Twenty-Four' ] );
|
||||
|
||||
if ( $context && in_array( $active_theme_name, $theme_include_list, true ) ) {
|
||||
if (
|
||||
'after' === $position &&
|
||||
'core/navigation' === $anchor_block &&
|
||||
$this->is_header_part_or_pattern( $context ) &&
|
||||
! $this->pattern_is_excluded( $context, $pattern_exclude_list ) &&
|
||||
! $this->has_mini_cart_block( $context )
|
||||
) {
|
||||
$hooked_blocks[] = 'woocommerce/' . $this->block_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $hooked_blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the pattern is excluded or not
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @param array $pattern_exclude_list List of pattern slugs to exclude.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function pattern_is_excluded( $context, $pattern_exclude_list ) {
|
||||
$pattern_slug = is_array( $context ) && isset( $context['slug'] ) ? $context['slug'] : '';
|
||||
return in_array( $pattern_slug, $pattern_exclude_list, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided context contains a mini-cart block.
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_mini_cart_block( $context ) {
|
||||
/**
|
||||
* Note: this won't work for parsing WP_Block_Template instance until it's fixed in core
|
||||
* because $context->content is set as the result of `traverse_and_serialize_blocks` so
|
||||
* the filter callback doesn't get the original content.
|
||||
*
|
||||
* @see https://core.trac.wordpress.org/ticket/59882
|
||||
*/
|
||||
$content = is_array( $context ) && isset( $context['content'] ) ? $context['content'] : '';
|
||||
$content = '' === $content && $context instanceof \WP_Block_Template ? $context->content : $content;
|
||||
return strpos( $content, 'wp:woocommerce/mini-cart' ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a provided context, returns whether the context refers to header content.
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_header_part_or_pattern( $context ) {
|
||||
$is_header_pattern = is_array( $context ) &&
|
||||
(
|
||||
( isset( $context['blockTypes'] ) && in_array( 'core/template-part/header', $context['blockTypes'], true ) ) ||
|
||||
( isset( $context['categories'] ) && in_array( 'header', $context['categories'], true ) )
|
||||
);
|
||||
$is_header_part = $context instanceof \WP_Block_Template && 'header' === $context->area;
|
||||
return ( $is_header_pattern || $is_header_part );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the Mini-Cart should be rendered or not.
|
||||
*
|
||||
|
|
|
@ -100,14 +100,6 @@ describe( 'Shopper → Mini-Cart', () => {
|
|||
await expect( page ).toMatchElement(
|
||||
'.wc-block-mini-cart__quantity-badge'
|
||||
);
|
||||
|
||||
// Make sure the initial quantity is 0.
|
||||
await expect( page ).toMatchElement(
|
||||
'.wc-block-mini-cart__amount',
|
||||
{
|
||||
text: '$0',
|
||||
}
|
||||
);
|
||||
await expect( page ).toMatchElement( '.wc-block-mini-cart__badge', {
|
||||
text: '',
|
||||
} );
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<!-- wp:woocommerce/mini-cart /-->
|
||||
|
||||
<!-- wp:woocommerce/all-products {"columns":3,"rows":3,"alignButtons":false,"contentVisibility":{"orderBy":true},"orderby":"date","layoutConfig":[["woocommerce/product-image",{"imageSizing":"thumbnail"}],["woocommerce/product-title"],["woocommerce/product-price"],["woocommerce/product-rating"],["woocommerce/product-button"]]} -->
|
||||
<div class="wp-block-woocommerce-all-products wc-block-all-products" data-attributes="{"alignButtons":false,"columns":3,"contentVisibility":{"orderBy":true},"isPreview":false,"layoutConfig":[["woocommerce/product-image",{"imageSizing":"thumbnail"}],["woocommerce/product-title"],["woocommerce/product-price"],["woocommerce/product-rating"],["woocommerce/product-button"]],"orderby":"date","rows":3}"></div>
|
||||
<!-- /wp:woocommerce/all-products -->
|
|
@ -2,11 +2,13 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { test, expect } from '@woocommerce/e2e-playwright-utils';
|
||||
import { Page } from '@playwright/test';
|
||||
import { FrontendUtils } from '@woocommerce/e2e-utils';
|
||||
|
||||
const openMiniCart = async ( page: Page ) => {
|
||||
await page.getByLabel( 'items in cart,' ).hover();
|
||||
await page.getByLabel( 'items in cart,' ).click();
|
||||
const blockName = 'woocommerce/mini-cart';
|
||||
|
||||
const openMiniCart = async ( frontendUtils: FrontendUtils ) => {
|
||||
const block = await frontendUtils.getBlockByName( blockName );
|
||||
await block.click();
|
||||
};
|
||||
|
||||
test.describe( `Mini Cart Block`, () => {
|
||||
|
@ -24,11 +26,29 @@ test.describe( `Mini Cart Block`, () => {
|
|||
} );
|
||||
|
||||
test.beforeEach( async ( { page } ) => {
|
||||
await page.goto( `/mini-cart-block`, { waitUntil: 'commit' } );
|
||||
await page.goto( `/shop`, { waitUntil: 'commit' } );
|
||||
} );
|
||||
|
||||
test( 'should open the empty cart drawer', async ( { page } ) => {
|
||||
await openMiniCart( page );
|
||||
test( 'should the Mini Cart block be present near the navigation block', async ( {
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
const block = await frontendUtils.getBlockByName( blockName );
|
||||
|
||||
// The Mini Cart block should be present near the navigation block.
|
||||
const navigationBlock = page.locator(
|
||||
`//div[@data-block-name='${ blockName }']/preceding-sibling::nav[contains(@class, 'wp-block-navigation')]`
|
||||
);
|
||||
|
||||
await expect( navigationBlock ).toBeVisible();
|
||||
await expect( block ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'should open the empty cart drawer', async ( {
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await openMiniCart( frontendUtils );
|
||||
|
||||
await expect( page.getByRole( 'dialog' ) ).toContainText(
|
||||
'Your cart is currently empty!'
|
||||
|
@ -37,8 +57,9 @@ test.describe( `Mini Cart Block`, () => {
|
|||
|
||||
test( 'should close the drawer when clicking on the close button', async ( {
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await openMiniCart( page );
|
||||
await openMiniCart( frontendUtils );
|
||||
|
||||
await expect( page.getByRole( 'dialog' ) ).toContainText(
|
||||
'Your cart is currently empty!'
|
||||
|
@ -51,26 +72,26 @@ test.describe( `Mini Cart Block`, () => {
|
|||
|
||||
test( 'should close the drawer when clicking outside the drawer', async ( {
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await openMiniCart( page );
|
||||
await openMiniCart( frontendUtils );
|
||||
|
||||
await expect( page.getByRole( 'dialog' ) ).toContainText(
|
||||
'Your cart is currently empty!'
|
||||
);
|
||||
|
||||
await expect(
|
||||
page.getByRole( 'button', { name: 'Close' } )
|
||||
).toBeInViewport();
|
||||
|
||||
await page.mouse.click( 50, 200 );
|
||||
await page.mouse.click( 0, 0 );
|
||||
|
||||
await expect( page.getByRole( 'dialog' ) ).toHaveCount( 0 );
|
||||
} );
|
||||
|
||||
test( 'should open the filled cart drawer', async ( { page } ) => {
|
||||
test( 'should open the filled cart drawer', async ( {
|
||||
page,
|
||||
frontendUtils,
|
||||
} ) => {
|
||||
await page.click( 'text=Add to cart' );
|
||||
|
||||
await openMiniCart( page );
|
||||
await openMiniCart( frontendUtils );
|
||||
|
||||
await expect( page.getByRole( 'dialog' ) ).toContainText(
|
||||
'Your cart (1 item)'
|
||||
|
|
|
@ -368,7 +368,7 @@ class ProductCollectionPage {
|
|||
* Private methods to be used by the class.
|
||||
*/
|
||||
private async refreshLocators( currentUI: 'editor' | 'frontend' ) {
|
||||
await this.waitForProductsToLoad();
|
||||
await this.waitForProductsToLoad( currentUI );
|
||||
|
||||
if ( currentUI === 'editor' ) {
|
||||
await this.initializeLocatorsForEditor();
|
||||
|
@ -417,11 +417,15 @@ class ProductCollectionPage {
|
|||
this.pagination = this.page.locator( SELECTORS.pagination.onFrontend );
|
||||
}
|
||||
|
||||
private async waitForProductsToLoad() {
|
||||
private async waitForProductsToLoad( currentUI: 'editor' | 'frontend' ) {
|
||||
// Wait for the product blocks to be loaded.
|
||||
await this.page.waitForSelector( SELECTORS.product );
|
||||
// Wait for the loading spinner to be detached.
|
||||
await this.page.waitForSelector( '.is-loading', { state: 'detached' } );
|
||||
if ( currentUI === 'editor' ) {
|
||||
// Wait for the loading spinner to be detached.
|
||||
await this.page.waitForSelector( '.is-loading', {
|
||||
state: 'detached',
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue