From 9d4b83cbbafcd6c6cbd20c86b572f458fc65ff16 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 11 Mar 2022 17:54:19 +0100 Subject: [PATCH 01/10] [RNMobile] Increase editor ready timeout in Android e2e tests (#39315) --- .../react-native-editor/__device-tests__/helpers/utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/helpers/utils.js b/packages/react-native-editor/__device-tests__/helpers/utils.js index 0daef79311aca..f250af69282ba 100644 --- a/packages/react-native-editor/__device-tests__/helpers/utils.js +++ b/packages/react-native-editor/__device-tests__/helpers/utils.js @@ -46,6 +46,8 @@ const strToKeycode = { [ backspace ]: 67, }; +const editorReadyTimeout = 8000; + const timer = ( ms ) => new Promise( ( res ) => setTimeout( res, ms ) ); const isAndroid = () => { @@ -157,8 +159,8 @@ const setupDriver = async () => { // eslint-disable-next-line no-console console.log( status ); - await driver.setImplicitWaitTimeout( 5000 ); - await timer( 5000 ); + await driver.setImplicitWaitTimeout( editorReadyTimeout ); + await timer( editorReadyTimeout ); await driver.setOrientation( 'PORTRAIT' ); return driver; From 493ff72df5c7f64d996aa8c8b5e12831f8116a8d Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Mon, 14 Mar 2022 15:25:51 +0800 Subject: [PATCH 02/10] Migrate post editor preferred style variations to preferences store (#39337) * Migrate post editor preferred style variations to preferences store * Add missing unit tests * Avoid double dispatch * Fix typo --- .../data/data-core-edit-post.md | 4 -- .../data/src/plugins/persistence/index.js | 5 ++ packages/edit-post/src/index.js | 1 + packages/edit-post/src/index.native.js | 1 + packages/edit-post/src/store/actions.js | 46 +++++++++++++---- packages/edit-post/src/store/defaults.js | 2 - packages/edit-post/src/store/reducer.js | 19 +------ packages/edit-post/src/store/selectors.js | 6 ++- packages/edit-post/src/store/test/actions.js | 49 +++++++++++++++++++ 9 files changed, 99 insertions(+), 34 deletions(-) diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index f11b3d65ab387..97c063fcdceb5 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -551,8 +551,4 @@ _Parameters_ - _blockName_ `string`: Name of the block. - _blockStyle_ `?string`: Name of the style that should be auto applied. If undefined, the "auto apply" setting of the block is removed. -_Returns_ - -- `Object`: Action object. - diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index a5b722bab07fb..5b19d5035a8a8 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -416,6 +416,11 @@ persistencePlugin.__unstableMigrate = ( pluginOptions ) => { 'core/edit-post', 'editorMode' ); + migrateIndividualPreferenceToPreferencesStore( + persistence, + 'core/edit-post', + 'preferredStyleVariations' + ); migrateFeaturePreferencesToPreferencesStore( persistence, 'core/edit-site' diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 05c3fa1fe4c3b..513915b2edd29 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -111,6 +111,7 @@ export function initializeEditor( fixedToolbar: false, fullscreenMode: true, hiddenBlockTypes: [], + preferredStyleVariations: {}, showBlockBreadcrumbs: true, showIconLabels: false, themeStyles: true, diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index d2d10efbde478..0316597aa8c39 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -26,6 +26,7 @@ export function initializeEditor( id, postType, postId ) { fixedToolbar: false, fullscreenMode: true, hiddenBlockTypes: [], + preferredStyleVariations: {}, welcomeGuide: true, } ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index ad353afff6b20..ae9f54d48bc22 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -192,16 +192,44 @@ export const togglePinnedPluginItem = ( pluginName ) => ( { registry } ) => { * * @param {string} blockName Name of the block. * @param {?string} blockStyle Name of the style that should be auto applied. If undefined, the "auto apply" setting of the block is removed. - * - * @return {Object} Action object. */ -export function updatePreferredStyleVariations( blockName, blockStyle ) { - return { - type: 'UPDATE_PREFERRED_STYLE_VARIATIONS', - blockName, - blockStyle, - }; -} +export const updatePreferredStyleVariations = ( blockName, blockStyle ) => ( { + registry, +} ) => { + if ( ! blockName ) { + return; + } + + const existingVariations = + registry + .select( preferencesStore ) + .get( 'core/edit-post', 'preferredStyleVariations' ) ?? {}; + + // When the blockStyle is omitted, remove the block's preferred variation. + if ( ! blockStyle ) { + const updatedVariations = { + ...existingVariations, + }; + + delete updatedVariations[ blockName ]; + + registry + .dispatch( preferencesStore ) + .set( + 'core/edit-post', + 'preferredStyleVariations', + updatedVariations + ); + } else { + // Else add the variation. + registry + .dispatch( preferencesStore ) + .set( 'core/edit-post', 'preferredStyleVariations', { + ...existingVariations, + [ blockName ]: blockStyle, + } ); + } +}; /** * Update the provided block types to be visible. diff --git a/packages/edit-post/src/store/defaults.js b/packages/edit-post/src/store/defaults.js index 774f7b6caacf2..4f23426405161 100644 --- a/packages/edit-post/src/store/defaults.js +++ b/packages/edit-post/src/store/defaults.js @@ -4,6 +4,4 @@ export const PREFERENCES_DEFAULTS = { opened: true, }, }, - hiddenBlockTypes: [], - preferredStyleVariations: {}, }; diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 200f9c06c1a62..ee4d4f47e5270 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flow, get, includes, omit } from 'lodash'; +import { flow, get, includes } from 'lodash'; /** * WordPress dependencies @@ -78,23 +78,6 @@ export const preferences = flow( [ return state; }, - preferredStyleVariations( state, action ) { - switch ( action.type ) { - case 'UPDATE_PREFERRED_STYLE_VARIATIONS': { - if ( ! action.blockName ) { - return state; - } - if ( ! action.blockStyle ) { - return omit( state, [ action.blockName ] ); - } - return { - ...state, - [ action.blockName ]: action.blockStyle, - }; - } - } - return state; - }, } ); /** diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index fc81507328ec7..19dfaf0c3a5e3 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -91,7 +91,11 @@ export const getActiveGeneralSidebarName = createRegistrySelector( // The current list of preference keys that have been migrated to the // preferences package. -const MIGRATED_KEYS = [ 'hiddenBlockTypes', 'editorMode' ]; +const MIGRATED_KEYS = [ + 'hiddenBlockTypes', + 'editorMode', + 'preferredStyleVariations', +]; /** * Returns the preferences (these preferences are persisted locally). diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index e8114e830a5a2..5547f2aa8a331 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -287,4 +287,53 @@ describe( 'actions', () => { } ); } ); } ); + + describe( 'updatePreferredStyleVariations', () => { + it( 'sets a preferred style variation for a block when a style name is passed', () => { + registry + .dispatch( 'core/edit-post' ) + .updatePreferredStyleVariations( 'core/paragraph', 'fancy' ); + registry + .dispatch( 'core/edit-post' ) + .updatePreferredStyleVariations( 'core/quote', 'posh' ); + + expect( + registry + .select( editPostStore ) + .getPreference( 'preferredStyleVariations' ) + ).toEqual( { + 'core/paragraph': 'fancy', + 'core/quote': 'posh', + } ); + } ); + + it( 'removes a preferred style variation for a block when a style name is omitted', () => { + registry + .dispatch( 'core/edit-post' ) + .updatePreferredStyleVariations( 'core/paragraph', 'fancy' ); + registry + .dispatch( 'core/edit-post' ) + .updatePreferredStyleVariations( 'core/quote', 'posh' ); + expect( + registry + .select( editPostStore ) + .getPreference( 'preferredStyleVariations' ) + ).toEqual( { + 'core/paragraph': 'fancy', + 'core/quote': 'posh', + } ); + + registry + .dispatch( 'core/edit-post' ) + .updatePreferredStyleVariations( 'core/paragraph' ); + + expect( + registry + .select( editPostStore ) + .getPreference( 'preferredStyleVariations' ) + ).toEqual( { + 'core/quote': 'posh', + } ); + } ); + } ); } ); From ee405ee8cfdbf3c3e1b79b05765f0dc72a390f8b Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 14 Mar 2022 09:38:04 +0000 Subject: [PATCH 03/10] Add aria label to Nav block on front of site (#39161) * Add aria label based on Navigation post title if present * Add e2e test coverage * Ensure unique aria label across renders * Simplify increment Co-authored-by: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com> * label the second block with a 2 not a 1 Co-authored-by: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com> Co-authored-by: Ben Dwyer --- .../block-library/src/navigation/index.php | 24 +++++++++++++-- .../specs/editor/blocks/navigation.test.js | 29 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 4e79f48b2ff27..fa524bf61703b 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -361,10 +361,14 @@ function block_core_navigation_get_fallback_blocks() { */ function render_block_core_navigation( $attributes, $content, $block ) { + static $seen_menu_names = array(); + // Flag used to indicate whether the rendered output is considered to be // a fallback (i.e. the block has no menu associated with it). $is_fallback = false; + $nav_menu_name = ''; + /** * Deprecated: * The rgbTextColor and rgbBackgroundColor attributes @@ -428,6 +432,14 @@ function render_block_core_navigation( $attributes, $content, $block ) { return ''; } + $nav_menu_name = $navigation_post->post_title; + + if ( isset( $seen_menu_names[ $nav_menu_name ] ) ) { + ++$seen_menu_names[ $nav_menu_name ]; + } else { + $seen_menu_names[ $nav_menu_name ] = 1; + } + $parsed_blocks = parse_blocks( $navigation_post->post_content ); // 'parse_blocks' includes a null block with '\n\n' as the content when @@ -508,10 +520,18 @@ function render_block_core_navigation( $attributes, $content, $block ) { $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; + // If the menu name has been used previously then append an ID + // to the name to ensure uniqueness across a given post. + if ( isset( $seen_menu_names[ $nav_menu_name ] ) && $seen_menu_names[ $nav_menu_name ] > 1 ) { + $count = $seen_menu_names[ $nav_menu_name ]; + $nav_menu_name = $nav_menu_name . ' ' . ( $count ); + } + $wrapper_attributes = get_block_wrapper_attributes( array( - 'class' => implode( ' ', $classes ), - 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'], + 'class' => implode( ' ', $classes ), + 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'], + 'aria-label' => $nav_menu_name, ) ); diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 5a6fdfd7c34c6..375a5dbb77a77 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -910,6 +910,35 @@ describe( 'Navigation', () => { } ); } ); + it( 'applies accessible label to block element', async () => { + await createNewPost(); + await insertBlock( 'Navigation' ); + const startEmptyButton = await page.waitForXPath( START_EMPTY_XPATH ); + await startEmptyButton.click(); + + const appender = await page.waitForSelector( + '.wp-block-navigation .block-list-appender' + ); + await appender.click(); + + // Add a link to the Link block. + await updateActiveNavigationLink( { + url: 'https://wordpress.org', + label: 'WP', + type: 'url', + } ); + + const previewPage = await openPreviewPage(); + await previewPage.bringToFront(); + await previewPage.waitForNetworkIdle(); + + const isAccessibleLabelPresent = await previewPage.$( + 'nav[aria-label="Navigation"]' + ); + + expect( isAccessibleLabelPresent ).toBeTruthy(); + } ); + it( 'does not load the frontend script if no navigation blocks are present', async () => { await createNewPost(); await insertBlock( 'Paragraph' ); From 0907f097a0bcb8d57329257258a2a09bcdc8b91d Mon Sep 17 00:00:00 2001 From: Tetsuaki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 14 Mar 2022 18:51:41 +0900 Subject: [PATCH 04/10] Remove unused npm scripts (#39369) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 01cc87dae7598..f4b537366996a 100755 --- a/package.json +++ b/package.json @@ -263,7 +263,6 @@ "lint:md-js": "wp-scripts lint-md-js", "lint:md-docs": "wp-scripts lint-md-docs", "native": "npm run --prefix packages/react-native-editor", - "pot-to-php": "./bin/pot-to-php.js", "postinstall": "patch-package && node ./patches/patch-xcode.js", "prepublishOnly": "npm run clean:package-types && npm run build:packages", "publish:check": "lerna updated", From e31303007c99b67abea3b16450c72e3a9c3075ad Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Mon, 14 Mar 2022 12:34:50 +0100 Subject: [PATCH 05/10] Use consistent terminology across @wordpress/core-data (#39349) * Refactor entities.js to talk about configs and loaders * Rename state.entities.data to state.entities.records * Rename getEntity to getEntityConfig * Rename Entity to Entity Config or Entity Provider, whichever was appropriate in the given context * Rename variables called entity/entities to entityConfig/configs * Adjust the terminology used in comments * Lint * Rename entity type to entity name * Regenerate docs * Migrate consumers of core-data to use getEntityConfig * Lint * Use a proper deprecation comment to indicate WordPress 6.0 * Update README with the new deprecation notices * Abracadabra, static checks turn green (Regenerate data-core.md) --- docs/reference-guides/data/data-core.md | 47 +++++-- packages/core-data/README.md | 47 +++++-- packages/core-data/src/actions.js | 52 ++++---- packages/core-data/src/entities.js | 44 ++++--- packages/core-data/src/entity-provider.js | 88 ++++++------- packages/core-data/src/index.js | 8 +- .../core-data/src/locks/test/selectors.js | 8 +- .../core-data/src/queried-data/reducer.js | 2 +- packages/core-data/src/reducer.js | 10 +- packages/core-data/src/resolvers.js | 47 +++---- packages/core-data/src/selectors.js | 121 ++++++++++++------ packages/core-data/src/test/actions.js | 42 +++--- packages/core-data/src/test/entities.js | 22 ++-- packages/core-data/src/test/reducer.js | 8 +- packages/core-data/src/test/resolvers.js | 10 +- packages/core-data/src/test/selectors.js | 44 +++---- .../src/utils/conservative-map-item.js | 2 +- .../core-data/src/utils/is-raw-attribute.js | 2 +- .../src/utils/test/is-raw-attribute.js | 8 +- packages/edit-site/src/store/actions.js | 8 +- .../entities-saved-states/entity-type-list.js | 9 +- 21 files changed, 376 insertions(+), 253 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 4d0b64340cd9e..f54e541122e15 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -148,7 +148,22 @@ _Returns_ ### getEntitiesByKind -Returns whether the entities for the give kind are loaded. +> **Deprecated** since WordPress 6.0. Use getEntitiesConfig instead + +Returns the loaded entities for the given kind. + +_Parameters_ + +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. + +_Returns_ + +- `Array`: Array of entities with config matching kind. + +### getEntitiesConfig + +Returns the loaded entities for the given kind. _Parameters_ @@ -161,7 +176,9 @@ _Returns_ ### getEntity -Returns the entity object given its kind and name. +> **Deprecated** since WordPress 6.0. Use getEntityConfig instead + +Returns the entity config given its kind and name. _Parameters_ @@ -171,7 +188,21 @@ _Parameters_ _Returns_ -- `Object`: Entity +- `Object`: Entity config + +### getEntityConfig + +Returns the entity config given its kind and name. + +_Parameters_ + +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. + +_Returns_ + +- `Object`: Entity config ### getEntityRecord @@ -530,9 +561,9 @@ Action triggered to delete an entity record. _Parameters_ -- _kind_ `string`: Kind of the deleted entity. -- _name_ `string`: Name of the deleted entity. -- _recordId_ `string`: Record ID of the deleted entity. +- _kind_ `string`: Kind of the deleted entity record. +- _name_ `string`: Name of the deleted entity record. +- _recordId_ `string`: Record ID of the deleted entity record. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. @@ -613,8 +644,8 @@ Returns an action object used in signalling that entity records have been receiv _Parameters_ -- _kind_ `string`: Kind of the received entity. -- _name_ `string`: Name of the received entity. +- _kind_ `string`: Kind of the received entity record. +- _name_ `string`: Name of the received entity record. - _records_ `Array|Object`: Records received. - _query_ `?Object`: Query Object. - _invalidateCache_ `?boolean`: Should invalidate query caches. diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 8f63886546c37..4b9d4536764fa 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -64,9 +64,9 @@ Action triggered to delete an entity record. _Parameters_ -- _kind_ `string`: Kind of the deleted entity. -- _name_ `string`: Name of the deleted entity. -- _recordId_ `string`: Record ID of the deleted entity. +- _kind_ `string`: Kind of the deleted entity record. +- _name_ `string`: Name of the deleted entity record. +- _recordId_ `string`: Record ID of the deleted entity record. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. @@ -147,8 +147,8 @@ Returns an action object used in signalling that entity records have been receiv _Parameters_ -- _kind_ `string`: Kind of the received entity. -- _name_ `string`: Name of the received entity. +- _kind_ `string`: Kind of the received entity record. +- _name_ `string`: Name of the received entity record. - _records_ `Array|Object`: Records received. - _query_ `?Object`: Query Object. - _invalidateCache_ `?boolean`: Should invalidate query caches. @@ -393,7 +393,22 @@ _Returns_ ### getEntitiesByKind -Returns whether the entities for the give kind are loaded. +> **Deprecated** since WordPress 6.0. Use getEntitiesConfig instead + +Returns the loaded entities for the given kind. + +_Parameters_ + +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. + +_Returns_ + +- `Array`: Array of entities with config matching kind. + +### getEntitiesConfig + +Returns the loaded entities for the given kind. _Parameters_ @@ -406,7 +421,23 @@ _Returns_ ### getEntity -Returns the entity object given its kind and name. +> **Deprecated** since WordPress 6.0. Use getEntityConfig instead + +Returns the entity config given its kind and name. + +_Parameters_ + +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. + +_Returns_ + +- `Object`: Entity config + +### getEntityConfig + +Returns the entity config given its kind and name. _Parameters_ @@ -416,7 +447,7 @@ _Parameters_ _Returns_ -- `Object`: Entity +- `Object`: Entity config ### getEntityRecord diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index d29063dfc24e3..cd8fed52937c0 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -15,7 +15,7 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; -import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; +import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; import { createBatch } from './batch'; import { STORE_NAME } from './name'; @@ -66,8 +66,8 @@ export function addEntities( entities ) { /** * Returns an action object used in signalling that entity records have been received. * - * @param {string} kind Kind of the received entity. - * @param {string} name Name of the received entity. + * @param {string} kind Kind of the received entity record. + * @param {string} name Name of the received entity record. * @param {Array|Object} records Records received. * @param {?Object} query Query Object. * @param {?boolean} invalidateCache Should invalidate query caches. @@ -209,9 +209,9 @@ export function receiveEmbedPreview( url, preview ) { /** * Action triggered to delete an entity record. * - * @param {string} kind Kind of the deleted entity. - * @param {string} name Name of the deleted entity. - * @param {string} recordId Record ID of the deleted entity. + * @param {string} kind Kind of the deleted entity record. + * @param {string} name Name of the deleted entity record. + * @param {string} recordId Record ID of the deleted entity record. * @param {?Object} query Special query parameters for the * DELETE API call. * @param {Object} [options] Delete options. @@ -226,17 +226,17 @@ export const deleteEntityRecord = ( query, { __unstableFetch = apiFetch } = {} ) => async ( { dispatch } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); let error; let deletedRecord = false; - if ( ! entity || entity?.__experimentalNoFetch ) { + if ( ! entityConfig || entityConfig?.__experimentalNoFetch ) { return; } const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, - [ 'entities', 'data', kind, name, recordId ], + [ 'entities', 'records', kind, name, recordId ], { exclusive: true } ); @@ -249,7 +249,7 @@ export const deleteEntityRecord = ( } ); try { - let path = `${ entity.baseURL }/${ recordId }`; + let path = `${ entityConfig.baseURL }/${ recordId }`; if ( query ) { path = addQueryArgs( path, query ); @@ -299,13 +299,13 @@ export const editEntityRecord = ( edits, options = {} ) => ( { select, dispatch } ) => { - const entity = select.getEntity( kind, name ); - if ( ! entity ) { + const entityConfig = select.getEntityConfig( kind, name ); + if ( ! entityConfig ) { throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); } - const { transientEdits = {}, mergedEdits = {} } = entity; + const { transientEdits = {}, mergedEdits = {} } = entityConfig; const record = select.getRawEntityRecord( kind, name, recordId ); const editedRecord = select.getEditedEntityRecord( kind, name, recordId ); @@ -401,17 +401,17 @@ export const saveEntityRecord = ( record, { isAutosave = false, __unstableFetch = apiFetch } = {} ) => async ( { select, resolveSelect, dispatch } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity || entity?.__experimentalNoFetch ) { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); + if ( ! entityConfig || entityConfig?.__experimentalNoFetch ) { return; } - const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; + const entityIdKey = entityConfig.key || DEFAULT_ENTITY_KEY; const recordId = record[ entityIdKey ]; const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, - [ 'entities', 'data', kind, name, recordId || uuid() ], + [ 'entities', 'records', kind, name, recordId || uuid() ], { exclusive: true } ); @@ -446,7 +446,7 @@ export const saveEntityRecord = ( let updatedRecord; let error; try { - const path = `${ entity.baseURL }${ + const path = `${ entityConfig.baseURL }${ recordId ? '/' + recordId : '' }`; const persistedRecord = select.getRawEntityRecord( @@ -543,10 +543,10 @@ export const saveEntityRecord = ( } } else { let edits = record; - if ( entity.__unstablePrePersist ) { + if ( entityConfig.__unstablePrePersist ) { edits = { ...edits, - ...entity.__unstablePrePersist( + ...entityConfig.__unstablePrePersist( persistedRecord, edits ), @@ -659,12 +659,12 @@ export const saveEditedEntityRecord = ( if ( ! select.hasEditsForEntityRecord( kind, name, recordId ) ) { return; } - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity ) { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); + if ( ! entityConfig ) { return; } - const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; + const entityIdKey = entityConfig.key || DEFAULT_ENTITY_KEY; const edits = select.getEntityRecordNonTransientEdits( kind, diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index eb4ab89b64910..e0332a27b562a 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -18,7 +18,7 @@ export const DEFAULT_ENTITY_KEY = 'id'; const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; -export const defaultEntities = [ +export const rootEntitiesConfig = [ { label: __( 'Base' ), name: '__unstableBase', @@ -177,9 +177,9 @@ export const defaultEntities = [ }, ]; -export const kinds = [ - { name: 'postType', loadEntities: loadPostTypeEntities }, - { name: 'taxonomy', loadEntities: loadTaxonomyEntities }, +export const additionalEntityConfigLoaders = [ + { kind: 'postType', loadEntities: loadPostTypeEntities }, + { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, ]; /** @@ -270,6 +270,15 @@ async function loadTaxonomyEntities() { /** * Returns the entity's getter method name given its kind and name. * + * @example + * ```js + * const nameSingular = getMethodName( 'root', 'theme', 'get' ); + * // nameSingular is getRootTheme + * + * const namePlural = getMethodName( 'root', 'theme', 'set' ); + * // namePlural is setRootThemes + * ``` + * * @param {string} kind Entity kind. * @param {string} name Entity name. * @param {string} prefix Function prefix. @@ -283,13 +292,13 @@ export const getMethodName = ( prefix = 'get', usePlural = false ) => { - const entity = find( defaultEntities, { kind, name } ); + const entityConfig = find( rootEntitiesConfig, { kind, name } ); const kindPrefix = kind === 'root' ? '' : upperFirst( camelCase( kind ) ); const nameSuffix = upperFirst( camelCase( name ) ) + ( usePlural ? 's' : '' ); const suffix = - usePlural && entity.plural - ? upperFirst( camelCase( entity.plural ) ) + usePlural && entityConfig.plural + ? upperFirst( camelCase( entityConfig.plural ) ) : nameSuffix; return `${ prefix }${ kindPrefix }${ suffix }`; }; @@ -301,19 +310,22 @@ export const getMethodName = ( * * @return {Array} Entities */ -export const getKindEntities = ( kind ) => async ( { select, dispatch } ) => { - let entities = select.getEntitiesByKind( kind ); - if ( entities && entities.length !== 0 ) { - return entities; +export const getOrLoadEntitiesConfig = ( kind ) => async ( { + select, + dispatch, +} ) => { + let configs = select.getEntitiesConfig( kind ); + if ( configs && configs.length !== 0 ) { + return configs; } - const kindConfig = find( kinds, { name: kind } ); - if ( ! kindConfig ) { + const loader = find( additionalEntityConfigLoaders, { kind } ); + if ( ! loader ) { return []; } - entities = await kindConfig.loadEntities(); - dispatch( addEntities( entities ) ); + configs = await loader.loadEntities(); + dispatch( addEntities( configs ) ); - return entities; + return configs; }; diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index fc842a14f41f7..82e6b5b7f03f6 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -20,48 +20,48 @@ const EMPTY_ARRAY = []; /** * Internal dependencies */ -import { defaultEntities, kinds } from './entities'; +import { rootEntitiesConfig, additionalEntityConfigLoaders } from './entities'; -const entities = { - ...defaultEntities.reduce( ( acc, entity ) => { - if ( ! acc[ entity.kind ] ) { - acc[ entity.kind ] = {}; +const entityContexts = { + ...rootEntitiesConfig.reduce( ( acc, loader ) => { + if ( ! acc[ loader.kind ] ) { + acc[ loader.kind ] = {}; } - acc[ entity.kind ][ entity.name ] = { context: createContext() }; + acc[ loader.kind ][ loader.name ] = { context: createContext() }; return acc; }, {} ), - ...kinds.reduce( ( acc, kind ) => { - acc[ kind.name ] = {}; + ...additionalEntityConfigLoaders.reduce( ( acc, loader ) => { + acc[ loader.kind ] = {}; return acc; }, {} ), }; -const getEntity = ( kind, type ) => { - if ( ! entities[ kind ] ) { +const getEntityContext = ( kind, name ) => { + if ( ! entityContexts[ kind ] ) { throw new Error( `Missing entity config for kind: ${ kind }.` ); } - if ( ! entities[ kind ][ type ] ) { - entities[ kind ][ type ] = { context: createContext() }; + if ( ! entityContexts[ kind ][ name ] ) { + entityContexts[ kind ][ name ] = { context: createContext() }; } - return entities[ kind ][ type ]; + return entityContexts[ kind ][ name ].context; }; /** * Context provider component for providing - * an entity for a specific entity type. + * an entity for a specific entity. * * @param {Object} props The component's props. * @param {string} props.kind The entity kind. - * @param {string} props.type The entity type. + * @param {string} props.type The entity name. * @param {number} props.id The entity ID. * @param {*} props.children The children to wrap. * * @return {Object} The provided children, wrapped with * the entity's context provider. */ -export default function EntityProvider( { kind, type, id, children } ) { - const Provider = getEntity( kind, type ).context.Provider; +export default function EntityProvider( { kind, type: name, id, children } ) { + const Provider = getEntityContext( kind, name ).Provider; return { children }; } @@ -70,10 +70,10 @@ export default function EntityProvider( { kind, type, id, children } ) { * provided entity of the specified type. * * @param {string} kind The entity kind. - * @param {string} type The entity type. + * @param {string} name The entity name. */ -export function useEntityId( kind, type ) { - return useContext( getEntity( kind, type ).context ); +export function useEntityProviderId( kind, name ) { + return useContext( getEntityContext( kind, name ) ); } /** @@ -82,7 +82,7 @@ export function useEntityId( kind, type ) { * entity of the specified type. * * @param {string} kind The entity kind. - * @param {string} type The entity type. + * @param {string} name The entity name. * @param {string} prop The property name. * @param {string} [_id] An entity ID to use instead of the context-provided one. * @@ -93,8 +93,8 @@ export function useEntityId( kind, type ) { * information like `raw`, `rendered` and * `protected` props. */ -export function useEntityProp( kind, type, prop, _id ) { - const providerId = useEntityId( kind, type ); +export function useEntityProp( kind, name, prop, _id ) { + const providerId = useEntityProviderId( kind, name ); const id = _id ?? providerId; const { value, fullValue } = useSelect( @@ -102,25 +102,25 @@ export function useEntityProp( kind, type, prop, _id ) { const { getEntityRecord, getEditedEntityRecord } = select( STORE_NAME ); - const entity = getEntityRecord( kind, type, id ); // Trigger resolver. - const editedEntity = getEditedEntityRecord( kind, type, id ); - return entity && editedEntity + const record = getEntityRecord( kind, name, id ); // Trigger resolver. + const editedRecord = getEditedEntityRecord( kind, name, id ); + return record && editedRecord ? { - value: editedEntity[ prop ], - fullValue: entity[ prop ], + value: editedRecord[ prop ], + fullValue: record[ prop ], } : {}; }, - [ kind, type, id, prop ] + [ kind, name, id, prop ] ); const { editEntityRecord } = useDispatch( STORE_NAME ); const setValue = useCallback( ( newValue ) => { - editEntityRecord( kind, type, id, { + editEntityRecord( kind, name, id, { [ prop ]: newValue, } ); }, - [ kind, type, id, prop ] + [ kind, name, id, prop ] ); return [ value, setValue, fullValue ]; @@ -138,25 +138,25 @@ export function useEntityProp( kind, type, prop, _id ) { * or similar components or hooks. * * @param {string} kind The entity kind. - * @param {string} type The entity type. + * @param {string} name The entity name. * @param {Object} options * @param {string} [options.id] An entity ID to use instead of the context-provided one. * * @return {[WPBlock[], Function, Function]} The block array and setters. */ -export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { - const providerId = useEntityId( kind, type ); +export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { + const providerId = useEntityProviderId( kind, name ); const id = _id ?? providerId; const { content, blocks } = useSelect( ( select ) => { const { getEditedEntityRecord } = select( STORE_NAME ); - const editedEntity = getEditedEntityRecord( kind, type, id ); + const editedRecord = getEditedEntityRecord( kind, name, id ); return { - blocks: editedEntity.blocks, - content: editedEntity.content, + blocks: editedRecord.blocks, + content: editedRecord.content, }; }, - [ kind, type, id ] + [ kind, name, id ] ); const { __unstableCreateUndoLevel, editEntityRecord } = useDispatch( STORE_NAME @@ -170,7 +170,7 @@ export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { const parsedContent = parse( content ); editEntityRecord( kind, - type, + name, id, { blocks: parsedContent, @@ -187,7 +187,7 @@ export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { const noChange = blocks === edits.blocks; if ( noChange ) { - return __unstableCreateUndoLevel( kind, type, id ); + return __unstableCreateUndoLevel( kind, name, id ); } // We create a new function here on every persistent edit @@ -196,18 +196,18 @@ export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { edits.content = ( { blocks: blocksForSerialization = [] } ) => __unstableSerializeAndClean( blocksForSerialization ); - editEntityRecord( kind, type, id, edits ); + editEntityRecord( kind, name, id, edits ); }, - [ kind, type, id, blocks ] + [ kind, name, id, blocks ] ); const onInput = useCallback( ( newBlocks, options ) => { const { selection } = options; const edits = { blocks: newBlocks, selection }; - editEntityRecord( kind, type, id, edits ); + editEntityRecord( kind, name, id, edits ); }, - [ kind, type, id ] + [ kind, name, id ] ); return [ blocks ?? EMPTY_ARRAY, onInput, onChange ]; diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index f5a9ebbc8d8d2..ebf616c5a0d57 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -11,7 +11,7 @@ import * as selectors from './selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; -import { defaultEntities, getMethodName } from './entities'; +import { rootEntitiesConfig, getMethodName } from './entities'; import { STORE_NAME } from './name'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents @@ -19,7 +19,7 @@ import { STORE_NAME } from './name'; // Instead of getEntityRecord, the consumer could use more user-frieldly named selector: getPostType, getTaxonomy... // The "kind" and the "name" of the entity are combined to generate these shortcuts. -const entitySelectors = defaultEntities.reduce( ( result, entity ) => { +const entitySelectors = rootEntitiesConfig.reduce( ( result, entity ) => { const { kind, name } = entity; result[ getMethodName( kind, name ) ] = ( state, key, query ) => selectors.getEntityRecord( state, kind, name, key, query ); @@ -28,7 +28,7 @@ const entitySelectors = defaultEntities.reduce( ( result, entity ) => { return result; }, {} ); -const entityResolvers = defaultEntities.reduce( ( result, entity ) => { +const entityResolvers = rootEntitiesConfig.reduce( ( result, entity ) => { const { kind, name } = entity; result[ getMethodName( kind, name ) ] = ( key, query ) => resolvers.getEntityRecord( kind, name, key, query ); @@ -45,7 +45,7 @@ const entityResolvers = defaultEntities.reduce( ( result, entity ) => { return result; }, {} ); -const entityActions = defaultEntities.reduce( ( result, entity ) => { +const entityActions = rootEntitiesConfig.reduce( ( result, entity ) => { const { kind, name } = entity; result[ getMethodName( kind, name, 'save' ) ] = ( key ) => actions.saveEntityRecord( kind, name, key ); diff --git a/packages/core-data/src/locks/test/selectors.js b/packages/core-data/src/locks/test/selectors.js index 667d93c635c00..a87c6932207fd 100644 --- a/packages/core-data/src/locks/test/selectors.js +++ b/packages/core-data/src/locks/test/selectors.js @@ -116,7 +116,7 @@ describe( 'isLockAvailable', () => { store: 'core', path: [ 'entities', - 'data', + 'records', 'postType', 'post', 16, @@ -163,7 +163,7 @@ describe( 'isLockAvailable', () => { entities: { locks: [], children: { - data: { + records: { locks: [], children: { postType: { @@ -178,7 +178,7 @@ describe( 'isLockAvailable', () => { path: [ 'core', 'entities', - 'data', + 'records', 'postType', 'book', 67, @@ -205,7 +205,7 @@ describe( 'isLockAvailable', () => { isLockAvailable( deepFreeze( subState ), 'core', - [ 'entities', 'data', 'postType', 'book', 67 ], + [ 'entities', 'records', 'postType', 'book', 67 ], { exclusive: false } ) ).toBe( false ); diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 7f656db0f1add..792b70c049ac8 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -130,7 +130,7 @@ export function itemIsComplete( state = {}, action ) { // An item is considered complete if it is received without an associated // fields query. Ideally, this would be implemented in such a way where the // complete aggregate of all fields would satisfy completeness. Since the - // fields are not consistent across all entity types, this would require + // fields are not consistent across all entities, this would require // introspection on the REST schema for each entity to know which fields // compose a complete item for that entity. const queryParts = query ? getQueryParts( query ) : {}; diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 5bae390fc6914..0d3f70a08daf9 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -14,7 +14,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; */ import { ifMatchingAction, replaceAction } from './utils'; import { reducer as queriedDataReducer } from './queried-data'; -import { defaultEntities, DEFAULT_ENTITY_KEY } from './entities'; +import { rootEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; /** * Reducer managing terms state. Keyed by taxonomy slug, the value is either @@ -335,7 +335,7 @@ function entity( entityConfig ) { * * @return {Object} Updated state. */ -export function entitiesConfig( state = defaultEntities, action ) { +export function entitiesConfig( state = rootEntitiesConfig, action ) { switch ( action.type ) { case 'ADD_ENTITIES': return [ ...state, ...action.entities ]; @@ -380,10 +380,10 @@ export const entities = ( state = {}, action ) => { ); } - const newData = entitiesDataReducer( state.data, action ); + const newData = entitiesDataReducer( state.records, action ); if ( - newData === state.data && + newData === state.records && newConfig === state.config && entitiesDataReducer === state.reducer ) { @@ -392,7 +392,7 @@ export const entities = ( state = {}, action ) => { return { reducer: entitiesDataReducer, - data: newData, + records: newData, config: newConfig, }; }; diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 8368fd7a8afc5..c46b4b4314827 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -13,7 +13,7 @@ import apiFetch from '@wordpress/api-fetch'; * Internal dependencies */ import { STORE_NAME } from './name'; -import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; +import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; import { forwardResolver, getNormalizedCommaSeparable } from './utils'; /** @@ -52,15 +52,15 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { select, dispatch, } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity || entity?.__experimentalNoFetch ) { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); + if ( ! entityConfig || entityConfig?.__experimentalNoFetch ) { return; } const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, - [ 'entities', 'data', kind, name, key ], + [ 'entities', 'records', kind, name, key ], { exclusive: false } ); @@ -73,7 +73,7 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { ...query, _fields: uniq( [ ...( getNormalizedCommaSeparable( query._fields ) || [] ), - entity.key || DEFAULT_ENTITY_KEY, + entityConfig.key || DEFAULT_ENTITY_KEY, ] ).join(), }; } @@ -85,10 +85,13 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { // for how the request is made to the REST API. // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const path = addQueryArgs( entity.baseURL + ( key ? '/' + key : '' ), { - ...entity.baseURLParams, - ...query, - } ); + const path = addQueryArgs( + entityConfig.baseURL + ( key ? '/' + key : '' ), + { + ...entityConfig.baseURLParams, + ...query, + } + ); if ( query !== undefined ) { query = { ...query, include: [ key ] }; @@ -133,15 +136,15 @@ export const getEditedEntityRecord = forwardResolver( 'getEntityRecord' ); export const getEntityRecords = ( kind, name, query = {} ) => async ( { dispatch, } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity || entity?.__experimentalNoFetch ) { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); + if ( ! entityConfig || entityConfig?.__experimentalNoFetch ) { return; } const lock = await dispatch.__unstableAcquireStoreLock( STORE_NAME, - [ 'entities', 'data', kind, name ], + [ 'entities', 'records', kind, name ], { exclusive: false } ); @@ -154,13 +157,13 @@ export const getEntityRecords = ( kind, name, query = {} ) => async ( { ...query, _fields: uniq( [ ...( getNormalizedCommaSeparable( query._fields ) || [] ), - entity.key || DEFAULT_ENTITY_KEY, + entityConfig.key || DEFAULT_ENTITY_KEY, ] ).join(), }; } - const path = addQueryArgs( entity.baseURL, { - ...entity.baseURLParams, + const path = addQueryArgs( entityConfig.baseURL, { + ...entityConfig.baseURLParams, ...query, } ); @@ -186,7 +189,7 @@ export const getEntityRecords = ( kind, name, query = {} ) => async ( { // resolve the `getEntityRecord` selector in addition to `getEntityRecords`. // See https://github.com/WordPress/gutenberg/pull/26575 if ( ! query?._fields && ! query.context ) { - const key = entity.key || DEFAULT_ENTITY_KEY; + const key = entityConfig.key || DEFAULT_ENTITY_KEY; const resolutionsArgs = records .filter( ( record ) => record[ key ] ) .map( ( record ) => [ kind, name, record[ key ] ] ); @@ -312,13 +315,13 @@ export const canUser = ( action, resource, id ) => async ( { dispatch } ) => { export const canUserEditEntityRecord = ( kind, name, recordId ) => async ( { dispatch, } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity ) { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = find( configs, { kind, name } ); + if ( ! entityConfig ) { return; } - const resource = entity.__unstable_rest_base; + const resource = entityConfig.__unstable_rest_base; await dispatch( canUser( 'update', resource, recordId ) ); }; diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 2370b15b6e72c..fb6a311be0099 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -95,27 +95,62 @@ export const getUserQueryResults = createSelector( ); /** - * Returns whether the entities for the give kind are loaded. + * Returns the loaded entities for the given kind. * + * @deprecated since WordPress 6.0. Use getEntitiesConfig instead * @param {Object} state Data state. * @param {string} kind Entity kind. * * @return {Array} Array of entities with config matching kind. */ export function getEntitiesByKind( state, kind ) { + deprecated( "wp.data.select( 'core' ).getEntitiesByKind()", { + since: '6.0', + alternative: "wp.data.select( 'core' ).getEntitiesConfig()", + } ); + return getEntitiesConfig( state, kind ); +} + +/** + * Returns the loaded entities for the given kind. + * + * @param {Object} state Data state. + * @param {string} kind Entity kind. + * + * @return {Array} Array of entities with config matching kind. + */ +export function getEntitiesConfig( state, kind ) { return filter( state.entities.config, { kind } ); } /** - * Returns the entity object given its kind and name. + * Returns the entity config given its kind and name. * + * @deprecated since WordPress 6.0. Use getEntityConfig instead * @param {Object} state Data state. * @param {string} kind Entity kind. * @param {string} name Entity name. * - * @return {Object} Entity + * @return {Object} Entity config */ export function getEntity( state, kind, name ) { + deprecated( "wp.data.select( 'core' ).getEntity()", { + since: '6.0', + alternative: "wp.data.select( 'core' ).getEntityConfig()", + } ); + return getEntityConfig( state, kind, name ); +} + +/** + * Returns the entity config given its kind and name. + * + * @param {Object} state Data state. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * + * @return {Object} Entity config + */ +export function getEntityConfig( state, kind, name ) { return find( state.entities.config, { kind, name } ); } @@ -134,7 +169,7 @@ export function getEntity( state, kind, name ) { */ export const getEntityRecord = createSelector( ( state, kind, name, key, query ) => { - const queriedState = get( state.entities.data, [ + const queriedState = get( state.entities.records, [ kind, name, 'queriedData', @@ -170,7 +205,7 @@ export const getEntityRecord = createSelector( ( state, kind, name, recordId, query ) => { const context = query?.context ?? 'default'; return [ - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -178,7 +213,7 @@ export const getEntityRecord = createSelector( context, recordId, ] ), - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -191,7 +226,7 @@ export const getEntityRecord = createSelector( ); /** - * Returns the Entity's record object by key. Doesn't trigger a resolver nor requests the entity from the API if the entity record isn't available in the local state. + * Returns the Entity's record object by key. Doesn't trigger a resolver nor requests the entity records from the API if the entity record isn't available in the local state. * * @param {Object} state State tree * @param {string} kind Entity kind. @@ -226,7 +261,9 @@ export const getRawEntityRecord = createSelector( return ( record && Object.keys( record ).reduce( ( accumulator, _key ) => { - if ( isRawAttribute( getEntity( state, kind, name ), _key ) ) { + if ( + isRawAttribute( getEntityConfig( state, kind, name ), _key ) + ) { // Because edits are the "raw" attribute values, // we return those from record selectors to make rendering, // comparisons, and joins with edits easier. @@ -246,7 +283,7 @@ export const getRawEntityRecord = createSelector( const context = query?.context ?? 'default'; return [ state.entities.config, - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -254,7 +291,7 @@ export const getRawEntityRecord = createSelector( context, recordId, ] ), - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -294,7 +331,7 @@ export function hasEntityRecords( state, kind, name, query ) { export function getEntityRecords( state, kind, name, query ) { // Queried data state is prepopulated for all known entities. If this is not // assigned for the given parameters, then it is known to not exist. - const queriedState = get( state.entities.data, [ + const queriedState = get( state.entities.records, [ kind, name, 'queriedData', @@ -315,13 +352,13 @@ export function getEntityRecords( state, kind, name, query ) { export const __experimentalGetDirtyEntityRecords = createSelector( ( state ) => { const { - entities: { data }, + entities: { records }, } = state; const dirtyRecords = []; - Object.keys( data ).forEach( ( kind ) => { - Object.keys( data[ kind ] ).forEach( ( name ) => { + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { const primaryKeys = Object.keys( - data[ kind ][ name ].edits + records[ kind ][ name ].edits ).filter( ( primaryKey ) => // The entity record must exist (not be deleted), @@ -331,7 +368,7 @@ export const __experimentalGetDirtyEntityRecords = createSelector( ); if ( primaryKeys.length ) { - const entity = getEntity( state, kind, name ); + const entityConfig = getEntityConfig( state, kind, name ); primaryKeys.forEach( ( primaryKey ) => { const entityRecord = getEditedEntityRecord( state, @@ -344,9 +381,10 @@ export const __experimentalGetDirtyEntityRecords = createSelector( // when it's used as an object key. key: entityRecord[ - entity.key || DEFAULT_ENTITY_KEY + entityConfig.key || DEFAULT_ENTITY_KEY ], - title: entity?.getTitle?.( entityRecord ) || '', + title: + entityConfig?.getTitle?.( entityRecord ) || '', name, kind, } ); @@ -357,7 +395,7 @@ export const __experimentalGetDirtyEntityRecords = createSelector( return dirtyRecords; }, - ( state ) => [ state.entities.data ] + ( state ) => [ state.entities.records ] ); /** @@ -370,19 +408,19 @@ export const __experimentalGetDirtyEntityRecords = createSelector( export const __experimentalGetEntitiesBeingSaved = createSelector( ( state ) => { const { - entities: { data }, + entities: { records }, } = state; const recordsBeingSaved = []; - Object.keys( data ).forEach( ( kind ) => { - Object.keys( data[ kind ] ).forEach( ( name ) => { + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { const primaryKeys = Object.keys( - data[ kind ][ name ].saving + records[ kind ][ name ].saving ).filter( ( primaryKey ) => isSavingEntityRecord( state, kind, name, primaryKey ) ); if ( primaryKeys.length ) { - const entity = getEntity( state, kind, name ); + const entityConfig = getEntityConfig( state, kind, name ); primaryKeys.forEach( ( primaryKey ) => { const entityRecord = getEditedEntityRecord( state, @@ -395,9 +433,10 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( // when it's used as an object key. key: entityRecord[ - entity.key || DEFAULT_ENTITY_KEY + entityConfig.key || DEFAULT_ENTITY_KEY ], - title: entity?.getTitle?.( entityRecord ) || '', + title: + entityConfig?.getTitle?.( entityRecord ) || '', name, kind, } ); @@ -407,7 +446,7 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( } ); return recordsBeingSaved; }, - ( state ) => [ state.entities.data ] + ( state ) => [ state.entities.records ] ); /** @@ -421,7 +460,7 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( * @return {Object?} The entity record's edits. */ export function getEntityRecordEdits( state, kind, name, recordId ) { - return get( state.entities.data, [ kind, name, 'edits', recordId ] ); + return get( state.entities.records, [ kind, name, 'edits', recordId ] ); } /** @@ -440,7 +479,7 @@ export function getEntityRecordEdits( state, kind, name, recordId ) { */ export const getEntityRecordNonTransientEdits = createSelector( ( state, kind, name, recordId ) => { - const { transientEdits } = getEntity( state, kind, name ) || {}; + const { transientEdits } = getEntityConfig( state, kind, name ) || {}; const edits = getEntityRecordEdits( state, kind, name, recordId ) || {}; if ( ! transientEdits ) { return edits; @@ -454,7 +493,7 @@ export const getEntityRecordNonTransientEdits = createSelector( }, ( state, kind, name, recordId ) => [ state.entities.config, - get( state.entities.data, [ kind, name, 'edits', recordId ] ), + get( state.entities.records, [ kind, name, 'edits', recordId ] ), ] ); @@ -497,7 +536,7 @@ export const getEditedEntityRecord = createSelector( const context = query?.context ?? 'default'; return [ state.entities.config, - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -505,7 +544,7 @@ export const getEditedEntityRecord = createSelector( context, recordId, ] ), - get( state.entities.data, [ + get( state.entities.records, [ kind, name, 'queriedData', @@ -513,7 +552,7 @@ export const getEditedEntityRecord = createSelector( context, recordId, ] ), - get( state.entities.data, [ kind, name, 'edits', recordId ] ), + get( state.entities.records, [ kind, name, 'edits', recordId ] ), ]; } ); @@ -530,7 +569,7 @@ export const getEditedEntityRecord = createSelector( */ export function isAutosavingEntityRecord( state, kind, name, recordId ) { const { pending, isAutosave } = get( - state.entities.data, + state.entities.records, [ kind, name, 'saving', recordId ], {} ); @@ -549,7 +588,7 @@ export function isAutosavingEntityRecord( state, kind, name, recordId ) { */ export function isSavingEntityRecord( state, kind, name, recordId ) { return get( - state.entities.data, + state.entities.records, [ kind, name, 'saving', recordId, 'pending' ], false ); @@ -567,7 +606,7 @@ export function isSavingEntityRecord( state, kind, name, recordId ) { */ export function isDeletingEntityRecord( state, kind, name, recordId ) { return get( - state.entities.data, + state.entities.records, [ kind, name, 'deleting', recordId, 'pending' ], false ); @@ -584,7 +623,7 @@ export function isDeletingEntityRecord( state, kind, name, recordId ) { * @return {Object?} The entity record's save error. */ export function getLastEntitySaveError( state, kind, name, recordId ) { - return get( state.entities.data, [ + return get( state.entities.records, [ kind, name, 'saving', @@ -604,7 +643,7 @@ export function getLastEntitySaveError( state, kind, name, recordId ) { * @return {Object?} The entity record's save error. */ export function getLastEntityDeleteError( state, kind, name, recordId ) { - return get( state.entities.data, [ + return get( state.entities.records, [ kind, name, 'deleting', @@ -780,11 +819,11 @@ export function canUser( state, action, resource, id ) { * or `undefined` if the OPTIONS request is still being made. */ export function canUserEditEntityRecord( state, kind, name, recordId ) { - const entity = getEntity( state, kind, name ); - if ( ! entity ) { + const entityConfig = getEntityConfig( state, kind, name ); + if ( ! entityConfig ) { return false; } - const resource = entity.__unstable_rest_base; + const resource = entityConfig.__unstable_rest_base; return canUser( state, 'update', resource, recordId ); } diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 2a376a6a87c05..e902c532a6193 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -30,21 +30,25 @@ jest.mock( '../batch', () => { describe( 'editEntityRecord', () => { it( 'throws when the edited entity does not have a loaded config.', async () => { - const entity = { kind: 'someKind', name: 'someName', id: 'someId' }; + const entityConfig = { + kind: 'someKind', + name: 'someName', + id: 'someId', + }; const select = { - getEntity: jest.fn(), + getEntityConfig: jest.fn(), }; const fulfillment = () => editEntityRecord( - entity.kind, - entity.name, - entity.id, + entityConfig.kind, + entityConfig.name, + entityConfig.id, {} )( { select } ); expect( fulfillment ).toThrow( - `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` + `The entity being edited (${ entityConfig.kind }, ${ entityConfig.name }) does not have a loaded config.` ); - expect( select.getEntity ).toHaveBeenCalledTimes( 1 ); + expect( select.getEntityConfig ).toHaveBeenCalledTimes( 1 ); } ); } ); @@ -56,7 +60,7 @@ describe( 'deleteEntityRecord', () => { it( 'triggers a DELETE request for an existing record', async () => { const deletedRecord = { title: 'new post', id: 10 }; - const entities = [ + const configs = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, ]; @@ -66,7 +70,7 @@ describe( 'deleteEntityRecord', () => { __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response apiFetch.mockImplementation( () => deletedRecord ); @@ -116,7 +120,7 @@ describe( 'saveEditedEntityRecord', () => { it( 'Uses "id" as a key when no entity key is provided', async () => { const area = { id: 1, menu: 0 }; - const entities = [ + const configs = [ { kind: 'root', name: 'navigationArea', @@ -132,7 +136,7 @@ describe( 'saveEditedEntityRecord', () => { saveEntityRecord: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response const updatedRecord = { ...area, menu: 10 }; @@ -156,7 +160,7 @@ describe( 'saveEditedEntityRecord', () => { it( 'Uses the entity key when provided', async () => { const area = { area: 'primary', menu: 0 }; - const entities = [ + const configs = [ { kind: 'root', name: 'navigationArea', @@ -173,7 +177,7 @@ describe( 'saveEditedEntityRecord', () => { saveEntityRecord: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response const updatedRecord = { ...area, menu: 10 }; @@ -204,7 +208,7 @@ describe( 'saveEntityRecord', () => { it( 'triggers a POST request for a new record', async () => { const post = { title: 'new post' }; - const entities = [ + const configs = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, ]; const select = { @@ -217,7 +221,7 @@ describe( 'saveEntityRecord', () => { __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response const updatedRecord = { ...post, id: 10 }; @@ -276,7 +280,7 @@ describe( 'saveEntityRecord', () => { it( 'triggers a PUT request for an existing record', async () => { const post = { id: 10, title: 'new post' }; - const entities = [ + const configs = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, ]; const select = { @@ -289,7 +293,7 @@ describe( 'saveEntityRecord', () => { __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response const updatedRecord = { ...post, id: 10 }; @@ -348,7 +352,7 @@ describe( 'saveEntityRecord', () => { it( 'triggers a PUT request for an existing record with a custom key', async () => { const postType = { slug: 'page', title: 'Pages' }; - const entities = [ + const configs = [ { name: 'postType', kind: 'root', @@ -366,7 +370,7 @@ describe( 'saveEntityRecord', () => { __unstableReleaseStoreLock: jest.fn(), } ); // Provide entities - dispatch.mockReturnValueOnce( entities ); + dispatch.mockReturnValueOnce( configs ); // Provide response apiFetch.mockImplementation( () => postType ); diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index 7c654b8d1732f..2780277643e5d 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -9,8 +9,8 @@ jest.mock( '@wordpress/api-fetch' ); */ import { getMethodName, - defaultEntities, - getKindEntities, + rootEntitiesConfig, + getOrLoadEntitiesConfig, prePersistPostType, } from '../entities'; @@ -40,10 +40,10 @@ describe( 'getMethodName', () => { } ); it( 'should include the kind in the method name', () => { - const id = defaultEntities.length; - defaultEntities[ id ] = { name: 'book', kind: 'postType' }; + const id = rootEntitiesConfig.length; + rootEntitiesConfig[ id ] = { name: 'book', kind: 'postType' }; const methodName = getMethodName( 'postType', 'book' ); - delete defaultEntities[ id ]; + delete rootEntitiesConfig[ id ]; expect( methodName ).toEqual( 'getPostTypeBook' ); } ); @@ -58,19 +58,19 @@ describe( 'getKindEntities', () => { it( 'shouldn’t do anything if the entities have already been resolved', async () => { const dispatch = jest.fn(); const select = { - getEntitiesByKind: jest.fn( () => entities ), + getEntitiesConfig: jest.fn( () => entities ), }; const entities = [ { kind: 'postType' } ]; - await getKindEntities( 'postType' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( 'postType' )( { dispatch, select } ); expect( dispatch ).not.toHaveBeenCalled(); } ); it( 'shouldn’t do anything if there no defined kind config', async () => { const dispatch = jest.fn(); const select = { - getEntitiesByKind: jest.fn( () => [] ), + getEntitiesConfig: jest.fn( () => [] ), }; - await getKindEntities( 'unknownKind' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( 'unknownKind' )( { dispatch, select } ); expect( dispatch ).not.toHaveBeenCalled(); } ); @@ -85,11 +85,11 @@ describe( 'getKindEntities', () => { ]; const dispatch = jest.fn(); const select = { - getEntitiesByKind: jest.fn( () => [] ), + getEntitiesConfig: jest.fn( () => [] ), }; triggerFetch.mockImplementation( () => fetchedEntities ); - await getKindEntities( 'postType' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( 'postType' )( { dispatch, select } ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); expect( dispatch.mock.calls[ 0 ][ 0 ].type ).toBe( 'ADD_ENTITIES' ); expect( dispatch.mock.calls[ 0 ][ 0 ].entities.length ).toBe( 1 ); diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index e3665d619d668..12f48dc9d1c9a 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -46,7 +46,7 @@ describe( 'entities', () => { it( 'returns the default state for all defined entities', () => { const state = entities( undefined, {} ); - expect( state.data.root.postType.queriedData ).toEqual( { + expect( state.records.root.postType.queriedData ).toEqual( { items: {}, queries: {}, itemIsComplete: {}, @@ -65,7 +65,7 @@ describe( 'entities', () => { name: 'postType', } ); - expect( state.data.root.postType.queriedData ).toEqual( { + expect( state.records.root.postType.queriedData ).toEqual( { items: { default: { b: { slug: 'b', title: 'beach' }, @@ -84,7 +84,7 @@ describe( 'entities', () => { it( 'appends the received post types by slug', () => { const originalState = deepFreeze( { - data: { + records: { root: { postType: { queriedData: { @@ -111,7 +111,7 @@ describe( 'entities', () => { name: 'postType', } ); - expect( state.data.root.postType.queriedData ).toEqual( { + expect( state.records.root.postType.queriedData ).toEqual( { items: { default: { w: { slug: 'w', title: 'water' }, diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 32b3310a9d1b7..a55639ad431ca 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -201,7 +201,7 @@ describe( 'getEntityRecords', () => { dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledWith( 'core', - [ 'entities', 'data', 'root', 'postType' ], + [ 'entities', 'records', 'root', 'postType' ], { exclusive: false } ); expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes( @@ -393,14 +393,14 @@ describe( 'getAutosaves', () => { const postType = 'post'; const postId = 1; const restBase = 'posts'; - const postEntity = { rest_base: restBase }; + const postEntityConfig = { rest_base: restBase }; triggerFetch.mockImplementation( () => SUCCESSFUL_RESPONSE ); const dispatch = Object.assign( jest.fn(), { receiveAutosaves: jest.fn(), } ); const resolveSelect = Object.assign( jest.fn(), { - getPostType: jest.fn( () => postEntity ), + getPostType: jest.fn( () => postEntityConfig ), } ); await getAutosaves( postType, postId )( { dispatch, resolveSelect } ); @@ -417,14 +417,14 @@ describe( 'getAutosaves', () => { const postType = 'post'; const postId = 1; const restBase = 'posts'; - const postEntity = { rest_base: restBase }; + const postEntityConfig = { rest_base: restBase }; triggerFetch.mockImplementation( () => [] ); const dispatch = Object.assign( jest.fn(), { receiveAutosaves: jest.fn(), } ); const resolveSelect = Object.assign( jest.fn(), { - getPostType: jest.fn( () => postEntity ), + getPostType: jest.fn( () => postEntityConfig ), } ); await getAutosaves( postType, postId )( { dispatch, resolveSelect } ); diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index 7ee39421a8b73..14d3e135438eb 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -32,7 +32,7 @@ describe.each( [ it( 'should return undefined for unknown entity kind, name', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -51,7 +51,7 @@ describe.each( [ it( 'should return undefined for unknown record’s key', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -70,7 +70,7 @@ describe.each( [ it( 'should return a record by key', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -105,7 +105,7 @@ describe.each( [ it( 'should return filtered item if complete item received, filtered item requested', () => { const state = deepFreeze( { entities: { - data: { + records: { postType: { post: { queriedData: { @@ -142,7 +142,7 @@ describe( 'hasEntityRecords', () => { it( 'returns false if entity records have not been received', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -162,7 +162,7 @@ describe( 'hasEntityRecords', () => { it( 'returns false if the entity configuration is not known', () => { const state = deepFreeze( { entities: { - data: {}, + records: {}, }, } ); @@ -172,7 +172,7 @@ describe( 'hasEntityRecords', () => { it( 'returns true if entity records have been received', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -239,7 +239,7 @@ describe( 'getRawEntityRecord', () => { name: 'someName', }, ], - data: { ...data }, + records: { ...data }, }, } ); expect( @@ -261,7 +261,7 @@ describe( 'getRawEntityRecord', () => { rawAttributes: [ 'title' ], }, ], - data: { ...data }, + records: { ...data }, }, } ); expect( @@ -278,7 +278,7 @@ describe( 'getEntityRecords', () => { it( 'should return null by default', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -297,7 +297,7 @@ describe( 'getEntityRecords', () => { it( 'should return null for an unknown entity configuration', () => { const state = deepFreeze( { entities: { - data: {}, + records: {}, }, } ); @@ -307,7 +307,7 @@ describe( 'getEntityRecords', () => { it( 'should return all the records', () => { const state = deepFreeze( { entities: { - data: { + records: { root: { postType: { queriedData: { @@ -343,7 +343,7 @@ describe( 'getEntityRecords', () => { it( 'should return filtered items', () => { const state = deepFreeze( { entities: { - data: { + records: { postType: { post: { queriedData: { @@ -382,7 +382,7 @@ describe( 'getEntityRecords', () => { it( 'should return the same instance with the same arguments', () => { let state = deepFreeze( { entities: { - data: {}, + records: {}, }, } ); @@ -427,7 +427,7 @@ describe( '__experimentalGetDirtyEntityRecords', () => { transientEdits: { someTransientEditProperty: true }, }, ], - data: { + records: { someKind: { someName: { queriedData: { @@ -476,7 +476,7 @@ describe( '__experimentalGetDirtyEntityRecords', () => { transientEdits: { someTransientEditProperty: true }, }, ], - data: { + records: { someKind: { someName: { queriedData: { @@ -533,7 +533,7 @@ describe( '__experimentalGetEntitiesBeingSaved', () => { transientEdits: { someTransientEditProperty: true }, }, ], - data: { + records: { someKind: { someName: { queriedData: { @@ -573,7 +573,7 @@ describe( '__experimentalGetEntitiesBeingSaved', () => { describe( 'getEntityRecordNonTransientEdits', () => { it( 'should return an empty object when the entity does not have a loaded config.', () => { const state = deepFreeze( { - entities: { config: [], data: {} }, + entities: { config: [], records: {} }, } ); expect( getEntityRecordNonTransientEdits( @@ -597,11 +597,11 @@ describe( 'getEmbedPreview()', () => { state = deepFreeze( { embedPreviews: { - 'http://example.com/': { data: 42 }, + 'http://example.com/': { records: 42 }, }, } ); expect( getEmbedPreview( state, 'http://example.com/' ) ).toEqual( { - data: 42, + records: 42, } ); } ); } ); @@ -653,7 +653,7 @@ describe( 'canUserEditEntityRecord', () => { it( 'returns false by default', () => { const state = deepFreeze( { userPermissions: {}, - entities: { data: {} }, + entities: { records: {} }, } ); expect( canUserEditEntityRecord( state, 'postType', 'post' ) ).toBe( false @@ -674,7 +674,7 @@ describe( 'canUserEditEntityRecord', () => { __unstable_rest_base: 'posts', }, ], - data: { + records: { root: { postType: { queriedData: { diff --git a/packages/core-data/src/utils/conservative-map-item.js b/packages/core-data/src/utils/conservative-map-item.js index 629347247bd1f..133a2e7d8d9d6 100644 --- a/packages/core-data/src/utils/conservative-map-item.js +++ b/packages/core-data/src/utils/conservative-map-item.js @@ -4,7 +4,7 @@ import { isEqual } from 'lodash'; /** - * Given the current and next item entity, returns the minimally "modified" + * Given the current and next item entity record, returns the minimally "modified" * result of the next item, preferring value references from the original item * if equal. If all values match, the original item is returned. * diff --git a/packages/core-data/src/utils/is-raw-attribute.js b/packages/core-data/src/utils/is-raw-attribute.js index f8e8d4de359a4..e795611dc8c33 100644 --- a/packages/core-data/src/utils/is-raw-attribute.js +++ b/packages/core-data/src/utils/is-raw-attribute.js @@ -1,7 +1,7 @@ /** * Checks whether the attribute is a "raw" attribute or not. * - * @param {Object} entity Entity data. + * @param {Object} entity Entity record. * @param {string} attribute Attribute name. * * @return {boolean} Is the attribute raw diff --git a/packages/core-data/src/utils/test/is-raw-attribute.js b/packages/core-data/src/utils/test/is-raw-attribute.js index 545fd7c84286f..882362d9067c2 100644 --- a/packages/core-data/src/utils/test/is-raw-attribute.js +++ b/packages/core-data/src/utils/test/is-raw-attribute.js @@ -5,18 +5,18 @@ import { isRawAttribute } from '../'; describe( 'isRawAttribute', () => { it( 'should correctly assess that the attribute is not raw', () => { - const entity = { + const entityConfig = { kind: 'someKind', name: 'someName', }; - expect( isRawAttribute( entity, 'title' ) ).toBe( false ); + expect( isRawAttribute( entityConfig, 'title' ) ).toBe( false ); } ); it( 'should correctly assess that the attribute is raw', () => { - const entity = { + const entityConfig = { kind: 'someKind', name: 'someName', rawAttributes: [ 'title' ], }; - expect( isRawAttribute( entity, 'title' ) ).toBe( true ); + expect( isRawAttribute( entityConfig, 'title' ) ).toBe( true ); } ); } ); diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 9a8caacc6b66f..1185865861c5f 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -333,11 +333,11 @@ export const revertTemplate = ( } try { - const templateEntity = registry + const templateEntityConfig = registry .select( coreStore ) - .getEntity( 'postType', template.type ); + .getEntityConfig( 'postType', template.type ); - if ( ! templateEntity ) { + if ( ! templateEntityConfig ) { registry .dispatch( noticesStore ) .createErrorNotice( @@ -350,7 +350,7 @@ export const revertTemplate = ( } const fileTemplatePath = addQueryArgs( - `${ templateEntity.baseURL }/${ template.id }`, + `${ templateEntityConfig.baseURL }/${ template.id }`, { context: 'edit', source: 'theme' } ); diff --git a/packages/editor/src/components/entities-saved-states/entity-type-list.js b/packages/editor/src/components/entities-saved-states/entity-type-list.js index ec32c79f77c7e..b9020842a9743 100644 --- a/packages/editor/src/components/entities-saved-states/entity-type-list.js +++ b/packages/editor/src/components/entities-saved-states/entity-type-list.js @@ -43,16 +43,19 @@ export default function EntityTypeList( { closePanel, } ) { const firstRecord = list[ 0 ]; - const entity = useSelect( + const entityConfig = useSelect( ( select ) => - select( coreStore ).getEntity( firstRecord.kind, firstRecord.name ), + select( coreStore ).getEntityConfig( + firstRecord.kind, + firstRecord.name + ), [ firstRecord.kind, firstRecord.name ] ); const { name } = firstRecord; const entityLabel = name === 'wp_template_part' ? _n( 'Template Part', 'Template Parts', list.length ) - : entity.label; + : entityConfig.label; // Set description based on type of entity. const description = getEntityDescription( name, list.length ); From e4dd93d70ce8b7c4deab25daac4fada41f6d1b25 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Mon, 14 Mar 2022 12:35:11 +0100 Subject: [PATCH 06/10] [core-data] Do not suppress errors in the getEntityRecord and getEntityRecords resolvers (#39317) * Remove the catch clause from getEntityRecord and getEntityRecords * Add missing endpoints to the failing tests * Restore the catch clause with a deprecation notice * Document the changes in unit tests * Remove the deprecation notice and the catch clause --- packages/core-data/src/resolvers.js | 8 -------- packages/editor/src/store/test/actions.js | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index c46b4b4314827..fda58b2fe9ea8 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -107,10 +107,6 @@ export const getEntityRecord = ( kind, name, key = '', query ) => async ( { const record = await apiFetch( { 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. - // See similar implementation in `getEntityRecords()`. } finally { dispatch.__unstableReleaseStoreLock( lock ); } @@ -205,10 +201,6 @@ export const getEntityRecords = ( kind, name, query = {} ) => async ( { args: resolutionsArgs, } ); } - } 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. - // See similar implementation in `getEntityRecord()`. } finally { dispatch.__unstableReleaseStoreLock( lock ); } diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 1b4454010ceaf..6931f1cbbfde9 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -83,6 +83,13 @@ describe( 'Post actions', () => { path.startsWith( `/wp/v2/posts/${ postId }` ) ) { return { ...post, ...data }; + } else if ( + // This URL is requested by the actions dispatched in this test. + // They are safe to ignore and are only listed here to avoid triggeringan error. + method === 'GET' && + path.startsWith( '/wp/v2/types/post' ) + ) { + return {}; } throw { @@ -163,6 +170,15 @@ describe( 'Post actions', () => { } else if ( method === 'GET' ) { return []; } + } else if ( method === 'GET' ) { + // These URLs are requested by the actions dispatched in this test. + // They are safe to ignore and are only listed here to avoid triggeringan error. + if ( + path.startsWith( '/wp/v2/types/post' ) || + path.startsWith( `/wp/v2/posts/${ postId }` ) + ) { + return {}; + } } throw { @@ -239,6 +255,13 @@ describe( 'Post actions', () => { ...data, }; } + // This URL is requested by the actions dispatched in this test. + // They are safe to ignore and are only listed here to avoid triggeringan error. + } else if ( + method === 'GET' && + path.startsWith( '/wp/v2/types/post' ) + ) { + return {}; } throw { From e6ff52c71b9693939aae2fdb9169790cf35352d1 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Mon, 14 Mar 2022 13:13:38 +0100 Subject: [PATCH 07/10] [Consistency] Use the word "record" for the entity record TypeScript types (#39251) * Rename "Entity type" interfaces to "Entity records" since that's what they actually model * Bring the record types back from the records directory to entity-types directory * Replace imports from ../helpers with ./helpers * Remove unnecessary blank line --- packages/core-data/src/entity-types/attachment.ts | 10 +++++----- ...ase-entity-types.ts => base-entity-records.ts} | 10 +++++----- packages/core-data/src/entity-types/comment.ts | 10 +++++----- packages/core-data/src/entity-types/helpers.ts | 2 +- packages/core-data/src/entity-types/index.ts | 6 ++---- .../core-data/src/entity-types/menu-location.ts | 10 +++++----- .../core-data/src/entity-types/nav-menu-item.ts | 15 ++++++++++----- packages/core-data/src/entity-types/nav-menu.ts | 10 +++++----- .../core-data/src/entity-types/navigation-area.ts | 10 +++++----- packages/core-data/src/entity-types/page.ts | 10 +++++----- packages/core-data/src/entity-types/plugin.ts | 15 ++++++++++----- packages/core-data/src/entity-types/post.ts | 10 +++++----- packages/core-data/src/entity-types/settings.ts | 15 ++++++++++----- packages/core-data/src/entity-types/sidebar.ts | 12 ++++++------ packages/core-data/src/entity-types/taxonomy.ts | 10 +++++----- packages/core-data/src/entity-types/theme.ts | 10 +++++----- packages/core-data/src/entity-types/type.ts | 10 +++++----- packages/core-data/src/entity-types/user.ts | 15 ++++++++++----- .../core-data/src/entity-types/widget-type.ts | 10 +++++----- packages/core-data/src/entity-types/widget.ts | 10 +++++----- .../src/entity-types/wp-template-part.ts | 10 +++++----- .../core-data/src/entity-types/wp-template.ts | 10 +++++----- 22 files changed, 124 insertions(+), 106 deletions(-) rename packages/core-data/src/entity-types/{base-entity-types.ts => base-entity-records.ts} (74%) diff --git a/packages/core-data/src/entity-types/attachment.ts b/packages/core-data/src/entity-types/attachment.ts index 4ad94226f0a87..22aceb5a00b86 100644 --- a/packages/core-data/src/entity-types/attachment.ts +++ b/packages/core-data/src/entity-types/attachment.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { Context, ContextualField, MediaType, @@ -12,10 +12,10 @@ import { PingStatus, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Attachment< C extends Context > { /** * The date the post was published, in the site's timezone. @@ -142,5 +142,5 @@ declare module './base-entity-types' { } export type Attachment< C extends Context > = OmitNevers< - _BaseEntityTypes.Attachment< C > + _BaseEntityRecords.Attachment< C > >; diff --git a/packages/core-data/src/entity-types/base-entity-types.ts b/packages/core-data/src/entity-types/base-entity-records.ts similarity index 74% rename from packages/core-data/src/entity-types/base-entity-types.ts rename to packages/core-data/src/entity-types/base-entity-records.ts index 790eaa63cfc9d..813df023a1114 100644 --- a/packages/core-data/src/entity-types/base-entity-types.ts +++ b/packages/core-data/src/entity-types/base-entity-records.ts @@ -1,10 +1,10 @@ /** - * This module exists solely to make the BaseEntityTypes namespace extensible + * This module exists solely to make the BaseEntityRecords namespace extensible * with declaration merging: * * ```ts - * declare module './base-entity-types' { - * export namespace BaseEntityTypes { + * declare module './base-entity-records' { + * export namespace BaseEntityRecords { * export interface Comment< C extends Context > { * id: number; * // ... @@ -19,7 +19,7 @@ * ```ts * import type { Context } from '@wordpress/core-data'; * declare module '@wordpress/core-data' { - * export namespace BaseEntityTypes { + * export namespace BaseEntityRecords { * export interface Comment< C extends Context > { * numberOfViews: number; * } @@ -33,4 +33,4 @@ * // c.id is still present * ``` */ -export namespace BaseEntityTypes {} +export namespace BaseEntityRecords {} diff --git a/packages/core-data/src/entity-types/comment.ts b/packages/core-data/src/entity-types/comment.ts index 56cd559780770..70a8fab8bfd61 100644 --- a/packages/core-data/src/entity-types/comment.ts +++ b/packages/core-data/src/entity-types/comment.ts @@ -1,19 +1,19 @@ /** * Internal dependencies */ -import { +import type { AvatarUrls, Context, ContextualField, OmitNevers, RenderedText, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; export type CommentStatus = 'hold' | 'approve' | 'spam' | 'trash' | '1' | '0'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Comment< C extends Context > { /** * Unique identifier for the comment. @@ -92,5 +92,5 @@ declare module './base-entity-types' { } export type Comment< C extends Context > = OmitNevers< - _BaseEntityTypes.Comment< C > + _BaseEntityRecords.Comment< C > >; diff --git a/packages/core-data/src/entity-types/helpers.ts b/packages/core-data/src/entity-types/helpers.ts index e3e60489bdaaf..59ed25a72bc8b 100644 --- a/packages/core-data/src/entity-types/helpers.ts +++ b/packages/core-data/src/entity-types/helpers.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { EntityRecord } from './index'; +import type { EntityRecord } from './index'; export interface AvatarUrls { /** diff --git a/packages/core-data/src/entity-types/index.ts b/packages/core-data/src/entity-types/index.ts index 80a173862b58a..19416f7b61fa4 100644 --- a/packages/core-data/src/entity-types/index.ts +++ b/packages/core-data/src/entity-types/index.ts @@ -1,6 +1,7 @@ /** * Internal dependencies */ +export type { Context, Updatable } from './helpers'; import type { Attachment } from './attachment'; import type { Comment } from './comment'; import type { MenuLocation } from './menu-location'; @@ -20,13 +21,10 @@ import type { Widget } from './widget'; import type { WidgetType } from './widget-type'; import type { WpTemplate } from './wp-template'; import type { WpTemplatePart } from './wp-template-part'; -import type { Context, Updatable } from './helpers'; -export type { BaseEntityTypes } from './base-entity-types'; +export type { BaseEntityRecords } from './base-entity-records'; export type { - Context, - Updatable, Attachment, Comment, MenuLocation, diff --git a/packages/core-data/src/entity-types/menu-location.ts b/packages/core-data/src/entity-types/menu-location.ts index 71fb5abab8714..a7d16067aa7a9 100644 --- a/packages/core-data/src/entity-types/menu-location.ts +++ b/packages/core-data/src/entity-types/menu-location.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, OmitNevers } from './helpers'; +import type { Context, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface MenuLocation< C extends Context > { /** * The name of the menu location. @@ -25,5 +25,5 @@ declare module './base-entity-types' { } export type MenuLocation< C extends Context > = OmitNevers< - _BaseEntityTypes.MenuLocation< C > + _BaseEntityRecords.MenuLocation< C > >; diff --git a/packages/core-data/src/entity-types/nav-menu-item.ts b/packages/core-data/src/entity-types/nav-menu-item.ts index dab6b9ffad2f2..901eeb78ba4b4 100644 --- a/packages/core-data/src/entity-types/nav-menu-item.ts +++ b/packages/core-data/src/entity-types/nav-menu-item.ts @@ -1,9 +1,14 @@ /** * Internal dependencies */ -import { RenderedText, Context, ContextualField, OmitNevers } from './helpers'; +import type { + RenderedText, + Context, + ContextualField, + OmitNevers, +} from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; export type NavMenuItemType = | 'taxonomy' @@ -18,8 +23,8 @@ export type NavMenuItemStatus = | 'private'; export type Target = '_blank' | ''; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface NavMenuItem< C extends Context > { /** * The title for the object. @@ -102,5 +107,5 @@ declare module './base-entity-types' { } export type NavMenuItem< C extends Context > = OmitNevers< - _BaseEntityTypes.NavMenuItem< C > + _BaseEntityRecords.NavMenuItem< C > >; diff --git a/packages/core-data/src/entity-types/nav-menu.ts b/packages/core-data/src/entity-types/nav-menu.ts index aa3417ddba04b..4bdf84c9cc90f 100644 --- a/packages/core-data/src/entity-types/nav-menu.ts +++ b/packages/core-data/src/entity-types/nav-menu.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, ContextualField, OmitNevers } from './helpers'; +import type { Context, ContextualField, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface NavMenu< C extends Context > { /** * Unique identifier for the term. @@ -49,5 +49,5 @@ declare module './base-entity-types' { } export type NavMenu< C extends Context > = OmitNevers< - _BaseEntityTypes.NavMenu< C > + _BaseEntityRecords.NavMenu< C > >; diff --git a/packages/core-data/src/entity-types/navigation-area.ts b/packages/core-data/src/entity-types/navigation-area.ts index ed5ae88769207..06250433f4638 100644 --- a/packages/core-data/src/entity-types/navigation-area.ts +++ b/packages/core-data/src/entity-types/navigation-area.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, OmitNevers } from './helpers'; +import type { Context, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface NavigationArea< C extends Context > { /** * The name of the navigation area. @@ -25,5 +25,5 @@ declare module './base-entity-types' { } export type NavigationArea< C extends Context > = OmitNevers< - _BaseEntityTypes.NavigationArea< C > + _BaseEntityRecords.NavigationArea< C > >; diff --git a/packages/core-data/src/entity-types/page.ts b/packages/core-data/src/entity-types/page.ts index eb95206b8424e..3240da1ffcda8 100644 --- a/packages/core-data/src/entity-types/page.ts +++ b/packages/core-data/src/entity-types/page.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { CommentingStatus, Context, ContextualField, @@ -11,10 +11,10 @@ import { OmitNevers, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Page< C extends Context > { /** * The date the post was published, in the site's timezone. @@ -140,5 +140,5 @@ declare module './base-entity-types' { } export type Page< C extends Context > = OmitNevers< - _BaseEntityTypes.Page< C > + _BaseEntityRecords.Page< C > >; diff --git a/packages/core-data/src/entity-types/plugin.ts b/packages/core-data/src/entity-types/plugin.ts index 968f71f7a5bb7..4d5d92c2b2fe6 100644 --- a/packages/core-data/src/entity-types/plugin.ts +++ b/packages/core-data/src/entity-types/plugin.ts @@ -1,12 +1,17 @@ /** * Internal dependencies */ -import { Context, ContextualField, RenderedText, OmitNevers } from './helpers'; +import type { + Context, + ContextualField, + RenderedText, + OmitNevers, +} from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Plugin< C extends Context > { /** * The plugin file. @@ -70,5 +75,5 @@ declare module './base-entity-types' { export type PluginStatus = 'active' | 'inactive'; export type Plugin< C extends Context > = OmitNevers< - _BaseEntityTypes.Plugin< C > + _BaseEntityRecords.Plugin< C > >; diff --git a/packages/core-data/src/entity-types/post.ts b/packages/core-data/src/entity-types/post.ts index 2c7ed4287f183..2308b76cb9264 100644 --- a/packages/core-data/src/entity-types/post.ts +++ b/packages/core-data/src/entity-types/post.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { CommentingStatus, Context, ContextualField, @@ -12,10 +12,10 @@ import { OmitNevers, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Post< C extends Context > { /** * The date the post was published, in the site's timezone. @@ -149,5 +149,5 @@ declare module './base-entity-types' { } export type Post< C extends Context > = OmitNevers< - _BaseEntityTypes.Post< C > + _BaseEntityRecords.Post< C > >; diff --git a/packages/core-data/src/entity-types/settings.ts b/packages/core-data/src/entity-types/settings.ts index 978ce32e9b416..ee48198a20803 100644 --- a/packages/core-data/src/entity-types/settings.ts +++ b/packages/core-data/src/entity-types/settings.ts @@ -1,12 +1,17 @@ /** * Internal dependencies */ -import { CommentingStatus, Context, OmitNevers, PingStatus } from './helpers'; +import type { + CommentingStatus, + Context, + OmitNevers, + PingStatus, +} from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Settings< C extends Context > { /** * What to show on the front page @@ -89,5 +94,5 @@ declare module './base-entity-types' { } export type Settings< C extends Context > = OmitNevers< - _BaseEntityTypes.Settings< C > + _BaseEntityRecords.Settings< C > >; diff --git a/packages/core-data/src/entity-types/sidebar.ts b/packages/core-data/src/entity-types/sidebar.ts index 7ff4b209ea3ce..cbfceefd901db 100644 --- a/packages/core-data/src/entity-types/sidebar.ts +++ b/packages/core-data/src/entity-types/sidebar.ts @@ -1,13 +1,13 @@ /** * Internal dependencies */ -import { Widget } from './widget'; -import { Context, OmitNevers } from './helpers'; +import type { Widget } from './widget'; +import type { Context, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Sidebar< C extends Context > { /** * ID of sidebar. @@ -56,5 +56,5 @@ declare module './base-entity-types' { type SidebarStatus = 'active' | 'inactive'; export type Sidebar< C extends Context > = OmitNevers< - _BaseEntityTypes.Sidebar< C > + _BaseEntityRecords.Sidebar< C > >; diff --git a/packages/core-data/src/entity-types/taxonomy.ts b/packages/core-data/src/entity-types/taxonomy.ts index 120831dbd7acf..b2587d0f8deb5 100644 --- a/packages/core-data/src/entity-types/taxonomy.ts +++ b/packages/core-data/src/entity-types/taxonomy.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, ContextualField, OmitNevers } from './helpers'; +import type { Context, ContextualField, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Taxonomy< C extends Context > { /** * All capabilities used by the taxonomy. @@ -88,5 +88,5 @@ declare module './base-entity-types' { } export type Taxonomy< C extends Context > = OmitNevers< - _BaseEntityTypes.Taxonomy< C > + _BaseEntityRecords.Taxonomy< C > >; diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts index d3e58fb994861..3ff8bdb800999 100644 --- a/packages/core-data/src/entity-types/theme.ts +++ b/packages/core-data/src/entity-types/theme.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, PostFormat, RenderedText, OmitNevers } from './helpers'; +import type { Context, PostFormat, RenderedText, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Theme< C extends Context > { /** * The theme's stylesheet. This uniquely identifies the theme. @@ -218,5 +218,5 @@ declare module './base-entity-types' { } export type Theme< C extends Context > = OmitNevers< - _BaseEntityTypes.Theme< C > + _BaseEntityRecords.Theme< C > >; diff --git a/packages/core-data/src/entity-types/type.ts b/packages/core-data/src/entity-types/type.ts index 2841559eb1139..c97c5821f2e4b 100644 --- a/packages/core-data/src/entity-types/type.ts +++ b/packages/core-data/src/entity-types/type.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, ContextualField, OmitNevers } from './helpers'; +import type { Context, ContextualField, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Type< C extends Context > { /** * All capabilities used by the post type. @@ -76,5 +76,5 @@ declare module './base-entity-types' { } export type Type< C extends Context > = OmitNevers< - _BaseEntityTypes.Type< C > + _BaseEntityRecords.Type< C > >; diff --git a/packages/core-data/src/entity-types/user.ts b/packages/core-data/src/entity-types/user.ts index 172d369b44bbb..ddc483fc934a7 100644 --- a/packages/core-data/src/entity-types/user.ts +++ b/packages/core-data/src/entity-types/user.ts @@ -1,12 +1,17 @@ /** * Internal dependencies */ -import { AvatarUrls, Context, ContextualField, OmitNevers } from './helpers'; +import type { + AvatarUrls, + Context, + ContextualField, + OmitNevers, +} from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface User< C extends Context > { /** * Unique identifier for the user. @@ -105,5 +110,5 @@ declare module './base-entity-types' { } export type User< C extends Context > = OmitNevers< - _BaseEntityTypes.User< C > + _BaseEntityRecords.User< C > >; diff --git a/packages/core-data/src/entity-types/widget-type.ts b/packages/core-data/src/entity-types/widget-type.ts index 82f65ea493141..7f513adab2819 100644 --- a/packages/core-data/src/entity-types/widget-type.ts +++ b/packages/core-data/src/entity-types/widget-type.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, OmitNevers } from './helpers'; +import type { Context, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface WidgetType< C extends Context > { /** * Unique slug identifying the widget type. @@ -33,5 +33,5 @@ declare module './base-entity-types' { } export type WidgetType< C extends Context > = OmitNevers< - _BaseEntityTypes.WidgetType< C > + _BaseEntityRecords.WidgetType< C > >; diff --git a/packages/core-data/src/entity-types/widget.ts b/packages/core-data/src/entity-types/widget.ts index f1b5a489e9297..1c8e2653822a4 100644 --- a/packages/core-data/src/entity-types/widget.ts +++ b/packages/core-data/src/entity-types/widget.ts @@ -1,12 +1,12 @@ /** * Internal dependencies */ -import { Context, ContextualField, OmitNevers } from './helpers'; +import type { Context, ContextualField, OmitNevers } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface Widget< C extends Context > { /** * Unique identifier for the widget. @@ -60,5 +60,5 @@ declare module './base-entity-types' { } export type Widget< C extends Context > = OmitNevers< - _BaseEntityTypes.Widget< C > + _BaseEntityRecords.Widget< C > >; diff --git a/packages/core-data/src/entity-types/wp-template-part.ts b/packages/core-data/src/entity-types/wp-template-part.ts index 36fc050900338..f569eb2d3cf40 100644 --- a/packages/core-data/src/entity-types/wp-template-part.ts +++ b/packages/core-data/src/entity-types/wp-template-part.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { Context, PostStatus, RenderedText, @@ -9,10 +9,10 @@ import { ContextualField, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface WpTemplatePart< C extends Context > { /** * ID of template. @@ -90,5 +90,5 @@ declare module './base-entity-types' { } export type WpTemplatePart< C extends Context > = OmitNevers< - _BaseEntityTypes.WpTemplatePart< C > + _BaseEntityRecords.WpTemplatePart< C > >; diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts index 33fa31787792a..a36678fc267a6 100644 --- a/packages/core-data/src/entity-types/wp-template.ts +++ b/packages/core-data/src/entity-types/wp-template.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { +import type { Context, PostStatus, RenderedText, @@ -9,10 +9,10 @@ import { ContextualField, } from './helpers'; -import { BaseEntityTypes as _BaseEntityTypes } from './base-entity-types'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -declare module './base-entity-types' { - export namespace BaseEntityTypes { +declare module './base-entity-records' { + export namespace BaseEntityRecords { export interface WpTemplate< C extends Context > { /** * ID of template. @@ -90,5 +90,5 @@ declare module './base-entity-types' { } export type WpTemplate< C extends Context > = OmitNevers< - _BaseEntityTypes.WpTemplate< C > + _BaseEntityRecords.WpTemplate< C > >; From 936ce3a79ac9f34cc12492e7df5f3320eaf2a6ca Mon Sep 17 00:00:00 2001 From: Alex Concha Date: Mon, 14 Mar 2022 14:02:33 +0100 Subject: [PATCH 08/10] Env: Fix PHP unit tests for "non-standard" cloned directories (#39362) * Fix PHP unit tests The PHP unit tests fail if the local copy is cloned into a "non-standard" directory. For example: ``` $ git clone https://github.com/WordPress/gutenberg wp-gutenberg $ npm ci; npm run build $ npm run test-unit-php Could not read "/var/www/html/wp-content/plugins/gutenberg/phpunit.xml.dist". ERROR: 1 ``` To avoid this, we add a new mapping so the hardcoded path on the npm "test-unit-php" script (https://github.com/WordPress/gutenberg/blob/2396f3a095d2dcf6248d60eae262a0564d13e2d9/package.json#L286) works as expected. * Don't add duplicate volumes to the generated docker-compose.yml file docker-compose fails to start if there are duplicate volumes. --- .wp-env.json | 1 + packages/env/lib/build-docker-compose-config.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index ac8ae3d834924..86e72e0964232 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -5,6 +5,7 @@ "env": { "tests": { "mappings": { + "wp-content/plugins/gutenberg": ".", "wp-content/mu-plugins": "./packages/e2e-tests/mu-plugins", "wp-content/plugins/gutenberg-test-plugins": "./packages/e2e-tests/plugins", "wp-content/themes/gutenberg-test-themes": "./packages/e2e-tests/themes" diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index fe8bf1e308379..808d3b5dea294 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -45,7 +45,14 @@ function getMounts( config, wordpressDefault = 'wordpress' ) { config.coreSource ? config.coreSource.path : wordpressDefault }:/var/www/html`; - return [ coreMount, ...directoryMounts, ...pluginMounts, ...themeMounts ]; + return [ + ...new Set( [ + coreMount, + ...directoryMounts, + ...pluginMounts, + ...themeMounts, + ] ), + ]; } /** From fb3d84b4a8245cc25b5978eddf95e4a3ff8d7477 Mon Sep 17 00:00:00 2001 From: Vicente Canales <1157901+vcanales@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:17:59 -0300 Subject: [PATCH 09/10] Flag invalid Navigation Link items. (#31716) * mark items that no longer exist as invalid * mock pages on mocked search response Necessary as the invalid links logic calls `getEntityRecord` on pages, and if they don't exist a 404 error is thrown, causing tests using `mocklSearchResponse` to fail, as the pages being looked up don't exist. Using `createPages` does not fix this issue. * remove redundant span from missing text * make tooltip text readable --- .../block-library/src/navigation-link/edit.js | 206 ++++++++++++------ .../src/navigation-link/editor.scss | 11 + .../specs/editor/blocks/navigation.test.js | 54 ++++- 3 files changed, 209 insertions(+), 62 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 68142bdc05c4a..43cab0244b940 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -18,6 +18,7 @@ import { ToolbarButton, Tooltip, ToolbarGroup, + KeyboardShortcuts, } from '@wordpress/components'; import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; import { __, sprintf } from '@wordpress/i18n'; @@ -265,6 +266,64 @@ export const updateNavigationLinkBlockAttributes = ( } ); }; +const useIsInvalidLink = ( kind, type, id ) => { + const isPostType = + kind === 'post-type' || type === 'post' || type === 'page'; + const hasId = Number.isInteger( id ); + const postStatus = useSelect( + ( select ) => { + if ( ! isPostType ) { + return null; + } + const { getEntityRecord } = select( coreStore ); + return getEntityRecord( 'postType', type, id )?.status; + }, + [ isPostType, type, id ] + ); + + // Check Navigation Link validity if: + // 1. Link is 'post-type'. + // 2. It has an id. + // 3. It's neither null, nor undefined, as valid items might be either of those while loading. + // If those conditions are met, check if + // 1. The post status is published. + // 2. The Navigation Link item has no label. + // If either of those is true, invalidate. + const isInvalid = + isPostType && hasId && postStatus && 'trash' === postStatus; + const isDraft = 'draft' === postStatus; + + return [ isInvalid, isDraft ]; +}; + +const useMissingText = ( type ) => { + let missingText = ''; + + switch ( type ) { + case 'post': + /* translators: label for missing post in navigation link block */ + missingText = __( 'Select post' ); + break; + case 'page': + /* translators: label for missing page in navigation link block */ + missingText = __( 'Select page' ); + break; + case 'category': + /* translators: label for missing category in navigation link block */ + missingText = __( 'Select category' ); + break; + case 'tag': + /* translators: label for missing tag in navigation link block */ + missingText = __( 'Select tag' ); + break; + default: + /* translators: label for missing values in navigation link block */ + missingText = __( 'Add link' ); + } + + return missingText; +}; + /** * Removes HTML from a given string. * Note the does not provide XSS protection or otherwise attempt @@ -329,6 +388,7 @@ export default function NavigationLinkEdit( { clientId, } ) { const { + id, label, type, opensInNewTab, @@ -339,6 +399,8 @@ export default function NavigationLinkEdit( { kind, } = attributes; + const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id ); + const link = { url, opensInNewTab, @@ -589,36 +651,23 @@ export default function NavigationLinkEdit( { onKeyDown, } ); - if ( ! url ) { + if ( ! url || isInvalid || isDraft ) { blockProps.onClick = () => setIsLinkOpen( true ); } const classes = classnames( 'wp-block-navigation-item__content', { - 'wp-block-navigation-link__placeholder': ! url, + 'wp-block-navigation-link__placeholder': ! url || isInvalid || isDraft, } ); - let missingText = ''; - switch ( type ) { - case 'post': - /* translators: label for missing post in navigation link block */ - missingText = __( 'Select post' ); - break; - case 'page': - /* translators: label for missing page in navigation link block */ - missingText = __( 'Select page' ); - break; - case 'category': - /* translators: label for missing category in navigation link block */ - missingText = __( 'Select category' ); - break; - case 'tag': - /* translators: label for missing tag in navigation link block */ - missingText = __( 'Select tag' ); - break; - default: - /* translators: label for missing values in navigation link block */ - missingText = __( 'Add link' ); - } + const missingText = useMissingText( type, isInvalid, isDraft ); + /* translators: Whether the navigation link is Invalid or a Draft. */ + const placeholderText = `(${ + isInvalid ? __( 'Invalid' ) : __( 'Draft' ) + })`; + const tooltipText = + isInvalid || isDraft + ? __( 'This item has been deleted, or is a draft' ) + : __( 'This item is missing a link' ); return ( @@ -677,46 +726,81 @@ export default function NavigationLinkEdit( { { /* eslint-enable */ } { ! url ? (
- - { missingText } + + <> + { missingText } + + { tooltipText } + +
) : ( - - setAttributes( { - label: labelValue, - } ) - } - onMerge={ mergeBlocks } - onReplace={ onReplace } - __unstableOnSplitAtEnd={ () => - insertBlocksAfter( - createBlock( 'core/navigation-link' ) - ) - } - aria-label={ __( 'Navigation link text' ) } - placeholder={ itemLabelPlaceholder } - withoutInteractiveFormatting - allowedFormats={ [ - 'core/bold', - 'core/italic', - 'core/image', - 'core/strikethrough', - ] } - onClick={ () => { - if ( ! url ) { - setIsLinkOpen( true ); - } - } } - /> + <> + { ! isInvalid && ! isDraft && ( + + setAttributes( { + label: labelValue, + } ) + } + onMerge={ mergeBlocks } + onReplace={ onReplace } + __unstableOnSplitAtEnd={ () => + insertBlocksAfter( + createBlock( + 'core/navigation-link' + ) + ) + } + aria-label={ __( 'Navigation link text' ) } + placeholder={ itemLabelPlaceholder } + withoutInteractiveFormatting + allowedFormats={ [ + 'core/bold', + 'core/italic', + 'core/image', + 'core/strikethrough', + ] } + onClick={ () => { + if ( ! url ) { + setIsLinkOpen( true ); + } + } } + /> + ) } + { ( isInvalid || isDraft ) && ( +
+ + isSelected && + setIsLinkOpen( true ), + } } + /> + + <> + + { + /* Trim to avoid trailing white space when the placeholder text is not present */ + `${ label } ${ placeholderText }`.trim() + } + + + { tooltipText } + + + +
+ ) } + ) } { isLinkOpen && ( reqUrl.includes( route ) ); +} + +function getEndpointMocks( matchingRoutes, responsesByMethod ) { + return [ 'GET', 'POST', 'DELETE', 'PUT' ].reduce( ( mocks, restMethod ) => { + if ( responsesByMethod[ restMethod ] ) { + return [ + ...mocks, + { + match: ( request ) => + matchUrlToRoute( request.url(), matchingRoutes ) && + request.method() === restMethod, + onRequestMatch: createJSONResponse( + responsesByMethod[ restMethod ] + ), + }, + ]; + } + + return mocks; + }, [] ); +} + +function getPagesMocks( responsesByMethod ) { + return getEndpointMocks( REST_PAGES_ROUTES, responsesByMethod ); +} async function mockSearchResponse( items ) { const mappedItems = items.map( ( { title, slug }, index ) => ( { @@ -48,7 +89,6 @@ async function mockSearchResponse( items ) { type: 'post', url: `https://this/is/a/test/search/${ slug }`, } ) ); - await setUpResponseMocking( [ { match: ( request ) => @@ -56,6 +96,18 @@ async function mockSearchResponse( items ) { request.url().includes( `search` ), onRequestMatch: createJSONResponse( mappedItems ), }, + ...getPagesMocks( { + GET: [ + { + type: 'page', + id: 1, + link: 'https://example.com/1', + title: { + rendered: 'My page', + }, + }, + ], + } ), ] ); } From cd0b8fb3b291a0048656ed0b6050fcfcc015ec62 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 15 Mar 2022 02:34:25 +0900 Subject: [PATCH 10/10] BaseControl: Add opt-in prop for margin-free styles (#39325) * BaseControl: Add opt-in prop for margin-free styles * Fix story `` is actually built with `BaseControl` already. Woops, BaseControl-ception. * Clear out margin bottom for help text * Add changelog entry * Fixup readme Co-authored-by: Marco Ciampini Co-authored-by: Marco Ciampini --- packages/components/CHANGELOG.md | 1 + .../components/src/base-control/README.md | 10 +++++- packages/components/src/base-control/index.js | 33 +++++++++++-------- .../src/base-control/stories/index.js | 4 +-- .../styles/base-control-styles.js | 24 +++++++++++++- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d8f53cd75fab5..fd22bd2f949e6 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `InputControl`: Allow `onBlur` for empty values to commit the change when `isPressEnterToChange` is true, and move reset behavior to the ESCAPE key. ([#39109](https://github.com/WordPress/gutenberg/pull/39109)). - `TreeGrid`: Add tests for Home/End keyboard navigation. Add `onFocusRow` callback for Home/End keyboard navigation, this was missed in the implementation PR. Modify test for expanding/collapsing a row as row 1 implements this now. Update README with latest changes. ([#39302](https://github.com/WordPress/gutenberg/pull/39302)) - `ToggleGroupControlOption`: Calculate width from button content and remove `LabelPlaceholderView` ([#39345](https://github.com/WordPress/gutenberg/pull/39345)) +- `BaseControl`: Add `__nextHasNoMarginBottom` prop for opting into the new margin-free styles ([#39325](https://github.com/WordPress/gutenberg/pull/39325)). ### Bug Fix diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index 6ed5b75b76b33..6e507ee4df87a 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -10,7 +10,7 @@ Render a BaseControl for a textarea input: import { BaseControl } from '@wordpress/components'; const MyBaseControl = () => ( - +