diff --git a/src/wp-includes/block-supports/border.php b/src/wp-includes/block-supports/border.php index a11c099d9eb32..f28a696c94e75 100644 --- a/src/wp-includes/block-supports/border.php +++ b/src/wp-includes/block-supports/border.php @@ -22,7 +22,7 @@ function wp_register_border_support( $block_type ) { $block_type->attributes = array(); } - if ( block_has_support( $block_type, '__experimentalBorder' ) && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( block_has_support( $block_type, 'border' ) && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -60,7 +60,7 @@ function wp_apply_border_support( $block_type, $block_attributes ) { if ( wp_has_border_feature_support( $block_type, 'radius' ) && isset( $block_attributes['style']['border']['radius'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'radius' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'radius' ) ) { $border_radius = $block_attributes['style']['border']['radius']; @@ -75,7 +75,7 @@ function wp_apply_border_support( $block_type, $block_attributes ) { if ( wp_has_border_feature_support( $block_type, 'style' ) && isset( $block_attributes['style']['border']['style'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) ) { $border_block_styles['style'] = $block_attributes['style']['border']['style']; } @@ -84,7 +84,7 @@ function wp_apply_border_support( $block_type, $block_attributes ) { if ( $has_border_width_support && isset( $block_attributes['style']['border']['width'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) ) { $border_width = $block_attributes['style']['border']['width']; @@ -99,7 +99,7 @@ function wp_apply_border_support( $block_type, $block_attributes ) { // Border color. if ( $has_border_color_support && - ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) + ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) ) { $preset_border_color = array_key_exists( 'borderColor', $block_attributes ) ? "var:preset|color|{$block_attributes['borderColor']}" : null; $custom_border_color = isset( $block_attributes['style']['border']['color'] ) ? $block_attributes['style']['border']['color'] : null; @@ -111,9 +111,9 @@ function wp_apply_border_support( $block_type, $block_attributes ) { foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) { $border = isset( $block_attributes['style']['border'][ $side ] ) ? $block_attributes['style']['border'][ $side ] : null; $border_side_values = array( - 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ? $border['width'] : null, - 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) ? $border['color'] : null, - 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) ? $border['style'] : null, + 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) ? $border['width'] : null, + 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) ? $border['color'] : null, + 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) ? $border['style'] : null, ); $border_block_styles[ $side ] = $border_side_values; } @@ -137,7 +137,7 @@ function wp_apply_border_support( $block_type, $block_attributes ) { /** * Checks whether the current block type supports the border feature requested. * - * If the `__experimentalBorder` support flag is a boolean `true` all border + * If the `border` support flag is a boolean `true` all border * support features are available. Otherwise, the specific feature's support * flag nested under `experimentalBorder` must be enabled for the feature * to be opted into. @@ -151,10 +151,10 @@ function wp_apply_border_support( $block_type, $block_attributes ) { * @return bool Whether the feature is supported. */ function wp_has_border_feature_support( $block_type, $feature, $default_value = false ) { - // Check if all border support features have been opted into via `"__experimentalBorder": true`. + // Check if all border support features have been opted into via `"border": true`. if ( $block_type instanceof WP_Block_Type ) { - $block_type_supports_border = isset( $block_type->supports['__experimentalBorder'] ) - ? $block_type->supports['__experimentalBorder'] + $block_type_supports_border = isset( $block_type->supports['border'] ) + ? $block_type->supports['border'] : $default_value; if ( true === $block_type_supports_border ) { return true; @@ -162,8 +162,8 @@ function wp_has_border_feature_support( $block_type, $feature, $default_value = } // Check if the specific feature has been opted into individually - // via nested flag under `__experimentalBorder`. - return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default_value ); + // via nested flag under `border`. + return block_has_support( $block_type, array( 'border', $feature ), $default_value ); } // Register the block support. diff --git a/src/wp-includes/block-supports/typography.php b/src/wp-includes/block-supports/typography.php index 0770400e0000d..dd35ef1b19ad5 100644 --- a/src/wp-includes/block-supports/typography.php +++ b/src/wp-includes/block-supports/typography.php @@ -25,16 +25,16 @@ function wp_register_typography_support( $block_type ) { return; } - $has_font_family_support = isset( $typography_supports['__experimentalFontFamily'] ) ? $typography_supports['__experimentalFontFamily'] : false; + $has_font_family_support = isset( $typography_supports['fontFamily'] ) ? $typography_supports['fontFamily'] : false; $has_font_size_support = isset( $typography_supports['fontSize'] ) ? $typography_supports['fontSize'] : false; - $has_font_style_support = isset( $typography_supports['__experimentalFontStyle'] ) ? $typography_supports['__experimentalFontStyle'] : false; - $has_font_weight_support = isset( $typography_supports['__experimentalFontWeight'] ) ? $typography_supports['__experimentalFontWeight'] : false; - $has_letter_spacing_support = isset( $typography_supports['__experimentalLetterSpacing'] ) ? $typography_supports['__experimentalLetterSpacing'] : false; + $has_font_style_support = isset( $typography_supports['fontStyle'] ) ? $typography_supports['fontStyle'] : false; + $has_font_weight_support = isset( $typography_supports['fontWeight'] ) ? $typography_supports['fontWeight'] : false; + $has_letter_spacing_support = isset( $typography_supports['letterSpacing'] ) ? $typography_supports['letterSpacing'] : false; $has_line_height_support = isset( $typography_supports['lineHeight'] ) ? $typography_supports['lineHeight'] : false; $has_text_align_support = isset( $typography_supports['textAlign'] ) ? $typography_supports['textAlign'] : false; $has_text_columns_support = isset( $typography_supports['textColumns'] ) ? $typography_supports['textColumns'] : false; - $has_text_decoration_support = isset( $typography_supports['__experimentalTextDecoration'] ) ? $typography_supports['__experimentalTextDecoration'] : false; - $has_text_transform_support = isset( $typography_supports['__experimentalTextTransform'] ) ? $typography_supports['__experimentalTextTransform'] : false; + $has_text_decoration_support = isset( $typography_supports['textDecoration'] ) ? $typography_supports['textDecoration'] : false; + $has_text_transform_support = isset( $typography_supports['textTransform'] ) ? $typography_supports['textTransform'] : false; $has_writing_mode_support = isset( $typography_supports['__experimentalWritingMode'] ) ? $typography_supports['__experimentalWritingMode'] : false; $has_typography_support = $has_font_family_support @@ -80,6 +80,7 @@ function wp_register_typography_support( $block_type ) { * @since 5.6.0 * @since 6.1.0 Used the style engine to generate CSS and classnames. * @since 6.3.0 Added support for text-columns. + * @since 6.8.0 Typography block supports now use stable non-experimental keys. * @access private * * @param WP_Block_Type $block_type Block type. @@ -102,16 +103,16 @@ function wp_apply_typography_support( $block_type, $block_attributes ) { return array(); } - $has_font_family_support = isset( $typography_supports['__experimentalFontFamily'] ) ? $typography_supports['__experimentalFontFamily'] : false; + $has_font_family_support = isset( $typography_supports['fontFamily'] ) ? $typography_supports['fontFamily'] : false; $has_font_size_support = isset( $typography_supports['fontSize'] ) ? $typography_supports['fontSize'] : false; - $has_font_style_support = isset( $typography_supports['__experimentalFontStyle'] ) ? $typography_supports['__experimentalFontStyle'] : false; - $has_font_weight_support = isset( $typography_supports['__experimentalFontWeight'] ) ? $typography_supports['__experimentalFontWeight'] : false; - $has_letter_spacing_support = isset( $typography_supports['__experimentalLetterSpacing'] ) ? $typography_supports['__experimentalLetterSpacing'] : false; + $has_font_style_support = isset( $typography_supports['fontStyle'] ) ? $typography_supports['fontStyle'] : false; + $has_font_weight_support = isset( $typography_supports['fontWeight'] ) ? $typography_supports['fontWeight'] : false; + $has_letter_spacing_support = isset( $typography_supports['letterSpacing'] ) ? $typography_supports['letterSpacing'] : false; $has_line_height_support = isset( $typography_supports['lineHeight'] ) ? $typography_supports['lineHeight'] : false; $has_text_align_support = isset( $typography_supports['textAlign'] ) ? $typography_supports['textAlign'] : false; $has_text_columns_support = isset( $typography_supports['textColumns'] ) ? $typography_supports['textColumns'] : false; - $has_text_decoration_support = isset( $typography_supports['__experimentalTextDecoration'] ) ? $typography_supports['__experimentalTextDecoration'] : false; - $has_text_transform_support = isset( $typography_supports['__experimentalTextTransform'] ) ? $typography_supports['__experimentalTextTransform'] : false; + $has_text_decoration_support = isset( $typography_supports['textDecoration'] ) ? $typography_supports['textDecoration'] : false; + $has_text_transform_support = isset( $typography_supports['textTransform'] ) ? $typography_supports['textTransform'] : false; $has_writing_mode_support = isset( $typography_supports['__experimentalWritingMode'] ) ? $typography_supports['__experimentalWritingMode'] : false; // Whether to skip individual block support features. @@ -268,6 +269,7 @@ function wp_apply_typography_support( $block_type, $block_attributes ) { * * It is necessary to parse older blocks whose typography styles contain presets. * * It mostly replaces the deprecated `wp_typography_get_css_variable_inline_style()`, * but skips compiling a CSS declaration as the style engine takes over this role. + * * @link https://github.com/wordpress/gutenberg/pull/27555 * * @since 6.1.0 diff --git a/src/wp-includes/block-supports/utils.php b/src/wp-includes/block-supports/utils.php index 7c5d7db48a150..135fdc7534d17 100644 --- a/src/wp-includes/block-supports/utils.php +++ b/src/wp-includes/block-supports/utils.php @@ -25,7 +25,7 @@ function wp_should_skip_block_supports_serialization( $block_type, $feature_set, return false; } - $path = array( $feature_set, '__experimentalSkipSerialization' ); + $path = array( $feature_set, 'skipSerialization' ); $skip_serialization = _wp_array_get( $block_type->supports, $path, false ); if ( is_array( $skip_serialization ) ) { diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php index 6916f6a462981..9aa51c6865b6a 100644 --- a/src/wp-includes/class-wp-block-type.php +++ b/src/wp-includes/class-wp-block-type.php @@ -272,6 +272,45 @@ class WP_Block_Type { 'style', ); + /** + * Experimental to stable block supports map. + * + * @since 6.8.0 + * @var array + */ + private $experimental_supports_map = array( '__experimentalBorder' => 'border' ); + + /** + * Map of shared experimental support properties to stable. + * + * This map relates to experimental block support flags that are + * across multiple different block supports. + * + * @since 6.8.0 + * @var array + */ + private $common_experimental_support_properties = array( + '__experimentalDefaultControls' => 'defaultControls', + '__experimentalSkipSerialization' => 'skipSerialization', + ); + + /** + * Experimental to stable block supports properties map. + * + * @since 6.8.0 + * @var array + */ + private $experimental_support_properties = array( + 'typography' => array( + '__experimentalFontFamily' => 'fontFamily', + '__experimentalFontStyle' => 'fontStyle', + '__experimentalFontWeight' => 'fontWeight', + '__experimentalLetterSpacing' => 'letterSpacing', + '__experimentalTextDecoration' => 'textDecoration', + '__experimentalTextTransform' => 'textTransform', + ), + ); + /** * Attributes supported by every block. * @@ -572,6 +611,11 @@ public function set_props( $args ) { */ $args = apply_filters( 'register_block_type_args', $args, $this->name ); + // Stabilize experimental block supports. + if ( isset( $args['supports'] ) && is_array( $args['supports'] ) ) { + $args['supports'] = $this->stabilize_supports( $args['supports'] ); + } + foreach ( $args as $property_name => $property_value ) { $this->$property_name = $property_value; } @@ -634,4 +678,116 @@ public function get_uses_context() { */ return apply_filters( 'get_block_type_uses_context', $this->uses_context, $this ); } + + /** + * Stabilize experimental block supports. This method transforms experimental + * block supports into their stable counterparts, by renaming the keys to the + * stable versions. + * + * @since 6.8.0 + * + * @param array $supports The block supports array from within the block type arguments. + * @return array The stabilized block supports array. + */ + private function stabilize_supports( $supports ) { + $done = array(); + $updated_supports = array(); + + foreach ( $supports as $support => $config ) { + /* + * If this support config has already been stabilized, skip it. + * A stable support key occurring after an experimental key, gets + * stabilized then so that the two configs can be merged effectively. + */ + if ( isset( $done[ $support ] ) ) { + continue; + } + + $stable_support_key = $this->experimental_supports_map[ $support ] ?? $support; + + /* + * Use the support's config as is when it's not in need of stabilization. + * + * A support does not need stabilization if: + * - The support key doesn't need stabilization AND + * - Either: + * - The config isn't an array, so can't have experimental properties OR + * - The config is an array but has no experimental properties to stabilize. + */ + if ( $support === $stable_support_key && + ( ! is_array( $config ) || + ( + ! isset( $this->experimental_support_properties[ $stable_support_key ] ) && + empty( array_intersect_key( $this->common_experimental_support_properties, $config ) ) + ) + ) + ) { + $updated_supports[ $support ] = $config; + continue; + } + + $stabilize_config = function ( $unstable_config, $stable_support_key ) { + if ( ! is_array( $unstable_config ) ) { + return $unstable_config; + } + + $stable_config = array(); + foreach ( $unstable_config as $key => $value ) { + // Get stable key from support-specific map, common properties map, or keep original. + $stable_key = $this->experimental_support_properties[ $stable_support_key ][ $key ] ?? + $this->common_experimental_support_properties[ $key ] ?? + $key; + + $stable_config[ $stable_key ] = $value; + } + return $stable_config; + }; + + // Stabilize the config value. + $stable_config = is_array( $config ) ? $stabilize_config( $config, $stable_support_key ) : $config; + + /* + * If a plugin overrides the support config with the `register_block_type_args` + * filter, both experimental and stable configs may be present. In that case, + * use the order keys are defined in to determine the final value. + * - If config is an array, merge the arrays in their order of definition. + * - If config is not an array, use the value defined last. + * + * The reason for preferring the last defined key is that after filters + * are applied, the last inserted key is likely the most up-to-date value. + * We cannot determine with certainty which value was "last modified" so + * the insertion order is the best guess. The extreme edge case of multiple + * filters tweaking the same support property will become less over time as + * extenders migrate existing blocks and plugins to stable keys. + */ + if ( $support !== $stable_support_key && isset( $supports[ $stable_support_key ] ) ) { + $key_positions = array_flip( array_keys( $supports ) ); + $experimental_first = + ( $key_positions[ $support ] ?? PHP_INT_MAX ) < + ( $key_positions[ $stable_support_key ] ?? PHP_INT_MAX ); + + /* + * To merge the alternative support config effectively, it also needs to be + * stabilized before merging to keep stabilized and experimental flags in sync. + */ + $supports[ $stable_support_key ] = $stabilize_config( $supports[ $stable_support_key ], $stable_support_key ); + // Prevents reprocessing this support as it was merged above. + $done[ $stable_support_key ] = true; + + if ( is_array( $stable_config ) && is_array( $supports[ $stable_support_key ] ) ) { + $stable_config = $experimental_first + ? array_merge( $stable_config, $supports[ $stable_support_key ] ) + : array_merge( $supports[ $stable_support_key ], $stable_config ); + } else { + $stable_config = $experimental_first + ? $supports[ $stable_support_key ] + : $stable_config; + } + } + + $updated_supports[ $stable_support_key ] = $stable_config; + } + + return $updated_supports; + } } diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index cb48672c438bb..09cb8146d2b6f 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -631,10 +631,10 @@ class WP_Theme_JSON { * @var string[] */ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( - '__experimentalBorder' => 'border', - 'color' => 'color', - 'spacing' => 'spacing', - 'typography' => 'typography', + 'border' => 'border', + 'color' => 'color', + 'spacing' => 'spacing', + 'typography' => 'typography', ); /** diff --git a/tests/phpunit/tests/block-supports/border.php b/tests/phpunit/tests/block-supports/border.php index cd1c1def70e26..63103d8604a93 100644 --- a/tests/phpunit/tests/block-supports/border.php +++ b/tests/phpunit/tests/block-supports/border.php @@ -86,11 +86,11 @@ public function test_border_with_skipped_serialization_block_supports() { ), 'supports' => array( '__experimentalBorder' => array( - 'color' => true, - 'radius' => true, - 'width' => true, - 'style' => true, - '__experimentalSkipSerialization' => true, + 'color' => true, + 'radius' => true, + 'width' => true, + 'style' => true, + 'skipSerialization' => true, ), ), ) @@ -159,4 +159,117 @@ public function test_radius_with_individual_skipped_serialization_block_supports $this->assertSame( $expected, $actual ); } + + /** + * Tests that stabilized border supports will also apply to blocks using + * the experimental syntax, for backwards compatibility with existing blocks. + * + * @ticket 61728 + * + * @covers ::wp_apply_border_support + */ + public function test_should_apply_experimental_border_supports() { + $this->test_block_name = 'test/experimental-border-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'style' => array( + 'border' => array( + 'color' => '#72aee6', + 'radius' => '10px', + 'style' => 'dashed', + 'width' => '2px', + ), + ), + ); + + $actual = wp_apply_border_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-border-color', + 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', + ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Tests that stabilized border supports are applied correctly. + * + * @ticket 61728 + * + * @covers ::wp_apply_border_support + */ + public function test_should_apply_stabilized_border_supports() { + $this->test_block_name = 'test/stabilized-border-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + ), + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'style' => array( + 'border' => array( + 'color' => '#72aee6', + 'radius' => '10px', + 'style' => 'dashed', + 'width' => '2px', + ), + ), + ); + + $actual = wp_apply_border_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-border-color', + 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', + ); + + $this->assertSame( $expected, $actual ); + } } diff --git a/tests/phpunit/tests/block-supports/typography.php b/tests/phpunit/tests/block-supports/typography.php index 59c47516caa2d..45f179f38d482 100644 --- a/tests/phpunit/tests/block-supports/typography.php +++ b/tests/phpunit/tests/block-supports/typography.php @@ -288,6 +288,115 @@ public function test_should_generate_classname_for_font_family() { $this->assertSame( $expected, $actual ); } + /** + * Tests that stabilized typography supports will also apply to blocks using + * the experimental syntax, for backwards compatibility with existing blocks. + * + * @ticket 61728 + * + * @covers ::wp_apply_typography_support + */ + public function test_should_apply_to_experimental_typography_supports() { + $this->test_block_name = 'test/experimental-typography-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + '__experimentalFontFamily' => true, + '__experimentalFontStyle' => true, + '__experimentalFontWeight' => true, + '__experimentalLetterSpacing' => true, + '__experimentalTextDecoration' => true, + '__experimentalTextTransform' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'fontFamily' => 'serif', + 'style' => array( + 'typography' => array( + 'fontStyle' => 'italic', + 'fontWeight' => 'bold', + 'letterSpacing' => '1px', + 'textDecoration' => 'underline', + 'textTransform' => 'uppercase', + ), + ), + ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-serif-font-family', + 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', + ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Tests that stabilized typography supports are applied correctly. + * + * @ticket 61728 + * + * @covers ::wp_apply_typography_support + */ + public function test_should_apply_to_stabilized_typography_supports() { + $this->test_block_name = 'test/experimental-typography-supports'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + 'fontFamily' => true, + 'fontStyle' => true, + 'fontWeight' => true, + 'letterSpacing' => true, + 'textDecoration' => true, + 'textTransform' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_atts = array( + 'fontFamily' => 'serif', + 'style' => array( + 'typography' => array( + 'fontStyle' => 'italic', + 'fontWeight' => 'bold', + 'letterSpacing' => '1px', + 'textDecoration' => 'underline', + 'textTransform' => 'uppercase', + ), + ), + ); + + $actual = wp_apply_typography_support( $block_type, $block_atts ); + $expected = array( + 'class' => 'has-serif-font-family', + 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', + ); + + $this->assertSame( $expected, $actual ); + } + /** * Tests generating font size values, including fluid formulae, from fontSizes preset. * diff --git a/tests/phpunit/tests/blocks/wpBlockType.php b/tests/phpunit/tests/blocks/wpBlockType.php index a73efa8ce8a7d..0649b47819b59 100644 --- a/tests/phpunit/tests/blocks/wpBlockType.php +++ b/tests/phpunit/tests/blocks/wpBlockType.php @@ -586,7 +586,7 @@ public function test_variations_callback_happens_only_once() { /** * Test filter function for get_block_type_variations filter. * - * @param array $variations Block variations before filter. + * @param array $variations Block variations before filter. * @param WP_Block_Type $block_type Block type. * * @return array Block variations after filter. @@ -652,4 +652,305 @@ public function mock_variation_callback() { array( 'name' => 'var2' ), ); } + + /** + * Tests stabilization of experimental block supports. + * + * @ticket 61728 + * @covers WP_Block_Type::stabilize_supports + */ + public function test_stabilize_block_supports() { + $reflection = new ReflectionClass( WP_Block_Type::class ); + $method = $reflection->getMethod( 'stabilize_supports' ); + $method->setAccessible( true ); + $block_type = new WP_Block_Type( 'test/block' ); + + $supports = array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'width' => true, + 'style' => true, + '__experimentalSkipSerialization' => true, + '__experimentalDefaultControls' => array( + 'color' => true, + 'radius' => true, + 'width' => true, + ), + ), + 'typography' => array( + 'fontSize' => true, + '__experimentalFontFamily' => true, + '__experimentalFontStyle' => true, + '__experimentalFontWeight' => true, + '__experimentalLetterSpacing' => true, + '__experimentalTextTransform' => true, + '__experimentalTextDecoration' => true, + '__experimentalSkipSerialization' => true, + '__experimentalDefaultControls' => array( + 'fontFamily' => true, + 'fontSize' => true, + 'letterSpacing' => true, + ), + ), + ); + + $actual = $method->invoke( $block_type, $supports ); + $expected = array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'width' => true, + 'style' => true, + 'skipSerialization' => true, + 'defaultControls' => array( + 'color' => true, + 'radius' => true, + 'width' => true, + ), + ), + 'typography' => array( + 'fontSize' => true, + 'fontFamily' => true, + 'fontStyle' => true, + 'fontWeight' => true, + 'letterSpacing' => true, + 'textTransform' => true, + 'textDecoration' => true, + 'skipSerialization' => true, + 'defaultControls' => array( + 'fontFamily' => true, + 'fontSize' => true, + 'letterSpacing' => true, + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $actual ); + } + + /** + * Tests that experimental and stable block supports can be merged based on definition order. + * + * If a plugin or theme overrides the support config with the `register_block_type_args` + * filter, both experimental and stable configs may be present. In that case, use the + * order keys are defined in to determine the final value: + * - If config is an array, merge the arrays in their order of definition. + * - If config is not an array, use the value defined last. + * + * The reason for preferring the last defined key is that after filters are applied, + * the last inserted key is likely the most up-to-date value. We cannot determine + * with certainty which value was "last modified" so the insertion order is the + * best guess. + * + * @ticket 61728 + * @covers WP_Block_Type::stabilize_supports + */ + public function test_stabilize_block_supports_merges_by_order() { + $reflection = new ReflectionClass( WP_Block_Type::class ); + $method = $reflection->getMethod( 'stabilize_supports' ); + $method->setAccessible( true ); + $block_type = new WP_Block_Type( 'test/block' ); + + // Test with experimental config first. + $experimental_first = array( + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => false, + 'defaultControls' => array( + 'color' => true, + 'radius' => false, + 'style' => true, + 'width' => true, + ), + ), + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => false, + 'width' => true, + 'skipSerialization' => true, + 'defaultControls' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + ); + + $actual = $method->invoke( $block_type, $experimental_first ); + $expected = array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => false, + 'width' => true, + 'skipSerialization' => true, + 'defaultControls' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $actual, 'Merged stabilized block support config does not match when experimental keys are first.' ); + + // Test with stable config first. + $stable_first = array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => false, + 'width' => true, + 'skipSerialization' => true, + 'defaultControls' => array( + 'color' => false, + 'radius' => false, + 'style' => false, + 'width' => false, + ), + ), + '__experimentalBorder' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => false, + 'defaultControls' => array( + 'color' => true, + 'radius' => false, + 'style' => true, + 'width' => true, + ), + ), + ); + + $actual = $method->invoke( $block_type, $stable_first ); + $expected = array( + 'border' => array( + 'color' => true, + 'radius' => true, + 'style' => true, + 'width' => true, + 'skipSerialization' => false, + 'defaultControls' => array( + 'color' => true, + 'radius' => false, + 'style' => true, + 'width' => true, + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $actual, 'Merged stabilized block support config does not match when stable keys are first.' ); + } + + /** + * Tests that boolean block support configurations are handled correctly. + * + * @ticket 61728 + * @covers WP_Block_Type::stabilize_supports + * @dataProvider data_boolean_block_supports + * + * @param array $supports The supports configuration to test. + * @param boolean|array $expected_value The expected final border support value. + */ + public function test_should_handle_boolean_block_supports( $supports, $expected_value ) { + $reflection = new ReflectionClass( WP_Block_Type::class ); + $method = $reflection->getMethod( 'stabilize_supports' ); + $method->setAccessible( true ); + $block_type = new WP_Block_Type( 'test/block' ); + + $actual = $method->invoke( $block_type, $supports ); + + $this->assertSame( $expected_value, $actual['border'] ); + } + + /** + * Data provider for boolean block support tests. + * + * @return array Test parameters. + */ + public function data_boolean_block_supports() { + return array( + 'experimental true only' => array( + array( + '__experimentalBorder' => true, + ), + true, + ), + 'experimental false only' => array( + array( + '__experimentalBorder' => false, + ), + false, + ), + 'experimental true before stable false' => array( + array( + '__experimentalBorder' => true, + 'border' => false, + ), + false, + ), + 'stable true before experimental false' => array( + array( + 'border' => true, + '__experimentalBorder' => false, + ), + false, + ), + 'experimental array before stable boolean' => array( + array( + '__experimentalBorder' => array( + 'color' => true, + 'width' => true, + ), + 'border' => false, + ), + false, + ), + 'stable array before experimental boolean' => array( + array( + 'border' => array( + 'color' => true, + 'width' => true, + ), + '__experimentalBorder' => true, + ), + true, + ), + 'experimental boolean before stable array' => array( + array( + '__experimentalBorder' => true, + 'border' => array( + 'color' => true, + 'width' => true, + ), + ), + array( + 'color' => true, + 'width' => true, + ), + ), + 'stable boolean before experimental array' => array( + array( + 'border' => false, + '__experimentalBorder' => array( + 'color' => true, + 'width' => true, + ), + ), + array( + 'color' => true, + 'width' => true, + ), + ), + ); + } }