Commit f45f0ff7a90 for woocommerce
commit f45f0ff7a903605290819545c6de2211a2f18a5b
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date: Mon Aug 17 11:58:32 2026 +0200
Fix duplicate Mini-Cart wrappers in pattern-backed templates (#67431)
* Fix duplicate Mini-Cart wrappers in pattern-backed templates
* Add changelog entry for Mini-Cart pattern wrapper fix
* Simplify Mini-Cart pattern wrapper detection
* Preserve Mini-Cart pattern rendering
* Simplify Mini-Cart pattern rendering test
* Fix Mini-Cart rendered button assertions
* Fix Mini-Cart pattern uniqueness assertions
* Stabilize Mini-Cart pattern rendering test
* Fix Mini-Cart duplicate wrapper assertion
diff --git a/plugins/woocommerce/changelog/fix-mini-cart-pattern-wrappers b/plugins/woocommerce/changelog/fix-mini-cart-pattern-wrappers
new file mode 100644
index 00000000000..7121342cc32
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-mini-cart-pattern-wrappers
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent duplicate Mini-Cart markup when a template part references a block pattern.
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
index fce4b28a622..7fc3a35cd7b 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
@@ -304,7 +304,7 @@ class MiniCart extends AbstractBlock {
$wrapper_styles = $classes_styles['styles'];
// Pre-render the template part so nested blocks enqueue their assets before the overlay is printed in wp_footer.
$template_part_contents = $this->get_template_part_contents( false );
- $template_part_contents = do_blocks( $this->process_template_contents( $template_part_contents ) );
+ $template_part_contents = $this->render_template_part_contents( $template_part_contents );
$cart_item_count = $cart ? $cart->get_cart_contents_count() : 0;
$display_cart_price_including_tax = get_option( 'woocommerce_tax_display_cart' ) === TaxDisplayMode::INCLUSIVE;
$cart_item_count = $cart ? $cart->get_cart_contents_count() : 0;
@@ -445,7 +445,7 @@ class MiniCart extends AbstractBlock {
*/
public function render_mini_cart_overlay() {
$template_part_contents = $this->get_template_part_contents( false );
- $template_part_contents = do_blocks( $this->process_template_contents( $template_part_contents ) );
+ $template_part_contents = $this->render_template_part_contents( $template_part_contents );
ob_start();
?>
<div
@@ -479,6 +479,34 @@ class MiniCart extends AbstractBlock {
echo wp_interactivity_process_directives( ob_get_clean() );
}
+ /**
+ * Render Mini-Cart template contents while removing legacy saved wrappers.
+ *
+ * @param string $template_contents The template contents to render.
+ * @return string The rendered template contents.
+ */
+ private function render_template_part_contents( $template_contents ) {
+ $process_legacy_wrappers = function ( $parsed_block ) {
+ if ( 'woocommerce/mini-cart-contents' !== ( $parsed_block['blockName'] ?? null ) ) {
+ return $parsed_block;
+ }
+
+ $processed_blocks = parse_blocks(
+ $this->process_template_contents( serialize_block( $parsed_block ) )
+ );
+
+ return $processed_blocks[0] ?? $parsed_block;
+ };
+
+ add_filter( 'render_block_data', $process_legacy_wrappers, 10, 1 );
+
+ try {
+ return do_blocks( $template_contents );
+ } finally {
+ remove_filter( 'render_block_data', $process_legacy_wrappers, 10 );
+ }
+ }
+
/**
* Process template contents to remove unwanted div wrappers.
*
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCart.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCart.php
index 9aae16102e9..17fcba41ea6 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCart.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCart.php
@@ -92,6 +92,19 @@ class MiniCart extends \WP_UnitTestCase {
<img class="wp-image-block" src="https://example.com/image.jpg" alt="Example Image" />
<!-- /wp:image -->
</div>
+ <!-- /wp:woocommerce/mini-cart-title-block -->
+
+ <!-- wp:woocommerce/mini-cart-footer-block -->
+ <div class="wp-block-woocommerce-mini-cart-footer-block">
+ <!-- wp:woocommerce/mini-cart-cart-button-block -->
+ <div class="wp-block-woocommerce-mini-cart-cart-button-block"></div>
+ <!-- /wp:woocommerce/mini-cart-cart-button-block -->
+
+ <!-- wp:woocommerce/mini-cart-checkout-button-block -->
+ <div class="wp-block-woocommerce-mini-cart-checkout-button-block"></div>
+ <!-- /wp:woocommerce/mini-cart-checkout-button-block -->
+ </div>
+ <!-- /wp:woocommerce/mini-cart-footer-block -->
</div>
<!-- /wp:woocommerce/filled-mini-cart-contents-block -->
</div>
@@ -235,6 +248,55 @@ class MiniCart extends \WP_UnitTestCase {
}
}
+ /**
+ * @testdox Should process legacy wrappers while preserving the pattern rendering lifecycle.
+ */
+ public function test_render_pattern_backed_template(): void {
+ $pattern_name = 'woocommerce-tests/mini-cart-template-part';
+ register_block_pattern(
+ $pattern_name,
+ array(
+ 'title' => 'Mini Cart template part',
+ 'content' => $this->current_template_with_user_edits,
+ )
+ );
+
+ $pattern_filter_calls = 0;
+ $pattern_filter = static function ( $content ) use ( &$pattern_filter_calls ) {
+ ++$pattern_filter_calls;
+ return $content . '<span data-pattern-filter-ran></span>';
+ };
+
+ add_filter( 'render_block_core/pattern', $pattern_filter, 10, 1 );
+
+ try {
+ $method = new \ReflectionMethod( MiniCartBlock::class, 'render_template_part_contents' );
+ $method->setAccessible( true );
+ $rendered_template = $method->invoke(
+ $this->mock,
+ '<!-- wp:pattern {"slug":"' . $pattern_name . '"} /-->'
+ );
+ } finally {
+ remove_filter( 'render_block_core/pattern', $pattern_filter, 10 );
+ unregister_block_pattern( $pattern_name );
+ }
+
+ $this->assertSame( 1, $pattern_filter_calls, 'The core pattern render filter should run exactly once.' );
+ $this->assertStringContainsString(
+ 'data-pattern-filter-ran',
+ $rendered_template,
+ 'The filtered pattern output should be preserved.'
+ );
+
+ $p = new \WP_HTML_Tag_Processor( $rendered_template );
+ $footer_wrapper_count = 0;
+ while ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-mini-cart-footer-block' ) ) ) {
+ ++$footer_wrapper_count;
+ }
+
+ $this->assertSame( 1, $footer_wrapper_count, 'The rendered template should contain exactly one footer wrapper.' );
+ }
+
/**
* Checks that process_template_contents removes the wrapper divs from the
* current template with user edits, but preserves the user edits.