diff --git a/src/wp-includes/block-supports/anchor.php b/src/wp-includes/block-supports/anchor.php new file mode 100644 index 0000000000000..651fcbde2a137 --- /dev/null +++ b/src/wp-includes/block-supports/anchor.php @@ -0,0 +1,76 @@ +attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'anchor', $block_type->attributes ) ) { + $block_type->attributes['anchor'] = array( + 'type' => 'string', + ); + } +} + +/** + * Add the anchor id to the output. + * + * @since 7.0.0 + * @access private + * + * @param WP_Block_Type $block_type Block Type. + * @param array $block_attributes Block attributes. + * + * @return array Block anchor id. + */ +function wp_apply_anchor_support( $block_type, $block_attributes ) { + if ( ! $block_attributes ) { + return array(); + } + + $has_anchor_support = block_has_support( $block_type, array( 'anchor' ), false ); + if ( ! $has_anchor_support ) { + return array(); + } + + $has_anchor = array_key_exists( 'anchor', $block_attributes ); + if ( ! $has_anchor ) { + return array(); + } + + $anchor_value = (string) $block_attributes['anchor']; + if ( '' === $anchor_value ) { + return array(); + } + + return array( 'id' => $block_attributes['anchor'] ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'anchor', + array( + 'register_attribute' => 'wp_register_anchor_support', + 'apply' => 'wp_apply_anchor_support', + ) +); diff --git a/src/wp-settings.php b/src/wp-settings.php index 8ad02ffe8fd8b..45f96ace09a1c 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -404,6 +404,7 @@ require ABSPATH . WPINC . '/block-supports/background.php'; require ABSPATH . WPINC . '/block-supports/block-style-variations.php'; require ABSPATH . WPINC . '/block-supports/aria-label.php'; +require ABSPATH . WPINC . '/block-supports/anchor.php'; require ABSPATH . WPINC . '/block-supports/block-visibility.php'; require ABSPATH . WPINC . '/style-engine.php'; require ABSPATH . WPINC . '/style-engine/class-wp-style-engine.php'; diff --git a/tests/phpunit/tests/block-supports/anchor..php b/tests/phpunit/tests/block-supports/anchor..php new file mode 100644 index 0000000000000..7f01f041cdf78 --- /dev/null +++ b/tests/phpunit/tests/block-supports/anchor..php @@ -0,0 +1,135 @@ +test_block_name = null; + } + + public function tear_down() { + unregister_block_type( $this->test_block_name ); + $this->test_block_name = null; + parent::tear_down(); + } + + /** + * Registers a new block for testing anchor support. + * + * @param string $block_name Name for the test block. + * @param array $supports Array defining block support configuration. + * + * @return WP_Block_Type The block type for the newly registered test block. + */ + private function register_anchor_block_with_support( $block_name, $supports = array() ) { + $this->test_block_name = $block_name; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'supports' => $supports, + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + + return $registry->get_registered( $this->test_block_name ); + } + + /** + * Tests that anchor block support works as expected. + * + * @dataProvider data_anchor_block_support + * + * @param boolean|array $support Anchor block support configuration. + * @param string $value Anchor value for attribute object. + * @param array $expected Expected anchor block support output. + */ + public function test_wp_apply_anchor_support( $support, $value, $expected ) { + $block_type = self::register_anchor_block_with_support( + 'test/anchor-block', + array( 'anchor' => $support ) + ); + $block_attrs = array( 'anchor' => $value ); + $actual = wp_apply_anchor_support( $block_type, $block_attrs ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_anchor_block_support() { + return array( + 'anchor id attribute is applied' => array( + 'support' => true, + 'value' => 'my-anchor', + 'expected' => array( 'id' => 'my-anchor' ), + ), + 'anchor id attribute is not applied if block does not support it' => array( + 'support' => false, + 'value' => 'my-anchor', + 'expected' => array(), + ), + 'empty anchor value returns empty array' => array( + 'support' => true, + 'value' => '', + 'expected' => array(), + ), + 'null anchor value returns empty array' => array( + 'support' => true, + 'value' => null, + 'expected' => array(), + ), + 'whitespace-only anchor value is applied' => array( + 'support' => true, + 'value' => ' ', + 'expected' => array( 'id' => ' ' ), + ), + 'anchor with hyphen and numbers' => array( + 'support' => true, + 'value' => 'section-123', + 'expected' => array( 'id' => 'section-123' ), + ), + 'anchor with underscore' => array( + 'support' => true, + 'value' => 'my_anchor_id', + 'expected' => array( 'id' => 'my_anchor_id' ), + ), + 'anchor with colon (valid in HTML5)' => array( + 'support' => true, + 'value' => 'my:anchor', + 'expected' => array( 'id' => 'my:anchor' ), + ), + 'anchor with period (valid in HTML5)' => array( + 'support' => true, + 'value' => 'my.anchor', + 'expected' => array( 'id' => 'my.anchor' ), + ), + 'numeric anchor value' => array( + 'support' => true, + 'value' => '123', + 'expected' => array( 'id' => '123' ), + ), + 'zero string anchor value is applied' => array( + 'support' => true, + 'value' => '0', + 'expected' => array( 'id' => '0' ), + ), + 'false value is treated as empty' => array( + 'support' => true, + 'value' => false, + 'expected' => array(), + ), + ); + } +}