Commit bf8df6d3aa7 for woocommerce
commit bf8df6d3aa78b6e84d1f9190fcd7d9b1b448d840
Author: Jan Lysý <lysyjan@users.noreply.github.com>
Date: Thu Apr 9 14:13:38 2026 +0200
Fix email preview padding for template-level blocks with preset variables (#64071)
* Resolve preset variables in Spacing_Preprocessor container padding
The Spacing_Preprocessor distributes container padding from template
groups to child blocks via get_block_horizontal_padding(), but returns
raw preset variable references (e.g. "var:preset|spacing|20") without
resolving them. This causes Content_Renderer::sum_padding_values() to
yield 0px instead of the correct value for template-level blocks like
the site title, creating a visible padding mismatch in email previews.
Pass the CSS variables map (already available in $styles['__variables_map'])
through add_block_gaps() to get_block_horizontal_padding(), which now
resolves preset references before returning padding values. This follows
the same pattern used by Blocks_Width_Preprocessor::resolve_preset_value().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract shared Preset_Variable_Resolver to eliminate duplication
The preset variable resolution logic (converting var:preset|spacing|20
to its pixel value via the CSS variables map) was duplicated in three
private methods: Content_Renderer::resolve_preset_padding(),
Blocks_Width_Preprocessor::resolve_preset_value(), and
Spacing_Preprocessor::resolve_preset_value(). A fourth copy existed
in Theme_Controller::recursive_extract_preset_variables() for the
var:preset → var(--wp--...) conversion.
Extract a shared Preset_Variable_Resolver utility class with static
methods: resolve(), to_css_var(), to_css_variable_name(), and
is_preset_reference(). Replace all four private implementations with
calls to the shared class.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix incorrect assertion in Theme_Controller_Test preset variables map
The test asserted that spacing preset slug "20" resolves to "20px", but
the theme.json defines it as "24px" (the slug is an identifier, not the
pixel value). Fix the assertion to match the actual theme definition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add unit tests for Preset_Variable_Resolver and make to_css_variable_name private
Add 10 test cases covering resolve(), is_preset_reference(), and
to_css_var() — including preset references, non-preset values, empty
map, missing keys, color presets, and zero values.
Make to_css_variable_name() private since it is only used internally
by resolve() and to_css_var().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add integration test for preset resolution in container padding
Add a Spacing_Preprocessor test that verifies the actual bug scenario:
a template group with preset padding (var:preset|spacing|20) wrapping
post-content has its padding resolved to pixel values (24px) when
distributed as container-padding to child blocks. This guards against
regressions at the integration point between the preprocessor and the
Preset_Variable_Resolver.
Also update changelog to mention the refactor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
diff --git a/packages/php/email-editor/changelog/fix-email-editor-preset-padding-resolution b/packages/php/email-editor/changelog/fix-email-editor-preset-padding-resolution
new file mode 100644
index 00000000000..99f9e6d2ab3
--- /dev/null
+++ b/packages/php/email-editor/changelog/fix-email-editor-preset-padding-resolution
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Resolve preset variable references in Spacing_Preprocessor container padding to fix padding mismatch for template-level blocks in email previews. Extract shared Preset_Variable_Resolver utility to eliminate duplicated resolution logic across Content_Renderer, Blocks_Width_Preprocessor, Spacing_Preprocessor, and Theme_Controller.
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
index f0ba2130574..a322d16cc5a 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
@@ -8,6 +8,8 @@
declare(strict_types = 1);
namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preset_Variable_Resolver;
+
/**
* This class sets the width of the blocks based on the layout width or column count.
* The final width in pixels is stored in the email_attrs array because we would like to avoid changing the original attributes.
@@ -64,8 +66,8 @@ class Blocks_Width_Preprocessor implements Preprocessor {
$layout_width -= $this->parse_number_from_string_with_pixels( $block['email_attrs']['root-padding-left'] ?? '0px' );
$layout_width -= $this->parse_number_from_string_with_pixels( $block['email_attrs']['root-padding-right'] ?? '0px' );
// Container padding may be preset references (var:preset|spacing|20).
- $layout_width -= $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $block['email_attrs']['container-padding-left'] ?? '0px', $variables_map ) );
- $layout_width -= $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $block['email_attrs']['container-padding-right'] ?? '0px', $variables_map ) );
+ $layout_width -= $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $block['email_attrs']['container-padding-left'] ?? '0px', $variables_map ) );
+ $layout_width -= $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $block['email_attrs']['container-padding-right'] ?? '0px', $variables_map ) );
}
// Resolve block padding — may be preset references like var:preset|spacing|20.
@@ -73,8 +75,8 @@ class Blocks_Width_Preprocessor implements Preprocessor {
// has been distributed per-block by the Spacing_Preprocessor. Zero it out
// so children get the full available width.
$suppress_h_padding = ! empty( $block['email_attrs']['suppress-horizontal-padding'] );
- $block_padding_left = $suppress_h_padding ? '0px' : $this->resolve_preset_value( $block['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map );
- $block_padding_right = $suppress_h_padding ? '0px' : $this->resolve_preset_value( $block['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map );
+ $block_padding_left = $suppress_h_padding ? '0px' : Preset_Variable_Resolver::resolve( $block['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map );
+ $block_padding_right = $suppress_h_padding ? '0px' : Preset_Variable_Resolver::resolve( $block['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map );
$width_input = $block['attrs']['width'] ?? '100%';
// Currently we support only % and px units in case only the number is provided we assume it's %
@@ -138,27 +140,6 @@ class Blocks_Width_Preprocessor implements Preprocessor {
return (float) str_replace( 'px', '', $value );
}
- /**
- * Resolve a CSS value that may contain a preset variable reference.
- *
- * Block attributes store padding as preset references like
- * "var:preset|spacing|20" which resolve to actual pixel values
- * (e.g. "8px"). This method converts the reference to its resolved
- * value using the variables map passed through styles.
- *
- * @param string $value The CSS value, possibly a preset reference.
- * @param array $variables_map Map of CSS variable names to resolved values.
- * @return string The resolved value (e.g. "8px") or the original value.
- */
- private function resolve_preset_value( string $value, array $variables_map ): string {
- if ( strpos( $value, 'var:preset|' ) !== 0 ) {
- return $value;
- }
-
- $css_var_name = '--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $value ) );
- return $variables_map[ $css_var_name ] ?? $value;
- }
-
/**
* Add missing column widths
*
@@ -177,8 +158,8 @@ class Blocks_Width_Preprocessor implements Preprocessor {
$defined_column_width += $this->convert_width_to_pixels( $column['attrs']['width'], $columns_width );
} else {
// When width is not set we need to add padding to the defined column width for better ratio accuracy.
- $defined_column_width += $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map ) );
- $defined_column_width += $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map ) );
+ $defined_column_width += $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map ) );
+ $defined_column_width += $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map ) );
$border_width = $column['attrs']['style']['border']['width'] ?? '0px';
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['left']['width'] ?? $border_width );
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['right']['width'] ?? $border_width );
@@ -191,8 +172,8 @@ class Blocks_Width_Preprocessor implements Preprocessor {
if ( ! isset( $column['attrs']['width'] ) || empty( $column['attrs']['width'] ) ) {
// Add padding to the specific column width because it's not included in the default width.
$column_width = $default_columns_width;
- $column_width += $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map ) );
- $column_width += $this->parse_number_from_string_with_pixels( $this->resolve_preset_value( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map ) );
+ $column_width += $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px', $variables_map ) );
+ $column_width += $this->parse_number_from_string_with_pixels( Preset_Variable_Resolver::resolve( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px', $variables_map ) );
$border_width = $column['attrs']['style']['border']['width'] ?? '0px';
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['left']['width'] ?? $border_width );
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['right']['width'] ?? $border_width );
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
index cf4134f0995..d6a81965ad8 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
@@ -8,6 +8,8 @@
declare(strict_types = 1);
namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preset_Variable_Resolver;
+
/**
* This preprocessor is responsible for setting default spacing values for blocks.
* In the early development phase, we are setting only margin-top for blocks that are not first or last in the columns block.
@@ -31,17 +33,22 @@ class Spacing_Preprocessor implements Preprocessor {
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
$root_padding = $this->get_root_padding( $styles );
$container_padding = $styles['__container_padding'] ?? array();
- $parsed_blocks = $this->add_block_gaps( $parsed_blocks, $styles['spacing']['blockGap'] ?? '', null, $root_padding, false, $container_padding );
+ $variables_map = $styles['__variables_map'] ?? array();
+ $parsed_blocks = $this->add_block_gaps( $parsed_blocks, $styles['spacing']['blockGap'] ?? '', null, $root_padding, false, $container_padding, $variables_map );
return $parsed_blocks;
}
/**
* Extract and validate horizontal padding from a block's style attributes.
*
+ * Preset variable references (e.g. "var:preset|spacing|20") are resolved
+ * to their pixel values using the variables map when provided.
+ *
* @param array $block The block to extract padding from.
+ * @param array $variables_map Map of CSS variable names to resolved values.
* @return array Padding with 'left' and 'right' keys, or empty array if invalid/absent.
*/
- private function get_block_horizontal_padding( array $block ): array {
+ private function get_block_horizontal_padding( array $block, array $variables_map = array() ): array {
$padding = $block['attrs']['style']['spacing']['padding'] ?? array();
$has_left = isset( $padding['left'] );
$has_right = isset( $padding['right'] );
@@ -57,6 +64,11 @@ class Spacing_Preprocessor implements Preprocessor {
return array();
}
+ // Resolve preset variable references (e.g. "var:preset|spacing|20")
+ // to their pixel values so downstream consumers get usable CSS values.
+ $left = Preset_Variable_Resolver::resolve( $left, $variables_map );
+ $right = Preset_Variable_Resolver::resolve( $right, $variables_map );
+
if ( $this->is_zero_value( $left ) && $this->is_zero_value( $right ) ) {
return array();
}
@@ -101,9 +113,10 @@ class Spacing_Preprocessor implements Preprocessor {
* @param array $root_padding Root horizontal padding with 'left' and 'right' keys.
* @param bool $apply_root_padding Whether this block should receive root padding (delegated by parent container).
* @param array $container_padding Container horizontal padding with 'left' and 'right' keys.
+ * @param array $variables_map Map of CSS variable names to resolved values for preset resolution.
* @return array
*/
- private function add_block_gaps( array $parsed_blocks, string $gap = '', $parent_block = null, array $root_padding = array(), bool $apply_root_padding = false, array $container_padding = array() ): array {
+ private function add_block_gaps( array $parsed_blocks, string $gap = '', $parent_block = null, array $root_padding = array(), bool $apply_root_padding = false, array $container_padding = array(), array $variables_map = array() ): array {
foreach ( $parsed_blocks as $key => $block ) {
$block_name = $block['blockName'] ?? '';
$parent_block_name = $parent_block['blockName'] ?? '';
@@ -177,7 +190,7 @@ class Spacing_Preprocessor implements Preprocessor {
// When a container wrapping post-content has its own non-zero
// horizontal padding, distribute it as container-padding to
// descendant blocks and suppress the container's own CSS padding.
- $block_padding = $this->get_block_horizontal_padding( $block );
+ $block_padding = $this->get_block_horizontal_padding( $block, $variables_map );
if ( ! empty( $block_padding ) ) {
$children_container_pad = $block_padding;
$block['email_attrs']['suppress-horizontal-padding'] = true;
@@ -186,7 +199,7 @@ class Spacing_Preprocessor implements Preprocessor {
// Root-level container with own padding that wraps post-content:
// distribute its padding as container-padding and suppress its own CSS.
$children_apply = true;
- $block_padding = $this->get_block_horizontal_padding( $block );
+ $block_padding = $this->get_block_horizontal_padding( $block, $variables_map );
if ( ! empty( $block_padding ) ) {
$children_container_pad = $block_padding;
$block['email_attrs']['suppress-horizontal-padding'] = true;
@@ -197,7 +210,7 @@ class Spacing_Preprocessor implements Preprocessor {
unset( $block['email_attrs']['root-padding-left'], $block['email_attrs']['root-padding-right'] );
}
- $block['innerBlocks'] = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block, $root_padding, $children_apply, $children_container_pad );
+ $block['innerBlocks'] = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block, $root_padding, $children_apply, $children_container_pad, $variables_map );
$parsed_blocks[ $key ] = $block;
}
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
index c6478e2c109..a334324799c 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
@@ -319,10 +319,10 @@ class Content_Renderer {
$padding = $block['attrs']['style']['spacing']['padding'] ?? array();
$result = array();
if ( isset( $padding['left'] ) && is_string( $padding['left'] ) ) {
- $result['left'] = $this->resolve_preset_padding( $padding['left'], $variables_map );
+ $result['left'] = Preset_Variable_Resolver::resolve( $padding['left'], $variables_map );
}
if ( isset( $padding['right'] ) && is_string( $padding['right'] ) ) {
- $result['right'] = $this->resolve_preset_padding( $padding['right'], $variables_map );
+ $result['right'] = Preset_Variable_Resolver::resolve( $padding['right'], $variables_map );
}
if ( ! empty( $result ) ) {
return $result;
@@ -474,25 +474,6 @@ class Content_Renderer {
return $sum;
}
- /**
- * Resolve a CSS value that may contain a preset variable reference.
- *
- * Block attributes store padding as preset references like
- * "var:preset|spacing|20" which resolve to actual pixel values.
- *
- * @param string $value The CSS value, possibly a preset reference.
- * @param array $variables_map Map of CSS variable names to resolved values.
- * @return string The resolved value (e.g. "8px") or the original value.
- */
- private function resolve_preset_padding( string $value, array $variables_map ): string {
- if ( strpos( $value, 'var:preset|' ) !== 0 ) {
- return $value;
- }
-
- $css_var_name = '--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $value ) );
- return $variables_map[ $css_var_name ] ?? $value;
- }
-
/**
* Set template globals
*
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-preset-variable-resolver.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-preset-variable-resolver.php
new file mode 100644
index 00000000000..c3d4e9cbac6
--- /dev/null
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-preset-variable-resolver.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare(strict_types = 1);
+namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer;
+
+/**
+ * Resolves WordPress preset variable references to their actual values.
+ *
+ * Block attributes store spacing values as preset references like
+ * "var:preset|spacing|20". This class provides shared methods to:
+ * - Convert these references to CSS variable names (--wp--preset--spacing--20)
+ * - Resolve them to pixel values (e.g. "20px") using a variables map
+ * - Convert them to CSS var() syntax for stylesheet use
+ *
+ * Used by Content_Renderer, Spacing_Preprocessor, and Blocks_Width_Preprocessor
+ * to avoid duplicating the same resolution logic.
+ */
+class Preset_Variable_Resolver {
+ /**
+ * Convert a preset variable reference to its CSS variable name.
+ *
+ * Transforms "var:preset|spacing|20" to "--wp--preset--spacing--20".
+ *
+ * @param string $value The preset reference string.
+ * @return string The CSS variable name.
+ */
+ private static function to_css_variable_name( string $value ): string {
+ return '--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $value ) );
+ }
+
+ /**
+ * Check if a value is a preset variable reference.
+ *
+ * @param string $value The CSS value to check.
+ * @return bool True if the value starts with "var:preset|".
+ */
+ public static function is_preset_reference( string $value ): bool {
+ return strpos( $value, 'var:preset|' ) === 0;
+ }
+
+ /**
+ * Resolve a preset variable reference to its actual value.
+ *
+ * Converts "var:preset|spacing|20" to the resolved pixel value (e.g. "20px")
+ * using the provided variables map. Returns the original value if not a preset
+ * reference or if the variable is not found in the map.
+ *
+ * @param string $value The CSS value, possibly a preset reference.
+ * @param array $variables_map Map of CSS variable names to resolved values.
+ * @return string The resolved value or the original value.
+ */
+ public static function resolve( string $value, array $variables_map ): string {
+ if ( empty( $variables_map ) || ! self::is_preset_reference( $value ) ) {
+ return $value;
+ }
+
+ $css_var_name = self::to_css_variable_name( $value );
+ return $variables_map[ $css_var_name ] ?? $value;
+ }
+
+ /**
+ * Convert a preset variable reference to CSS var() syntax.
+ *
+ * Transforms "var:preset|spacing|20" to "var(--wp--preset--spacing--20)".
+ * Returns the original value if not a preset reference.
+ *
+ * @param string $value The CSS value, possibly a preset reference.
+ * @return string The CSS var() expression or the original value.
+ */
+ public static function to_css_var( string $value ): string {
+ if ( ! self::is_preset_reference( $value ) ) {
+ return $value;
+ }
+
+ return 'var(' . self::to_css_variable_name( $value ) . ')';
+ }
+}
diff --git a/packages/php/email-editor/src/Engine/class-theme-controller.php b/packages/php/email-editor/src/Engine/class-theme-controller.php
index af6b20005a0..8e2fa632633 100644
--- a/packages/php/email-editor/src/Engine/class-theme-controller.php
+++ b/packages/php/email-editor/src/Engine/class-theme-controller.php
@@ -8,6 +8,7 @@
declare(strict_types = 1);
namespace Automattic\WooCommerce\EmailEditor\Engine;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preset_Variable_Resolver;
use WP_Block_Template;
use WP_Post;
use WP_Theme_JSON;
@@ -118,9 +119,8 @@ class Theme_Controller {
foreach ( $styles as $key => $style_value ) {
if ( is_array( $style_value ) ) {
$styles[ $key ] = $this->recursive_extract_preset_variables( $style_value );
- } elseif ( is_string( $style_value ) && strpos( $style_value, 'var:preset|' ) === 0 ) {
- /** @var string $style_value */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
- $styles[ $key ] = 'var(--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $style_value ) ) . ')';
+ } elseif ( is_string( $style_value ) && Preset_Variable_Resolver::is_preset_reference( $style_value ) ) {
+ $styles[ $key ] = Preset_Variable_Resolver::to_css_var( $style_value );
} else {
$styles[ $key ] = $style_value;
}
diff --git a/packages/php/email-editor/tests/integration/Engine/Theme_Controller_Test.php b/packages/php/email-editor/tests/integration/Engine/Theme_Controller_Test.php
index 5b58496b511..69b81f1c9e2 100644
--- a/packages/php/email-editor/tests/integration/Engine/Theme_Controller_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Theme_Controller_Test.php
@@ -192,6 +192,6 @@ class Theme_Controller_Test extends \Email_Editor_Integration_Test_Case {
public function testItReturnsCorrectPresetVariablesMap(): void {
$variable_map = $this->theme_controller->get_variables_values_map();
$this->assertSame( '#000000', $variable_map['--wp--preset--color--black'] );
- $this->assertSame( '20px', $variable_map['--wp--preset--spacing--20'] );
+ $this->assertSame( '24px', $variable_map['--wp--preset--spacing--20'] );
}
}
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
index f8c0c5c4bf7..4f97afa039e 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
@@ -912,4 +912,50 @@ class Spacing_Preprocessor_Test extends \Email_Editor_Unit_Test {
// Should not have padding-left due to malicious value.
$this->assertArrayNotHasKey( 'padding-left', $second_column['email_attrs'] );
}
+
+ /**
+ * Test preset variable references in container padding are resolved to pixel values
+ */
+ public function testItResolvesPresetReferencesInContainerPadding(): void {
+ $styles = $this->styles;
+ $styles['__variables_map'] = array(
+ '--wp--preset--spacing--20' => '24px',
+ );
+
+ $blocks = array(
+ array(
+ 'blockName' => 'core/group',
+ 'attrs' => array(
+ 'style' => array(
+ 'spacing' => array(
+ 'padding' => array(
+ 'left' => 'var:preset|spacing|20',
+ 'right' => 'var:preset|spacing|20',
+ ),
+ ),
+ ),
+ ),
+ 'innerBlocks' => array(
+ array(
+ 'blockName' => 'core/post-content',
+ 'attrs' => array(),
+ 'innerBlocks' => array(
+ array(
+ 'blockName' => 'core/paragraph',
+ 'attrs' => array(),
+ 'innerBlocks' => array(),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $result = $this->preprocessor->preprocess( $blocks, $this->layout, $styles );
+ $paragraph = $result[0]['innerBlocks'][0]['innerBlocks'][0];
+
+ // Container padding should be resolved pixel values, not raw preset references.
+ $this->assertEquals( '24px', $paragraph['email_attrs']['container-padding-left'] );
+ $this->assertEquals( '24px', $paragraph['email_attrs']['container-padding-right'] );
+ }
}
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preset_Variable_Resolver_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preset_Variable_Resolver_Test.php
new file mode 100644
index 00000000000..28a9aa1dfb8
--- /dev/null
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preset_Variable_Resolver_Test.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare(strict_types = 1);
+namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer;
+
+/**
+ * Unit test for Preset_Variable_Resolver
+ */
+class Preset_Variable_Resolver_Test extends \Email_Editor_Unit_Test {
+
+ /**
+ * Variables map used across tests.
+ *
+ * @var array
+ */
+ private array $variables_map;
+
+ /**
+ * Set up the test
+ */
+ protected function setUp(): void {
+ parent::setUp();
+ $this->variables_map = array(
+ '--wp--preset--spacing--10' => '10px',
+ '--wp--preset--spacing--20' => '24px',
+ '--wp--preset--spacing--30' => '30px',
+ '--wp--preset--color--black' => '#000000',
+ );
+ }
+
+ /**
+ * Test resolve returns pixel value for a preset reference.
+ */
+ public function testResolveConvertsPresetToPixelValue(): void {
+ $this->assertSame( '24px', Preset_Variable_Resolver::resolve( 'var:preset|spacing|20', $this->variables_map ) );
+ }
+
+ /**
+ * Test resolve returns original value when not a preset reference.
+ */
+ public function testResolveReturnsOriginalForNonPreset(): void {
+ $this->assertSame( '20px', Preset_Variable_Resolver::resolve( '20px', $this->variables_map ) );
+ }
+
+ /**
+ * Test resolve returns original value when variables map is empty.
+ */
+ public function testResolveReturnsOriginalWhenMapIsEmpty(): void {
+ $this->assertSame( 'var:preset|spacing|20', Preset_Variable_Resolver::resolve( 'var:preset|spacing|20', array() ) );
+ }
+
+ /**
+ * Test resolve returns original preset when key is not in the map.
+ */
+ public function testResolveReturnsOriginalWhenKeyNotFound(): void {
+ $this->assertSame( 'var:preset|spacing|99', Preset_Variable_Resolver::resolve( 'var:preset|spacing|99', $this->variables_map ) );
+ }
+
+ /**
+ * Test resolve works with non-spacing presets (e.g. color).
+ */
+ public function testResolveWorksWithColorPresets(): void {
+ $this->assertSame( '#000000', Preset_Variable_Resolver::resolve( 'var:preset|color|black', $this->variables_map ) );
+ }
+
+ /**
+ * Test resolve passes through zero values.
+ */
+ public function testResolvePassesThroughZeroValues(): void {
+ $this->assertSame( '0px', Preset_Variable_Resolver::resolve( '0px', $this->variables_map ) );
+ $this->assertSame( '0', Preset_Variable_Resolver::resolve( '0', $this->variables_map ) );
+ }
+
+ /**
+ * Test is_preset_reference returns true for preset references.
+ */
+ public function testIsPresetReferenceReturnsTrueForPreset(): void {
+ $this->assertTrue( Preset_Variable_Resolver::is_preset_reference( 'var:preset|spacing|20' ) );
+ $this->assertTrue( Preset_Variable_Resolver::is_preset_reference( 'var:preset|color|black' ) );
+ }
+
+ /**
+ * Test is_preset_reference returns false for non-preset values.
+ */
+ public function testIsPresetReferenceReturnsFalseForNonPreset(): void {
+ $this->assertFalse( Preset_Variable_Resolver::is_preset_reference( '20px' ) );
+ $this->assertFalse( Preset_Variable_Resolver::is_preset_reference( '0' ) );
+ $this->assertFalse( Preset_Variable_Resolver::is_preset_reference( '' ) );
+ $this->assertFalse( Preset_Variable_Resolver::is_preset_reference( 'var(--wp--preset--spacing--20)' ) );
+ }
+
+ /**
+ * Test to_css_var converts preset reference to CSS var() syntax.
+ */
+ public function testToCssVarConvertsPresetToCssVar(): void {
+ $this->assertSame( 'var(--wp--preset--spacing--20)', Preset_Variable_Resolver::to_css_var( 'var:preset|spacing|20' ) );
+ $this->assertSame( 'var(--wp--preset--color--black)', Preset_Variable_Resolver::to_css_var( 'var:preset|color|black' ) );
+ }
+
+ /**
+ * Test to_css_var returns original value for non-preset.
+ */
+ public function testToCssVarReturnsOriginalForNonPreset(): void {
+ $this->assertSame( '20px', Preset_Variable_Resolver::to_css_var( '20px' ) );
+ }
+}