Skip to content

Commit

Permalink
Migrate getEntityRecord to thunks (#34576)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamziel authored Sep 6, 2021
1 parent cee9136 commit 867de92
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 58 deletions.
33 changes: 13 additions & 20 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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.
Expand Down
112 changes: 74 additions & 38 deletions packages/core-data/src/test/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
*/
import { apiFetch } from '@wordpress/data-controls';

import triggerFetch from '@wordpress/api-fetch';

jest.mock( '@wordpress/api-fetch' );

/**
* Internal dependencies
*/
Expand Down Expand Up @@ -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
);
} );
} );

Expand Down

0 comments on commit 867de92

Please sign in to comment.