Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<section>
<h2>Region 1</h2>
<div
data-testid="region-1"
data-wp-interactive="router-regions"
data-wp-router-region="region-1"
>
Expand Down Expand Up @@ -54,8 +55,11 @@
<section>
<h2>Region 2</h2>
<div
data-testid="region-2"
data-wp-key="region-2"
data-wp-interactive="router-regions"
data-wp-router-region="region-2"
data-wp-run="callbacks.nope"
>
<p
data-testid="region-2-text"
Expand Down Expand Up @@ -113,6 +117,7 @@
<!-- Router region inside data-wp-interactive -->
<div
data-testid="valid-inside-interactive"
data-wp-key="valid-inside-interactive"
data-wp-interactive="router-regions"
data-wp-router-region="valid-inside-interactive"
data-wp-context='{ "counter": { "value": 0 } }'
Expand All @@ -131,6 +136,7 @@
<!-- Router region inside data-wp-router-region -->
<div
data-testid="valid-inside-router-region"
data-wp-key="valid-inside-router-region"
data-wp-interactive="router-regions"
data-wp-router-region="valid-inside-router-region"
data-wp-context='{ "counter": { "value": 0 } }'
Expand Down Expand Up @@ -192,6 +198,7 @@
data-wp-interactive="router-regions"
data-wp-router-region='$region_data'
data-testid="$region_id"
data-wp-key="$region_id"
$has_directives
>
<div $context_data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,8 @@ const { state } = store( 'router-regions', {
init() {
state.initCount += 1;
},
nope() {
// This function does nothing.
},
},
} );
4 changes: 4 additions & 0 deletions packages/interactivity-router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Bug Fixes

- Prevent router regions with data-wp-key from being recreated on navigation. ([#74750](https://github.com/WordPress/gutenberg/pull/74750))

## 2.38.0 (2026-01-16)

## 2.36.0 (2025-11-26)
Expand Down
4 changes: 2 additions & 2 deletions packages/interactivity-router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {
populateServerData,
batch,
routerRegions,
cloneElement,
h: createElement,
navigationSignal,
} = privateApis(
'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WordPress.'
Expand Down Expand Up @@ -117,7 +117,7 @@ const cloneRouterRegionContent = ( vdom: any ) => {
: allPriorityLevels;

return priorityLevels.length > 0
? cloneElement( vdom, {
? createElement( vdom.type, {
...vdom.props,
priorityLevels,
} )
Expand Down
116 changes: 116 additions & 0 deletions test/e2e/specs/interactivity/router-regions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ test.describe( 'Router regions', () => {

test( 'should preserve state across pages', async ( { page } ) => {
const counter = page.getByTestId( 'state-counter' );

await expect( counter ).toHaveText( '0' );

await counter.click( { clickCount: 3, delay: 50 } );
Expand Down Expand Up @@ -477,4 +478,119 @@ test.describe( 'Router regions', () => {
await expect( region7 ).toBeVisible();
await expect( region8 ).toBeVisible();
} );

test( 'should be preserved on first navigation without `data-wp-key`', async ( {
page,
} ) => {
const region1 = page.getByTestId( 'region-1' );
const region1Text = page.getByTestId( 'region-1-text' );
await expect( region1Text ).toHaveText( 'hydrated' );

// Adds a tag to know whether the counter element was replaced.
await region1.evaluate( ( ref ) => {
if ( ref instanceof HTMLElement ) {
ref.dataset.tag = 'region-1';
}
} );

// Navigate to the next page.
await page.getByTestId( 'next' ).click();
await expect( page ).toHaveTitle(
'router regions – page 2 – gutenberg'
);

// The region element should retain the same attributes when it doesn't change.
await expect( region1 ).toHaveAttribute( 'data-tag', 'region-1' );
} );

test( 'should be preserved on first navigation with `data-wp-key` and other directives ', async ( {
page,
} ) => {
const region2 = page.getByTestId( 'region-2' );
const validInsideInteractive = page.getByTestId(
'valid-inside-interactive'
);
const validInsideRouterRegion = page.getByTestId(
'valid-inside-router-region'
);

// Add tags to know whether the region elements were replaced.
await region2.evaluate( ( el ) => {
el.dataset.tag = 'region-2';
} );
await validInsideInteractive.evaluate( ( el ) => {
el.dataset.tag = 'valid-inside-interactive';
} );
await validInsideRouterRegion.evaluate( ( el ) => {
el.dataset.tag = 'valid-inside-router-region';
} );

// Navigate to the next page.
await page.getByTestId( 'next' ).click();
await expect( page ).toHaveTitle(
'router regions – page 2 – gutenberg'
);

// Regions with `data-wp-key` and other directives should not be recreated.
await expect( region2 ).toHaveAttribute( 'data-tag', 'region-2' );
await expect( validInsideInteractive ).toHaveAttribute(
'data-tag',
'valid-inside-interactive'
);
await expect( validInsideRouterRegion ).toHaveAttribute(
'data-tag',
'valid-inside-router-region'
);
} );

test( 'should be preserved on first navigation with `data-wp-key` and `attachTo`', async ( {
page,
interactivityUtils: utils,
} ) => {
await page.goto(
utils.getLink( 'router regions - page 1 - attachTo' )
);

const bodyLocator = page.locator( 'body' );
const regionsLocator = page.locator( '#regions-with-attach-to' );

const region3 = bodyLocator.getByTestId( 'region3' );
const region4 = regionsLocator.getByTestId( 'region4' );
const region5 = bodyLocator.getByTestId( 'region5' );
const region6 = regionsLocator.getByTestId( 'region6' );

const regions: Record< string, Locator > = {
region3,
region4,
region5,
region6,
};

// The text of this element is used to check a navigation is completed.
const region1Ssr = page.getByTestId( 'region-1-ssr' );

await expect( region1Ssr ).toHaveText( 'content from page 1' );

// Navigate to "Page attachTo 1".
await page.getByTestId( 'next' ).click();
await expect( region1Ssr ).toHaveText( 'content from page attachTo1' );

// Add tags to know whether the region elements were replaced.
for ( const regionId in regions ) {
const region = regions[ regionId ];
await region.evaluate( ( el, id ) => {
el.dataset.tag = id;
}, regionId );
}

// Navigate to "Page attachTo 2".
await page.getByTestId( 'next' ).click();
await expect( region1Ssr ).toHaveText( 'content from page attachTo2' );

// Regions with `data-wp-key` and other directives should not be recreated.
for ( const regionId in regions ) {
const region = regions[ regionId ];
await expect( region ).toHaveAttribute( 'data-tag', regionId );
}
} );
} );
Loading