diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php
index ef950f80ce9448..b51fec1c5c99fb 100644
--- a/lib/block-supports/duotone.php
+++ b/lib/block-supports/duotone.php
@@ -426,128 +426,12 @@ function gutenberg_register_duotone_support( $block_type ) {
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
+ * @deprecated 6.3.0 Use WP_Duotone_Gutenberg::render_duotone_support() instead.
* @return string Filtered block content.
*/
function gutenberg_render_duotone_support( $block_content, $block ) {
- $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
-
- $duotone_support = false;
- if ( $block_type && property_exists( $block_type, 'supports' ) ) {
- $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
- }
-
- $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
-
- if (
- ! $duotone_support ||
- ! $has_duotone_attribute
- ) {
- return $block_content;
- }
-
- // Possible values for duotone attribute:
- // 1. Array of colors - e.g. array('#000000', '#ffffff').
- // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|green-blue'.
- // 3. A CSS string - e.g. 'unset' to remove globally applied duotone.
- $duotone_attr = $block['attrs']['style']['color']['duotone'];
-
- $is_preset = is_string( $duotone_attr ) && strpos( $duotone_attr, 'var:preset|duotone|' ) === 0;
- $is_css = is_string( $duotone_attr ) && strpos( $duotone_attr, 'var:preset|duotone|' ) === false;
- $is_custom = is_array( $duotone_attr );
-
- // Generate the pieces needed for rendering a duotone to the page.
- if ( $is_preset ) {
- // Extract the slug from the preset variable string.
- $slug = str_replace( 'var:preset|duotone|', '', $duotone_attr );
-
- // Utilize existing preset CSS custom property.
- $filter_property = "var(--wp--preset--duotone--$slug)";
- } elseif ( $is_css ) {
- // Build a unique slug for the filter based on the CSS value.
- $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) );
-
- // Pass through the CSS value.
- $filter_property = $duotone_attr;
- } elseif ( $is_custom ) {
- // Build a unique slug for the filter based on the array of colors.
- $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) );
-
- // This has the same shape as a preset, so it can be used in place of a
- // preset when getting the filter property and SVG filter.
- $filter_data = array(
- 'slug' => $slug,
- 'colors' => $duotone_attr,
- );
-
- // Build a customized CSS filter property for unique slug.
- $filter_property = gutenberg_get_duotone_filter_property( $filter_data );
-
- // SVG will be output on the page later.
- $filter_svg = gutenberg_get_duotone_filter_svg( $filter_data );
- }
-
- // - Applied as a class attribute to the block wrapper.
- // - Used as a selector to apply the filter to the block.
- $filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) );
-
- // Build the CSS selectors to which the filter will be applied.
- $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_support );
-
- // Calling gutenberg_style_engine_get_stylesheet_from_css_rules ensures that
- // the styles are rendered in an inline for block supports because we're
- // using the `context` option to instruct it so.
- gutenberg_style_engine_get_stylesheet_from_css_rules(
- array(
- array(
- 'selector' => $selector,
- 'declarations' => array(
- // !important is needed because these styles
- // render before global styles,
- // and they should be overriding the duotone
- // filters set by global styles.
- 'filter' => $filter_property . ' !important',
- ),
- ),
- ),
- array(
- 'context' => 'block-supports',
- )
- );
-
- // If we needed to generate an SVG, output it on the page.
- if ( isset( $filter_svg ) ) {
- add_action(
- 'wp_footer',
- static function () use ( $filter_svg, $selector ) {
- echo $filter_svg;
-
- /*
- * Safari renders elements incorrectly on first paint when the
- * SVG filter comes after the content that it is filtering, so
- * we force a repaint with a WebKit hack which solves the issue.
- */
- global $is_safari;
- if ( $is_safari ) {
- /*
- * Simply accessing el.offsetHeight flushes layout and style
- * changes in WebKit without having to wait for setTimeout.
- */
- printf(
- '',
- wp_json_encode( $selector )
- );
- }
- }
- );
- }
-
- // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
- return preg_replace(
- '/' . preg_quote( 'class="', '/' ) . '/',
- 'class="' . $filter_id . ' ',
- $block_content,
- 1
- );
+ _deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone_Gutenberg::render_duotone_support' );
+ return WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block );
}
// Register the block support.
@@ -558,6 +442,10 @@ static function () use ( $filter_svg, $selector ) {
)
);
+add_action( 'wp_loaded', array( 'WP_Duotone_Gutenberg', 'set_global_styles_presets' ), 10 );
+add_action( 'wp_loaded', array( 'WP_Duotone_Gutenberg', 'set_global_style_block_names' ), 10 );
// Remove WordPress core filter to avoid rendering duplicate support elements.
remove_filter( 'render_block', 'wp_render_duotone_support', 10, 2 );
-add_filter( 'render_block', 'gutenberg_render_duotone_support', 10, 2 );
+add_filter( 'render_block', array( 'WP_Duotone_Gutenberg', 'render_duotone_support' ), 10, 2 );
+add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_global_styles' ), 11 );
+add_action( 'wp_footer', array( 'WP_Duotone_Gutenberg', 'output_footer_assets' ), 10 );
diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php
new file mode 100644
index 00000000000000..29e19a93ad0ae7
--- /dev/null
+++ b/lib/class-wp-duotone-gutenberg.php
@@ -0,0 +1,370 @@
+
+ * [
+ * 'slug' => 'blue-orange',
+ * 'colors' => [ '#0000ff', '#ffcc00' ],
+ * ]
+ * ],
+ * …
+ * ]
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private static $global_styles_presets = array();
+
+ /**
+ * An array of block names from global, theme, and custom styles that have duotone presets. We'll use this to quickly
+ * check if a block being rendered needs to have duotone applied, and which duotone preset to use.
+ *
+ * Example:
+ * [
+ * 'core/featured-image' => 'blue-orange',
+ * …
+ * ]
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private static $global_styles_block_names = array();
+
+ /**
+ * An array of Duotone SVG and CSS output needed for the frontend duotone rendering based on what is
+ * being output on the page. Organized by a slug of the preset/color group and the information needed
+ * to generate the SVG and CSS at render.
+ *
+ * Example:
+ * [
+ * 'blue-orange' => [
+ * 'slug' => 'blue-orange',
+ * 'colors' => [ '#0000ff', '#ffcc00' ],
+ * ],
+ * 'wp-duotone-000000-ffffff-2' => [
+ * 'slug' => 'wp-duotone-000000-ffffff-2',
+ * 'colors' => [ '#000000', '#ffffff' ],
+ * ],
+ * ]
+ *
+ * @since 6.3.0
+ * @var array
+ */
+ private static $output = array();
+
+ /**
+ * Prefix used for generating and referencing duotone CSS custom properties.
+ */
+ const CSS_VAR_PREFIX = '--wp--preset--duotone--';
+
+ /**
+ * Get all possible duotone presets from global and theme styles and store as slug => [ colors array ]
+ * We only want to process this one time. On block render we'll access and output only the needed presets for that page.
+ */
+ public static function set_global_styles_presets() {
+ // Get the per block settings from the theme.json.
+ $tree = gutenberg_get_global_settings();
+ $presets_by_origin = _wp_array_get( $tree, array( 'color', 'duotone' ), array() );
+
+ foreach ( $presets_by_origin as $presets ) {
+ foreach ( $presets as $preset ) {
+ self::$global_styles_presets[ _wp_to_kebab_case( $preset['slug'] ) ] = array(
+ 'slug' => $preset['slug'],
+ 'colors' => $preset['colors'],
+ );
+ }
+ }
+ }
+
+ /**
+ * Scrape all block names from global styles and store in self::$global_styles_block_names
+ */
+ public static function set_global_style_block_names() {
+ // Get the per block settings from the theme.json.
+ $tree = WP_Theme_JSON_Resolver::get_merged_data();
+ $block_nodes = $tree->get_styles_block_nodes();
+ $theme_json = $tree->get_raw_data();
+
+ foreach ( $block_nodes as $block_node ) {
+ // This block definition doesn't include any duotone settings. Skip it.
+ if ( empty( $block_node['duotone'] ) ) {
+ continue;
+ }
+
+ // Value looks like this: 'var(--wp--preset--duotone--blue-orange)' or 'var:preset|duotone|default-filter'.
+ $duotone_attr_path = array_merge( $block_node['path'], array( 'filter', 'duotone' ) );
+ $duotone_attr = _wp_array_get( $theme_json, $duotone_attr_path, array() );
+
+ if ( empty( $duotone_attr ) ) {
+ continue;
+ }
+ // If it has a duotone filter preset, save the block name and the preset slug.
+ $slug = self::gutenberg_get_slug_from_attr( $duotone_attr );
+
+ if ( $slug && $slug !== $duotone_attr ) {
+ self::$global_styles_block_names[ $block_node['name'] ] = $slug;
+ }
+ }
+ }
+
+ /**
+ * Take the inline CSS duotone variable from a block and return the slug. Handles styles slugs like:
+ * var:preset|duotone|default-filter
+ * var(--wp--preset--duotone--blue-orange)
+ *
+ * @param string $duotone_attr The duotone attribute from a block.
+ * @return string The slug of the duotone preset or an empty string if no slug is found.
+ */
+ private static function gutenberg_get_slug_from_attr( $duotone_attr ) {
+ // Uses Branch Reset Groups `(?|…)` to return one capture group.
+ preg_match( '/(?|var:preset\|duotone\|(\S+)|var\(--wp--preset--duotone--(\S+)\))/', $duotone_attr, $matches );
+
+ return ! empty( $matches[1] ) ? $matches[1] : '';
+ }
+
+ /**
+ * Check if we have a valid duotone preset.
+ *
+ * @param string $duotone_attr The duotone attribute from a block.
+ * @return bool True if the duotone preset present and valid.
+ */
+ private static function is_preset( $duotone_attr ) {
+ $slug = self::gutenberg_get_slug_from_attr( $duotone_attr );
+
+ return array_key_exists( $slug, self::$global_styles_presets );
+ }
+
+ /**
+ * Get the CSS variable name for a duotone preset.
+ *
+ * @param string $slug The slug of the duotone preset.
+ * @return string The CSS variable name.
+ */
+ private static function get_css_custom_property_name( $slug ) {
+ return self::CSS_VAR_PREFIX . $slug;
+ }
+
+ /**
+ * Get the CSS variable for a duotone preset.
+ *
+ * @param string $slug The slug of the duotone preset.
+ * @return string The CSS variable.
+ */
+ private static function get_css_var( $slug ) {
+ return 'var(' . self::get_css_custom_property_name( $slug ) . ')';
+ }
+
+ /**
+ * Get the CSS declaration for a duotone preset.
+ * Example: --wp--preset--duotone--blue-orange: url('#wp-duotone-blue-orange');
+ *
+ * @param array $filter_data The duotone data for presets and custom filters.
+ * @return string The CSS declaration.
+ */
+ private static function get_css_custom_property_declaration( $filter_data ) {
+ $declaration_value = gutenberg_get_duotone_filter_property( $filter_data );
+ $duotone_preset_css_property_name = self::get_css_custom_property_name( $filter_data['slug'] );
+ return $duotone_preset_css_property_name . ': ' . $declaration_value . ';';
+ }
+
+ /**
+ * Safari renders elements incorrectly on first paint when the SVG filter comes after the content that it is filtering,
+ * so we force a repaint with a WebKit hack which solves the issue.
+ *
+ * @param string $selector The selector to apply the hack for.
+ */
+ private static function safari_rerender_hack( $selector ) {
+ /*
+ * Simply accessing el.offsetHeight flushes layout and style
+ * changes in WebKit without having to wait for setTimeout.
+ */
+ printf(
+ '',
+ wp_json_encode( $selector )
+ );
+ }
+
+ /**
+ * Outputs all necessary SVG for duotone filters, CSS for classic themes, and safari rerendering hack
+ */
+ public static function output_footer_assets() {
+ foreach ( self::$output as $filter_data ) {
+
+ // SVG will be output on the page later.
+ $filter_svg = gutenberg_get_duotone_filter_svg( $filter_data );
+
+ echo $filter_svg;
+
+ // This is for classic themes - in block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action.
+ if ( ! wp_is_block_theme() ) {
+ wp_add_inline_style( 'core-block-supports', 'body{' . self::get_css_custom_property_declaration( $filter_data ) . '}' );
+ }
+
+ global $is_safari;
+ if ( $is_safari ) {
+ self::safari_rerender_hack( $filter_data['selector'] );
+ }
+ }
+ }
+
+ /**
+ * Appends the used global style duotone filter CSS Vars to the inline global styles CSS
+ */
+ public static function output_global_styles() {
+
+ if ( empty( self::$output ) ) {
+ return;
+ }
+
+ $duotone_css_vars = '';
+
+ foreach ( self::$output as $filter_data ) {
+ if ( ! array_key_exists( $filter_data['slug'], self::$global_styles_presets ) ) {
+ continue;
+ }
+
+ $duotone_css_vars .= self::get_css_custom_property_declaration( $filter_data );
+ }
+
+ if ( ! empty( $duotone_css_vars ) ) {
+ wp_add_inline_style( 'global-styles', 'body{' . $duotone_css_vars . '}' );
+ }
+ }
+
+ /**
+ * Render out the duotone CSS styles and SVG.
+ *
+ * @param string $block_content Rendered block content.
+ * @param array $block Block object.
+ * @return string Filtered block content.
+ */
+ public static function render_duotone_support( $block_content, $block ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
+
+ $duotone_support = false;
+ if ( $block_type && property_exists( $block_type, 'supports' ) ) {
+ $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
+ }
+
+ // The block should have a duotone attribute or have duotone defined in its theme.json to be processed.
+ $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
+ $has_global_styles_duotone = array_key_exists( $block['blockName'], self::$global_styles_block_names );
+
+ if (
+ empty( $block_content ) ||
+ ! $duotone_support ||
+ ( ! $has_duotone_attribute && ! $has_global_styles_duotone )
+ ) {
+ return $block_content;
+ }
+
+ // Generate the pieces needed for rendering a duotone to the page.
+ if ( $has_duotone_attribute ) {
+
+ // Possible values for duotone attribute:
+ // 1. Array of colors - e.g. array('#000000', '#ffffff').
+ // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|green-blue' or 'var(--wp--preset--duotone--green-blue)''
+ // 3. A CSS string - e.g. 'unset' to remove globally applied duotone.
+
+ $duotone_attr = $block['attrs']['style']['color']['duotone'];
+ $is_preset = is_string( $duotone_attr ) && self::is_preset( $duotone_attr );
+ $is_css = is_string( $duotone_attr ) && ! $is_preset;
+ $is_custom = is_array( $duotone_attr );
+
+ if ( $is_preset ) {
+
+ // Extract the slug from the preset variable string.
+ $slug = self::gutenberg_get_slug_from_attr( $duotone_attr );
+
+ // Utilize existing preset CSS custom property.
+ $declaration_value = self::get_css_var( $slug );
+
+ self::$output[ $slug ] = self::$global_styles_presets[ $slug ];
+
+ } elseif ( $is_css ) {
+ // Build a unique slug for the filter based on the CSS value.
+ $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) );
+
+ // Pass through the CSS value.
+ $declaration_value = $duotone_attr;
+ } elseif ( $is_custom ) {
+ // Build a unique slug for the filter based on the array of colors.
+ $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) );
+
+ $filter_data = array(
+ 'slug' => $slug,
+ 'colors' => $duotone_attr,
+ );
+ // Build a customized CSS filter property for unique slug.
+ $declaration_value = gutenberg_get_duotone_filter_property( $filter_data );
+
+ self::$output[ $slug ] = $filter_data;
+ }
+ } elseif ( $has_global_styles_duotone ) {
+ $slug = self::$global_styles_block_names[ $block['blockName'] ];
+
+ // Utilize existing preset CSS custom property.
+ $declaration_value = self::get_css_var( $slug );
+
+ self::$output[ $slug ] = self::$global_styles_presets[ $slug ];
+ }
+
+ // - Applied as a class attribute to the block wrapper.
+ // - Used as a selector to apply the filter to the block.
+ $filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) );
+
+ // Build the CSS selectors to which the filter will be applied.
+ $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_support );
+
+ // We only want to add the selector if we have it in the output already, essentially skipping 'unset'.
+ if ( array_key_exists( $slug, self::$output ) ) {
+ self::$output[ $slug ]['selector'] = $selector;
+ }
+
+ // Pass styles to the block-supports stylesheet via the style engine.
+ // This ensures that Duotone styles are included in a single stylesheet,
+ // avoiding multiple style tags or multiple stylesheets being output to
+ // the site frontend.
+ gutenberg_style_engine_get_stylesheet_from_css_rules(
+ array(
+ array(
+ 'selector' => $selector,
+ 'declarations' => array(
+ // !important is needed because these styles
+ // render before global styles,
+ // and they should be overriding the duotone
+ // filters set by global styles.
+ 'filter' => $declaration_value . ' !important',
+ ),
+ ),
+ ),
+ array(
+ 'context' => 'block-supports',
+ )
+ );
+
+ // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
+ return preg_replace(
+ '/' . preg_quote( 'class="', '/' ) . '/',
+ 'class="' . $filter_id . ' ',
+ $block_content,
+ 1
+ );
+ }
+}
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index b92e1106c1aa1c..444910cc5d0873 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -144,8 +144,8 @@ class WP_Theme_JSON_Gutenberg {
'path' => array( 'color', 'duotone' ),
'prevent_override' => array( 'color', 'defaultDuotone' ),
'use_default_names' => false,
- 'value_func' => 'gutenberg_get_duotone_filter_property',
- 'css_vars' => '--wp--preset--duotone--$slug',
+ 'value_func' => null, // CSS Custom Properties for duotone are handled by block supports in class-wp-duotone-gutenberg.php.
+ 'css_vars' => null,
'classes' => array(),
'properties' => array( 'filter' ),
),
diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php
new file mode 100644
index 00000000000000..8a8e41ef2be919
--- /dev/null
+++ b/lib/compat/wordpress-6.3/script-loader.php
@@ -0,0 +1,15 @@
+ 'core/image',
- 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => 'var:preset|duotone|slug' ) ) ),
- );
- $block_content = '
';
- $expected = '
';
- $this->assertSame( $expected, gutenberg_render_duotone_support( $block_content, $block ) );
- }
-
- public function test_gutenberg_render_duotone_support_css() {
- $block = array(
- 'blockName' => 'core/image',
- 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => 'unset' ) ) ),
- );
- $block_content = '
';
- $expected = '/
<\\/figure>/';
- $this->assertMatchesRegularExpression( $expected, gutenberg_render_duotone_support( $block_content, $block ) );
- }
-
- public function test_gutenberg_render_duotone_support_custom() {
- $block = array(
- 'blockName' => 'core/image',
- 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => array( '#FFFFFF', '#000000' ) ) ) ),
- );
- $block_content = '
';
- $expected = '/
<\\/figure>/';
- $this->assertMatchesRegularExpression( $expected, gutenberg_render_duotone_support( $block_content, $block ) );
- }
-
-}
diff --git a/phpunit/class-wp-duotone-test.php b/phpunit/class-wp-duotone-test.php
new file mode 100644
index 00000000000000..21df35847fac50
--- /dev/null
+++ b/phpunit/class-wp-duotone-test.php
@@ -0,0 +1,92 @@
+ 'core/image',
+ 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => 'var:preset|duotone|blue-orange' ) ) ),
+ );
+ $block_content = '
';
+ $expected = '
';
+ $this->assertSame( $expected, WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ) );
+ }
+
+ public function test_gutenberg_render_duotone_support_css() {
+ $block = array(
+ 'blockName' => 'core/image',
+ 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => 'unset' ) ) ),
+ );
+ $block_content = '
';
+ $expected = '/
<\\/figure>/';
+ $this->assertMatchesRegularExpression( $expected, WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ) );
+ }
+
+ public function test_gutenberg_render_duotone_support_custom() {
+ $block = array(
+ 'blockName' => 'core/image',
+ 'attrs' => array( 'style' => array( 'color' => array( 'duotone' => array( '#FFFFFF', '#000000' ) ) ) ),
+ );
+ $block_content = '
';
+ $expected = '/
<\\/figure>/';
+ $this->assertMatchesRegularExpression( $expected, WP_Duotone_Gutenberg::render_duotone_support( $block_content, $block ) );
+ }
+
+ public function data_gutenberg_get_slug_from_attr() {
+ return array(
+ 'pipe-slug' => array( 'var:preset|duotone|blue-orange', 'blue-orange' ),
+ 'css-var' => array( 'var(--wp--preset--duotone--blue-orange)', 'blue-orange' ),
+ 'css-var-invalid-slug-chars' => array( 'var(--wp--preset--duotone--.)', '.' ),
+ 'css-var-missing-end-parenthesis' => array( 'var(--wp--preset--duotone--blue-orange', '' ),
+ 'invalid' => array( 'not a valid attribute', '' ),
+ 'css-var-no-value' => array( 'var(--wp--preset--duotone--)', '' ),
+ 'pipe-slug-no-value' => array( 'var:preset|duotone|', '' ),
+ 'css-var-spaces' => array( 'var(--wp--preset--duotone-- ', '' ),
+ 'pipe-slug-spaces' => array( 'var:preset|duotone| ', '' ),
+ );
+ }
+
+ /**
+ * @dataProvider data_gutenberg_get_slug_from_attr
+ */
+ public function test_gutenberg_get_slug_from_attr( $data_attr, $expected ) {
+
+ $reflection = new ReflectionMethod( 'WP_Duotone_Gutenberg', 'gutenberg_get_slug_from_attr' );
+ $reflection->setAccessible( true );
+
+ $this->assertSame( $expected, $reflection->invoke( null, $data_attr ) );
+ }
+
+ public function data_is_preset() {
+ return array(
+ 'pipe-slug' => array( 'var:preset|duotone|blue-orange', true ),
+ 'css-var' => array( 'var(--wp--preset--duotone--blue-orange)', true ),
+ 'css-var-invalid-slug-chars' => array( 'var(--wp--preset--duotone--.)', false ),
+ 'css-var-missing-end-parenthesis' => array( 'var(--wp--preset--duotone--blue-orange', false ),
+ 'invalid' => array( 'not a valid attribute', false ),
+ );
+ }
+
+ /**
+ * @dataProvider data_is_preset
+ */
+ public function test_is_preset( $data_attr, $expected ) {
+ $reflection = new ReflectionMethod( 'WP_Duotone_Gutenberg', 'is_preset' );
+ $reflection->setAccessible( true );
+
+ $this->assertSame( $expected, $reflection->invoke( null, $data_attr ) );
+ }
+}