diff --git a/.github/workflows/sync-backport-changelog.yml b/.github/workflows/sync-backport-changelog.yml index 46465cdaab58ad..76a87f89ba7030 100644 --- a/.github/workflows/sync-backport-changelog.yml +++ b/.github/workflows/sync-backport-changelog.yml @@ -14,10 +14,11 @@ jobs: with: fetch-depth: 2 # Fetch the last two commits to compare changes - name: Check for changes in backport-changelog + id: check-for-changes run: | - git diff --quiet HEAD^ HEAD -- backport-changelog || echo "changes=true" >> $GITHUB_OUTPUT + git diff --quiet HEAD^ HEAD -- backport-changelog || echo "HAS_CHANGES=1" >> "$GITHUB_OUTPUT" - name: Sync Issue - if: env.changes == 'true' + if: steps.check-for-changes.outputs.HAS_CHANGES uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | @@ -52,18 +53,20 @@ jobs: const endDelimiter = ''; const autoGeneratedContent = `${startDelimiter}\n${processedChangelog}\n${endDelimiter}`; - const regex = new RegExp(`${startDelimiter}[\\s\\S]*${endDelimiter}`); + const existingBody = latestIssue.body ?? ''; + let newBody; - if (regex.test(latestIssue.body)) { + const regex = new RegExp(`${startDelimiter}[\\s\\S]*${endDelimiter}`); + if (regex.test(existingBody)) { // If delimiters exist, replace the content between them - newBody = latestIssue.body.replace(regex, autoGeneratedContent); + newBody = existingBody.replace(regex, autoGeneratedContent); } else { // If delimiters don't exist, append the new content at the end - newBody = `${latestIssue.body}\n\n${autoGeneratedContent}`; + newBody = `${existingBody}\n\n${autoGeneratedContent}`; } - if (newBody.trim() !== latestIssue.body.trim()) { + if (newBody.trim() !== existingBody.trim()) { await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/backport-changelog/6.7/7020.md b/backport-changelog/6.7/7020.md new file mode 100644 index 00000000000000..8eacb220d340e5 --- /dev/null +++ b/backport-changelog/6.7/7020.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7020 + +* https://github.com/WordPress/gutenberg/pull/63470 diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md index 0f14906165160c..00030465415323 100644 --- a/docs/reference-guides/interactivity-api/api-reference.md +++ b/docs/reference-guides/interactivity-api/api-reference.md @@ -840,7 +840,7 @@ const { state } = store("myPlugin", { }); ``` -As mentioned above with [`wp-on`](#wp-on), [`wp-on-window`](#wp-on-window), and [`wp-on-document`](#wp-on-document), an async action should be used whenever the `async` versions of the aforementioned directives cannot be used due to the action requiring synchronous access to the `event` object. Synchronous access is reqired whenever the action needs to call `event.preventDefault()`, `event.stopPropagation()`, or `event.stopImmediatePropagation()`. To ensure that the action code does not contribute to a long task, you may manually yield to the main thread after calling the synchronous event API. For example: +As mentioned above with [`wp-on`](#wp-on), [`wp-on-window`](#wp-on-window), and [`wp-on-document`](#wp-on-document), an async action should be used whenever the `async` versions of the aforementioned directives cannot be used due to the action requiring synchronous access to the `event` object. Synchronous access is required whenever the action needs to call `event.preventDefault()`, `event.stopPropagation()`, or `event.stopImmediatePropagation()`. To ensure that the action code does not contribute to a long task, you may manually yield to the main thread after calling the synchronous event API. For example: ```js // Note: In WordPress 6.6 this splitTask function is exported by @wordpress/interactivity. diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php new file mode 100644 index 00000000000000..4c82dc6683f370 --- /dev/null +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -0,0 +1,40 @@ + $source_properties ) { + // Add source with the label to editor settings. + $editor_settings['blockBindingsSources'][ $source_name ] = array( + 'label' => $source_properties->label, + ); + // Add `usesContext` property if exists. + if ( ! empty( $source_properties->uses_context ) ) { + $editor_settings['blockBindingsSources'][ $source_name ]['usesContext'] = $source_properties->uses_context; + } + } + } + return $editor_settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_add_server_block_bindings_sources_to_editor_settings', 10 ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index ca5c87eec737be..74a133da05c976 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -184,7 +184,7 @@ function gutenberg_display_experiment_field( $args ) { */ function gutenberg_display_experiment_section() { ?> -

