diff --git a/packages/e2e-tests/plugins/interactive-blocks/generator-scope/block.json b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/block.json new file mode 100644 index 00000000000000..32388d7a641d60 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/block.json @@ -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" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/generator-scope/render.php b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/render.php new file mode 100644 index 00000000000000..c9991e441627d1 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/render.php @@ -0,0 +1,16 @@ + + +
'' ) ); ?> +> + + + +
diff --git a/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.asset.php b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.asset.php new file mode 100644 index 00000000000000..db23afdf657a19 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.asset.php @@ -0,0 +1 @@ + array( '@wordpress/interactivity' ) ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.js b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.js new file mode 100644 index 00000000000000..26d526263cb462 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/generator-scope/view.js @@ -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(); + } + }, + }, +} ); diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index e54d34f518a580..1df9f0b7f1ec6e 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -1,6 +1,12 @@ -## 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 diff --git a/packages/interactivity/src/store.ts b/packages/interactivity/src/store.ts index d160e2fd1c71f0..28600abd3c4dbc 100644 --- a/packages/interactivity/src/store.ts +++ b/packages/interactivity/src/store.ts @@ -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 ) { @@ -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; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 882eaa06b15e02..279ed611bf4bd6 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -77,6 +77,7 @@ /phpunit/* + *\.asset\.php$ /phpunit/* diff --git a/test/e2e/specs/interactivity/async-actions.spec.ts b/test/e2e/specs/interactivity/async-actions.spec.ts new file mode 100644 index 00000000000000..55e44d6cd4a085 --- /dev/null +++ b/test/e2e/specs/interactivity/async-actions.spec.ts @@ -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: 😘' ); + } ); +} );