diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index db24143ac74044..e6ef5d5329dd1a 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -15,19 +15,6 @@ class WP_Navigation_Block_Renderer { */ private static $has_submenus = false; - /** - * Used to determine which blocks are wrapped in an
  • . - * - * @var array - */ - private static $nav_blocks_wrapped_in_list_item = array( - 'core/navigation-link', - 'core/home-link', - 'core/site-title', - 'core/site-logo', - 'core/navigation-submenu', - ); - /** * Used to determine which blocks need an
  • wrapper. * @@ -117,7 +104,23 @@ private static function is_interactive( $attributes, $inner_blocks ) { * @return bool Returns whether or not a block needs a list item wrapper. */ private static function does_block_need_a_list_item_wrapper( $block ) { - return in_array( $block->name, static::$needs_list_item_wrapper, true ); + + /** + * Filter the list of blocks that need a list item wrapper. + * + * Affords the ability to customize which blocks need a list item wrapper when rendered + * within a core/navigation block. + * This is useful for blocks that are not list items but should be wrapped in a list + * item when used as a child of a navigation block. + * + * @since 6.5.0 + * + * @param array $needs_list_item_wrapper The list of blocks that need a list item wrapper. + * @return array The list of blocks that need a list item wrapper. + */ + $needs_list_item_wrapper = apply_filters( 'block_core_navigation_listable_blocks', static::$needs_list_item_wrapper ); + + return in_array( $block->name, $needs_list_item_wrapper, true ); } /** @@ -161,7 +164,9 @@ private static function get_inner_blocks_html( $attributes, $inner_blocks ) { $is_list_open = false; foreach ( $inner_blocks as $inner_block ) { - $is_list_item = in_array( $inner_block->name, static::$nav_blocks_wrapped_in_list_item, true ); + $inner_block_markup = static::get_markup_for_inner_block( $inner_block ); + $p = new WP_HTML_Tag_Processor( $inner_block_markup ); + $is_list_item = $p->next_tag( 'LI' ); if ( $is_list_item && ! $is_list_open ) { $is_list_open = true; @@ -176,7 +181,7 @@ private static function get_inner_blocks_html( $attributes, $inner_blocks ) { $inner_blocks_html .= ''; } - $inner_blocks_html .= static::get_markup_for_inner_block( $inner_block ); + $inner_blocks_html .= $inner_block_markup; } if ( $is_list_open ) { diff --git a/phpunit/blocks/class-wp-navigation-block-renderer-test.php b/phpunit/blocks/class-wp-navigation-block-renderer-test.php index 6bcf08c179e90b..63efc3bfe35de0 100644 --- a/phpunit/blocks/class-wp-navigation-block-renderer-test.php +++ b/phpunit/blocks/class-wp-navigation-block-renderer-test.php @@ -63,6 +63,102 @@ public function test_gutenberg_get_markup_for_inner_block_site_title() { $this->assertEquals( $expected, $result ); } + /** + * Test that a given block will not be automatically wrapped in a list item by default. + * + * @group navigation-renderer + * + * @covers WP_Navigation_Block_Renderer::get_markup_for_inner_block + */ + public function test_gutenberg_block_not_automatically_wrapped_with_li_tag() { + + register_block_type( + 'testsuite/sample-block', + array( + 'api_version' => 2, + 'render_callback' => function ( $attributes ) { + return '
    ' . $attributes['content'] . '
    '; + }, + ) + ); + + // We are testing the site title block because we manually add list items around it. + $parsed_blocks = parse_blocks( + '' + ); + $parsed_block = $parsed_blocks[0]; + $context = array(); + $heading_block = new WP_Block( $parsed_block, $context ); + + // Setup an empty testing instance of `WP_Navigation_Block_Renderer` and save the original. + $reflection = new ReflectionClass( 'WP_Navigation_Block_Renderer_Gutenberg' ); + $method = $reflection->getMethod( 'get_markup_for_inner_block' ); + $method->setAccessible( true ); + // Invoke the private method. + $result = $method->invoke( $reflection, $heading_block ); + + $expected = '
    Hello World
    '; + $this->assertEquals( $expected, $result ); + + unregister_block_type( 'testsuite/sample-block' ); + } + + /** + * Test that a block can be added to the list of blocks which require a wrapping list item. + * This allows extenders to opt in to the rendering behavior of the Navigation block + * which helps to preserve accessible markup. + * + * @group navigation-renderer + * + * @covers WP_Navigation_Block_Renderer::get_markup_for_inner_block + */ + public function test_gutenberg_block_is_automatically_wrapped_with_li_tag_when_filtered() { + + register_block_type( + 'testsuite/sample-block', + array( + 'api_version' => 2, + 'render_callback' => function ( $attributes ) { + return '
    ' . $attributes['content'] . '
    '; + }, + ) + ); + + $filter_needs_list_item_wrapper_function = static function ( $needs_list_item_wrapper ) { + $needs_list_item_wrapper[] = 'testsuite/sample-block'; + return $needs_list_item_wrapper; + }; + + add_filter( + 'block_core_navigation_listable_blocks', + $filter_needs_list_item_wrapper_function, + 10, + 1 + ); + + // We are testing the site title block because we manually add list items around it. + $parsed_blocks = parse_blocks( + '' + ); + $parsed_block = $parsed_blocks[0]; + $context = array(); + $heading_block = new WP_Block( $parsed_block, $context ); + + // Setup an empty testing instance of `WP_Navigation_Block_Renderer` and save the original. + $reflection = new ReflectionClass( 'WP_Navigation_Block_Renderer_Gutenberg' ); + $method = $reflection->getMethod( 'get_markup_for_inner_block' ); + $method->setAccessible( true ); + // Invoke the private method. + $result = $method->invoke( $reflection, $heading_block ); + + $expected = '
  • Hello World
  • '; + $this->assertEquals( $expected, $result ); + + remove_filter( 'block_core_navigation_listable_blocks', $filter_needs_list_item_wrapper_function, 10, 1 ); + + unregister_block_type( 'testsuite/sample-block' ); + } + /** * Test that the `get_inner_blocks_from_navigation_post` method returns an empty block list for a non-existent post. *