Skip to content

Commit

Permalink
Interactivity: Restore scope on thrown exception in async callback (#…
Browse files Browse the repository at this point in the history
…59708)

Ensure scope is properly restored when an exception is thrown in a generator callback.

For example, the following block should have the correct context in the catch block:

try {
  yield Promise.reject( '💥' );
} catch ( err ) {
  getContext().error = err;
}

---

Co-authored-by: sirreal <[email protected]>
Co-authored-by: DAreRodz <[email protected]>
Co-authored-by: gziolo <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: anton-vlasenko <[email protected]>
# Conflicts:
#	packages/interactivity/CHANGELOG.md
  • Loading branch information
sirreal authored and getdave committed Mar 11, 2024
1 parent e67cb2f commit 588cc8b
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "test/generator-scope",
"title": "E2E Interactivity tests - generator scope",
"category": "text",
"icon": "heart",
"description": "",
"supports": {
"interactivity": true
},
"textdomain": "e2e-interactivity",
"viewScriptModule": "file:./view.js",
"render": "file:./render.php"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/**
* HTML for testing scope restoration with generators.
*
* @package gutenberg-test-interactive-blocks
*/
?>

<div
data-wp-interactive="test/generator-scope"
<?php echo wp_interactivity_data_wp_context( array( 'result' => '' ) ); ?>
>
<input readonly data-wp-bind--value="context.result" data-testid="result" />
<button type="button" data-wp-on--click="callbacks.resolve" data-testid="resolve">Async resolve</button>
<button type="button" data-wp-on--click="callbacks.reject" data-testid="reject">Async reject</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php return array( 'dependencies' => array( '@wordpress/interactivity' ) );
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* WordPress dependencies
*/
import { store, getContext } from '@wordpress/interactivity';

store( 'test/generator-scope', {
callbacks: {
*resolve() {
try {
getContext().result = yield Promise.resolve( 'ok' );
} catch ( err ) {
getContext().result = err.toString();
}
},
*reject() {
try {
getContext().result = yield Promise.reject( new Error( '😘' ) );
} catch ( err ) {
getContext().result = err.toString();
}
},
},
} );
8 changes: 7 additions & 1 deletion packages/interactivity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. -->

## Unreleased
## 5.3.0 (2024-03-11)

### Bug Fixes

- Ensure scope is restored when catching exceptions thrown in async generator actions. ([#59708](https://github.com/WordPress/gutenberg/pull/59708))

## 5.2.0 (2024-03-06)

### Bug Fixes

Expand Down
7 changes: 6 additions & 1 deletion packages/interactivity/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const handlers = {
const scope = getScope();
const gen: Generator< any > = result( ...args );

let value: any;
let value: unknown;
let it: IteratorResult< any >;

while ( true ) {
Expand All @@ -125,7 +125,12 @@ const handlers = {
try {
value = await it.value;
} catch ( e ) {
setNamespace( ns );
setScope( scope );
gen.throw( e );
} finally {
resetScope();
resetNamespace();
}

if ( it.done ) break;
Expand Down
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<!-- Exclude PHPUnit tests from file and class name sniffs (for Core parity). -->
<rule ref="WordPress.Files.FileName.NotHyphenatedLowercase">
<exclude-pattern>/phpunit/*</exclude-pattern>
<exclude-pattern>*\.asset\.php$</exclude-pattern>
</rule>
<rule ref="PEAR.NamingConventions.ValidClassName.Invalid">
<exclude-pattern>/phpunit/*</exclude-pattern>
Expand Down
31 changes: 31 additions & 0 deletions test/e2e/specs/interactivity/async-actions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

test.describe( 'async actions', () => {
test.beforeAll( async ( { interactivityUtils: utils } ) => {
await utils.activatePlugins();
await utils.addPostWithBlock( 'test/generator-scope' );
} );
test.beforeEach( async ( { interactivityUtils: utils, page } ) => {
await page.goto( utils.getLink( 'test/generator-scope' ) );
} );
test.afterAll( async ( { interactivityUtils: utils } ) => {
await utils.deactivatePlugins();
await utils.deleteAllPosts();
} );

test( 'Promise generator callbacks should restore scope on resolve and reject', async ( {
page,
} ) => {
const resultInput = page.getByTestId( 'result' );
await expect( resultInput ).toHaveValue( '' );

await page.getByTestId( 'resolve' ).click();
await expect( resultInput ).toHaveValue( 'ok' );

await page.getByTestId( 'reject' ).click();
await expect( resultInput ).toHaveValue( 'Error: 😘' );
} );
} );

0 comments on commit 588cc8b

Please sign in to comment.