+

{ + const { getCurrentTheme, canUser } = select( coreStore ); + return { + isBlockBasedTheme: getCurrentTheme()?.is_block_theme, + canCreateTemplatePart: canUser( 'create', { + kind: 'postType', + name: 'wp_template_part', + } ), + }; + }, + [] + ); + const [ showTitleModal, setShowTitleModal ] = useState( false ); const areaObject = useTemplatePartArea( area ); const createFromBlocks = useCreateTemplatePartFromBlocks( @@ -39,11 +56,19 @@ export default function TemplatePartPlaceholder( { { isResolving && } @@ -54,7 +79,7 @@ export default function TemplatePartPlaceholder( { ) } - { ! isResolving && ( + { ! isResolving && isBlockBasedTheme && canCreateTemplatePart && ( diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 78aab99b11b617..886309d8bd8f3e 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -769,7 +769,7 @@ export const unregisterBlockVariation = ( blockName, variationName ) => { * * @param {Object} source Properties of the source to be registered. * @param {string} source.name The unique and machine-readable name. - * @param {string} source.label Human-readable label. + * @param {string} [source.label] Human-readable label. * @param {Function} [source.getValues] Function to get the values from the source. * @param {Function} [source.setValues] Function to update multiple values connected to the source. * @param {Function} [source.getPlaceholder] Function to get the placeholder when the value is undefined. @@ -800,11 +800,15 @@ export const registerBlockBindingsSource = ( source ) => { canUserEditValue, } = source; - // Check if the source is already registered. const existingSource = unlock( select( blocksStore ) ).getBlockBindingsSource( name ); - if ( existingSource ) { + + /* + * Check if the source has been already registered on the client. + * If the `getValues` property is defined, it could be assumed the source is already registered. + */ + if ( existingSource?.getValues ) { warning( 'Block bindings source "' + name + '" is already registered.' ); @@ -844,12 +848,21 @@ export const registerBlockBindingsSource = ( source ) => { } // Check the `label` property is correct. - if ( ! label ) { + if ( label && existingSource?.label ) { + warning( + 'Block bindings "' + + name + + '" source label is already defined in the server.' + ); + return; + } + + if ( ! label && ! existingSource?.label ) { warning( 'Block bindings source must contain a label.' ); return; } - if ( typeof label !== 'string' ) { + if ( label && typeof label !== 'string' ) { warning( 'Block bindings source label must be a string.' ); return; } diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index bc1057597bcd7a..d36abee2930bfa 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1512,6 +1512,22 @@ describe( 'blocks', () => { expect( getBlockBindingsSource( 'core/testing' ) ).toBeUndefined(); } ); + it( 'should not override label from the server', () => { + // Simulate bootstrapping a source from the server registration. + registerBlockBindingsSource( { + name: 'core/server', + label: 'Server label', + } ); + // Override the source with a different label in the client. + registerBlockBindingsSource( { + name: 'core/server', + label: 'Client label', + } ); + expect( console ).toHaveWarnedWith( + 'Block bindings "core/server" source label is already defined in the server.' + ); + } ); + // Check the `getValues` callback is correct. it( 'should reject invalid getValues callback', () => { registerBlockBindingsSource( { @@ -1600,6 +1616,7 @@ describe( 'blocks', () => { const source = { name: 'core/test-source', label: 'Test Source', + getValues: () => 'value', }; registerBlockBindingsSource( source ); registerBlockBindingsSource( source ); diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index 55cdb2128895f5..6f7581da53de36 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -69,3 +69,17 @@ export function removeBlockBindingsSource( name ) { name, }; } + +/** + * Add bootstrapped block bindings sources, usually initialized from the server. + * + * @param {string} source Name of the source to bootstrap. + */ +export function addBootstrappedBlockBindingsSource( source ) { + return { + type: 'ADD_BOOTSTRAPPED_BLOCK_BINDINGS_SOURCE', + name: source.name, + label: source.label, + usesContext: source.usesContext, + }; +} diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index fc386e7ea9f557..5cffb0abc91973 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -377,13 +377,22 @@ export function blockBindingsSources( state = {}, action ) { return { ...state, [ action.name ]: { - label: action.label, + // Don't override the label if it's already set. + label: state[ action.name ]?.label || action.label, getValues: action.getValues, setValues: action.setValues, getPlaceholder: action.getPlaceholder, canUserEditValue: action.canUserEditValue, }, }; + case 'ADD_BOOTSTRAPPED_BLOCK_BINDINGS_SOURCE': + return { + ...state, + [ action.name ]: { + label: action.label, + usesContext: action.usesContext, + }, + }; case 'REMOVE_BLOCK_BINDINGS_SOURCE': return omit( state, action.name ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 46e357a008b3f0..ec1736e3adafa9 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -28,6 +28,10 @@ - `Tabs`: Vertical Tabs should be 40px min height. ([#63446](https://github.com/WordPress/gutenberg/pull/63446)). - `ColorPicker`: Use `minimal` variant for `SelectControl` ([#63676](https://github.com/WordPress/gutenberg/pull/63676)). +### Documentation + +- `BaseControl`: Improve the base control help prop documentation. ([#63693](https://github.com/WordPress/gutenberg/pull/63693)). + ## 28.3.0 (2024-07-10) ### Enhancements diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index bc46629d2b6182..dc3d8c0e29c8e0 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -53,7 +53,7 @@ If true, the label will only be visible to screen readers. ### help -Additional description for the control. The element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. +Additional description for the control. Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. - Type: `ReactNode` - Required: No diff --git a/packages/components/src/base-control/types.ts b/packages/components/src/base-control/types.ts index eeb8736cf1b956..07a358d09c14b4 100644 --- a/packages/components/src/base-control/types.ts +++ b/packages/components/src/base-control/types.ts @@ -21,7 +21,7 @@ export type BaseControlProps = { /** * Additional description for the control. * - * The element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. + * Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. */ help?: ReactNode; /** diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index 74aec2adb500fb..b2eb9d797610d5 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -8,9 +8,19 @@ */ /** -* Register custom fields. +* Register custom fields and custom block bindings sources. */ -function gutenberg_test_block_bindings_register_custom_fields() { +function gutenberg_test_block_bindings_registration() { + // Register custom block bindings sources. + register_block_bindings_source( + 'core/server-source', + array( + 'label' => 'Server Source', + 'get_value_callback' => function () {}, + ) + ); + + // Register custom fields. register_meta( 'post', 'text_custom_field', @@ -51,4 +61,4 @@ function gutenberg_test_block_bindings_register_custom_fields() { ) ); } -add_action( 'init', 'gutenberg_test_block_bindings_register_custom_fields' ); +add_action( 'init', 'gutenberg_test_block_bindings_registration' ); diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 2cbf1958719c68..aa3473e6e55d45 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -29,6 +29,7 @@ const { BackButton: __experimentalMainDashboardButton, registerDefaultActions, registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, } = unlock( editorPrivateApis ); /** @@ -87,6 +88,7 @@ export function initializeEditor( } registerCoreBlocks(); + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindingsSources ); registerCoreBlockBindingsSources(); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); diff --git a/packages/edit-site/src/components/global-styles/font-families.js b/packages/edit-site/src/components/global-styles/font-families.js index 811d4e66b27a0d..5119ca679e40ad 100644 --- a/packages/edit-site/src/components/global-styles/font-families.js +++ b/packages/edit-site/src/components/global-styles/font-families.js @@ -26,8 +26,14 @@ import { unlock } from '../../lock-unlock'; const { useGlobalSetting } = unlock( blockEditorPrivateApis ); function FontFamilies() { - const { modalTabOpen, setModalTabOpen } = useContext( FontLibraryContext ); + const { baseCustomFonts, modalTabOpen, setModalTabOpen } = + useContext( FontLibraryContext ); const [ fontFamilies ] = useGlobalSetting( 'typography.fontFamilies' ); + const [ baseFontFamilies ] = useGlobalSetting( + 'typography.fontFamilies', + undefined, + 'base' + ); const themeFonts = fontFamilies?.theme ? fontFamilies.theme .map( ( f ) => setUIValuesNeeded( f, { source: 'theme' } ) ) @@ -40,6 +46,11 @@ function FontFamilies() { : []; const hasFonts = 0 < customFonts.length || 0 < themeFonts.length; + const hasInstalledFonts = + hasFonts || + baseFontFamilies?.theme?.length > 0 || + baseCustomFonts?.length > 0; + return ( <> { !! modalTabOpen && ( @@ -89,7 +100,11 @@ function FontFamilies() { { ! hasFonts && ( { __( 'Fonts' ) } - { __( 'No fonts installed.' ) } + + { hasInstalledFonts + ? __( 'No fonts activated.' ) + : __( 'No fonts installed.' ) } + ) } diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 02d974e037c045..922e2f6ab933ab 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -28,8 +28,11 @@ import { store as editSiteStore } from './store'; import { unlock } from './lock-unlock'; import App from './components/app'; -const { registerDefaultActions, registerCoreBlockBindingsSources } = - unlock( editorPrivateApis ); +const { + registerDefaultActions, + registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, +} = unlock( editorPrivateApis ); /** * Initializes the site editor screen. @@ -46,6 +49,7 @@ export function initializeEditor( id, settings ) { ( { name } ) => name !== 'core/freeform' ); registerCoreBlocks( coreBlocks ); + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindingsSources ); registerCoreBlockBindingsSources(); dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' ); registerLegacyWidgetBlock( { inserter: false } ); diff --git a/packages/editor/src/bindings/api.js b/packages/editor/src/bindings/api.js index 0037f3334215b8..2cfed5168a143e 100644 --- a/packages/editor/src/bindings/api.js +++ b/packages/editor/src/bindings/api.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { privateApis as blocksPrivateApis } from '@wordpress/blocks'; +import { + privateApis as blocksPrivateApis, + store as blocksStore, +} from '@wordpress/blocks'; +import { dispatch } from '@wordpress/data'; /** * Internal dependencies @@ -25,3 +29,29 @@ export function registerCoreBlockBindingsSources() { registerBlockBindingsSource( patternOverrides ); registerBlockBindingsSource( postMeta ); } + +/** + * Function to bootstrap core block bindings sources defined in the server. + * + * @param {Object} sources Object containing the sources to bootstrap. + * + * @example + * ```js + * import { bootstrapBlockBindingsSourcesFromServer } from '@wordpress/editor'; + * + * bootstrapBlockBindingsSourcesFromServer( sources ); + * ``` + */ +export function bootstrapBlockBindingsSourcesFromServer( sources ) { + if ( sources ) { + const { addBootstrappedBlockBindingsSource } = unlock( + dispatch( blocksStore ) + ); + for ( const [ name, args ] of Object.entries( sources ) ) { + addBootstrappedBlockBindingsSource( { + name, + ...args, + } ); + } + } +} diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index b299211900095d..88c6c73bdc61c1 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -1,14 +1,12 @@ /** * WordPress dependencies */ -import { _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; const CONTENT = 'content'; export default { name: 'core/pattern-overrides', - label: _x( 'Pattern Overrides', 'block bindings source' ), getValues( { registry, clientId, context, bindings } ) { const patternOverridesContent = context[ 'pattern/overrides' ]; const { getBlockAttributes } = registry.select( blockEditorStore ); diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index f8161dd47b5c41..aafc784a21bd4a 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { store as coreDataStore } from '@wordpress/core-data'; -import { _x } from '@wordpress/i18n'; /** * Internal dependencies @@ -11,7 +10,6 @@ import { store as editorStore } from '../store'; export default { name: 'core/post-meta', - label: _x( 'Post Meta', 'block bindings source' ), getPlaceholder( { args } ) { return args.key; }, diff --git a/packages/editor/src/components/post-comments/index.js b/packages/editor/src/components/post-comments/index.js index 75ca1ec0ee016c..c8fac57d00e09d 100644 --- a/packages/editor/src/components/post-comments/index.js +++ b/packages/editor/src/components/post-comments/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { RadioControl, __experimentalText as Text, @@ -18,7 +18,7 @@ const COMMENT_OPTIONS = [ { label: ( <> - { __( 'Open' ) } + { _x( 'Open', 'Adjective: e.g. "Comments are open"' ) } { __( 'Visitors can add new comments and replies.' ) } diff --git a/packages/editor/src/components/post-discussion/panel.js b/packages/editor/src/components/post-discussion/panel.js index 718754d56857aa..c539791d404dec 100644 --- a/packages/editor/src/components/post-discussion/panel.js +++ b/packages/editor/src/components/post-discussion/panel.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { Dropdown, Button, @@ -62,9 +62,11 @@ function PostDiscussionToggle( { isOpen, onClick } ) { let label; if ( commentStatus === 'open' ) { if ( pingStatus === 'open' ) { - label = __( 'Open' ); + label = _x( 'Open', 'Adjective: e.g. "Comments are open"' ); } else { - label = trackbacksSupported ? __( 'Comments only' ) : __( 'Open' ); + label = trackbacksSupported + ? __( 'Comments only' ) + : _x( 'Open', 'Adjective: e.g. "Comments are open"' ); } } else if ( pingStatus === 'open' ) { label = commentsSupported ? __( 'Pings only' ) : __( 'Pings enabled' ); diff --git a/packages/editor/src/components/site-discussion/index.js b/packages/editor/src/components/site-discussion/index.js index b80b44b1f59c13..b35ba9db476570 100644 --- a/packages/editor/src/components/site-discussion/index.js +++ b/packages/editor/src/components/site-discussion/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { @@ -25,7 +25,7 @@ const COMMENT_OPTIONS = [ { label: ( <> - { __( 'Open' ) } + { _x( 'Open', 'Adjective: e.g. "Comments are open"' ) } { __( 'Visitors can add new comments and replies.' ) } diff --git a/packages/editor/src/components/visual-editor/index.js b/packages/editor/src/components/visual-editor/index.js index 17f983293f1d46..8b4877704f5f18 100644 --- a/packages/editor/src/components/visual-editor/index.js +++ b/packages/editor/src/components/visual-editor/index.js @@ -34,7 +34,7 @@ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import EditTemplateBlocksNotification from './edit-template-blocks-notification'; import ResizableEditor from '../resizable-editor'; -import useSelectNearestEditableBlock from '../../hooks/use-select-nearest-editable-block'; +import useSelectNearestEditableBlock from './use-select-nearest-editable-block'; import { NAVIGATION_POST_TYPE, PATTERN_POST_TYPE, diff --git a/packages/editor/src/hooks/use-select-nearest-editable-block.js b/packages/editor/src/components/visual-editor/use-select-nearest-editable-block.js similarity index 98% rename from packages/editor/src/hooks/use-select-nearest-editable-block.js rename to packages/editor/src/components/visual-editor/use-select-nearest-editable-block.js index f6e621a25bf43e..5cc84cb7fb5037 100644 --- a/packages/editor/src/hooks/use-select-nearest-editable-block.js +++ b/packages/editor/src/components/visual-editor/use-select-nearest-editable-block.js @@ -8,7 +8,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { unlock } from '../lock-unlock'; +import { unlock } from '../../lock-unlock'; const DISTANCE_THRESHOLD = 500; diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index 58688a9099e879..f949be8e9321fb 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -24,7 +24,10 @@ import { GlobalStylesProvider, } from './components/global-styles-provider'; import registerDefaultActions from './dataviews/actions'; -import { registerCoreBlockBindingsSources } from './bindings/api'; +import { + registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, +} from './bindings/api'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -45,6 +48,7 @@ lock( privateApis, { ResizableEditor, registerDefaultActions, registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, // This is a temporary private API while we're updating the site editor to use EditorProvider. useBlockEditorSettings, diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js index 222004c7c1bccc..1499e255482c63 100644 --- a/test/e2e/specs/editor/various/block-bindings.spec.js +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -2172,4 +2172,33 @@ test.describe( 'Block bindings', () => { } ); } ); } ); + + test.describe( 'Sources registration', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { title: 'Test bindings' } ); + } ); + + test( 'should show the label of a source only registered in the server', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + metadata: { + bindings: { + content: { + source: 'core/server-source', + }, + }, + }, + }, + } ); + + const bindingLabel = page.locator( + '.components-item__block-bindings-source' + ); + await expect( bindingLabel ).toHaveText( 'Server Source' ); + } ); + } ); } );