Skip to content

Commit

Permalink
Interactivity: Export withScope() and allow to use it with asynchro…
Browse files Browse the repository at this point in the history
…nous operations. (#58013)

* Initial commit

* Commit to change branches

* Support generators in with scope

* Update changelog
  • Loading branch information
cbravobernal committed Jan 22, 2024
1 parent c35e95a commit f4c34a4
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 6 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/with-scope",
"title": "E2E Interactivity tests - with scope",
"category": "text",
"icon": "heart",
"description": "",
"supports": {
"interactivity": true
},
"textdomain": "e2e-interactivity",
"viewScript": "with-scope-view",
"render": "file:./render.php"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* HTML for testing the directive `data-wp-on`.
*
* @package gutenberg-test-interactive-blocks
*/

gutenberg_enqueue_module( 'with-scope-view' );
?>

<div data-wp-interactive='{ "namespace": "with-scope" }' data-wp-context='{"asyncCounter": 0, "syncCounter": 0}' data-wp-init--a='callbacks.asyncInit' data-wp-init--b='callbacks.syncInit'>
<p data-wp-text="context.asyncCounter" data-testid="asyncCounter">0</p>
<p data-wp-text="context.syncCounter" data-testid="syncCounter">0</p>
</div>
20 changes: 20 additions & 0 deletions packages/e2e-tests/plugins/interactive-blocks/with-scope/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* WordPress dependencies
*/
import { store, getContext, withScope } from '@wordpress/interactivity';

store( 'with-scope', {
callbacks: {
asyncInit: () => {
setTimeout( withScope(function*() {
yield new Promise(resolve => setTimeout(resolve, 1));
const context = getContext()
context.asyncCounter += 1;
}, 1 ));
},
syncInit: () => {
const context = getContext()
context.syncCounter += 1;
}
},
} );
7 changes: 4 additions & 3 deletions packages/interactivity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
### Enhancements

- Prevent the usage of Preact components in `wp-text`. ([#57879](https://github.com/WordPress/gutenberg/pull/57879))
- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([57891](https://github.com/WordPress/gutenberg/pull/57891))
- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([#57891](https://github.com/WordPress/gutenberg/pull/57891))
- Export `withScope()` and allow to use it with asynchronous operations. ([#58013](https://github.com/WordPress/gutenberg/pull/58013))

### New Features

- Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([57805](https://github.com/WordPress/gutenberg/pull/57805))
- Add `wp-data-on-window` and `wp-data-on-document` directives. ([57931](https://github.com/WordPress/gutenberg/pull/57931))
- Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([#57805](https://github.com/WordPress/gutenberg/pull/57805))
- Add `wp-data-on-window` and `wp-data-on-document` directives. ([#57931](https://github.com/WordPress/gutenberg/pull/57931))

### Breaking Changes

Expand Down
1 change: 1 addition & 0 deletions packages/interactivity/src/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export default () => {
// data-wp-init--[name]
directive( 'init', ( { directives: { init }, evaluate } ) => {
init.forEach( ( entry ) => {
// TODO: Replace with useEffect to prevent unneeded scopes.
useInit( () => evaluate( entry ) );
} );
} );
Expand Down
2 changes: 2 additions & 0 deletions packages/interactivity/src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export const resetScope = () => {
scopeStack.pop();
};

export const getNamespace = () => namespaceStack.slice( -1 )[ 0 ];

export const setNamespace = ( namespace: string ) => {
namespaceStack.push( namespace );
};
Expand Down
3 changes: 2 additions & 1 deletion packages/interactivity/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import registerDirectives from './directives';
import { init } from './router';

export { store } from './store';
export { directive, getContext, getElement } from './hooks';
export { directive, getContext, getElement, getNamespace } from './hooks';
export { navigate, prefetch } from './router';
export {
withScope,
useWatch,
useInit,
useEffect,
Expand Down
38 changes: 36 additions & 2 deletions packages/interactivity/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { effect } from '@preact/signals';
/**
* Internal dependencies
*/
import { getScope, setScope, resetScope } from './hooks';
import {
getScope,
setScope,
resetScope,
getNamespace,
setNamespace,
resetNamespace,
} from './hooks';

const afterNextFrame = ( callback ) => {
return new Promise( ( resolve ) => {
Expand Down Expand Up @@ -71,13 +78,40 @@ export function useSignalEffect( callback ) {
* @param {Function} func The passed function.
* @return {Function} The wrapped function.
*/
const withScope = ( func ) => {
export const withScope = ( func ) => {
const scope = getScope();
const ns = getNamespace();
if ( func?.constructor?.name === 'GeneratorFunction' ) {
return async ( ...args ) => {
const gen = func( ...args );
let value;
let it;
while ( true ) {
setNamespace( ns );
setScope( scope );
try {
it = gen.next( value );
} finally {
resetNamespace();
resetScope();
}
try {
value = await it.value;
} catch ( e ) {
gen.throw( e );
}
if ( it.done ) break;
}
return value;
};
}
return ( ...args ) => {
setNamespace( ns );
setScope( scope );
try {
return func( ...args );
} finally {
resetNamespace();
resetScope();
}
};
Expand Down
27 changes: 27 additions & 0 deletions test/e2e/specs/interactivity/with-scope.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

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

test( 'directives using withScope should work with async and sync functions', async ( {
page,
} ) => {
const asyncCounter = page.getByTestId( 'asyncCounter' );
await expect( asyncCounter ).toHaveText( '1' );
const syncCounter = page.getByTestId( 'syncCounter' );
await expect( syncCounter ).toHaveText( '1' );
} );
} );

0 comments on commit f4c34a4

Please sign in to comment.