Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions packages/block-editor/src/hooks/block-fields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useContext, useState, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import { replacePatternOverridesDefaultBinding } from '../../utils/block-bindings';
import BlockContext from '../../components/block-context';
import BlockIcon from '../../components/block-icon';
import useBlockDisplayTitle from '../../components/block-title/use-block-display-title';
Expand Down Expand Up @@ -80,15 +81,39 @@ function BlockFields( {
return _attributes;
}

/*
* The pattern overrides `__default` binding is a placeholder, not
* a real attribute: expand it to the block's bindable attributes
* before resolving values, as other bindings consumers do.
*/
const { __experimentalBlockBindingsSupportedAttributes } =
select( blockEditorStore ).getSettings();
const bindableAttributes =
__experimentalBlockBindingsSupportedAttributes?.[
blockType?.name
];
const bindings = bindableAttributes
? replacePatternOverridesDefaultBinding(
_attributes.metadata.bindings,
bindableAttributes
)
: _attributes.metadata.bindings;

const { getBlockBindingsSource } = unlock( select( blocksStore ) );
return Object.entries( _attributes.metadata.bindings ).reduce(
return Object.entries( bindings ).reduce(
( acc, [ attribute, binding ] ) => {
// Skip a `__default` binding that could not be expanded:
// it is not a real block attribute.
if ( attribute === '__default' ) {
return acc;
}
const source = getBlockBindingsSource( binding.source );
if ( ! source ) {
return acc;
}
const values = source.getValues( {
select,
clientId,
context: blockContext,
bindings: { [ attribute ]: binding },
} );
Expand All @@ -97,7 +122,7 @@ function BlockFields( {
_attributes
);
},
[ blockContext, clientId ]
[ blockContext, clientId, blockType?.name ]
);
const { selectBlock, toggleBlockHighlight } =
useDispatch( blockEditorStore );
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/bindings/pattern-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default {
// Check undefined because empty string is a valid value.
if ( overridableValue === undefined ) {
overridesValues[ attributeName ] =
currentBlockAttributes[ attributeName ];
currentBlockAttributes?.[ attributeName ];
continue;
} else {
overridesValues[ attributeName ] =
Expand Down
65 changes: 65 additions & 0 deletions packages/editor/src/bindings/test/pattern-overrides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Internal dependencies
*/
import patternOverridesBindings from '../pattern-overrides';

describe( 'pattern-overrides bindings', () => {
const blockAttributes = {
content: 'original content',
metadata: {
name: 'Editable Paragraph',
bindings: {
content: { source: 'core/pattern-overrides' },
},
},
};

const makeSelect = ( attributes ) => () => ( {
getBlockAttributes: () => attributes,
} );

describe( 'getValues', () => {
it( 'returns the current attribute value when there is no override', () => {
const values = patternOverridesBindings.getValues( {
select: makeSelect( blockAttributes ),
clientId: 'block-1',
context: {},
bindings: {
content: { source: 'core/pattern-overrides' },
},
} );

expect( values ).toEqual( { content: 'original content' } );
} );

it( 'returns the override value when the context provides one', () => {
const values = patternOverridesBindings.getValues( {
select: makeSelect( blockAttributes ),
clientId: 'block-1',
context: {
'pattern/overrides': {
'Editable Paragraph': { content: 'overridden' },
},
},
bindings: {
content: { source: 'core/pattern-overrides' },
},
} );

expect( values ).toEqual( { content: 'overridden' } );
} );

it( 'does not throw when block attributes are unavailable', () => {
expect( () =>
patternOverridesBindings.getValues( {
select: makeSelect( null ),
clientId: 'missing-client-id',
context: {},
bindings: {
__default: { source: 'core/pattern-overrides' },
},
} )
).not.toThrow();
} );
} );
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* WordPress dependencies
*/
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );

// The Block Fields panel resolves bound attribute values when rendering the
// inspector. A `core/pattern-overrides` binding used to crash it with
// "TypeError: Cannot read properties of null". These tests rely on the e2e
// fixtures failing any test that logs a console error.
// See https://github.com/WordPress/gutenberg/issues/79090.
test.describe( 'Block Fields with pattern overrides bindings', () => {
test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.setGutenbergExperiments( [
'gutenberg-content-only-inspector-fields',
] );
} );

test.beforeEach( async ( { admin } ) => {
await admin.createNewPost( { title: 'Block Fields bindings' } );
} );

test.afterEach( async ( { requestUtils } ) => {
await requestUtils.deleteAllPosts();
} );

test.afterAll( async ( { requestUtils } ) => {
await requestUtils.setGutenbergExperiments( [] );
} );

test( 'renders the panel for a `__default` binding without crashing', async ( {
editor,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: 'Overridable paragraph',
metadata: {
name: 'Editable Paragraph',
bindings: {
__default: { source: 'core/pattern-overrides' },
},
},
},
} );

const paragraph = editor.canvas.getByRole( 'document', {
name: 'Block: Paragraph',
} );
await paragraph.click();
await editor.openDocumentSettingsSidebar();

// The canvas keeps rendering the block: no crash boundary fallback.
await expect( paragraph ).toHaveText( 'Overridable paragraph' );
} );

test( 'renders the panel for an expanded binding without crashing', async ( {
editor,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: 'Expanded binding paragraph',
metadata: {
name: 'Editable Paragraph',
bindings: {
content: { source: 'core/pattern-overrides' },
},
},
},
} );

const paragraph = editor.canvas.getByRole( 'document', {
name: 'Block: Paragraph',
} );
await paragraph.click();
await editor.openDocumentSettingsSidebar();

await expect( paragraph ).toHaveText( 'Expanded binding paragraph' );
} );
} );
Loading