From 398adf756d6406adf101facd00452c15d63a3a57 Mon Sep 17 00:00:00 2001 From: sanketio Date: Fri, 14 Aug 2026 12:39:31 +0530 Subject: [PATCH] Block Supports: Reuse block style variation instance for consecutive blocks --- .../block-supports/block-style-variations.php | 34 ++++++++ .../block-supports/block-style-variations.php | 86 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 5daccd629c055..e4fd980720344 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -103,6 +103,33 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } + /* + * A block style variation resolves to the same data every time it is + * encountered, so consecutive blocks using the same variation generate + * byte-identical CSS. Reuse the previous instance's class name in that case + * so the stylesheet is only generated and enqueued once. + * + * Reuse is deliberately limited to the immediately preceding instance. + * Variation selectors are wrapped in `:where()`, so every variation rule has + * the same specificity and source order alone decides which one wins. Sharing + * a class across an intervening variation would move it in the cascade and + * reintroduce the conflicting styles fixed in #61877. + */ + static $last_variation_key = null; + static $last_variation_class = null; + + $variation_key = $parsed_block['blockName'] . '|' . $variation; + + if ( null !== $last_variation_class && $last_variation_key === $variation_key ) { + _wp_array_set( + $parsed_block, + array( 'attrs', 'className' ), + $parsed_block['attrs']['className'] . " $last_variation_class" + ); + + return $parsed_block; + } + /* * Recursively resolve any ref values with the appropriate value within the * theme_json data. @@ -194,6 +221,13 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) ); wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); + /* + * Record the instance so an immediately following block using the same + * variation can reuse it instead of emitting a duplicate stylesheet. + */ + $last_variation_key = $variation_key; + $last_variation_class = $class_name; + /* * Add variation instance class name to block's className string so it can * be enforced in the block markup via render_block filter. diff --git a/tests/phpunit/tests/block-supports/block-style-variations.php b/tests/phpunit/tests/block-supports/block-style-variations.php index 0627820bd8d4d..6263e3134b7e8 100644 --- a/tests/phpunit/tests/block-supports/block-style-variations.php +++ b/tests/phpunit/tests/block-supports/block-style-variations.php @@ -321,6 +321,92 @@ public function test_block_style_variation_ref_values() { $this->assertSameSetsWithIndex( $expected, $variation_data, 'Variation data with resolved ref values does not match' ); } + /** + * Renders a group block with the given block style variation applied. + * + * @param string $variation Block style variation slug. + * + * @return array The parsed block after the block style variation support has run. + */ + private function render_group_with_variation( $variation ) { + return wp_render_block_style_variation_support_styles( + array( + 'blockName' => 'core/group', + 'attrs' => array( 'className' => "is-style-$variation" ), + ) + ); + } + + /** + * Tests that consecutive blocks using the same block style variation share a + * single instance class, so the identical stylesheet is only emitted once. + * + * @ticket 65876 + * + * @covers ::wp_render_block_style_variation_support_styles + */ + public function test_block_style_variation_reuses_instance_for_consecutive_blocks() { + switch_theme( 'block-theme' ); + + /* + * Render a different variation first so the reuse cache is primed with a + * known value regardless of what earlier tests left behind. + */ + $this->render_group_with_variation( 'block-style-variation-b' ); + + // Start counting emitted stylesheets from here. + $GLOBALS['wp_styles'] = null; + + $first = $this->render_group_with_variation( 'block-style-variation-a' ); + $second = $this->render_group_with_variation( 'block-style-variation-a' ); + + $this->assertSame( + $first['attrs']['className'], + $second['attrs']['className'], + 'Consecutive blocks using the same variation should share one instance class' + ); + + $this->assertCount( + 1, + wp_styles()->get_data( 'block-style-variation-styles', 'after' ), + 'The variation stylesheet should only be emitted once for consecutive identical variations' + ); + } + + /** + * Tests that a variation interrupted by a different one gets a fresh instance + * class. + * + * Variation selectors are wrapped in `:where()`, so they all share the same + * specificity and source order alone decides which rule wins. Sharing a class + * across an intervening variation would move it in the cascade and + * reintroduce the conflicting styles fixed in #61877. + * + * @ticket 65876 + * @ticket 61877 + * + * @covers ::wp_render_block_style_variation_support_styles + */ + public function test_block_style_variation_does_not_reuse_instance_across_a_different_variation() { + switch_theme( 'block-theme' ); + + $first = $this->render_group_with_variation( 'block-style-variation-a' ); + $second = $this->render_group_with_variation( 'block-style-variation-b' ); + $third = $this->render_group_with_variation( 'block-style-variation-a' ); + + $this->assertNotSame( + $first['attrs']['className'], + $third['attrs']['className'], + 'A variation separated by a different variation should get a fresh instance class' + ); + + $this->assertNotSame( + $first['attrs']['className'], + $second['attrs']['className'], + 'Different variations should never share an instance class' + ); + } + /** * Tests that a non-string `className` attribute does not cause a fatal * error and the block content is returned unmodified.