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.' );
+	}
+}