Skip to content

Commit

Permalink
Add more tests and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jsnajdr committed Aug 5, 2024
1 parent 46bdd57 commit 75d3f94
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 42 deletions.
4 changes: 4 additions & 0 deletions packages/hooks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- added new `doAsyncAction` and `applyAsyncFilters` functions to run hooks in async mode ([#64204](https://github.com/WordPress/gutenberg/pull/64204)).

## 4.4.0 (2024-07-24)

## 4.3.0 (2024-07-10)
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ One notable difference between the JS and PHP hooks API is that in the JS versio
- `removeAllActions( 'hookName' )`
- `removeAllFilters( 'hookName' )`
- `doAction( 'hookName', arg1, arg2, moreArgs, finalArg )`
- `doAsyncAction( 'hookName', arg1, arg2, moreArgs, finalArg )`
- `applyFilters( 'hookName', content, arg1, arg2, moreArgs, finalArg )`
- `applyAsyncFilters( 'hookName', content, arg1, arg2, moreArgs, finalArg )`
- `doingAction( 'hookName' )`
- `doingFilter( 'hookName' )`
- `didAction( 'hookName' )`
Expand Down
65 changes: 33 additions & 32 deletions packages/hooks/src/createRunHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,42 +42,43 @@ function createRunHook( hooks, storeKey, returnFirstArg, async ) {
currentIndex: 0,
};

hooksStore.__current.add( hookInfo );

const runner = async
? async function () {
let result = returnFirstArg ? args[ 0 ] : undefined;
async function asyncRunner() {
try {
hooksStore.__current.add( hookInfo );
let result = returnFirstArg ? args[ 0 ] : undefined;
while ( hookInfo.currentIndex < handlers.length ) {
const handler = handlers[ hookInfo.currentIndex ];
result = await handler.callback.apply( null, args );
if ( returnFirstArg ) {
args[ 0 ] = await result; // resolve the potential promise that was passed as arg;
}
while ( hookInfo.currentIndex < handlers.length ) {
const handler = handlers[ hookInfo.currentIndex ];
result = await handler.callback.apply( null, args );
if ( returnFirstArg ) {
args[ 0 ] = result;
}
hookInfo.currentIndex++;
}
return returnFirstArg ? result : undefined;
}
: function () {
let result = returnFirstArg ? args[ 0 ] : undefined;
while ( hookInfo.currentIndex < handlers.length ) {
const handler = handlers[ hookInfo.currentIndex ];
result = handler.callback.apply( null, args );
if ( returnFirstArg ) {
args[ 0 ] = result;
}
hookInfo.currentIndex++;
args[ 0 ] = result;
}
return returnFirstArg ? result : undefined;
};

const rv = runner();
hookInfo.currentIndex++;
}
return returnFirstArg ? result : undefined;
} finally {
hooksStore.__current.delete( hookInfo );
}
}

hooksStore.__current.delete( hookInfo );
function syncRunner() {
try {
hooksStore.__current.add( hookInfo );
let result = returnFirstArg ? args[ 0 ] : undefined;
while ( hookInfo.currentIndex < handlers.length ) {
const handler = handlers[ hookInfo.currentIndex ];
result = handler.callback.apply( null, args );
if ( returnFirstArg ) {
args[ 0 ] = result;
}
hookInfo.currentIndex++;
}
return returnFirstArg ? result : undefined;
} finally {
hooksStore.__current.delete( hookInfo );
}
}

return rv;
return ( async ? asyncRunner : syncRunner )();
};
}

Expand Down
90 changes: 80 additions & 10 deletions packages/hooks/src/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import {
removeAllActions,
removeAllFilters,
doAction,
doAsyncAction,
applyFilters,
applyAsyncFilters,
currentAction,
currentFilter,
doingAction,
Expand All @@ -21,7 +23,6 @@ import {
didFilter,
actions,
filters,
applyAsyncFilters,
} from '..';

function filterA( str ) {
Expand Down Expand Up @@ -945,16 +946,85 @@ test( 'checking hasFilter with named callbacks and removeAllActions', () => {
expect( hasFilter( 'test.filter', 'my_second_callback' ) ).toBe( false );
} );

test( 'async filter', async () => {
addFilter( 'test.async.filter', 'callback_plus1', ( value ) => {
return new Promise( ( r ) => setTimeout( () => r( value + 1 ), 10 ) );
describe( 'async filter', () => {
test( 'runs all registered handlers', async () => {
addFilter( 'test.async.filter', 'callback_plus1', ( value ) => {
return new Promise( ( r ) =>
setTimeout( () => r( value + 1 ), 10 )
);
} );
addFilter( 'test.async.filter', 'callback_times2', ( value ) => {
return new Promise( ( r ) =>
setTimeout( () => r( value * 2 ), 10 )
);
} );

expect( await applyAsyncFilters( 'test.async.filter', 2 ) ).toBe( 6 );
} );
addFilter( 'test.async.filter', 'callback_times2', ( value ) => {
return new Promise( ( r ) => setTimeout( () => r( value * 2 ), 10 ) );

test( 'aborts when handler throws an error', async () => {
const sqrt = jest.fn( async ( value ) => {
if ( value < 0 ) {
throw new Error( 'cannot pass negative value to sqrt' );
}
return Math.sqrt( value );
} );

const plus1 = jest.fn( async ( value ) => {
return value + 1;
} );

addFilter( 'test.async.filter', 'callback_sqrt', sqrt );
addFilter( 'test.async.filter', 'callback_plus1', plus1 );

await expect(
applyAsyncFilters( 'test.async.filter', -1 )
).rejects.toThrow( 'cannot pass negative value to sqrt' );
expect( sqrt ).toHaveBeenCalledTimes( 1 );
expect( plus1 ).not.toHaveBeenCalled();
} );
} );

describe( 'async action', () => {
test( 'runs all registered handlers sequentially', async () => {
const outputs = [];
const action1 = async () => {
outputs.push( 1 );
await new Promise( ( r ) => setTimeout( () => r(), 10 ) );
outputs.push( 2 );
};

const action2 = async () => {
outputs.push( 3 );
await new Promise( ( r ) => setTimeout( () => r(), 10 ) );
outputs.push( 4 );
};

addAction( 'test.async.action', 'action1', action1 );
addAction( 'test.async.action', 'action2', action2 );

await doAsyncAction( 'test.async.action' );
expect( outputs ).toEqual( [ 1, 2, 3, 4 ] );
} );

expect( await applyAsyncFilters( 'test.async.filter', 2 ) ).toBe( 6 );
expect(
await applyAsyncFilters( 'test.async.filter', Promise.resolve( 2 ) )
).toBe( 6 );
test( 'aborts when handler throws an error', async () => {
const outputs = [];
const action1 = async () => {
throw new Error( 'aborting' );
};

const action2 = async () => {
outputs.push( 3 );
await new Promise( ( r ) => setTimeout( () => r(), 10 ) );
outputs.push( 4 );
};

addAction( 'test.async.action', 'action1', action1 );
addAction( 'test.async.action', 'action2', action2 );

await expect( doAsyncAction( 'test.async.action' ) ).rejects.toThrow(
'aborting'
);
expect( outputs ).toEqual( [] );
} );
} );

0 comments on commit 75d3f94

Please sign in to comment.