diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 7026937858b474..0c5fc493113cd5 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -9,6 +9,8 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash'; import { addQueryArgs } from '@wordpress/url'; import { controls } from '@wordpress/data'; import { apiFetch } from '@wordpress/data-controls'; +import triggerFetch from '@wordpress/api-fetch'; + /** * Internal dependencies */ @@ -63,16 +65,17 @@ export function* getCurrentUser() { * @param {Object|undefined} query Optional object of query parameters to * include with request. */ -export function* getEntityRecord( kind, name, key = '', query ) { - const entities = yield getKindEntities( kind ); +export const getEntityRecord = ( kind, name, key = '', query ) => async ( { + select, + dispatch, +} ) => { + const entities = await dispatch( getKindEntities( kind ) ); const entity = find( entities, { kind, name } ); if ( ! entity ) { return; } - const lock = yield controls.dispatch( - STORE_NAME, - '__unstableAcquireStoreLock', + const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, [ 'entities', 'data', kind, name, key ], { exclusive: false } @@ -110,31 +113,21 @@ export function* getEntityRecord( kind, name, key = '', query ) { // The resolution cache won't consider query as reusable based on the // fields, so it's tested here, prior to initiating the REST request, // and without causing `getEntityRecords` resolution to occur. - const hasRecords = yield controls.select( - STORE_NAME, - 'hasEntityRecords', - kind, - name, - query - ); + const hasRecords = select.hasEntityRecords( kind, name, query ); if ( hasRecords ) { return; } } - const record = yield apiFetch( { path } ); - yield receiveEntityRecords( kind, name, record, query ); + const record = await triggerFetch( { path } ); + dispatch.receiveEntityRecords( kind, name, record, query ); } catch ( error ) { // We need a way to handle and access REST API errors in state // Until then, catching the error ensures the resolver is marked as resolved. } finally { - yield controls.dispatch( - STORE_NAME, - '__unstableReleaseStoreLock', - lock - ); + dispatch.__unstableReleaseStoreLock( lock ); } -} +}; /** * Requests an entity's record from the REST API. diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 2ff24b126ab8d7..d593b719f838f5 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -3,6 +3,10 @@ */ import { apiFetch } from '@wordpress/data-controls'; +import triggerFetch from '@wordpress/api-fetch'; + +jest.mock( '@wordpress/api-fetch' ); + /** * Internal dependencies */ @@ -32,68 +36,100 @@ describe( 'getEntityRecord', () => { baseURLParams: { context: 'edit' }, }, ]; + beforeEach( async () => { + triggerFetch.mockReset(); + jest.useFakeTimers(); + } ); it( 'yields with requested post type', async () => { - const fulfillment = getEntityRecord( 'root', 'postType', 'post' ); - // Trigger generator - fulfillment.next(); - // Provide entities and acquire lock - expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - '@@data/DISPATCH' - ); - // trigger apiFetch - const { value: apiFetchAction } = fulfillment.next(); - expect( apiFetchAction.request ).toEqual( { + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); + + // Provide response + triggerFetch.mockImplementation( () => POST_TYPE ); + + await getEntityRecord( 'root', 'postType', 'post' )( { dispatch } ); + + // Fetch request should have been issued + expect( triggerFetch ).toHaveBeenCalledWith( { path: '/wp/v2/types/post?context=edit', } ); - // Provide response and trigger action - const { value: received } = fulfillment.next( POST_TYPE ); - expect( received ).toEqual( - receiveEntityRecords( 'root', 'postType', POST_TYPE ) + + // The record should have been received + expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + POST_TYPE, + undefined + ); + + // Locks should have been acquired and released + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); } ); it( 'accepts a query that overrides default api path', async () => { const query = { context: 'view', _envelope: '1' }; const queryObj = { include: [ 'post' ], ...query }; - const fulfillment = getEntityRecord( + const select = { + hasEntityRecords: jest.fn( () => {} ), + }; + + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( ENTITIES ); + + // Provide response + triggerFetch.mockImplementation( () => POST_TYPE ); + + await getEntityRecord( 'root', 'postType', 'post', query - ); - - // Trigger generator - fulfillment.next(); - - // Provide entities and acquire lock - expect( fulfillment.next( ENTITIES ).value.type ).toEqual( - '@@data/DISPATCH' - ); + )( { dispatch, select } ); // Check resolution cache for an existing entity that fulfills the request with query - const { - value: { args: selectArgs }, - } = fulfillment.next(); - expect( selectArgs ).toEqual( [ 'root', 'postType', queryObj ] ); + expect( select.hasEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + queryObj + ); // Trigger apiFetch, test that the query is present in the url - const { value: apiFetchAction } = fulfillment.next(); - expect( apiFetchAction.request ).toEqual( { + expect( triggerFetch ).toHaveBeenCalledWith( { path: '/wp/v2/types/post?context=view&_envelope=1', } ); - // Receive response - const { value: received } = fulfillment.next( POST_TYPE ); - expect( received ).toEqual( - receiveEntityRecords( 'root', 'postType', POST_TYPE, queryObj ) + // The record should have been received + expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith( + 'root', + 'postType', + POST_TYPE, + queryObj ); - // Release lock - expect( fulfillment.next().value.type ).toEqual( '@@data/DISPATCH' ); + // Locks should have been acquired and released + expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes( + 1 + ); + expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( + 1 + ); } ); } );