diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 43357b766f618e..d9614a7ba35baf 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -430,6 +430,7 @@ function mapResolveSelectors( selectors, store ) { getResolutionState, getResolutionError, hasResolvingSelectors, + countSelectorsByStatus, ...storeSelectors } = selectors; diff --git a/packages/data/src/redux-store/metadata/selectors.js b/packages/data/src/redux-store/metadata/selectors.js index 6cde6ec54b24e1..4fe535e7dbb51e 100644 --- a/packages/data/src/redux-store/metadata/selectors.js +++ b/packages/data/src/redux-store/metadata/selectors.js @@ -153,3 +153,33 @@ 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 function countSelectorsByStatus( 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; +} diff --git a/packages/data/src/redux-store/metadata/test/selectors.js b/packages/data/src/redux-store/metadata/test/selectors.js index 2eea14a7b6059f..3baa5d4ff9b713 100644 --- a/packages/data/src/redux-store/metadata/test/selectors.js +++ b/packages/data/src/redux-store/metadata/test/selectors.js @@ -356,3 +356,66 @@ 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, + } ); + } ); +} );