From 02414638ce37bb4a58fad2491151fb2fcf4a4e73 Mon Sep 17 00:00:00 2001 From: Tonya Mork Date: Tue, 26 Apr 2022 14:46:37 +0000 Subject: [PATCH] Themes: Add internal-only theme.json's webfonts handler (stopgap). Adds `_wp_theme_json_webfonts_handler()` for handling `fontFace` declarations in a theme's `theme.json` file to generate the `@font-face` styles for both the editor and front-end. Design notes: * It is not a public API, but rather an internal, Core-only handler. * It is a stopgap implementation that will be replaced when the public Webfonts API is introduced in Core. * The code design is intentional, albeit funky, with the purpose of avoiding backwards-compatibility issues when the public Webfonts API is introduced in Core. * It hides the inter-workings. * Does not exposing API ins and outs for external consumption. * Only works for `theme.json`. * Does not provide registration or enqueuing access for plugins. For more context on the decision to include this stopgap and the Webfonts API, see: * Core's PR 40493 https://github.com/WordPress/gutenberg/pull/40493 * Gutenberg's tracking issue 40472 https://github.com/WordPress/gutenberg/issues/40472 Props aristath, hellofromTonya, peterwilsoncc, costdev, jffng, zieladam, gziolo, bph, jonoaldersonwp, desrosj. See #55567, #46370. git-svn-id: https://develop.svn.wordpress.org/trunk@53282 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/default-filters.php | 1 + src/wp-includes/script-loader.php | 496 ++++++++++++++++++ .../empty-fontface-theme/functions.php | 1 + .../themedir1/empty-fontface-theme/index.php | 4 + .../themedir1/empty-fontface-theme/style.css | 7 + .../empty-fontface-theme/templates/index.html | 3 + .../themedir1/empty-fontface-theme/theme.json | 88 ++++ .../themedir1/webfonts-theme/functions.php | 1 + .../data/themedir1/webfonts-theme/index.php | 4 + .../data/themedir1/webfonts-theme/style.css | 7 + .../webfonts-theme/templates/index.html | 3 + .../data/themedir1/webfonts-theme/theme.json | 103 ++++ tests/phpunit/tests/theme/themeDir.php | 2 + .../webfonts/wpThemeJsonWebfontsHandler.php | 135 +++++ 14 files changed, 855 insertions(+) create mode 100644 tests/phpunit/data/themedir1/empty-fontface-theme/functions.php create mode 100644 tests/phpunit/data/themedir1/empty-fontface-theme/index.php create mode 100644 tests/phpunit/data/themedir1/empty-fontface-theme/style.css create mode 100644 tests/phpunit/data/themedir1/empty-fontface-theme/templates/index.html create mode 100644 tests/phpunit/data/themedir1/empty-fontface-theme/theme.json create mode 100644 tests/phpunit/data/themedir1/webfonts-theme/functions.php create mode 100644 tests/phpunit/data/themedir1/webfonts-theme/index.php create mode 100644 tests/phpunit/data/themedir1/webfonts-theme/style.css create mode 100644 tests/phpunit/data/themedir1/webfonts-theme/templates/index.html create mode 100644 tests/phpunit/data/themedir1/webfonts-theme/theme.json create mode 100644 tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 0648603ab2bde..ed12faf1ecba6 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -351,6 +351,7 @@ add_action( 'after_switch_theme', '_wp_menus_changed' ); add_action( 'after_switch_theme', '_wp_sidebars_changed' ); add_action( 'wp_print_styles', 'print_emoji_styles' ); +add_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); if ( isset( $_GET['replytocom'] ) ) { add_filter( 'wp_robots', 'wp_robots_no_robots' ); diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 0f8eaeacadbb8..72642dab28f69 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3027,3 +3027,499 @@ function wp_enqueue_block_style( $block_name, $args ) { // Enqueue assets in the editor. add_action( 'enqueue_block_assets', $callback ); } + +/** + * Runs the theme.json webfonts handler. + * + * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined + * in the `theme.json` for the current selection and style + * variations, validates the font-face properties, generates + * the '@font-face' style declarations, and then enqueues the + * styles for both the editor and front-end. + * + * Design Notes: + * This is not a public API, but rather an internal handler. + * A future public Webfonts API will replace this stopgap code. + * + * This code design is intentional. + * a. It hides the inner-workings. + * b. It does not expose API ins or outs for consumption. + * c. It only works with a theme's `theme.json`. + * + * Why? + * a. To avoid backwards-compatibility issues when + * the Webfonts API is introduced in Core. + * b. To make `fontFace` declarations in `theme.json` work. + * + * @link https://github.com/WordPress/gutenberg/issues/40472 + * + * @since 6.0.0 + * @access private + */ +function _wp_theme_json_webfonts_handler() { + // Webfonts to be processed. + $registered_webfonts = array(); + + /** + * Gets the webfonts from theme.json. + * + * @since 6.0.0 + * + * @return array Array of defined webfonts. + */ + $fn_get_webfonts_from_theme_json = static function() { + // Get settings from theme.json. + $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); + + // If in the editor, add webfonts defined in variations. + if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + $variations = WP_Theme_JSON_Resolver::get_style_variations(); + foreach ( $variations as $variation ) { + // Skip if fontFamilies are not defined in the variation. + if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) { + continue; + } + + // Initialize the array structure. + if ( empty( $settings['typography'] ) ) { + $settings['typography'] = array(); + } + if ( empty( $settings['typography']['fontFamilies'] ) ) { + $settings['typography']['fontFamilies'] = array(); + } + if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) { + $settings['typography']['fontFamilies']['theme'] = array(); + } + + // Combine variations with settings. Remove duplicates. + $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] ); + $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); + } + } + + // Bail out early if there are no settings for webfonts. + if ( empty( $settings['typography']['fontFamilies'] ) ) { + return array(); + } + + $webfonts = array(); + + // Look for fontFamilies. + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $font_families as $font_family ) { + + // Skip if fontFace is not defined. + if ( empty( $font_family['fontFace'] ) ) { + continue; + } + + // Skip if fontFace is not an array of webfonts. + if ( ! is_array( $font_family['fontFace'] ) ) { + continue; + } + + $webfonts = array_merge( $webfonts, $font_family['fontFace'] ); + } + } + + return $webfonts; + }; + + /** + * Transforms each 'src' into an URI by replacing 'file:./' + * placeholder from theme.json. + * + * The absolute path to the webfont file(s) cannot be defined in + * theme.json. `file:./` is the placeholder which is replaced by + * the theme's URL path to the theme's root. + * + * @since 6.0.0 + * + * @param array $src Webfont file(s) `src`. + * @return array Webfont's `src` in URI. + */ + $fn_transform_src_into_uri = static function( array $src ) { + foreach ( $src as $key => $url ) { + // Tweak the URL to be relative to the theme root. + if ( ! str_starts_with( $url, 'file:./' ) ) { + continue; + } + + $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) ); + } + + return $src; + }; + + /** + * Converts the font-face properties (i.e. keys) into kebab-case. + * + * @since 6.0.0 + * + * @param array $font_face Font face to convert. + * @return array Font faces with each property in kebab-case format. + */ + $fn_convert_keys_to_kebab_case = static function( array $font_face ) { + foreach ( $font_face as $property => $value ) { + $kebab_case = _wp_to_kebab_case( $property ); + $font_face[ $kebab_case ] = $value; + if ( $kebab_case !== $property ) { + unset( $font_face[ $property ] ); + } + } + + return $font_face; + }; + + /** + * Validates a webfont. + * + * @since 6.0.0 + * + * @param array $webfont The webfont arguments. + * @return array|false The validated webfont arguments, or false if the webfont is invalid. + */ + $fn_validate_webfont = static function( $webfont ) { + $webfont = wp_parse_args( + $webfont, + array( + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + 'src' => array(), + ) + ); + + // Check the font-family. + if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) { + trigger_error( __( 'Webfont font family must be a non-empty string.', 'gutenberg' ) ); + + return false; + } + + // Check that the `src` property is defined and a valid type. + if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { + trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.', 'gutenberg' ) ); + + return false; + } + + // Validate the `src` property. + foreach ( (array) $webfont['src'] as $src ) { + if ( ! is_string( $src ) || '' === trim( $src ) ) { + trigger_error( __( 'Each webfont src must be a non-empty string.', 'gutenberg' ) ); + + return false; + } + } + + // Check the font-weight. + if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { + trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.', 'gutenberg' ) ); + + return false; + } + + // Check the font-display. + if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'swap' ), true ) ) { + $webfont['font-display'] = 'fallback'; + } + + $valid_props = array( + 'ascend-override', + 'descend-override', + 'font-display', + 'font-family', + 'font-stretch', + 'font-style', + 'font-weight', + 'font-variant', + 'font-feature-settings', + 'font-variation-settings', + 'line-gap-override', + 'size-adjust', + 'src', + 'unicode-range', + ); + + foreach ( $webfont as $prop => $value ) { + if ( ! in_array( $prop, $valid_props, true ) ) { + unset( $webfont[ $prop ] ); + } + } + + return $webfont; + }; + + /** + * Registers webfonts declared in theme.json. + * + * @since 6.0.0 + * + * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). + * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json. + * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case. + * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json. + */ + $fn_register_webfonts = static function() use ( &$registered_webfonts, $fn_get_webfonts_from_theme_json, $fn_convert_keys_to_kebab_case, $fn_validate_webfont, $fn_transform_src_into_uri ) { + $registered_webfonts = array(); + + foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) { + if ( ! is_array( $webfont ) ) { + continue; + } + + $webfont = $fn_convert_keys_to_kebab_case( $webfont ); + + $webfont = $fn_validate_webfont( $webfont ); + + $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] ); + + // Skip if not valid. + if ( empty( $webfont ) ) { + continue; + } + + $registered_webfonts[] = $webfont; + } + }; + + /** + * Orders 'src' items to optimize for browser support. + * + * @since 6.0.0 + * + * @param array $webfont Webfont to process. + * @return array Ordered `src` items. + */ + $fn_order_src = static function( array $webfont ) { + $src = array(); + $src_ordered = array(); + + foreach ( $webfont['src'] as $url ) { + // Add data URIs first. + if ( str_starts_with( trim( $url ), 'data:' ) ) { + $src_ordered[] = array( + 'url' => $url, + 'format' => 'data', + ); + continue; + } + $format = pathinfo( $url, PATHINFO_EXTENSION ); + $src[ $format ] = $url; + } + + // Add woff2. + if ( ! empty( $src['woff2'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff2'] ), + 'format' => 'woff2', + ); + } + + // Add woff. + if ( ! empty( $src['woff'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff'] ), + 'format' => 'woff', + ); + } + + // Add ttf. + if ( ! empty( $src['ttf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['ttf'] ), + 'format' => 'truetype', + ); + } + + // Add eot. + if ( ! empty( $src['eot'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['eot'] ), + 'format' => 'embedded-opentype', + ); + } + + // Add otf. + if ( ! empty( $src['otf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['otf'] ), + 'format' => 'opentype', + ); + } + $webfont['src'] = $src_ordered; + + return $webfont; + }; + + /** + * Compiles the 'src' into valid CSS. + * + * @since 6.0.0 + * + * @param string $font_family Font family. + * @param array $value Value to process. + * @return string The CSS. + */ + $fn_compile_src = static function( $font_family, array $value ) { + $src = "local($font_family)"; + + foreach ( $value as $item ) { + + if ( + str_starts_with( $item['url'], site_url() ) || + str_starts_with( $item['url'], home_url() ) + ) { + $item['url'] = wp_make_link_relative( $item['url'] ); + } + + $src .= ( 'data' === $item['format'] ) + ? ", url({$item['url']})" + : ", url('{$item['url']}') format('{$item['format']}')"; + } + + return $src; + }; + + /** + * Compiles the font variation settings. + * + * @since 6.0.0 + * + * @param array $font_variation_settings Array of font variation settings. + * @return string The CSS. + */ + $fn_compile_variations = static function( array $font_variation_settings ) { + $variations = ''; + + foreach ( $font_variation_settings as $key => $value ) { + $variations .= "$key $value"; + } + + return $variations; + }; + + /** + * Builds the font-family's CSS. + * + * @since 6.0.0 + * + * @uses $fn_compile_src To run the function that compiles the src. + * @uses $fn_compile_variations To run the function that compiles the variations. + * + * @param array $webfont Webfont to process. + * @return string This font-family's CSS. + */ + $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) { + $css = ''; + + // Wrap font-family in quotes if it contains spaces. + if ( + str_contains( $webfont['font-family'], ' ' ) && + ! str_contains( $webfont['font-family'], '"' ) && + ! str_contains( $webfont['font-family'], "'" ) + ) { + $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; + } + + foreach ( $webfont as $key => $value ) { + /* + * Skip "provider", since it's for internal API use, + * and not a valid CSS property. + */ + if ( 'provider' === $key ) { + continue; + } + + // Compile the "src" parameter. + if ( 'src' === $key ) { + $value = $fn_compile_src( $webfont['font-family'], $value ); + } + + // If font-variation-settings is an array, convert it to a string. + if ( 'font-variation-settings' === $key && is_array( $value ) ) { + $value = $fn_compile_variations( $value ); + } + + if ( ! empty( $value ) ) { + $css .= "$key:$value;"; + } + } + + return $css; + }; + + /** + * Gets the '@font-face' CSS styles for locally-hosted font files. + * + * @since 6.0.0 + * + * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). + * @uses $fn_order_src To run the function that orders the src. + * @uses $fn_build_font_face_css To run the function that builds the font-face CSS. + * + * @return string The `@font-face` CSS. + */ + $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) { + $css = ''; + + foreach ( $registered_webfonts as $webfont ) { + // Order the webfont's `src` items to optimize for browser support. + $webfont = $fn_order_src( $webfont ); + + // Build the @font-face CSS for this webfont. + $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}'; + } + + return $css; + }; + + /** + * Generates and enqueues webfonts styles. + * + * @since 6.0.0 + * + * @uses $fn_get_css To run the function that gets the CSS. + */ + $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) { + // Generate the styles. + $styles = $fn_get_css(); + + // Bail out if there are no styles to enqueue. + if ( '' === $styles ) { + return; + } + + // Enqueue the stylesheet. + wp_register_style( 'wp-webfonts', '' ); + wp_enqueue_style( 'wp-webfonts' ); + + // Add the styles to the stylesheet. + wp_add_inline_style( 'wp-webfonts', $styles ); + }; + + /** + * Generates and enqueues editor styles. + * + * @since 6.0.0 + * + * @uses $fn_get_css To run the function that gets the CSS. + */ + $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) { + // Generate the styles. + $styles = $fn_get_css(); + + // Bail out if there are no styles to enqueue. + if ( '' === $styles ) { + return; + } + + wp_add_inline_style( 'wp-block-library', $styles ); + }; + + add_action( 'wp_loaded', $fn_register_webfonts ); + add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles ); + add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles ); +} diff --git a/tests/phpunit/data/themedir1/empty-fontface-theme/functions.php b/tests/phpunit/data/themedir1/empty-fontface-theme/functions.php new file mode 100644 index 0000000000000..b3d9bbc7f3711 --- /dev/null +++ b/tests/phpunit/data/themedir1/empty-fontface-theme/functions.php @@ -0,0 +1 @@ + +

