From aef01f83967464249d986410cac692b50ef155df Mon Sep 17 00:00:00 2001 From: Enrique Piqueras Date: Wed, 20 Nov 2019 09:10:35 -0800 Subject: [PATCH] Core Data: Add checks to actions and selectors to avoid throwing errors when the relevant config is not loaded. (#18559) * Core Data: Add checks to selectors to avoid throwing errors when the relevant config is not loaded. * Core Data: Throw when `editEntityRecord` targets an entity without a config. * Core Data: Fix default value typo in `getEntityRecordNonTransientEdits`. * Core Data: Test the behavior of `editEntityRecord` and `getEntityRecordNonTransientEdits` on entities without configs. --- packages/core-data/src/actions.js | 10 ++++---- packages/core-data/src/selectors.js | 7 ++++-- packages/core-data/src/test/actions.js | 29 +++++++++++++++++++++++- packages/core-data/src/test/selectors.js | 12 ++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index dc77d54994ac0..98fa321df3749 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -136,11 +136,11 @@ export function receiveEmbedPreview( url, preview ) { * @return {Object} Action object. */ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { - const { transientEdits = {}, mergedEdits = {} } = yield select( - 'getEntity', - kind, - name - ); + const entity = yield select( 'getEntity', kind, name ); + if ( ! entity ) { + throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); + } + const { transientEdits = {}, mergedEdits = {} } = entity; const record = yield select( 'getRawEntityRecord', kind, name, recordId ); const editedRecord = yield select( 'getEditedEntityRecord', diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 4c61d59220843..2114caae10f27 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -183,8 +183,11 @@ export function getEntityRecordEdits( state, kind, name, recordId ) { */ export const getEntityRecordNonTransientEdits = createSelector( ( state, kind, name, recordId ) => { - const { transientEdits = {} } = getEntity( state, kind, name ); - const edits = getEntityRecordEdits( state, kind, name, recordId ) || []; + const { transientEdits } = getEntity( state, kind, name ) || {}; + const edits = getEntityRecordEdits( state, kind, name, recordId ) || {}; + if ( ! transientEdits ) { + return edits; + } return Object.keys( edits ).reduce( ( acc, key ) => { if ( ! transientEdits[ key ] ) { acc[ key ] = edits[ key ]; diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index cf839cf9a3493..ffcee0efbca50 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -1,7 +1,34 @@ /** * Internal dependencies */ -import { saveEntityRecord, receiveEntityRecords, receiveUserPermission, receiveAutosaves, receiveCurrentUser } from '../actions'; +import { + editEntityRecord, + saveEntityRecord, + receiveEntityRecords, + receiveUserPermission, + receiveAutosaves, + receiveCurrentUser, +} from '../actions'; +import { select } from '../controls'; + +describe( 'editEntityRecord', () => { + it( 'throws when the edited entity does not have a loaded config.', () => { + const entity = { kind: 'someKind', name: 'someName', id: 'someId' }; + const fulfillment = editEntityRecord( + entity.kind, + entity.name, + entity.id, + {} + ); + expect( fulfillment.next().value ).toEqual( + select( 'getEntity', entity.kind, entity.name ) + ); + // Don't pass back an entity config. + expect( fulfillment.next.bind( fulfillment ) ).toThrow( + `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` + ); + } ); +} ); describe( 'saveEntityRecord', () => { it( 'triggers a POST request for a new record', async () => { diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index a52c13d99c7d1..13e1551f2f1d6 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -9,6 +9,7 @@ import deepFreeze from 'deep-freeze'; import { getEntityRecord, getEntityRecords, + getEntityRecordNonTransientEdits, getEmbedPreview, isPreviewEmbedFallback, canUser, @@ -104,6 +105,17 @@ describe( 'getEntityRecords', () => { } ); } ); +describe( 'getEntityRecordNonTransientEdits', () => { + it( 'should return an empty object when the entity does not have a loaded config.', () => { + const state = deepFreeze( { + entities: { config: {}, data: {} }, + } ); + expect( + getEntityRecordNonTransientEdits( state, 'someKind', 'someName', 'someId' ) + ).toEqual( {} ); + } ); +} ); + describe( 'getEmbedPreview()', () => { it( 'returns preview stored for url', () => { let state = deepFreeze( {