Make WordPress Core


Ignore:
Timestamp:
06/09/2026 04:25:45 AM (3 months ago)
Author:
isabel_brison
Message:

Editor: add support for layout responsive styles.

Adds support for setting responsive layout, block spacing and child layout styles.

Props isabel_brison, ramonopoly, audrasjb, desrosj, sabernhardt.
Fixes #65164.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/block-supports/states.php

    r62453 r62478  
    3838         * @param string $block_name Block name.
    3939         * @param array  $selectors  Optional block selectors, e.g. array( 'root' => '.foo .bar' ).
     40         * @param array  $supports   Optional block supports.
    4041         * @return WP_Block_Type
    4142         */
    42         private function ensure_block_registered( $block_name, $selectors = array() ) {
     43        private function ensure_block_registered( $block_name, $selectors = array(), $supports = array() ) {
    4344                $registered_block = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
    4445                if ( $registered_block ) {
     
    5758                if ( ! empty( $selectors ) ) {
    5859                        $args['selectors'] = $selectors;
     60                }
     61                if ( ! empty( $supports ) ) {
     62                        $args['supports'] = $supports;
    5963                }
    6064                register_block_type( $block_name, $args );
     
    838842
    839843        /**
     844         * Tests that a responsive block gap state generates layout spacing CSS.
     845         *
     846         * Responsive layout CSS is owned by wp_render_layout_support_flag()
     847         * so it shares a selector with the base layout (the inner block wrapper for
     848         * wrapper blocks) instead of being scoped to a separate `wp-states-...` class.
     849         *
     850         * @covers ::wp_render_layout_support_flag
     851         *
     852         * @ticket 65164
     853         */
     854        public function test_responsive_block_gap_state_generates_layout_spacing_css() {
     855                $this->ensure_block_registered(
     856                        'test/responsive-flow-layout-state',
     857                        array(),
     858                        array(
     859                                'layout'  => array(
     860                                        'default' => array(
     861                                                'type' => 'default',
     862                                        ),
     863                                ),
     864                                'spacing' => array(
     865                                        'blockGap' => true,
     866                                ),
     867                        )
     868                );
     869
     870                add_theme_support( 'appearance-tools' );
     871                WP_Theme_JSON_Resolver::clean_cached_data();
     872
     873                try {
     874                        $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     875                        $block         = array(
     876                                'blockName'    => 'test/responsive-flow-layout-state',
     877                                'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     878                                'attrs'        => array(
     879                                        'layout' => array(
     880                                                'type' => 'default',
     881                                        ),
     882                                        'style'  => array(
     883                                                'mobile' => array(
     884                                                        'spacing' => array(
     885                                                                'blockGap' => '12px',
     886                                                        ),
     887                                                ),
     888                                        ),
     889                                ),
     890                        );
     891
     892                        $actual = wp_render_layout_support_flag( $block_content, $block );
     893                        preg_match( '/wp-container-test-responsive-flow-layout-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     894                        $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     895                        $container_class   = $matches[0];
     896                        $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     897
     898                        $this->assertStringContainsString(
     899                                '@media (width <= 480px){.' . $container_class . ' > *{margin-block-start:0;margin-block-end:0;}}',
     900                                $actual_stylesheet
     901                        );
     902                        $this->assertStringContainsString(
     903                                '@media (width <= 480px){.' . $container_class . ' > * + *{margin-block-start:12px;margin-block-end:0;}}',
     904                                $actual_stylesheet
     905                        );
     906                } finally {
     907                        remove_theme_support( 'appearance-tools' );
     908                        WP_Theme_JSON_Resolver::clean_cached_data();
     909                }
     910        }
     911
     912        /**
     913         * Tests that responsive block gap state CSS uses the block's active layout type.
     914         *
     915         * @covers ::wp_render_layout_support_flag
     916         *
     917         * @ticket 65164
     918         */
     919        public function test_responsive_block_gap_state_uses_active_layout_type() {
     920                $this->ensure_block_registered(
     921                        'test/responsive-flex-layout-state',
     922                        array(),
     923                        array(
     924                                'layout'  => array(
     925                                        'default' => array(
     926                                                'type' => 'flex',
     927                                        ),
     928                                ),
     929                                'spacing' => array(
     930                                        'blockGap' => true,
     931                                ),
     932                        )
     933                );
     934
     935                add_theme_support( 'appearance-tools' );
     936                WP_Theme_JSON_Resolver::clean_cached_data();
     937
     938                try {
     939                        $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     940                        $block         = array(
     941                                'blockName'    => 'test/responsive-flex-layout-state',
     942                                'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     943                                'attrs'        => array(
     944                                        'layout' => array(
     945                                                'type' => 'flex',
     946                                        ),
     947                                        'style'  => array(
     948                                                'mobile' => array(
     949                                                        'spacing' => array(
     950                                                                'blockGap' => '12px',
     951                                                        ),
     952                                                ),
     953                                        ),
     954                                ),
     955                        );
     956
     957                        $actual = wp_render_layout_support_flag( $block_content, $block );
     958                        preg_match( '/wp-container-test-responsive-flex-layout-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     959                        $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     960                        $container_class   = $matches[0];
     961                        $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     962
     963                        $this->assertStringContainsString(
     964                                '@media (width <= 480px){.' . $container_class . '{gap:12px;}}',
     965                                $actual_stylesheet
     966                        );
     967                } finally {
     968                        remove_theme_support( 'appearance-tools' );
     969                        WP_Theme_JSON_Resolver::clean_cached_data();
     970                }
     971        }
     972
     973        /**
     974         * Tests that responsive layout state CSS can override grid layout values.
     975         *
     976         * @covers ::wp_render_layout_support_flag
     977         *
     978         * @ticket 65164
     979         */
     980        public function test_responsive_layout_state_generates_grid_layout_css() {
     981                $this->ensure_block_registered(
     982                        'test/responsive-grid-layout-state',
     983                        array(),
     984                        array(
     985                                'layout' => array(
     986                                        'default' => array(
     987                                                'type' => 'grid',
     988                                        ),
     989                                ),
     990                        )
     991                );
     992
     993                $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     994                $block         = array(
     995                        'blockName'    => 'test/responsive-grid-layout-state',
     996                        'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     997                        'attrs'        => array(
     998                                'layout' => array(
     999                                        'type' => 'grid',
     1000                                ),
     1001                                'style'  => array(
     1002                                        'mobile' => array(
     1003                                                'layout' => array(
     1004                                                        'minimumColumnWidth' => '8rem',
     1005                                                ),
     1006                                        ),
     1007                                ),
     1008                        ),
     1009                );
     1010
     1011                $actual = wp_render_layout_support_flag( $block_content, $block );
     1012                preg_match( '/wp-container-test-responsive-grid-layout-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     1013                $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     1014                $container_class   = $matches[0];
     1015                $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1016
     1017                $this->assertStringContainsString(
     1018                        '@media (width <= 480px){.' . $container_class . '{grid-template-columns:repeat(auto-fill, minmax(min(8rem, 100%), 1fr));}}',
     1019                        $actual_stylesheet
     1020                );
     1021        }
     1022
     1023        /**
     1024         * Tests that responsive layout state CSS can override grid columns.
     1025         *
     1026         * @covers ::wp_render_layout_support_flag
     1027         *
     1028         * @ticket 65164
     1029         */
     1030        public function test_responsive_layout_state_generates_grid_column_count_css() {
     1031                $this->ensure_block_registered(
     1032                        'test/responsive-grid-column-layout-state',
     1033                        array(),
     1034                        array(
     1035                                'layout' => array(
     1036                                        'default' => array(
     1037                                                'type' => 'grid',
     1038                                        ),
     1039                                ),
     1040                        )
     1041                );
     1042
     1043                $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     1044                $block         = array(
     1045                        'blockName'    => 'test/responsive-grid-column-layout-state',
     1046                        'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     1047                        'attrs'        => array(
     1048                                'layout' => array(
     1049                                        'type' => 'grid',
     1050                                ),
     1051                                'style'  => array(
     1052                                        'mobile' => array(
     1053                                                'layout' => array(
     1054                                                        'columnCount' => 3,
     1055                                                ),
     1056                                        ),
     1057                                ),
     1058                        ),
     1059                );
     1060
     1061                $actual = wp_render_layout_support_flag( $block_content, $block );
     1062                preg_match( '/wp-container-test-responsive-grid-column-layout-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     1063                $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     1064                $container_class   = $matches[0];
     1065                $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1066
     1067                $this->assertStringContainsString(
     1068                        '@media (width <= 480px){.' . $container_class . '{grid-template-columns:repeat(3, minmax(0, 1fr));}}',
     1069                        $actual_stylesheet
     1070                );
     1071        }
     1072
     1073        /**
     1074         * Tests that different responsive layout states generate different container
     1075         * classes, even when the base layout configuration is identical.
     1076         *
     1077         * @covers ::wp_render_layout_support_flag
     1078         *
     1079         * @ticket 65164
     1080         */
     1081        public function test_responsive_layout_state_generates_distinct_container_classes_for_distinct_viewport_styles() {
     1082                $this->ensure_block_registered(
     1083                        'test/responsive-grid-distinct-layout-state',
     1084                        array(),
     1085                        array(
     1086                                'layout' => array(
     1087                                        'default' => array(
     1088                                                'type' => 'grid',
     1089                                        ),
     1090                                ),
     1091                        )
     1092                );
     1093
     1094                $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     1095                $base_block    = array(
     1096                        'blockName'    => 'test/responsive-grid-distinct-layout-state',
     1097                        'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     1098                        'attrs'        => array(
     1099                                'layout' => array(
     1100                                        'type' => 'grid',
     1101                                ),
     1102                        ),
     1103                );
     1104                $first_block   = array_replace_recursive(
     1105                        $base_block,
     1106                        array(
     1107                                'attrs' => array(
     1108                                        'style' => array(
     1109                                                'mobile' => array(
     1110                                                        'layout' => array(
     1111                                                                'columnCount' => 3,
     1112                                                        ),
     1113                                                ),
     1114                                        ),
     1115                                ),
     1116                        )
     1117                );
     1118                $second_block  = array_replace_recursive(
     1119                        $base_block,
     1120                        array(
     1121                                'attrs' => array(
     1122                                        'style' => array(
     1123                                                'mobile' => array(
     1124                                                        'layout' => array(
     1125                                                                'columnCount' => 4,
     1126                                                        ),
     1127                                                ),
     1128                                        ),
     1129                                ),
     1130                        )
     1131                );
     1132
     1133                $first_actual  = wp_render_layout_support_flag( $block_content, $first_block );
     1134                $second_actual = wp_render_layout_support_flag( $block_content, $second_block );
     1135
     1136                preg_match( '/wp-container-test-responsive-grid-distinct-layout-state-is-layout-[a-f0-9]{8}/', $first_actual, $first_matches );
     1137                preg_match( '/wp-container-test-responsive-grid-distinct-layout-state-is-layout-[a-f0-9]{8}/', $second_actual, $second_matches );
     1138
     1139                $this->assertNotEmpty( $first_matches, "wp-container class missing in: $first_actual" );
     1140                $this->assertNotEmpty( $second_matches, "wp-container class missing in: $second_actual" );
     1141
     1142                $first_container_class  = $first_matches[0];
     1143                $second_container_class = $second_matches[0];
     1144
     1145                $this->assertNotSame( $first_container_class, $second_container_class );
     1146
     1147                $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1148
     1149                $this->assertStringContainsString(
     1150                        '@media (width <= 480px){.' . $first_container_class . '{grid-template-columns:repeat(3, minmax(0, 1fr));}}',
     1151                        $actual_stylesheet
     1152                );
     1153                $this->assertStringContainsString(
     1154                        '@media (width <= 480px){.' . $second_container_class . '{grid-template-columns:repeat(4, minmax(0, 1fr));}}',
     1155                        $actual_stylesheet
     1156                );
     1157        }
     1158
     1159        /**
     1160         * Tests that responsive grid layout and block gap state CSS are both generated.
     1161         *
     1162         * @covers ::wp_render_layout_support_flag
     1163         *
     1164         * @ticket 65164
     1165         */
     1166        public function test_responsive_layout_state_generates_grid_columns_and_gap_css() {
     1167                $this->ensure_block_registered(
     1168                        'test/responsive-grid-columns-gap-layout-state',
     1169                        array(),
     1170                        array(
     1171                                'layout'  => array(
     1172                                        'default' => array(
     1173                                                'type' => 'grid',
     1174                                        ),
     1175                                ),
     1176                                'spacing' => array(
     1177                                        'blockGap' => true,
     1178                                ),
     1179                        )
     1180                );
     1181
     1182                add_theme_support( 'appearance-tools' );
     1183                WP_Theme_JSON_Resolver::clean_cached_data();
     1184
     1185                try {
     1186                        $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     1187                        $block         = array(
     1188                                'blockName'    => 'test/responsive-grid-columns-gap-layout-state',
     1189                                'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     1190                                'attrs'        => array(
     1191                                        'layout' => array(
     1192                                                'type' => 'grid',
     1193                                        ),
     1194                                        'style'  => array(
     1195                                                'mobile' => array(
     1196                                                        'layout'  => array(
     1197                                                                'columnCount' => 3,
     1198                                                        ),
     1199                                                        'spacing' => array(
     1200                                                                'blockGap' => '12px',
     1201                                                        ),
     1202                                                ),
     1203                                        ),
     1204                                ),
     1205                        );
     1206
     1207                        $actual = wp_render_layout_support_flag( $block_content, $block );
     1208                        preg_match( '/wp-container-test-responsive-grid-columns-gap-layout-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     1209                        $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     1210                        $container_class   = $matches[0];
     1211                        $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1212
     1213                        $this->assertStringContainsString(
     1214                                '@media (width <= 480px){.' . $container_class . '{grid-template-columns:repeat(3, minmax(0, 1fr));gap:12px;}}',
     1215                                $actual_stylesheet
     1216                        );
     1217                } finally {
     1218                        remove_theme_support( 'appearance-tools' );
     1219                        WP_Theme_JSON_Resolver::clean_cached_data();
     1220                }
     1221        }
     1222
     1223        /**
     1224         * Tests that responsive grid block gap CSS does not repeat unchanged layout declarations.
     1225         *
     1226         * @covers ::wp_render_layout_support_flag
     1227         *
     1228         * @ticket 65164
     1229         */
     1230        public function test_responsive_grid_block_gap_state_only_outputs_changed_layout_css() {
     1231                $this->ensure_block_registered(
     1232                        'test/responsive-grid-gap-state',
     1233                        array(),
     1234                        array(
     1235                                'layout'  => array(
     1236                                        'default' => array(
     1237                                                'type'               => 'grid',
     1238                                                'minimumColumnWidth' => '12rem',
     1239                                        ),
     1240                                ),
     1241                                'spacing' => array(
     1242                                        'blockGap' => true,
     1243                                ),
     1244                        )
     1245                );
     1246
     1247                add_theme_support( 'appearance-tools' );
     1248                WP_Theme_JSON_Resolver::clean_cached_data();
     1249
     1250                try {
     1251                        $block_content = '<div class="wp-block-test"><p>One</p><p>Two</p></div>';
     1252                        $block         = array(
     1253                                'blockName'    => 'test/responsive-grid-gap-state',
     1254                                'innerContent' => array( '<div class="wp-block-test">', null, '</div>' ),
     1255                                'attrs'        => array(
     1256                                        'layout' => array(
     1257                                                'type'               => 'grid',
     1258                                                'minimumColumnWidth' => '12rem',
     1259                                        ),
     1260                                        'style'  => array(
     1261                                                'tablet' => array(
     1262                                                        'spacing' => array(
     1263                                                                'blockGap' => '12px',
     1264                                                        ),
     1265                                                ),
     1266                                        ),
     1267                                ),
     1268                        );
     1269
     1270                        $actual = wp_render_layout_support_flag( $block_content, $block );
     1271                        preg_match( '/wp-container-test-responsive-grid-gap-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     1272                        $this->assertNotEmpty( $matches, "wp-container class missing in: $actual" );
     1273                        $container_class   = $matches[0];
     1274                        $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1275
     1276                        $this->assertStringContainsString(
     1277                                '@media (480px < width <= 782px){.' . $container_class . '{gap:12px;}}',
     1278                                $actual_stylesheet
     1279                        );
     1280                        $this->assertStringNotContainsString(
     1281                                '@media (480px < width <= 782px){.' . $container_class . '{grid-template-columns:',
     1282                                $actual_stylesheet
     1283                        );
     1284                        $this->assertStringNotContainsString(
     1285                                '@media (480px < width <= 782px){.' . $container_class . '{container-type:',
     1286                                $actual_stylesheet
     1287                        );
     1288                } finally {
     1289                        remove_theme_support( 'appearance-tools' );
     1290                        WP_Theme_JSON_Resolver::clean_cached_data();
     1291                }
     1292        }
     1293
     1294        /**
     1295         * Tests that responsive child layout state CSS is generated.
     1296         *
     1297         * @covers ::wp_render_layout_support_flag
     1298         *
     1299         * @ticket 65164
     1300         */
     1301        public function test_responsive_child_layout_state_generates_grid_span_css() {
     1302                $this->ensure_block_registered( 'test/responsive-child-layout-state' );
     1303
     1304                $block_content = '<p>Some text.</p>';
     1305                $block         = array(
     1306                        'blockName'    => 'test/responsive-child-layout-state',
     1307                        'innerContent' => array( '<p>Some text.</p>' ),
     1308                        'attrs'        => array(
     1309                                'style' => array(
     1310                                        'mobile' => array(
     1311                                                'layout' => array(
     1312                                                        'columnSpan' => '2',
     1313                                                ),
     1314                                        ),
     1315                                ),
     1316                        ),
     1317                        'parentLayout' => array(
     1318                                'type'        => 'grid',
     1319                                'columnCount' => 3,
     1320                        ),
     1321                );
     1322
     1323                $actual = wp_render_layout_support_flag( $block_content, $block );
     1324                preg_match( '/wp-container-content-[a-f0-9]{8}/', $actual, $matches );
     1325                $this->assertNotEmpty( $matches, "wp-container-content class missing in: $actual" );
     1326                $container_content_class = $matches[0];
     1327                $actual_stylesheet       = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1328
     1329                $this->assertStringContainsString(
     1330                        '@media (width <= 480px){.' . $container_content_class . '{grid-column:span 2;}}',
     1331                        $actual_stylesheet
     1332                );
     1333        }
     1334
     1335        /**
     1336         * Tests that a wrapper block (markup with an inner content wrapper) receives
     1337         * responsive grid layout CSS scoped to the inner wrapper, not the outermost tag.
     1338         *
     1339         * Regression test for the bug where wp-states-... was added to the outer tag
     1340         * while the wp-container-... layout class lives on the inner wrapper, causing
     1341         * the responsive @media rule to apply to the wrong element.
     1342         *
     1343         * @covers ::wp_render_layout_support_flag
     1344         *
     1345         * @ticket 65164
     1346         */
     1347        public function test_responsive_layout_state_targets_inner_wrapper_for_wrapper_blocks() {
     1348                $this->ensure_block_registered(
     1349                        'test/responsive-wrapper-grid-state',
     1350                        array(),
     1351                        array(
     1352                                'layout' => array(
     1353                                        'default' => array(
     1354                                                'type' => 'grid',
     1355                                        ),
     1356                                ),
     1357                        )
     1358                );
     1359
     1360                $block_content = '<div class="wp-block-wrapper"><div class="wp-block-wrapper__inner-container"><p>One</p></div></div>';
     1361                $block         = array(
     1362                        'blockName'    => 'test/responsive-wrapper-grid-state',
     1363                        'innerContent' => array(
     1364                                '<div class="wp-block-wrapper"><div class="wp-block-wrapper__inner-container">',
     1365                                null,
     1366                                '</div></div>',
     1367                        ),
     1368                        'attrs'        => array(
     1369                                'layout' => array(
     1370                                        'type' => 'grid',
     1371                                ),
     1372                                'style'  => array(
     1373                                        'mobile' => array(
     1374                                                'layout' => array(
     1375                                                        'columnCount' => 3,
     1376                                                ),
     1377                                        ),
     1378                                ),
     1379                        ),
     1380                );
     1381
     1382                $actual = wp_render_layout_support_flag( $block_content, $block );
     1383
     1384                // The wp-container-...-is-layout-... class should land on the inner wrapper.
     1385                $this->assertMatchesRegularExpression(
     1386                        '/<div class="wp-block-wrapper__inner-container [^"]*wp-container-test-responsive-wrapper-grid-state-is-layout-[a-f0-9]{8}/',
     1387                        $actual
     1388                );
     1389
     1390                preg_match( '/wp-container-test-responsive-wrapper-grid-state-is-layout-[a-f0-9]{8}/', $actual, $matches );
     1391                $container_class   = $matches[0];
     1392                $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) );
     1393
     1394                // The responsive @media rule must target the same selector that lives on
     1395                // the inner wrapper element.
     1396                $this->assertStringContainsString(
     1397                        '@media (width <= 480px){.' . $container_class . '{grid-template-columns:repeat(3, minmax(0, 1fr));}}',
     1398                        $actual_stylesheet
     1399                );
     1400        }
     1401
     1402        /**
    8401403         * Tests that state declarations are marked important.
    8411404         *
Note: See TracChangeset for help on using the changeset viewer.