Product Collection: Don't render when empty unless the no results block is present (#50404)

* Disable PC render when content empty unless no results block is present

* Introduce empty PC classname

* Add E2E test

* Add changelog entry

* Ensure the render is preserved as expected

* Do not render empty div

* Update tests

* Try a different logic

* Add hint to the docblock

* Remove obsolete e2e test

* Update render handler name to acknowledge context

* Fix tests failing due to recent v3 update

* Restore block interactivity

---------

Co-authored-by: Manish Menaria <the.manish.menaria@gmail.com>
This commit is contained in:
Bart Kalisz 2024-08-30 08:48:54 +02:00 committed by GitHub
parent 062c4ed76a
commit ff197207ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 160 additions and 1 deletions

View File

@ -81,6 +81,87 @@ test.describe( 'Product Collection', () => {
).toBeVisible();
} );
test.describe( 'when no results are found', () => {
test.beforeEach( async ( { admin } ) => {
await admin.createNewPost();
} );
test( 'does not render', async ( { page, editor, pageObject } ) => {
await pageObject.insertProductCollection();
await pageObject.chooseCollectionInPost( 'featured' );
await pageObject.addFilter( 'Price Range' );
await pageObject.setPriceRange( {
max: '1',
} );
const featuredBlock = editor.canvas.getByLabel( 'Block: Featured' );
await expect(
featuredBlock.getByText( 'Featured products' )
).toBeVisible();
// The "No results found" info is rendered in editor for all collections.
await expect(
featuredBlock.getByText( 'No results found' )
).toBeVisible();
await pageObject.publishAndGoToFrontend();
const content = page.locator( 'main' );
await expect( content ).not.toContainText( 'Featured products' );
await expect( content ).not.toContainText( 'No results found' );
} );
// This test ensures the runtime render state is correctly reset for
// each block.
test( 'does not prevent subsequent blocks from render', async ( {
page,
pageObject,
} ) => {
await pageObject.insertProductCollection();
await pageObject.chooseCollectionInPost( 'featured' );
await pageObject.addFilter( 'Price Range' );
await pageObject.setPriceRange( {
max: '1',
} );
await pageObject.insertProductCollection();
await pageObject.chooseCollectionInPost( 'topRated' );
await pageObject.refreshLocators( 'editor' );
await expect( pageObject.products ).toHaveCount( 5 );
await pageObject.publishAndGoToFrontend();
await pageObject.refreshLocators( 'frontend' );
await expect( pageObject.products ).toHaveCount( 5 );
await expect( page.locator( 'main' ) ).not.toContainText(
'Featured products'
);
} );
test( 'renders if No Results block is present', async ( {
page,
editor,
pageObject,
} ) => {
await pageObject.insertProductCollection();
await pageObject.chooseCollectionInPost( 'productCatalog' );
await pageObject.addFilter( 'Price Range' );
await pageObject.setPriceRange( {
max: '1',
} );
await expect(
editor.canvas.getByText( 'No results found' )
).toBeVisible();
await pageObject.publishAndGoToFrontend();
await expect( page.getByText( 'No results found' ) ).toBeVisible();
} );
} );
test.describe( 'Renders correctly with all Product Elements', () => {
const expectedProductContent = [
'Beanie', // core/post-title

View File

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Product Collection: Don't render when empty unless the no results block is present.

View File

@ -47,6 +47,18 @@ class ProductCollection extends AbstractBlock {
protected $custom_order_opts = array( 'popularity', 'rating' );
/**
* The render state of the product collection block.
*
* These props are runtime-based and reinitialize for every block on a page.
*
* @var array
*/
private $render_state = array(
'has_results' => false,
'has_no_results_block' => false,
);
/**
* Initialize this block type.
*
@ -80,8 +92,30 @@ class ProductCollection extends AbstractBlock {
// Provide location context into block's context.
add_filter( 'render_block_context', array( $this, 'provide_location_context_for_inner_blocks' ), 11, 1 );
// Disable block render if the ProductTemplate block is empty.
add_filter(
'render_block_woocommerce/product-template',
function ( $html ) {
$this->render_state['has_results'] = ! empty( $html );
return $html;
},
100,
1
);
// Enable block render if the ProductCollectionNoResults block is rendered.
add_filter(
'render_block_woocommerce/product-collection-no-results',
function ( $html ) {
$this->render_state['has_no_results_block'] = ! empty( $html );
return $html;
},
100,
1
);
// Interactivity API: Add navigation directives to the product collection block.
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'enhance_product_collection_with_interactivity' ), 10, 2 );
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 );
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
@ -90,6 +124,46 @@ class ProductCollection extends AbstractBlock {
add_filter( 'render_block_data', array( $this, 'disable_enhanced_pagination' ), 10, 1 );
}
/**
* Handle the rendering of the block.
*
* @param string $block_content The block content about to be rendered.
* @param array $block The block being rendered.
*
* @return string
*/
public function handle_rendering( $block_content, $block ) {
if ( $this->should_prevent_render() ) {
return ''; // Prevent rendering.
}
// Reset the render state for the next render.
$this->reset_render_state();
return $this->enhance_product_collection_with_interactivity( $block_content, $block );
}
/**
* Check if the block should be prevented from rendering.
*
* @return bool
*/
private function should_prevent_render() {
return ! $this->render_state['has_results'] && ! $this->render_state['has_no_results_block'];
}
/**
* Reset the render state.
*/
private function reset_render_state() {
$this->render_state = array(
'has_results' => false,
'has_no_results_block' => false,
);
}
/**
* Provides the location context to each inner block of the product collection block.
* Hint: Only blocks using the 'query' context will be affected.