Commit 7eb20e53a07 for woocommerce
commit 7eb20e53a07c6811e817b39b18db76795b2fc63b
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Tue Jun 16 14:20:29 2026 +0200
Fix custom font sizes not working on the Breadcrumbs block (#65537)
* Add changelog
* Fix custom font sizes not working on the Breadcrumbs block
* Enhancements
* Update changelog file
* Simplify code
* Add extra test
* PHPStan
* CodeRabbit suggestions
* CodeRabbit suggestions (II)
* Prevent errors if is not an object
* Make sure theme.json Breadcrumbs font size is also applied in the editor, matching the frontend
* Add additional explanatory comment
diff --git a/plugins/woocommerce/changelog/fix-breadcrumbs-block-custom-font-size b/plugins/woocommerce/changelog/fix-breadcrumbs-block-custom-font-size
new file mode 100644
index 00000000000..8ba919a5ffb
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-breadcrumbs-block-custom-font-size
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Fix custom font sizes not working on the Breadcrumbs block
+
+
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/edit.tsx
index b091ebfca33..2f33f2e339c 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/edit.tsx
@@ -4,16 +4,48 @@
import { useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
+import type { BlockEditProps } from '@wordpress/blocks';
-export interface Attributes {
+/**
+ * Internal dependencies
+ */
+import { useBreadcrumbsThemeFontSize } from './hooks';
+
+export type Attributes = {
className?: string;
-}
+ fontSize?: string;
+};
-const Edit = () => {
+const Edit = ( { attributes }: BlockEditProps< Attributes > ) => {
const blockProps = useBlockProps( {
className: 'woocommerce wc-block-breadcrumbs',
} );
+ const themeFontSize = useBreadcrumbsThemeFontSize();
+
+ // Remove the default 'has-small-font-size' class when the block has a
+ // custom font size defined in theme.json.
+ // This is needed because block.json defines a default font size, which is
+ // considered an anti-pattern since styles should be defined by themes and
+ // plugins instead.
+ // As a result, font sizes defined in theme.json will take priority over a
+ // `small` font size selected in the editor. When selecting other font
+ // sizes, the editor font size will take priority over the theme.json font
+ // size as expected.
+ // That's a trade-off we are making until we can migrate this block to a new
+ // version or to the WP core Breadcrumbs block.
+ if (
+ attributes.fontSize === 'small' &&
+ themeFontSize &&
+ themeFontSize !== 'var(--wp--preset--font-size--small)'
+ ) {
+ blockProps.className = blockProps.className
+ .split( ' ' )
+ .filter( ( cls ) => cls && cls !== 'has-small-font-size' )
+ .join( ' ' );
+ blockProps.style.fontSize = themeFontSize;
+ }
+
return (
<div { ...blockProps }>
<Disabled>
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/hooks.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/hooks.ts
new file mode 100644
index 00000000000..e37352ce490
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/breadcrumbs/hooks.ts
@@ -0,0 +1,42 @@
+/**
+ * External dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { store as blockEditorStore } from '@wordpress/block-editor';
+
+type EditorSettingsWithGlobalStyles = Record< string | symbol, unknown > & {
+ blocks?: Record<
+ string,
+ {
+ typography?: {
+ fontSize?: string;
+ };
+ }
+ >;
+};
+
+/**
+ * Returns the theme.json font size for the Store Breadcrumbs block.
+ */
+export function useBreadcrumbsThemeFontSize(): string | undefined {
+ return useSelect( ( select ) => {
+ const settings = select(
+ blockEditorStore
+ ).getSettings() as unknown as Record< string | symbol, unknown >;
+
+ const globalStylesKey = Object.getOwnPropertySymbols( settings ).find(
+ ( key ) => key.description === 'globalStylesDataKey'
+ );
+
+ if ( ! globalStylesKey ) {
+ return undefined;
+ }
+
+ const globalStyles = settings[
+ globalStylesKey
+ ] as EditorSettingsWithGlobalStyles;
+
+ return globalStyles?.blocks?.[ 'woocommerce/breadcrumbs' ]?.typography
+ ?.fontSize;
+ }, [] );
+}
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index b6e12167b47..abea0a1a209 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -50649,12 +50649,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/AttributeFilter.php
- -
- message: '#^Parameter \$block of method Automattic\\WooCommerce\\Blocks\\BlockTypes\\Breadcrumbs\:\:render\(\) has invalid type Automattic\\WooCommerce\\Blocks\\BlockTypes\\WP_Block\.$#'
- identifier: class.notFound
- count: 1
- path: src/Blocks/BlockTypes/Breadcrumbs.php
-
-
message: '#^Method Automattic\\WooCommerce\\Blocks\\BlockTypes\\Cart\:\:enqueue_assets\(\) has no return type specified\.$#'
identifier: missingType.return
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php b/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php
index b44a735be91..3b33961c63a 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php
@@ -1,8 +1,10 @@
<?php
+declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
+use WP_Block;
/**
* Breadcrumbs class.
@@ -36,9 +38,9 @@ class Breadcrumbs extends AbstractBlock {
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'font_size' ) );
- $font_size_classes_and_styles = $this->get_font_size_classes_and_styles( $attributes );
- $classes_and_styles['classes'] = $classes_and_styles['classes'] . ' ' . $font_size_classes_and_styles['class'] . ' ';
- $classes_and_styles['styles'] = $classes_and_styles['styles'] . ' ' . $font_size_classes_and_styles['style'] . ' ';
+ $font_size_classes_and_styles = $this->get_font_size_classes_and_styles( $attributes, $block );
+ $classes_and_styles['classes'] = $classes_and_styles['classes'] . ' ' . ( $font_size_classes_and_styles['class'] ?? '' ) . ' ';
+ $classes_and_styles['styles'] = $classes_and_styles['styles'] . ' ' . ( $font_size_classes_and_styles['style'] ?? '' ) . ' ';
$wrapper_attributes = get_block_wrapper_attributes(
array(
@@ -47,6 +49,15 @@ class Breadcrumbs extends AbstractBlock {
)
);
+ $has_non_small_custom_font_size = strpos( $font_size_classes_and_styles['class'] ?? '', 'has-small-font-size' ) === false;
+
+ // Remove the default 'has-small-font-size' class when the block has a custom font size different from small.
+ // This is needed because the block.json defines a default font size, which is considered an anti-pattern
+ // since styles should be defined by themes and plugins instead.
+ if ( $has_non_small_custom_font_size ) {
+ $wrapper_attributes = str_replace( 'has-small-font-size', '', $wrapper_attributes );
+ }
+
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
@@ -66,36 +77,81 @@ class Breadcrumbs extends AbstractBlock {
/**
* Gets font size classes and styles for the breadcrumbs block.
*
- * Note: This implementation intentionally avoids using StyleAttributesUtils::get_font_size_class_and_style()
- * and get_block_wrapper_attributes() to ensure style attributes take precedence over the class attribute fontSize.
- * This is needed because the block.json defines a default fontSize, which is considered an anti-pattern
- * since styles should be defined by themes and plugins instead.
- *
- * @param array $attributes The block attributes.
+ * @param array $attributes The block attributes.
+ * @param WP_Block $block The block instance.
* @return array The font size classes and styles.
*/
- private function get_font_size_classes_and_styles( $attributes ) {
- $font_size = $attributes['fontSize'] ?? '';
-
+ private function get_font_size_classes_and_styles( array $attributes, $block ) {
$custom_font_size = $attributes['style']['typography']['fontSize'] ?? '';
- if ( ! $font_size && '' === $custom_font_size ) {
+ if ( '' !== $custom_font_size ) {
return array(
'class' => null,
+ 'style' => sprintf( 'font-size: %s;', $custom_font_size ),
+ );
+ }
+
+ $explicit_font_size = isset( $block->parsed_block['attrs']['fontSize'] ) ? $block->parsed_block['attrs']['fontSize'] : null;
+
+ if ( is_string( $explicit_font_size ) && '' !== $explicit_font_size ) {
+ return array(
+ 'class' => sprintf( 'has-font-size has-%s-font-size', $explicit_font_size ),
'style' => null,
);
}
- if ( '' !== $custom_font_size ) {
+ $theme_font_size_classes_and_styles = $this->get_theme_font_size_classes_and_styles();
+
+ if ( $theme_font_size_classes_and_styles['class'] || $theme_font_size_classes_and_styles['style'] ) {
+ return $theme_font_size_classes_and_styles;
+ }
+
+ $font_size = $attributes['fontSize'] ?? '';
+
+ if ( $font_size ) {
return array(
- 'class' => null,
- 'style' => sprintf( 'font-size: %s;', $custom_font_size ),
+ 'class' => sprintf( 'has-font-size has-%s-font-size', $font_size ),
+ 'style' => null,
);
}
return array(
- 'class' => sprintf( 'has-font-size has-%s-font-size', $font_size ),
+ 'class' => null,
'style' => null,
);
}
+
+ /**
+ * Gets font size classes and styles from theme.json block styles.
+ *
+ * @return array The font size classes and styles.
+ */
+ private function get_theme_font_size_classes_and_styles() {
+ $theme_font_size = wp_get_global_styles(
+ array( 'blocks', 'woocommerce/breadcrumbs', 'typography', 'fontSize' )
+ );
+
+ if ( ! is_string( $theme_font_size ) || '' === $theme_font_size ) {
+ return array(
+ 'class' => null,
+ 'style' => null,
+ );
+ }
+
+ $preset_prefix = 'var(--wp--preset--font-size--';
+
+ if ( str_starts_with( $theme_font_size, $preset_prefix ) ) {
+ $slug = rtrim( substr( $theme_font_size, strlen( $preset_prefix ) ), ')' );
+
+ return array(
+ 'class' => sprintf( 'has-font-size has-%s-font-size', $slug ),
+ 'style' => null,
+ );
+ }
+
+ return array(
+ 'class' => null,
+ 'style' => sprintf( 'font-size: %s;', $theme_font_size ),
+ );
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/BreadcrumbsTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/BreadcrumbsTest.php
new file mode 100644
index 00000000000..4c68325012d
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/BreadcrumbsTest.php
@@ -0,0 +1,195 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+
+use WP_Theme_JSON_Data;
+use WP_UnitTestCase;
+
+/**
+ * Tests for the Breadcrumbs block type.
+ */
+class BreadcrumbsTest extends WP_UnitTestCase {
+
+ /**
+ * Active theme before each test, restored in tearDown.
+ *
+ * @var string
+ */
+ private string $original_theme;
+
+ /**
+ * Set up test fixtures.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->original_theme = get_stylesheet();
+ }
+
+ /**
+ * Tear down test fixtures.
+ */
+ public function tearDown(): void {
+ remove_all_filters( 'wp_theme_json_data_user' );
+ switch_theme( $this->original_theme );
+ parent::tearDown();
+ }
+
+ /**
+ * Render the Breadcrumbs block via do_blocks().
+ *
+ * @param string $attrs_json JSON object string for block attributes, e.g. '{"fontSize":"large"}'.
+ * @return string Rendered markup.
+ */
+ private function render_breadcrumbs( string $attrs_json = '' ): string {
+ $attrs = '' !== $attrs_json ? ' ' . $attrs_json : '';
+ return do_blocks( "<!-- wp:woocommerce/breadcrumbs{$attrs} /-->" );
+ }
+
+ /**
+ * Switch to a block theme for global styles resolution.
+ */
+ private function switch_to_block_theme(): void {
+ switch_theme( 'twentytwentyfour' );
+ }
+
+ /**
+ * Inject a theme.json font size for the breadcrumbs block via user global styles.
+ *
+ * @param string $font_size CSS font-size value, e.g. var(--wp--preset--font-size--large).
+ */
+ private function set_breadcrumbs_theme_font_size( string $font_size ): void {
+ add_filter(
+ 'wp_theme_json_data_user',
+ function ( $theme_json ) use ( $font_size ) {
+ $data = $theme_json->get_data();
+ $data['styles']['blocks']['woocommerce/breadcrumbs']['typography']['fontSize'] = $font_size;
+ return new WP_Theme_JSON_Data( $data, 'user' );
+ }
+ );
+ }
+
+ /**
+ * @testdox Should render the default wrapper with base and font-size classes.
+ */
+ public function test_default_wrapper_has_base_and_font_size_classes(): void {
+ $markup = $this->render_breadcrumbs();
+
+ $this->assertStringContainsString( 'wc-block-breadcrumbs', $markup, 'Wrapper should include the block class.' );
+ $this->assertStringContainsString( 'has-small-font-size', $markup, 'Default font size from block.json should be small.' );
+ $this->assertStringContainsString( 'alignwide', $markup, 'Default align from block.json should be wide.' );
+ }
+
+ /**
+ * @testdox Should apply preset large font-size class and remove the default small class.
+ */
+ public function test_preset_large_font_size(): void {
+ $markup = $this->render_breadcrumbs( '{"fontSize":"large"}' );
+
+ $this->assertStringContainsString( 'has-large-font-size', $markup, 'Preset large font size class should be present.' );
+ $this->assertStringNotContainsString( 'has-small-font-size', $markup, 'Default small font size class should be removed for large preset.' );
+ }
+
+ /**
+ * @testdox Should keep has-small-font-size when preset font size is small.
+ */
+ public function test_preset_small_font_size(): void {
+ $markup = $this->render_breadcrumbs( '{"fontSize":"small"}' );
+
+ $this->assertStringContainsString( 'has-small-font-size', $markup, 'Preset small font size class should be present.' );
+ }
+
+ /**
+ * @testdox Should apply custom typography font-size inline style and remove the default small class.
+ */
+ public function test_custom_typography_font_size(): void {
+ $markup = $this->render_breadcrumbs( '{"style":{"typography":{"fontSize":"2rem"}}}' );
+
+ $this->assertStringContainsString( 'font-size: 2rem', $markup, 'Custom font size should be applied as inline style.' );
+ $this->assertStringNotContainsString( 'has-small-font-size', $markup, 'Default small font size class should be removed for custom typography.' );
+ }
+
+ /**
+ * @testdox Should apply other styling attributes.
+ */
+ public function test_align_full(): void {
+ $markup = $this->render_breadcrumbs( '{"align":"full","textColor":"contrast"}' );
+
+ $this->assertStringContainsString( 'alignfull', $markup, 'Full alignment class should be present.' );
+ $this->assertStringContainsString( 'has-contrast-color', $markup, 'Text color class from palette slug should be present.' );
+ }
+
+ /**
+ * @testdox Should remove has-small-font-size when theme font size is large.
+ */
+ public function test_theme_large_font_size_removes_small_class(): void {
+ $this->switch_to_block_theme();
+ $this->assertTrue( wp_is_block_theme(), 'Block theme is required for wp_get_global_styles().' );
+
+ $this->set_breadcrumbs_theme_font_size( 'var(--wp--preset--font-size--large)' );
+
+ $markup = $this->render_breadcrumbs();
+
+ $this->assertStringNotContainsString( 'has-small-font-size', $markup, 'Theme large font size should remove the default small class.' );
+ $this->assertStringContainsString( 'has-large-font-size', $markup, 'Theme large font size should add the large font size class.' );
+ }
+
+ /**
+ * @testdox Should keep has-small-font-size when theme font size is explicitly small.
+ */
+ public function test_theme_small_font_size_keeps_small_class(): void {
+ $this->switch_to_block_theme();
+ $this->assertTrue( wp_is_block_theme(), 'Block theme is required for wp_get_global_styles().' );
+
+ $this->set_breadcrumbs_theme_font_size( 'var(--wp--preset--font-size--small)' );
+
+ $markup = $this->render_breadcrumbs();
+
+ $this->assertStringContainsString( 'has-small-font-size', $markup, 'Theme small font size should keep the small class.' );
+ }
+
+ /**
+ * @testdox Should apply theme custom numeric font-size as inline style and remove the default small class.
+ */
+ public function test_theme_custom_numeric_font_size(): void {
+ $this->switch_to_block_theme();
+ $this->assertTrue( wp_is_block_theme(), 'Block theme is required for wp_get_global_styles().' );
+
+ $this->set_breadcrumbs_theme_font_size( '50px' );
+
+ $markup = $this->render_breadcrumbs();
+
+ $this->assertStringContainsString( 'font-size: 50px', $markup, 'Theme custom numeric font size should be applied as inline style.' );
+ $this->assertStringNotContainsString( 'has-small-font-size', $markup, 'Theme custom numeric font size should remove the default small class.' );
+ }
+
+ /**
+ * @testdox Should apply preset font-size class when theme font size is also set.
+ */
+ public function test_preset_font_size_with_theme_override(): void {
+ $this->switch_to_block_theme();
+ $this->set_breadcrumbs_theme_font_size( 'var(--wp--preset--font-size--large)' );
+
+ $markup = $this->render_breadcrumbs( '{"fontSize":"small"}' );
+
+ $this->assertStringContainsString( 'has-font-size', $markup, 'Preset small font size should add the has-font-size class.' );
+ $this->assertStringContainsString( 'has-small-font-size', $markup, 'Breadcrumbs block font size should take priority over theme font size.' );
+ $this->assertStringNotContainsString( 'has-large-font-size', $markup, 'Theme large font size should not be applied.' );
+ }
+
+ /**
+ * @testdox Should apply custom typography font size and remove default small class when theme font size is also set.
+ */
+ public function test_custom_typography_takes_precedence_over_theme(): void {
+ $this->switch_to_block_theme();
+ $this->set_breadcrumbs_theme_font_size( 'var(--wp--preset--font-size--large)' );
+
+ $markup = $this->render_breadcrumbs( '{"style":{"typography":{"fontSize":"3rem"}}}' );
+
+ $this->assertStringContainsString( 'font-size:', $markup, 'Custom typography font size should be applied as inline style.' );
+ $this->assertStringContainsString( '3rem', $markup, 'Custom typography value should appear in the rendered style.' );
+ $this->assertStringNotContainsString( 'has-small-font-size', $markup, 'Custom typography should remove the default small class.' );
+ $this->assertStringNotContainsString( 'has-large-font-size', $markup, 'Theme large font size should not be applied.' );
+ }
+}