diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index a893dfb6a9..6fd954705e 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1032,7 +1032,7 @@ class WP_Theme_JSON { if ( ! empty( $block_type->styles ) ) { $style_selectors = array(); foreach ( $block_type->styles as $style ) { - $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } @@ -3925,4 +3925,38 @@ class WP_Theme_JSON { $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars ); return $theme_json; } + + /** + * Generates a selector for a block style variation. + * + * @since 6.5.0 + * + * @param string $variation_name Name of the block style variation. + * @param string $block_selector CSS selector for the block. + * @return string Block selector with block style variation selector added to it. + */ + protected static function get_block_style_variation_selector( $variation_name, $block_selector ) { + $variation_class = ".is-style-$variation_name"; + + if ( ! $block_selector ) { + return $variation_class; + } + + $limit = 1; + $selector_parts = explode( ',', $block_selector ); + $result = array(); + + foreach ( $selector_parts as $part ) { + $result[] = preg_replace_callback( + '/((?::\([^)]+\))?\s*)([^\s:]+)/', + function ( $matches ) use ( $variation_class ) { + return $matches[1] . $matches[2] . $variation_class; + }, + $part, + $limit + ); + } + + return implode( ',', $result ); + } } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index ff98a98e0a..25c1d58a62 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -5120,4 +5120,92 @@ class Tests_Theme_wpThemeJson extends WP_UnitTestCase { $sanitized_theme_json = $theme_json->get_raw_data(); $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' ); } + + /** + * Tests the correct application of a block style variation's selector to + * a block's selector. + * + * @ticket 60453 + * + * @dataProvider data_get_block_style_variation_selector + * + * @param string $selector CSS selector. + * @param string $expected Expected block style variation CSS selector. + */ + public function test_get_block_style_variation_selector( $selector, $expected ) { + $theme_json = new ReflectionClass( 'WP_Theme_JSON' ); + + $func = $theme_json->getMethod( 'get_block_style_variation_selector' ); + $func->setAccessible( true ); + + $actual = $func->invoke( null, 'custom', $selector ); + + $this->assertEquals( $expected, $actual ); + } + + /** + * Data provider for generating block style variation selectors. + * + * @return array[] + */ + public function data_get_block_style_variation_selector() { + return array( + 'empty block selector' => array( + 'selector' => '', + 'expected' => '.is-style-custom', + ), + 'class selector' => array( + 'selector' => '.wp-block', + 'expected' => '.wp-block.is-style-custom', + ), + 'id selector' => array( + 'selector' => '#wp-block', + 'expected' => '#wp-block.is-style-custom', + ), + 'element tag selector' => array( + 'selector' => 'p', + 'expected' => 'p.is-style-custom', + ), + 'attribute selector' => array( + 'selector' => '[style*="color"]', + 'expected' => '[style*="color"].is-style-custom', + ), + 'descendant selector' => array( + 'selector' => '.wp-block .inner', + 'expected' => '.wp-block.is-style-custom .inner', + ), + 'comma separated selector' => array( + 'selector' => '.wp-block .inner, .wp-block .alternative', + 'expected' => '.wp-block.is-style-custom .inner, .wp-block.is-style-custom .alternative', + ), + 'pseudo selector' => array( + 'selector' => 'div:first-child', + 'expected' => 'div.is-style-custom:first-child', + ), + ':is selector' => array( + 'selector' => '.wp-block:is(.outer .inner:first-child)', + 'expected' => '.wp-block.is-style-custom:is(.outer .inner:first-child)', + ), + ':not selector' => array( + 'selector' => '.wp-block:not(.outer .inner:first-child)', + 'expected' => '.wp-block.is-style-custom:not(.outer .inner:first-child)', + ), + ':has selector' => array( + 'selector' => '.wp-block:has(.outer .inner:first-child)', + 'expected' => '.wp-block.is-style-custom:has(.outer .inner:first-child)', + ), + ':where selector' => array( + 'selector' => '.wp-block:where(.outer .inner:first-child)', + 'expected' => '.wp-block.is-style-custom:where(.outer .inner:first-child)', + ), + 'wrapping :where selector' => array( + 'selector' => ':where(.outer .inner:first-child)', + 'expected' => ':where(.outer.is-style-custom .inner:first-child)', + ), + 'complex' => array( + 'selector' => '.wp:where(.something):is(.test:not(.nothing p)):has(div[style]) .content, .wp:where(.nothing):not(.test:is(.something div)):has(span[style]) .inner', + 'expected' => '.wp.is-style-custom:where(.something):is(.test:not(.nothing p)):has(div[style]) .content, .wp.is-style-custom:where(.nothing):not(.test:is(.something div)):has(span[style]) .inner', + ), + ); + } }