Index Template

+ diff --git a/tests/phpunit/data/themedir1/empty-fontface-theme/theme.json b/tests/phpunit/data/themedir1/empty-fontface-theme/theme.json new file mode 100644 index 0000000000000..a9f1cb5080ade --- /dev/null +++ b/tests/phpunit/data/themedir1/empty-fontface-theme/theme.json @@ -0,0 +1,88 @@ +{ + "version": 2, + "customTemplates": [ + { + "name": "blank", + "title": "Blank", + "postTypes": [ + "page", + "post" + ] + } + ], + "settings": { + "appearanceTools": true, + "color": { + "duotone": [], + "gradients": [], + "palette": [] + }, + "custom": {}, + "spacing": { + "units": [ + "%", + "px", + "em", + "rem", + "vh", + "vw" + ] + }, + "typography": { + "dropCap": false, + "fontFamilies": [ + { + "fontFamily": "Roboto", + "name": "Roboto", + "slug": "roboto", + "fontFace": [] + } + ], + "fontSizes": [ + { + "size": "1rem", + "slug": "small" + }, + { + "size": "1.125rem", + "slug": "medium" + }, + { + "size": "1.75rem", + "slug": "large" + }, + { + "size": "clamp(1.75rem, 3vw, 2.25rem)", + "slug": "x-large" + } + ] + }, + "layout": { + "contentSize": "650px", + "wideSize": "1000px" + } + }, + "styles": { + "blocks": {}, + "color": { + "background": "var(--wp--preset--color--background)", + "text": "var(--wp--preset--color--foreground)" + }, + "elements": {}, + "spacing": { + "blockGap": "1.5rem" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)" + } + }, + "templateParts": [ + { + "name": "header", + "title": "Header", + "area": "header" + } + ] +} diff --git a/tests/phpunit/data/themedir1/webfonts-theme/functions.php b/tests/phpunit/data/themedir1/webfonts-theme/functions.php new file mode 100644 index 0000000000000..b3d9bbc7f3711 --- /dev/null +++ b/tests/phpunit/data/themedir1/webfonts-theme/functions.php @@ -0,0 +1 @@ + +

Index Template

+ diff --git a/tests/phpunit/data/themedir1/webfonts-theme/theme.json b/tests/phpunit/data/themedir1/webfonts-theme/theme.json new file mode 100644 index 0000000000000..0606b9b780971 --- /dev/null +++ b/tests/phpunit/data/themedir1/webfonts-theme/theme.json @@ -0,0 +1,103 @@ +{ + "version": 2, + "customTemplates": [ + { + "name": "blank", + "title": "Blank", + "postTypes": [ + "page", + "post" + ] + } + ], + "settings": { + "appearanceTools": true, + "color": { + "duotone": [], + "gradients": [], + "palette": [] + }, + "custom": {}, + "spacing": { + "units": [ + "%", + "px", + "em", + "rem", + "vh", + "vw" + ] + }, + "typography": { + "dropCap": false, + "fontFamilies": [ + { + "fontFamily": "\"Source Serif Pro\", serif", + "name": "Source Serif Pro", + "slug": "source-serif-pro", + "fontFace": [ + { + "fontFamily": "Source Serif Pro", + "fontWeight": "200 900", + "fontStyle": "normal", + "fontStretch": "normal", + "src": [ "file:./assets/fonts/SourceSerif4Variable-Roman.ttf.woff2" ] + }, + { + "fontFamily": "Source Serif Pro", + "fontWeight": "200 900", + "fontStyle": "italic", + "fontStretch": "normal", + "src": [ "file:./assets/fonts/SourceSerif4Variable-Italic.ttf.woff2" ] + } + ] + } + ], + "fontSizes": [ + { + "size": "1rem", + "slug": "small" + }, + { + "size": "1.125rem", + "slug": "medium" + }, + { + "size": "1.75rem", + "slug": "large" + }, + { + "size": "clamp(1.75rem, 3vw, 2.25rem)", + "slug": "x-large" + } + ] + }, + "layout": { + "contentSize": "650px", + "wideSize": "1000px" + } + }, + "styles": { + "blocks": {}, + "color": { + "background": "var(--wp--preset--color--background)", + "text": "var(--wp--preset--color--foreground)" + }, + "elements": {}, + "spacing": { + "blockGap": "1.5rem" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--system-font)", + "lineHeight": "var(--wp--custom--typography--line-height--normal)", + "fontSize": "var(--wp--preset--font-size--medium)" + } + }, + "templateParts": [ + { + "name": "header", + "title": "Header", + "area": "header" + } + ] +} diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index 377ff79fc1041..652a2f6cfa269 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -165,6 +165,8 @@ public function test_theme_list() { 'Block Theme Child Theme', 'Block Theme [0.4.0]', 'Block Theme [1.0.0] in subdirectory', + 'Webfonts theme', + 'Empty `fontFace` in theme.json - no webfonts defined', ); sort( $theme_names ); diff --git a/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php b/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php new file mode 100644 index 0000000000000..df50c5079a420 --- /dev/null +++ b/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php @@ -0,0 +1,135 @@ +orig_wp_styles = $wp_styles; + $wp_styles = null; + + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + $theme_root_callback = function () { + return $this->theme_root; + }; + + add_filter( 'theme_root', $theme_root_callback ); + add_filter( 'stylesheet_root', $theme_root_callback ); + add_filter( 'template_root', $theme_root_callback ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + global $wp_styles; + $wp_styles = $this->orig_wp_styles; + + // Restore the original theme directory setup. + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + parent::tear_down(); + } + + /** + * @ticket 55567 + * @ticket 46370 + */ + public function test_font_face_generated_from_themejson() { + $this->setup_theme_and_test( 'webfonts-theme' ); + + $expected = << +@font-face{font-family:"Source Serif Pro";font-style:normal;font-weight:200 900;font-display:fallback;src:local("Source Serif Pro"), url('THEME_ROOT_URL/assets/fonts/SourceSerif4Variable-Roman.ttf.woff2') format('woff2');font-stretch:normal;}@font-face{font-family:"Source Serif Pro";font-style:italic;font-weight:200 900;font-display:fallback;src:local("Source Serif Pro"), url('THEME_ROOT_URL/assets/fonts/SourceSerif4Variable-Italic.ttf.woff2') format('woff2');font-stretch:normal;} + +EOF; + $expected = str_replace( 'THEME_ROOT_URL', get_stylesheet_directory_uri(), $expected ); + + $this->assertStringContainsString( + $expected, + get_echo( 'wp_print_styles' ) + ); + } + + /** + * @dataProvider data_font_face_not_generated + * + * @ticket 55567 + * @ticket 46370 + */ + public function test_font_face_not_generated( $theme_name ) { + $this->setup_theme_and_test( $theme_name ); + + $actual = get_echo( 'wp_print_styles' ); + $this->assertStringNotContainsString( "