Skip to content

Commit 08dba8c

Browse files
author
Marcus Karlos
authored
Add e2e test for router hydration race condition
Two Playwright tests asserting that context-driven and state-driven data-wp-on--click bindings are correctly attached after hydration, even when the router is evaluated before hydrateRegions() completes. Both tests fail on the un-patched codebase and pass with initialVdomPromise in place. (see WordPress#75778)
1 parent 4214b0d commit 08dba8c

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
2+
3+
/**
4+
* Covers the race condition from #75778 fixed in #76053.
5+
*
6+
* The test/router-race-condition block declares the router as a STATIC
7+
* dependency in view.asset.php ('import' => 'static'). This places the
8+
* router at the root of the ES module graph so its module-level code
9+
* always runs before hydrateRegions() populates initialVdom -- making
10+
* the race deterministic in all browsers, not just Firefox and Safari.
11+
*
12+
* Without the fix:
13+
* router reads empty initialVdom -> calls toVdom() on full document ->
14+
* every island is added to hydratedIslands -> hydrateRegions() skips
15+
* all islands -> dead DOM, no event listeners attached.
16+
*
17+
* With the fix (initialVdomPromise):
18+
* router awaits the promise before reading initialVdom ->
19+
* hydrateRegions() runs normally -> all bindings attached.
20+
*/
21+
test.describe( 'Interactivity API router hydration race condition', () => {
22+
/**
23+
* context-counter: data-wp-on--click bound to local context.
24+
* If hydration is skipped the click has no effect and the assertion
25+
* times out, reproducing the dead DOM observable in #75778.
26+
*/
27+
test( 'context-driven binding works when router is a static dependency', async ( {
28+
page,
29+
admin,
30+
} ) => {
31+
await admin.createNewPost( { postType: 'page' } );
32+
await admin.insertBlock( 'test/router-race-condition' );
33+
await admin.publishPost();
34+
35+
await page.goto( page.url() );
36+
37+
const button = page.getByTestId( 'context-counter' );
38+
39+
await expect( button ).toHaveText( '0' );
40+
41+
await button.click();
42+
await expect( button ).toHaveText( '1' );
43+
44+
await button.click();
45+
await expect( button ).toHaveText( '2' );
46+
} );
47+
48+
/**
49+
* global-counter: data-wp-on--click bound to global state.
50+
* Tests that state-driven bindings are also correctly hydrated when
51+
* the router is loaded statically.
52+
*/
53+
test( 'global-state binding works when router is a static dependency', async ( {
54+
page,
55+
admin,
56+
} ) => {
57+
await admin.createNewPost( { postType: 'page' } );
58+
await admin.insertBlock( 'test/router-race-condition' );
59+
await admin.publishPost();
60+
61+
await page.goto( page.url() );
62+
63+
const button = page.getByTestId( 'global-counter' );
64+
65+
await expect( button ).toHaveText( '0' );
66+
67+
await button.click();
68+
await expect( button ).toHaveText( '1' );
69+
70+
await button.click();
71+
await expect( button ).toHaveText( '2' );
72+
} );
73+
} );

0 commit comments

Comments
 (0)