Skip to content

Commit

Permalink
Server directive processing: Process only root blocks (#55739)
Browse files Browse the repository at this point in the history
* Test that we can identify the real root blocks

* Add some tests, fix yoda condition and linting issues

* Refactor to static function filter

* Small nitpicks

* Update test with a pattern

* Refactor and use test for counting root blocks

* Simplify tests

* Fix e2e tests

---------

Co-authored-by: Luis Herranz <[email protected]>
  • Loading branch information
cbravobernal and luisherranz authored Nov 8, 2023
1 parent fd42f04 commit f8cfba6
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 56 deletions.
84 changes: 48 additions & 36 deletions lib/experimental/interactivity-api/directive-processing.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,66 @@
*/

/**
* Process directives in each block.
*
* @param string $block_content The block content.
* @param array $block The full block.
*
* @return string Filtered block content.
*/
function gutenberg_interactivity_process_directives_in_root_blocks( $block_content, $block ) {
// Don't process inner blocks or root blocks that don't contain directives.
if ( ! WP_Directive_Processor::is_root_block( $block ) || strpos( $block_content, 'data-wp-' ) === false ) {
return $block_content;
}

// TODO: Add some directive/components registration mechanism.
$directives = array(
'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
);

$tags = new WP_Directive_Processor( $block_content );
$tags = gutenberg_interactivity_process_directives( $tags, 'data-wp-', $directives );
return $tags->get_updated_html();
}
add_filter( 'render_block', 'gutenberg_interactivity_process_directives_in_root_blocks', 10, 2 );

/**
* Mark the inner blocks with a temporary property so we can discard them later,
* and process only the root blocks.
* Process the Interactivity API directives using the root blocks of the
* outermost rendering, ignoring the root blocks of inner blocks like Patterns,
* Template Parts or Content.
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
* @param array $parent_block The parent block.
*
* @return array The parsed block.
*/
function gutenberg_interactivity_mark_inner_blocks( $parsed_block, $source_block, $parent_block ) {
if ( ! isset( $parent_block ) ) {
function gutenberg_interactivity_process_directives( $parsed_block, $source_block, $parent_block ) {
static $is_inside_root_block = false;
static $process_directives_in_root_blocks = null;

if ( ! isset( $process_directives_in_root_blocks ) ) {
/**
* Process directives in each root block.
*
* @param string $block_content The block content.
* @param array $block The full block.
*
* @return string Filtered block content.
*/
$process_directives_in_root_blocks = static function ( $block_content, $block ) use ( &$is_inside_root_block ) {

if ( WP_Directive_Processor::is_root_block( $block ) ) {

$directives = array(
'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
);

$tags = new WP_Directive_Processor( $block_content );
$tags = gutenberg_interactivity_process_rendered_html( $tags, 'data-wp-', $directives );
$is_inside_root_block = false;
return $tags->get_updated_html();

}

return $block_content;
};
add_filter( 'render_block', $process_directives_in_root_blocks, 10, 2 );
}

if ( ! isset( $parent_block ) && ! $is_inside_root_block ) {
WP_Directive_Processor::add_root_block( $parsed_block );
$is_inside_root_block = true;
}

return $parsed_block;
}
add_filter( 'render_block_data', 'gutenberg_interactivity_mark_inner_blocks', 10, 3 );
add_filter( 'render_block_data', 'gutenberg_interactivity_process_directives', 10, 3 );


/**
* Process directives.
* Traverses the HTML searching for Interactivity API directives and processing
* them.
*
* @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor.
* @param string $prefix Attribute prefix.
Expand All @@ -64,7 +76,7 @@ function gutenberg_interactivity_mark_inner_blocks( $parsed_block, $source_block
* @return WP_Directive_Processor The modified instance of the
* WP_Directive_Processor.
*/
function gutenberg_interactivity_process_directives( $tags, $prefix, $directives ) {
function gutenberg_interactivity_process_rendered_html( $tags, $prefix, $directives ) {
$context = new WP_Directive_Context();
$tag_stack = array();

Expand Down
4 changes: 2 additions & 2 deletions packages/e2e-tests/plugins/interactive-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ function () {
// HTML is not correct or malformed.
if ( 'true' === $_GET['disable_directives_ssr'] ) {
remove_filter(
'render_block',
'gutenberg_interactivity_process_directives_in_root_blocks'
'render_block_data',
'gutenberg_interactivity_process_directives'
);
}
}
Expand Down
102 changes: 84 additions & 18 deletions phpunit/experimental/interactivity-api/directive-processing-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ function gutenberg_test_process_directives_helper_increment( $store ) {
}

/**
* Tests for the gutenberg_interactivity_process_directives function.
* Tests for the gutenberg_interactivity_process_rendered_html function.
*
* @group interactivity-api
* @covers gutenberg_interactivity_process_directives
* @covers gutenberg_interactivity_process_rendered_html
*/
class Tests_Process_Directives extends WP_UnitTestCase {
public function test_correctly_call_attribute_directive_processor_on_closing_tag() {
Expand All @@ -40,44 +40,110 @@ public function test_correctly_call_attribute_directive_processor_on_closing_tag
$test_helper = $this->createMock( Helper_Class::class );

$test_helper->expects( $this->exactly( 2 ) )
->method( 'process_foo_test' )
->with(
$this->callback(
function ( $p ) {
return 'DIV' === $p->get_tag() && (
// Either this is a closing tag...
$p->is_tag_closer() ||
// ...or it is an open tag, and has the directive attribute set.
( ! $p->is_tag_closer() && 'abc' === $p->get_attribute( 'foo-test' ) )
);
}
)
);
->method( 'process_foo_test' )
->with(
$this->callback(
function ( $p ) {
return 'DIV' === $p->get_tag() && (
// Either this is a closing tag...
$p->is_tag_closer() ||
// ...or it is an open tag, and has the directive attribute set.
( ! $p->is_tag_closer() && 'abc' === $p->get_attribute( 'foo-test' ) )
);
}
)
);

$directives = array(
'foo-test' => array( $test_helper, 'process_foo_test' ),
);

$markup = '<div>Example: <div foo-test="abc"><img><span>This is a test></span><div>Here is a nested div</div></div></div>';
$tags = new WP_HTML_Tag_Processor( $markup );
gutenberg_interactivity_process_directives( $tags, 'foo-', $directives );
gutenberg_interactivity_process_rendered_html( $tags, 'foo-', $directives );
}

public function test_directives_with_double_hyphen_processed_correctly() {
$test_helper = $this->createMock( Helper_Class::class );
$test_helper->expects( $this->atLeastOnce() )
->method( 'process_foo_test' );
->method( 'process_foo_test' );

$directives = array(
'foo-test' => array( $test_helper, 'process_foo_test' ),
);

$markup = '<div foo-test--value="abc"></div>';
$tags = new WP_HTML_Tag_Processor( $markup );
gutenberg_interactivity_process_directives( $tags, 'foo-', $directives );
gutenberg_interactivity_process_rendered_html( $tags, 'foo-', $directives );
}

public function test_interactivity_process_directives_in_root_blocks() {
$pattern_content =
'<!-- wp:paragraph -->' .
'<p>Pattern Content Block 1</p>' .
'<!-- /wp:paragraph -->' .
'<!-- wp:paragraph -->' .
'<p>Pattern Content Block 2</p>' .
'<!-- /wp:paragraph -->';
register_block_pattern(
'core/interactivity-pattern',
array(
'title' => 'Interactivity Pattern',
'content' => $pattern_content,
)
);

$providers = $this->data_only_root_blocks_are_processed();
foreach ( $providers as $provider ) {
do_blocks( $provider['page_content'] );
$this->assertSame( $provider['root_blocks'], count( WP_Directive_Processor::$root_blocks ) );

}
}

/**
* Data provider .
*
* @return array
**/
public function data_only_root_blocks_are_processed() {

return array(
array(
'root_blocks' => 2,
'page_content' =>
'<!-- wp:quote -->' .
'<blockquote class="wp-block-quote">
<!-- wp:paragraph -->' .
'<p>The XYZ Doohickey Company was founded in 1971, and has been providing' .
'quality doohickeys to the public ever since. Located in Gotham City, XYZ employs' .
'over 2,000 people and does all kinds of awesome things for the Gotham community.</p>' .
'<!-- /wp:paragraph -->
</blockquote>' .
'<!-- /wp:quote -->' .
'<!-- wp:quote -->' .
'<blockquote class="wp-block-quote">
<!-- wp:paragraph -->' .
'<p>The XYZ Doohickey Company was founded in 1971, and has been providing' .
'quality doohickeys to the public ever since. Located in Gotham City, XYZ employs' .
'over 2,000 people and does all kinds of awesome things for the Gotham community.</p>' .
'<!-- /wp:paragraph -->
</blockquote>' .
'<!-- /wp:quote -->',
),
array(
'root_blocks' => 2,
'page_content' =>
'<!-- wp:paragraph -->' .
'<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>' .
'<!-- /wp:paragraph -->' .
'<!-- wp:pattern {"slug":"core/interactivity-pattern"} /-->',
),
);
}
}


/**
* Tests for the gutenberg_interactivity_evaluate_reference function.
*
Expand Down

0 comments on commit f8cfba6

Please sign in to comment.