diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index 3d4af47227670..fd7986e199666 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -1,14 +1,14 @@ /** * External dependencies */ -import { assign, difference, omit } from 'lodash'; +import { assign, difference } from 'lodash'; import classnames from 'classnames'; /** * WordPress dependencies */ import { Fragment } from '@wordpress/element'; -import { addFilter } from '@wordpress/hooks'; +import { addFilter, removeFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { @@ -132,19 +132,21 @@ export function getHTMLRootElementClasses( innerHTML ) { */ export function addParsedDifference( blockAttributes, blockType, innerHTML ) { if ( hasBlockSupport( blockType, 'customClassName', true ) ) { - // To determine difference, serialize block given the known set of - // attributes, with the exception of `className`. This will determine - // the default set of classes. From there, any difference in innerHTML - // can be considered as custom classes. - const attributesSansClassName = omit( blockAttributes, [ 'className' ] ); - const serialized = getSaveContent( blockType, attributesSansClassName ); + // To determine difference, serialize the block given the known set of + // attributes to determine the default set of classes. From there, any + // difference in innerHTML can be considered as custom classes. + removeFilter( 'blocks.getSaveContent.extraProps', 'core/custom-class-name/save-props' ); + const serialized = getSaveContent( blockType, blockAttributes ); + addFilter( 'blocks.getSaveContent.extraProps', 'core/custom-class-name/save-props', addSaveProps ); const defaultClasses = getHTMLRootElementClasses( serialized ); const actualClasses = getHTMLRootElementClasses( innerHTML ); const customClasses = difference( actualClasses, defaultClasses ); - if ( customClasses.length ) { - blockAttributes.className = customClasses.join( ' ' ); - } else if ( serialized ) { + blockAttributes.className = classnames( { + [ blockAttributes.className ]: ! serialized, + }, ...customClasses ); + + if ( ! blockAttributes.className ) { delete blockAttributes.className; } } diff --git a/packages/block-editor/src/hooks/test/custom-class-name.js b/packages/block-editor/src/hooks/test/custom-class-name.js index 6b2c84b886139..c03703ab45b92 100644 --- a/packages/block-editor/src/hooks/test/custom-class-name.js +++ b/packages/block-editor/src/hooks/test/custom-class-name.js @@ -10,7 +10,13 @@ import { getHTMLRootElementClasses } from '../custom-class-name'; describe( 'custom className', () => { const blockSettings = { - save: () =>
, + save: ( { attributes } ) => { + if ( attributes.className === 'is-varied-on-classname' ) { + return
; + } + + return
; + }, category: 'common', title: 'block title', }; @@ -157,6 +163,16 @@ describe( 'custom className', () => { expect( attributes.className ).toBe( 'custom1 custom3' ); } ); + it( 'should allow save to consider the className attribute', () => { + const attributes = addParsedDifference( + { className: 'is-varied-on-classname' }, + blockSettings, + '
' + ); + + expect( attributes.className ).toBe( 'is-varied-on-classname foo' ); + } ); + it( 'should not remove the custom classes for dynamic blocks', () => { const attributes = addParsedDifference( { className: 'custom1' }, diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index 452c8f832a43f..4cb616ac7b4f4 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -620,30 +620,30 @@ export function isEquivalentHTML( actual, expected ) { * * Logs to console in development environments when invalid. * - * @param {string|Object} blockTypeOrName Block type. - * @param {Object} attributes Parsed block attributes. - * @param {string} innerHTML Original block content. + * @param {string|Object} blockTypeOrName Block type. + * @param {Object} attributes Parsed block attributes. + * @param {string} expectedBlockContent Original block content. * * @return {boolean} Whether block is valid. */ -export function isValidBlockContent( blockTypeOrName, attributes, innerHTML ) { +export function isValidBlockContent( blockTypeOrName, attributes, expectedBlockContent ) { const blockType = normalizeBlockType( blockTypeOrName ); - let saveContent; + let actualBlockContent; try { - saveContent = getSaveContent( blockType, attributes ); + actualBlockContent = getSaveContent( blockType, attributes ); } catch ( error ) { log.error( 'Block validation failed because an error occurred while generating block content:\n\n%s', error.toString() ); return false; } - const isValid = isEquivalentHTML( innerHTML, saveContent ); + const isValid = isEquivalentHTML( expectedBlockContent, actualBlockContent ); if ( ! isValid ) { log.error( 'Block validation failed for `%s` (%o).\n\nExpected:\n\n%s\n\nActual:\n\n%s', blockType.name, blockType, - saveContent, - innerHTML + expectedBlockContent, + actualBlockContent ); }