Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/e2e-tests/plugins/interactive-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
* @package gutenberg-test-interactive-blocks
*/

/*
* Simulates the WordPress core feature that marks the style assets managed by
* the Interactivity API router, which is not available in the WordPress
* version bundled with `wp-env`.
*/
require_once __DIR__ . '/interactive-blocks/router-styles-managed/router-managed-styles.php';

add_action(
'init',
function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "test/router-styles-managed",
"title": "E2E Interactivity tests - router styles - Managed wrapper",
"category": "text",
"icon": "heart",
"description": "",
"supports": {
"interactivity": true
},
"textdomain": "e2e-interactivity",
"viewScriptModule": "file:./view.js",
"viewStyle": "file:./style.css",
"render": "file:./render.php"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
* HTML for testing the iAPI's style assets management when the server marks
* its style assets with the `data-wp-router-managed` attribute.
*
* The marking is done in `router-managed-styles.php`, which simulates the
* WordPress core feature that is not available in the WordPress version
* bundled with `wp-env`.
*
* @package gutenberg-test-interactive-blocks
*
* @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
*/

add_action(
'wp_enqueue_scripts',
function () {
wp_enqueue_style(
'managed-wrapper-styles-from-link',
plugin_dir_url( __FILE__ ) . 'style-from-link.css',
array()
);

$custom_css = '
.managed-from-inline {
color: rgb(128, 0, 128);
}
';

wp_register_style( 'test-router-styles-managed', false );
wp_enqueue_style( 'test-router-styles-managed' );
wp_add_inline_style( 'test-router-styles-managed', $custom_css );
}
);

$links = isset( $attributes['links'] ) ? $attributes['links'] : array();
$injected_style_url = plugin_dir_url( __FILE__ ) . 'style-injected.css';
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
<!-- These get colored when the corresponding inner block is present. -->
<fieldset>
<legend>Styles from inner block styles</legend>
<p data-testid="red" class="red">Red</p>
<p data-testid="green" class="green">Green</p>
<p data-testid="blue" class="blue">Blue</p>
</fieldset>

<!-- These are colored by the style assets of this very block, present in every page containing it. -->
<fieldset>
<legend>Styles from this block's own assets</legend>
<p data-testid="managed-from-link" class="managed-from-link">Managed from link</p>
<p data-testid="managed-from-inline" class="managed-from-inline">Managed from inline</p>
</fieldset>

<!-- These are only colored by style assets injected by the client. -->
<fieldset>
<legend>Targets for client-injected styles</legend>
<p data-testid="injected" class="injected-target">Injected</p>
<p data-testid="injected-from-link" class="injected-from-link-target">Injected from link</p>
</fieldset>

<!-- URL of a style sheet the server never enqueues, so tests can inject it from the client. -->
<div
data-testid="injected-style-sheet"
data-url="<?php echo esc_url( $injected_style_url ); ?>"
hidden
></div>

<!-- Links to pages with different blocks combination. -->
<nav data-wp-interactive="test/router-styles-managed">
<?php foreach ( $links as $label => $link ) : ?>
<a
data-testid="link <?php echo $label; ?>"
data-wp-on--click="actions.navigate"
href="<?php echo $link; ?>"
>
<?php echo $label; ?>
</a>
<?php endforeach; ?>
</nav>

<!-- HTML updated on navigation. -->
<div
data-wp-interactive="test/router-styles-managed"
data-wp-router-region="router-styles"
>
<?php echo $content; ?>
</div>

<!-- Flag to check whether hydration has occurred. -->
<div
data-testid="hydrated"
data-wp-interactive="test/router-styles-managed"
data-wp-bind--hidden="!state.hydrated"
data-wp-init="callbacks.setHydrated"
hidden
>
Hydrated
</div>

<!-- Text to check whether a navigation was client-side. -->
<div
data-testid="client-side navigation"
data-wp-interactive="test/router-styles-managed"
data-wp-bind--hidden="!state.clientSideNavigation"
hidden
>
Client-side navigation
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Simulates the server-side marking of style assets managed by the
* Interactivity API router.
*
* WordPress core marks every `<style>` and `<link rel="stylesheet">` element
* it renders with an empty `data-wp-router-managed` attribute, so the router
* can tell them apart from the style assets injected at runtime by scripts
* unaware of it. That feature is not available in the WordPress version
* bundled with `wp-env`, so this file reproduces it with an output buffer for
* those pages containing the `test/router-styles-managed` block.
*
* @package gutenberg-test-interactive-blocks
*/

/**
* Adds the `data-wp-router-managed` attribute to every style asset in the
* passed HTML.
*
* Style elements inside `<noscript>` are skipped, as they are not rendered by
* the server-side implementation either.
*
* @param string $html Full HTML of the page.
* @return string HTML with all the style assets marked.
*/
function gutenberg_test_mark_router_managed_styles( $html ) {
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $html;
}

$processor = new WP_HTML_Tag_Processor( $html );
$in_noscript = false;

while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag = $processor->get_tag();

if ( 'NOSCRIPT' === $tag ) {
$in_noscript = ! $processor->is_tag_closer();
continue;
}

if ( $in_noscript || $processor->is_tag_closer() ) {
continue;
}

if ( 'STYLE' === $tag ) {
$processor->set_attribute( 'data-wp-router-managed', true );
} elseif ( 'LINK' === $tag ) {
$rel = $processor->get_attribute( 'rel' );
if (
is_string( $rel ) &&
in_array(
'stylesheet',
preg_split( '/\s+/', strtolower( trim( $rel ) ) ),
true
)
) {
$processor->set_attribute( 'data-wp-router-managed', true );
}
}
}

return $processor->get_updated_html();
}

add_action(
'template_redirect',
function () {
if ( ! is_singular() ) {
return;
}

$post = get_post();

if (
! $post instanceof WP_Post ||
! has_block( 'test/router-styles-managed', $post )
) {
return;
}

/*
* The buffer is not explicitly ended. PHP flushes it at the end of the
* request, calling the passed callback with the full page HTML.
*/
ob_start( 'gutenberg_test_mark_router_managed_styles' );
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.managed-from-link {
color: rgb(0, 128, 128);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* This style sheet is never enqueued by the server. It is only meant to be
* injected by the client during the tests, to simulate a script that adds
* style assets the router doesn't know about.
*/
.injected-from-link-target {
color: rgb(0, 100, 200);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.wp-block-test-router-styles-managed {
color: rgb(160, 12, 60);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php return array(
'dependencies' => array(
'@wordpress/interactivity',
array(
'id' => '@wordpress/interactivity-router',
'import' => 'dynamic',
),
),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { store, withSyncEvent } from '@wordpress/interactivity';

const { state } = store( 'test/router-styles-managed', {
state: {
clientSideNavigation: false,
hydrated: false,
},
actions: {
navigate: withSyncEvent( function* ( e ) {
e.preventDefault();
state.clientSideNavigation = false;
const { actions } = yield import(
'@wordpress/interactivity-router'
);
yield actions.navigate( e.target.href );
state.clientSideNavigation = true;
} ),
},
callbacks: {
setHydrated() {
state.hydrated = true;
},
},
} );
3 changes: 3 additions & 0 deletions packages/interactivity-router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Enhancements

- Preserve style assets injected by client scripts during client-side navigation on pages marked with the `data-wp-router-managed` attribute. ([#76031](https://github.com/WordPress/gutenberg/discussions/76031))

## 2.52.0 (2026-07-29)

Expand Down
Loading
Loading