diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index ae6bcff379d65..c721e42b85e43 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3894,8 +3894,18 @@ public function get_styles_for_block( $block_metadata ) { // Only store if the variation has blockGap defined. if ( isset( $style_variation_node['spacing']['blockGap'] ) ) { // Append block selector to the variation selector for proper targeting. - $variation_metadata_with_selector = $style_variation; - $variation_metadata_with_selector['selector'] = $style_variation['selector'] . $block_metadata['css']; + $variation_metadata_with_selector = $style_variation; + $variation_metadata_with_selector['selector'] = $style_variation['selector'] . $block_metadata['css']; + + /* + * `get_layout_styles()` reads `name` as a block name, to check that the block + * supports layout at all. A variation node's `name` is the variation slug, + * which is never a registered block, so the check fails and every variation + * gap rule is discarded. Pass the block the variation belongs to, so the + * support check answers the question it is actually asking. + */ + $variation_metadata_with_selector['name'] = $block_name; + $style_variation_layout_metadata[ $style_variation['selector'] ] = array( 'metadata' => $variation_metadata_with_selector, 'node' => $style_variation_node, @@ -3951,7 +3961,11 @@ public function get_styles_for_block( $block_metadata ) { if ( isset( $breakpoint_node['spacing']['blockGap'] ) ) { $variation_layout_metadata = $style_variation; $variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css']; - $variation_responsive_css .= $this->get_layout_styles( + + // The variation slug is not a block name here either. See above. + $variation_layout_metadata['name'] = $block_name; + + $variation_responsive_css .= $this->get_layout_styles( $variation_layout_metadata, array( 'node' => $breakpoint_node, diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 407489e6d26f2..7ff768737c10e 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -7829,6 +7829,193 @@ public function test_opt_in_to_block_style_variations() { $this->assertSame( $expected, $button_variations ); } + /** + * Tests that a block style variation declaring `spacing.blockGap` emits a layout + * gap rule scoped to the variation. + * + * The variation node carries the variation slug in its `name`, which + * `get_layout_styles()` reads as a block name. The metadata passed to that + * method therefore has to name the block the variation belongs to. + * + * @ticket 66044 + * + * @covers WP_Theme_JSON::get_styles_for_block + */ + public function test_block_style_variation_with_block_gap_emits_layout_styles() { + $registry = WP_Block_Styles_Registry::get_instance(); + $registry->register( 'core/group', array( 'name' => 'custom-group' ) ); + + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'variations' => array( + 'custom-group' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + ), + ), + ), + ), + ), + 'blocks' + ); + + $stylesheet = $theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'include_block_style_variations' => true, + 'skip_root_layout_styles' => true, + ) + ); + + $registry->unregister( 'core/group', 'custom-group' ); + + $this->assertStringContainsString( + ':root :where(.wp-block-group.is-style-custom-group.wp-block-group-is-layout-flex){gap: 3em;}', + $stylesheet, + 'The variation should emit a gap rule scoped to itself.' + ); + } + + /** + * Tests that a block style variation declaring `spacing.blockGap` inside a + * viewport breakpoint emits a layout gap rule within the media query. + * + * The responsive branch takes its own copy of the variation metadata, so it + * needs the owning block name for the same reason the base branch does. + * + * @ticket 66044 + * + * @covers WP_Theme_JSON::get_styles_for_block + */ + public function test_block_style_variation_with_responsive_block_gap_emits_layout_styles() { + $registry = WP_Block_Styles_Registry::get_instance(); + $registry->register( 'core/group', array( 'name' => 'custom-group' ) ); + + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, + ), + 'viewport' => array( + 'mobile' => '599px', + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'variations' => array( + 'custom-group' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + '@mobile' => array( + 'spacing' => array( + 'blockGap' => '1em', + ), + ), + ), + ), + ), + ), + ), + ), + 'blocks' + ); + + $stylesheet = $theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'include_block_style_variations' => true, + 'skip_root_layout_styles' => true, + ) + ); + + $registry->unregister( 'core/group', 'custom-group' ); + + $this->assertStringContainsString( + '@media (width <= 599px)', + $stylesheet, + 'The breakpoint media query should be emitted.' + ); + $this->assertStringContainsString( + ':root :where(.wp-block-group.is-style-custom-group.wp-block-group-is-layout-flex){gap: 1em;}', + $stylesheet, + 'The variation should emit a gap rule inside the breakpoint.' + ); + } + + /** + * Tests that the layout support check still applies to a variation's blockGap. + * + * The variation metadata carries the block it belongs to, so a block without + * layout support emits no gap rule -- the check is answered, not skipped. + * + * @ticket 66044 + * + * @covers WP_Theme_JSON::get_styles_for_block + */ + public function test_block_style_variation_block_gap_respects_layout_support() { + $registry = WP_Block_Styles_Registry::get_instance(); + $registry->register( 'core/paragraph', array( 'name' => 'custom-paragraph' ) ); + + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'blockGap' => true, + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'variations' => array( + 'custom-paragraph' => array( + 'spacing' => array( + 'blockGap' => '3em', + ), + ), + ), + ), + ), + ), + ), + 'blocks' + ); + + $stylesheet = $theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'include_block_style_variations' => true, + 'skip_root_layout_styles' => true, + ) + ); + + $registry->unregister( 'core/paragraph', 'custom-paragraph' ); + + $this->assertStringNotContainsString( + 'is-style-custom-paragraph', + $stylesheet, + 'core/paragraph has no layout support, so its variation should emit no gap rule.' + ); + } + /** * Tests that block-level settings inherit global default settings when not explicitly set. *