Skip to content

Commit

Permalink
Data: Introduce countSelectorsByStatus redux metadata selector (#53767
Browse files Browse the repository at this point in the history
)

* Data: Introduce `countSelectorsByStatus` redux metadata selector

* Add rememo dependency

* Add memoization and tests

* Specify createSelector's getDependants explicitly
  • Loading branch information
tyxla committed Sep 7, 2023
1 parent c791a7d commit 1ab8aa6
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 0 deletions.
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"is-plain-object": "^5.0.0",
"is-promise": "^4.0.0",
"redux": "^4.1.2",
"rememo": "^4.0.2",
"turbo-combine-reducers": "^1.0.2",
"use-memo-one": "^1.1.1"
},
Expand Down
1 change: 1 addition & 0 deletions packages/data/src/redux-store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ function mapResolveSelectors( selectors, store ) {
getResolutionState,
getResolutionError,
hasResolvingSelectors,
countSelectorsByStatus,
...storeSelectors
} = selectors;

Expand Down
40 changes: 40 additions & 0 deletions packages/data/src/redux-store/metadata/selectors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import createSelector from 'rememo';

/**
* Internal dependencies
*/
Expand Down Expand Up @@ -153,3 +158,38 @@ export function hasResolvingSelectors( state ) {
)
);
}

/**
* Retrieves the total number of selectors, grouped per status.
*
* @param {State} state Data state.
*
* @return {Object} Object, containing selector totals by status.
*/
export const countSelectorsByStatus = createSelector(
( state ) => {
const selectorsByStatus = {};

Object.values( state ).forEach( ( selectorState ) =>
/**
* This uses the internal `_map` property of `EquivalentKeyMap` for
* optimization purposes, since the `EquivalentKeyMap` implementation
* does not support a `.values()` implementation.
*
* @see https://github.com/aduth/equivalent-key-map
*/
Array.from( selectorState._map.values() ).forEach(
( resolution ) => {
const currentStatus = resolution[ 1 ]?.status ?? 'error';
if ( ! selectorsByStatus[ currentStatus ] ) {
selectorsByStatus[ currentStatus ] = 0;
}
selectorsByStatus[ currentStatus ]++;
}
)
);

return selectorsByStatus;
},
( state ) => [ state ]
);
87 changes: 87 additions & 0 deletions packages/data/src/redux-store/metadata/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,90 @@ describe( 'hasResolvingSelectors', () => {
expect( result ).toBe( true );
} );
} );

describe( 'countSelectorsByStatus', () => {
let registry;

beforeEach( () => {
registry = createRegistry();
registry.registerStore( 'store', {
reducer: ( state = null, action ) => {
if ( action.type === 'RECEIVE' ) {
return action.items;
}

return state;
},
selectors: {
getFoo: ( state ) => state,
getBar: ( state ) => state,
getBaz: ( state ) => state,
getFailingFoo: ( state ) => state,
getFailingBar: ( state ) => state,
},
resolvers: {
getFailingFoo: () => {
throw new Error( 'error fetching' );
},
getFailingBar: () => {
throw new Error( 'error fetching' );
},
},
} );
} );

it( 'counts selectors properly by status, excluding missing statuses', () => {
registry.dispatch( 'store' ).startResolution( 'getFoo', [] );
registry.dispatch( 'store' ).startResolution( 'getBar', [] );
registry.dispatch( 'store' ).startResolution( 'getBaz', [] );
registry.dispatch( 'store' ).finishResolution( 'getFoo', [] );
registry.dispatch( 'store' ).finishResolution( 'getBaz', [] );

const { countSelectorsByStatus } = registry.select( 'store' );
const result = countSelectorsByStatus();

expect( result ).toEqual( {
finished: 2,
resolving: 1,
} );
} );

it( 'counts errors properly', async () => {
registry.dispatch( 'store' ).startResolution( 'getFoo', [] );
await resolve( registry, 'getFailingFoo' );
await resolve( registry, 'getFailingBar' );
registry.dispatch( 'store' ).finishResolution( 'getFoo', [] );

const { countSelectorsByStatus } = registry.select( 'store' );
const result = countSelectorsByStatus();

expect( result ).toEqual( {
finished: 1,
error: 2,
} );
} );

it( 'applies memoization and returns the same object for the same state', () => {
const { countSelectorsByStatus } = registry.select( 'store' );

expect( countSelectorsByStatus() ).toBe( countSelectorsByStatus() );

registry.dispatch( 'store' ).startResolution( 'getFoo', [] );
registry.dispatch( 'store' ).finishResolution( 'getFoo', [] );

expect( countSelectorsByStatus() ).toBe( countSelectorsByStatus() );
} );

it( 'returns a new object when different state is provided', () => {
const { countSelectorsByStatus } = registry.select( 'store' );

const result1 = countSelectorsByStatus();

registry.dispatch( 'store' ).startResolution( 'getFoo', [] );
registry.dispatch( 'store' ).finishResolution( 'getFoo', [] );

const result2 = countSelectorsByStatus();

expect( result1 ).not.toBe( result2 );
} );
} );

1 comment on commit 1ab8aa6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 1ab8aa6.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6107426000
📝 Reported issues:

Please sign in to comment.