<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[57547] trunk: Editor: Fix block style variation selector generation.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/57547">57547</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/57547","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>youknowriad</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-02-07 08:51:39 +0000 (Wed, 07 Feb 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Editor: Fix block style variation selector generation.

These changes fix the generation of selectors for block style variations. Previously, an incorrect CSS selector could be generated if the block's base selector used an element tag etc.

Props aaronrobertshaw, youknowriad, mukesh27.
Fixes <a href="https://core.trac.wordpress.org/ticket/60453">#60453</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpthemejsonphp">trunk/src/wp-includes/class-wp-theme-json.php</a></li>
<li><a href="#trunktestsphpunitteststhemewpThemeJsonphp">trunk/tests/phpunit/tests/theme/wpThemeJson.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpthemejsonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme-json.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme-json.php     2024-02-07 04:35:34 UTC (rev 57546)
+++ trunk/src/wp-includes/class-wp-theme-json.php       2024-02-07 08:51:39 UTC (rev 57547)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1032,7 +1032,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! empty( $block_type->styles ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $style_selectors = array();
</span><span class="cx" style="display: block; padding: 0 10px">                                foreach ( $block_type->styles as $style ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                                static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3925,4 +3925,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars );
</span><span class="cx" style="display: block; padding: 0 10px">                return $theme_json;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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 );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunitteststhemewpThemeJsonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/theme/wpThemeJson.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/theme/wpThemeJson.php   2024-02-07 04:35:34 UTC (rev 57546)
+++ trunk/tests/phpunit/tests/theme/wpThemeJson.php     2024-02-07 08:51:39 UTC (rev 57547)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5120,4 +5120,92 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $sanitized_theme_json = $theme_json->get_raw_data();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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',
+                       ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>