From 91d18ad18ee9575a67800261c16d99f56904f783 Mon Sep 17 00:00:00 2001 From: hellofromtonya Date: Wed, 13 Oct 2021 20:37:23 -0500 Subject: [PATCH] Moves validation from providers to Validator. Moves all of the validation logic from the providers to the Validator. Adds tests. --- .../class-wp-webfonts-registry.php | 35 +- .../class-wp-webfonts-schema-validator.php | 220 +++++--- .../class-wp-webfonts-google-provider.php | 20 +- .../class-wp-webfonts-local-provider.php | 223 ++++++-- .../providers/class-wp-webfonts-provider.php | 228 ++------ ...class-my-custom-webfonts-provider-mock.php | 21 +- .../providers/wpWebfontsGoogleProvider.php | 151 +++++ .../providers/wpWebfontsLocalProvider.php | 244 ++++++++ .../webfonts-api/wpWebfontsController.php | 412 ++++++++------ .../tests/webfonts-api/wpWebfontsRegistry.php | 524 ++++++++++++------ .../wpWebfontsSchemaValidator.php | 393 +++++++++++++ 11 files changed, 1823 insertions(+), 648 deletions(-) create mode 100644 tests/phpunit/tests/webfonts-api/providers/wpWebfontsGoogleProvider.php create mode 100644 tests/phpunit/tests/webfonts-api/providers/wpWebfontsLocalProvider.php create mode 100644 tests/phpunit/tests/webfonts-api/wpWebfontsSchemaValidator.php diff --git a/src/wp-includes/webfonts-api/class-wp-webfonts-registry.php b/src/wp-includes/webfonts-api/class-wp-webfonts-registry.php index e43e29304838b..ff4bca0b9649f 100644 --- a/src/wp-includes/webfonts-api/class-wp-webfonts-registry.php +++ b/src/wp-includes/webfonts-api/class-wp-webfonts-registry.php @@ -88,7 +88,7 @@ public function get_by_provider( $provider_id ) { * @since 5.9.0 * * @param string $font_family Family font to fetch. - * @return string[][] Registered webfonts. + * @return array[] Registered webfonts. */ public function get_by_font_family( $font_family ) { if ( ! is_string( $font_family ) || '' === $font_family ) { @@ -116,17 +116,19 @@ public function get_by_font_family( $font_family ) { * * @since 5.9.0 * - * @param string[] $webfont Webfont definition. + * @param array $webfont Webfont definition. * @return string Registration key. */ public function register( array $webfont ) { - $webfont = $this->merge_optional_parameters( $webfont ); + $webfont = $this->convert_to_kabeb_case( $webfont ); // Validate schema. - if ( ! $this->is_schema_valid( $webfont ) ) { + if ( ! $this->validator->is_valid_schema( $webfont ) ) { return ''; } + $webfont = $this->validator->set_valid_properties( $webfont ); + // Add to registry. $registration_key = $this->generate_registration_key( $webfont ); if ( ! isset( $this->registry[ $registration_key ] ) ) { @@ -138,29 +140,24 @@ public function register( array $webfont ) { } /** - * Merge optional parameters into webfont definition. + * Convert camelCase parameters into kabeb_case. * * @since 5.9.0 * * @param string[] $webfont Webfont definition. - * @return string[] Webfont with optional parameters. + * @return array Webfont with kabeb_case parameters (keys). */ - private function merge_optional_parameters( array $webfont ) { - if ( ! isset( $webfont['fontStyle'] ) ) { - $webfont['fontStyle'] = 'normal'; - } - - if ( ! isset( $webfont['fontWeight'] ) ) { - $webfont['fontWeight'] = '400'; - } + private function convert_to_kabeb_case( array $webfont ) { + $kebab_case = preg_replace( '/(?convert_font_family_into_key( $webfont['fontFamily'] ), - trim( $webfont['fontStyle'] ), - trim( $webfont['fontWeight'] ) + $this->convert_font_family_into_key( $webfont['font-family'] ), + trim( $webfont['font-style'] ), + trim( $webfont['font-weight'] ) ); } diff --git a/src/wp-includes/webfonts-api/class-wp-webfonts-schema-validator.php b/src/wp-includes/webfonts-api/class-wp-webfonts-schema-validator.php index 5b81a66b72b1b..055039249a1d5 100644 --- a/src/wp-includes/webfonts-api/class-wp-webfonts-schema-validator.php +++ b/src/wp-includes/webfonts-api/class-wp-webfonts-schema-validator.php @@ -21,45 +21,87 @@ class WP_Webfonts_Schema_Validator { * * @var string[] */ - private $valid_font_style = array( - 'normal', - 'italic', - 'oblique', - // Global values. - 'inherit', - 'initial', - 'revert', - 'unset', + protected $font_style = array( 'normal', 'italic', 'oblique', 'inherit', 'initial', 'revert', 'unset' ); + + /** + * Valid font weight values. + * + * @since 5.9.0 + * + * @var string[] + */ + protected $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', 'inherit' ); + + /** + * Valid font display values. + * + * @since 5.9.0 + * + * @var string[] + */ + protected $font_display = array( 'auto', 'block', 'swap', 'fallback' ); + + /** + * An array of valid CSS properties for @font-face. + * + * @since 5.9.0 + * + * @var string[] + */ + protected $font_face_properties = 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', + ); + + /** + * Basic schema structure. + * + * @since 5.9.0 + * + * @var array + */ + protected $basic_schema = array( + 'provider' => '', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', ); /** * Webfont being validated. * - * @var string[] + * Set as a property for performance. + * + * @var array */ private $webfont = array(); /** - * Checks if the given webfont schema is validate. + * Checks if the given webfont schema is valid. * * @since 5.9.0 * - * @param string[] $webfont Webfont definition. + * @param array $webfont Webfont to validate. * @return bool True when valid. False when invalid. */ - public function is_schema_valid( array $webfont ) { - $this->webfont = $webfont; - - $is_valid = ( - $this->is_provider_valid() && - $this->is_font_family_valid() && - $this->is_font_style_valid() && - $this->is_font_weight_valid() + public function is_valid_schema( array $webfont ) { + return ( + $this->is_valid_provider( $webfont ) && + $this->is_valid_font_family( $webfont ) ); - - $this->webfont = array(); - - return $is_valid; } /** @@ -67,12 +109,16 @@ public function is_schema_valid( array $webfont ) { * * @since 5.9.0 * + * @param array $webfont Webfont to valiate. * @return bool True if valid. False if invalid. */ - private function is_provider_valid() { + private function is_valid_provider( array $webfont ) { // @todo check if provider is registered. - if ( empty( $this->webfont['provider'] ) || ! is_string( $this->webfont['provider'] ) ) { + if ( + empty( $webfont['provider'] ) || + ! is_string( $webfont['provider'] ) + ) { trigger_error( __( 'Webfont provider must be a non-empty string.' ) ); return false; @@ -86,10 +132,14 @@ private function is_provider_valid() { * * @since 5.9.0 * + * @param array $webfont Webfont to validate. * @return bool True if valid. False if invalid. */ - private function is_font_family_valid() { - if ( empty( $this->webfont['fontFamily'] ) || ! is_string( $this->webfont['fontFamily'] ) ) { + private function is_valid_font_family( array $webfont ) { + if ( + empty( $webfont['font-family'] ) || + ! is_string( $webfont['font-family'] ) + ) { trigger_error( __( 'Webfont font family must be a non-empty string.' ) ); return false; @@ -99,67 +149,109 @@ private function is_font_family_valid() { } /** - * Checks if the font style is validate. + * Sets valid properties. * * @since 5.9.0 * - * @return bool True if valid. False if invalid. + * @param array $webfont Webfont definition. + * @return array Updated webfont. */ - private function is_font_style_valid() { - if ( empty( $this->webfont['fontStyle'] ) || ! is_string( $this->webfont['fontStyle'] ) ) { - trigger_error( __( 'Webfont font style must be a non-empty string.' ) ); - return false; - } + public function set_valid_properties( array $webfont ) { + $this->webfont = array_merge( $this->basic_schema, $webfont ); - if ( ! $this->is_font_style_value_valid( $this->webfont['fontStyle'] ) ) { - trigger_error( - sprintf( - /* translators: 1: Slant angle, 2: Given font style. */ - __( 'Webfont font style must be normal, italic, oblique, or oblique %1$s. Given: %2$s.' ), - '', - $this->webfont['fontStyle'] - ) - ); + $this->set_valid_font_face_property(); + $this->set_valid_font_style(); + $this->set_valid_font_weight(); + $this->set_valid_font_display(); - return false; - } + $webfont = $this->webfont; + $this->webfont = array(); // Reset property. - return true; + return $webfont; } /** - * Checks if the given font-style is valid. + * Checks if the CSS property is valid for @font-face. * * @since 5.9.0 - * - * @param string $font_style Font style to validate. - * @return bool True when font-style is valid. */ - private function is_font_style_value_valid( $font_style ) { - if ( in_array( $font_style, $this->valid_font_style, true ) ) { - return true; + private function set_valid_font_face_property() { + foreach ( array_keys( $this->webfont ) as $property ) { + /* + * Skip valid configuration parameters (these are configuring the webfont + * but are not @font-face properties. + */ + if ( 'provider' === $property ) { + continue; + } + + if ( ! in_array( $property, $this->font_face_properties, true ) ) { + unset( $this->webfont[ $property ] ); + } } + } - // @todo Check for oblique . + /** + * Checks if the font style is validate. + * + * @since 5.9.0 + */ + private function set_valid_font_style() { + // If empty or not a string, trigger an error and then set the default value. + if ( + empty( $this->webfont['font-style'] ) || + ! is_string( $this->webfont['font-style'] ) + ) { + trigger_error( __( 'Webfont font style must be a non-empty string.' ) ); + + } elseif ( // Bail out if the font-weight is a valid value. + in_array( $this->webfont['font-style'], $this->font_style, true ) || + preg_match( '/^oblique\s+(\d+)%/', $this->webfont['font-style'] ) + ) { + return; + } - return false; + $this->webfont['font-style'] = 'normal'; } /** - * Checks if the font weight is validate. + * Sets a default font weight if invalid. * * @since 5.9.0 - * - * @return bool True if valid. False if invalid. */ - private function is_font_weight_valid() { - // @todo validate the value. - if ( empty( $this->webfont['fontWeight'] ) || ! is_string( $this->webfont['fontWeight'] ) ) { + private function set_valid_font_weight() { + // If empty or not a string, trigger an error and then set the default value. + if ( + empty( $this->webfont['font-weight'] ) || + ! is_string( $this->webfont['font-weight'] ) + ) { trigger_error( __( 'Webfont font weight must be a non-empty string.' ) ); - return false; + } elseif ( // Bail out if the font-weight is a valid value. + in_array( $this->webfont['font-weight'], $this->font_weight, true ) || + preg_match( '/^(\d+)$/', $this->webfont['font-weight'], $matches ) || + preg_match( '/^(\d+)\s+(\d+)$/', $this->webfont['font-weight'], $matches ) + ) { + return; } - return true; + // Not valid. Set the default value. + $this->webfont['font-weight'] = '400'; + } + + /** + * Sets a default font display if invalid. + * + * @since 5.9.0 + */ + private function set_valid_font_display() { + if ( + ! empty( $this->webfont['font-display'] ) && + in_array( $this->webfont['font-display'], $this->font_display, true ) + ) { + return; + } + + $this->webfont['font-display'] = 'fallback'; } } diff --git a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-google-provider.php b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-google-provider.php index 3dc2cce97745b..c4065feb2da83 100644 --- a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-google-provider.php +++ b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-google-provider.php @@ -1,6 +1,6 @@ $font ) { - $fonts[ $key ] = $this->get_validated_params( $font ); - } - // Group by font-display. // Each font-display will need to be a separate request. $font_display_groups = array(); @@ -140,7 +136,7 @@ protected function build_collection_api_urls( $fonts ) { */ public function get_css() { $css = ''; - $urls = $this->build_collection_api_urls( $this->params ); + $urls = $this->build_collection_api_urls( $this->webfonts ); foreach ( $urls as $url ) { $css .= $this->get_cached_remote_styles( 'google_fonts_' . md5( $url ), $url ); diff --git a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-local-provider.php b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-local-provider.php index 9fd7c9abf0244..dc1da5cb8daf2 100644 --- a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-local-provider.php +++ b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-local-provider.php @@ -22,78 +22,205 @@ class WP_Webfonts_Local_Provider extends WP_Webfonts_Provider { protected $id = 'local'; /** - * Get validated params. + * Prepares the given webfont. * * @since 5.9.0 * - * @param array $params The webfont's parameters. + * @param array $webfont Webfont to validate. * @return array */ - public function get_validated_params( $params ) { - $params = parent::get_validated_params( $params ); + protected function prepare( array $webfont ) { + $webfont = parent::prepare( $webfont ); + $webfont = $this->order_src( $webfont ); // Wrap font-family in quotes if it contains spaces. - if ( false !== strpos( $params['font-family'], ' ' ) && false === strpos( $params['font-family'], '"' ) && false === strpos( $params['font-family'], "'" ) ) { - $params['font-family'] = '"' . $params['font-family'] . '"'; + if ( + false !== strpos( $webfont['font-family'], ' ' ) && + false === strpos( $webfont['font-family'], '"' ) && + false === strpos( $webfont['font-family'], "'" ) + ) { + $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; } - return $params; + + return $webfont; + } + + /** + * Order `src` items to optimize for browser support. + * + * @since 5.9.0 + * + * @param string[] $webfont Webfont to process. + * @return string[] + */ + private function order_src( array $webfont ) { + if ( ! is_array( $webfont['src'] ) ) { + $webfont['src'] = (array) $webfont['src']; + } + + $src = array(); + $src_ordered = array(); + + foreach ( $webfont['src'] as $url ) { + // Add data URIs first. + if ( 0 === strpos( 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' => $src['woff2'], + 'format' => 'woff2', + ); + } + + // Add woff. + if ( ! empty( $src['woff'] ) ) { + $src_ordered[] = array( + 'url' => $src['woff'], + 'format' => 'woff', + ); + } + + // Add ttf. + if ( ! empty( $src['ttf'] ) ) { + $src_ordered[] = array( + 'url' => $src['ttf'], + 'format' => 'truetype', + ); + } + + // Add eot. + if ( ! empty( $src['eot'] ) ) { + $src_ordered[] = array( + 'url' => $src['eot'], + 'format' => 'embedded-opentype', + ); + } + + // Add otf. + if ( ! empty( $src['otf'] ) ) { + $src_ordered[] = array( + 'url' => $src['otf'], + 'format' => 'opentype', + ); + } + $webfont['src'] = $src_ordered; + + return $webfont; } /** - * Get the CSS for a collection of fonts. + * Get the CSS for a collection of webfonts. * * @since 5.9.0 * - * @return string + * @return string The CSS. */ public function get_css() { $css = ''; - foreach ( $this->params as $font ) { - // Validate font params. - $font = $this->get_validated_params( $font ); + foreach ( $this->webfonts as $webfont ) { + $css .= "@font-face{\n" . $this->build_font_css( $webfont ) . "}\n"; + } - if ( empty( $font['font-family'] ) ) { - continue; + return $css; + } + + /** + * Builds the font-family's CSS. + * + * @since 5.9.0 + * + * @param array $webfont Webfont to process. + * @return string This font-family's CSS. + */ + private function build_font_css( array $webfont ) { + $css = ''; + foreach ( $webfont as $key => $value ) { + + // Compile the "src" parameter. + if ( 'src' === $key ) { + $value = $this->compile_src( $webfont['font-family'], $value ); + } + + // @todo Is this a needed configuration parameter? If yes, need to add to Validator; else will be stripped out. + // If font-variation-settings is an array, convert it to a string. + if ( 'font-variation-settings' === $key && is_array( $value ) ) { + $value = $this->compile_variations( $value ); } - $css .= '@font-face{'; - foreach ( $font as $key => $value ) { - - // Compile the "src" parameter. - if ( 'src' === $key ) { - $src = "local({$font['font-family']})"; - foreach ( $value as $item ) { - - // If the URL starts with "file:./" then it originated in a theme.json file. - // Tweak the URL to be relative to the theme root. - if ( 0 === strpos( $item['url'], 'file:./' ) ) { - $item['url'] = wp_make_link_relative( get_theme_file_uri( str_replace( 'file:./', '', $item['url'] ) ) ); - } - - $src .= ( 'data' === $item['format'] ) - ? ", url({$item['url']})" - : ", url('{$item['url']}') format('{$item['format']}')"; - } - $value = $src; - } - - // If font-variation-settings is an array, convert it to a string. - if ( 'font-variation-settings' === $key && is_array( $value ) ) { - $variations = array(); - foreach ( $value as $key => $val ) { - $variations[] = "$key $val"; - } - $value = implode( ', ', $variations ); - } - - if ( ! empty( $value ) ) { - $css .= "$key:$value;"; - } + if ( ! empty( $value ) ) { + $css .= "\t$key:$value;\n"; } - $css .= '}'; } return $css; } + + /** + * Compiles the `src` into valid CSS. + * + * @since 5.9.0 + * + * @param string $font_family Font family. + * @param array $value Value to process. + * @return string The CSS. + */ + private function compile_src( $font_family, array $value ) { + $src = "local($font_family)"; + + foreach ( $value as $item ) { + // If the URL starts with "file:./" then it originated in a theme.json file. + // Tweak the URL to be relative to the theme root. + if ( 0 === strpos( $item['url'], 'file:./' ) ) { + $item['url'] = $this->replace_url_temp_placeholder( $item['url'] ); + } + + $src .= ( 'data' === $item['format'] ) + ? ", url({$item['url']})" + : ", url('{$item['url']}') format('{$item['format']}')"; + } + return $src; + } + + /** + * Replace URL's temporary placeholder with theme path. + * + * @since 5.9.0 + * + * @param string $url URL with temporary placeholder. + * @return string URL to font file. + */ + private function replace_url_temp_placeholder( $url ) { + $url = str_replace( 'file:./', '', $url ); + + return wp_make_link_relative( get_theme_file_uri( $url ) ); + } + + /** + * Compiles the font variation settings. + * + * @since 5.9.0 + * + * @param array $font_variation_settings Array of font variation settings. + * @return string The CSS. + */ + private function compile_variations( array $font_variation_settings ) { + $variations = ''; + + foreach ( $font_variation_settings as $key => $value ) { + $variations .= "$key $value"; + } + + return $variations; + } } diff --git a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-provider.php b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-provider.php index bee09cbfcd233..f8fb2553331be 100644 --- a/src/wp-includes/webfonts-api/providers/class-wp-webfonts-provider.php +++ b/src/wp-includes/webfonts-api/providers/class-wp-webfonts-provider.php @@ -42,37 +42,13 @@ abstract class WP_Webfonts_Provider { protected $root_url = ''; /** - * Webfont parameters. + * Webfonts to be processed. * * @since 5.9.0 * - * @var array - */ - protected $params = array(); - - /** - * An array of valid CSS properties for @font-face. - * - * @since 5.9.0 - * - * @var array + * @var array[] */ - protected $valid_font_face_properties = 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', - ); + protected $webfonts = array(); /** * An array of API parameters which will not be added to the @font-face. @@ -81,7 +57,7 @@ abstract class WP_Webfonts_Provider { * * @var array */ - protected $api_params = array(); + protected $invalid_parameters = array(); /** * Get the provider's unique ID. @@ -117,157 +93,59 @@ public function get_preconnect_urls() { } /** - * Validate the $params array. + * Sets the webfonts. * - * @todo Move to validator.Validation should happen during webfont collection, i.e.during the schema validation. + * The webfonts have been validated, are in kebab_case, and + * are arranged by provider and then by font-family. * * @since 5.9.0 * - * @param array $params The parameters to validate. - * - * @return array + * @param array[] $webfonts The webfont's parameters. */ - public function get_validated_params( $params ) { - - // Convert camelCase to kebab-case. - $kebab = array(); - foreach ( $params as $key => $value ) { - $kebab[ $this->camel_to_kebab( $key ) ] = $value; - } - - // Default values. - $defaults = array( - 'font-weight' => '400', - 'font-style' => 'normal', - 'font-display' => 'fallback', - 'src' => array(), - ); - - // Merge defaults with passed params. - $params = wp_parse_args( $kebab, $defaults ); - - // Whitelisted params. - $whitelist = array_merge( $this->valid_font_face_properties, $this->api_params ); - - // Only allow whitelisted properties. - foreach ( $params as $key => $value ) { - if ( ! in_array( $key, $whitelist, true ) ) { - unset( $params[ $key ] ); - } - } - - // Order $src items to optimize for browser support. - if ( ! empty( $params['src'] ) ) { - $params['src'] = (array) $params['src']; - $src = array(); - $src_ordered = array(); - - foreach ( $params['src'] as $url ) { - // Add data URIs first. - if ( 0 === strpos( 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' => $src['woff2'], - 'format' => 'woff2', - ); - } - - // Add woff. - if ( ! empty( $src['woff'] ) ) { - $src_ordered[] = array( - 'url' => $src['woff'], - 'format' => 'woff', - ); - } - - // Add ttf. - if ( ! empty( $src['ttf'] ) ) { - $src_ordered[] = array( - 'url' => $src['ttf'], - 'format' => 'truetype', - ); - } - - // Add eot. - if ( ! empty( $src['eot'] ) ) { - $src_ordered[] = array( - 'url' => $src['eot'], - 'format' => 'embedded-opentype', - ); - } - - // Add otf. - if ( ! empty( $src['otf'] ) ) { - $src_ordered[] = array( - 'url' => $src['otf'], - 'format' => 'opentype', - ); - } - $params['src'] = $src_ordered; - } - - // Only allow valid font-display values. - if ( - ! empty( $params['font-display'] ) && - ! in_array( $params['font-display'], array( 'auto', 'block', 'swap', 'fallback' ), true ) - ) { - $params['font-display'] = 'fallback'; - } - - // Only allow valid font-style values. - if ( - ! empty( $params['font-style'] ) && - ! in_array( $params['font-style'], array( 'normal', 'italic', 'oblique' ), true ) && - ! preg_match( '/^oblique\s+(\d+)%/', $params['font-style'], $matches ) - ) { - $params['font-style'] = 'normal'; - } + public function set_webfonts( array $webfonts ) { + $this->webfonts = $webfonts; - // Only allow valid font-weight values. - if ( - ! empty( $params['font-weight'] ) && - ! in_array( $params['font-weight'], array( 'normal', 'bold', 'bolder', 'lighter', 'inherit' ), true ) && - ! preg_match( '/^(\d+)$/', $params['font-weight'], $matches ) && - ! preg_match( '/^(\d+)\s+(\d+)$/', $params['font-weight'], $matches ) - ) { - $params['font-weight'] = 'normal'; + foreach ( $this->webfonts as $registered_key => $webfont ) { + $this->webfonts[ $registered_key ] = $this->prepare( $webfont ); } - - return $params; } /** - * Set the object's params. + * Prepares the given webfont. * * @since 5.9.0 * - * @param string[][] $webfonts The webfont's parameters. + * @param array $webfont Webfont to validate. + * @return array */ - public function set_webfonts( array $webfonts ) { - //$params = $this->get_validated_params( $params ); - $this->params = $webfonts; + protected function prepare( array $webfont ) { + if ( empty( $this->invalid_parameters ) ) { + return $webfont; + } + + return $this->remove_invalid_properties( $webfont ); } /** - * Get the object's params. + * Removes invalid properties from the webfont. * * @since 5.9.0 * - * @return array + * @param array $webfont Webfont to process. + * @return array Webfont with valid properties. */ - public function get_params() { - return $this->params; + private function remove_invalid_properties( array $webfont ) { + $invalid_parameters = $this->invalid_parameters; + + $webfont = array_filter( + $webfont, + static function( $key ) use ( $invalid_parameters ) { + return ! in_array( $key, $invalid_parameters, true ); + }, + ARRAY_FILTER_USE_KEY + ); + + return $webfont; } /** @@ -275,24 +153,22 @@ public function get_params() { * * @since 5.9.0 * - * @return string + * @return string Webfonts CSS. */ abstract public function get_css(); /** * Get cached styles from a remote URL. * - * @access public * @since 5.9.0 * - * @param string $url The URL to fetch. * @param string $id An ID used to cache the styles. + * @param string $url The URL to fetch. * @param array $args The arguments to pass to wp_remote_get(). * @param array $additional_props Additional properties to add to the @font-face styles. - * * @return string The styles. */ - public function get_cached_remote_styles( $id, $url, $args = array(), $additional_props = array() ) { + public function get_cached_remote_styles( $id, $url, array $args = array(), array $additional_props = array() ) { $css = get_site_transient( $id ); // Get remote response and cache the CSS if it hasn't been cached already. @@ -329,22 +205,16 @@ public function get_cached_remote_styles( $id, $url, $args = array(), $additiona * * @param string $url The URL to fetch. * @param array $args The arguments to pass to wp_remote_get(). - * @return string The styles. + * @return string The styles on success. Empty string on failure. */ - public function get_remote_styles( $url, $args = array() ) { - $args = wp_parse_args( - $args, - array( - // Use a modern user-agent, to get woff2 files. - 'user-agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0', - ) - ); + public function get_remote_styles( $url, array $args = array() ) { + // Use a modern user-agent, to get woff2 files. + $args['user-agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0'; // Get the remote URL contents. $response = wp_remote_get( $url, $args ); // Early return if the request failed. - // Cache an empty string for 60 seconds to avoid bottlenecks. if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return ''; } @@ -352,16 +222,4 @@ public function get_remote_styles( $url, $args = array() ) { // Get the response body. return wp_remote_retrieve_body( $response ); } - - /** - * Convert camelCase to kebab-case. - * - * @since 5.9.0 - * - * @param string $string The string to convert. - * @return string - */ - public function camel_to_kebab( $string ) { - return strtolower( preg_replace( '/(?provider = new WP_Webfonts_Google_Provider(); + } + + /** + * @covers WP_Webfonts_Google_Provider::set_webfonts + */ + public function test_set_webfonts() { + $webfonts = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'italic', + 'font-weight' => '700', + 'font-display' => 'fallback', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => '900', + 'font-display' => 'fallback', + ), + ); + + $this->provider->set_webfonts( $webfonts ); + + $this->assertSame( $webfonts, $this->get_webfonts_property() ); + } + + /** + * @covers WP_Webfonts_Google_Provider::build_collection_api_urls + * + * @dataProvider data_build_collection_api_urls + * + * @since 5.9.0 + * + * @param array $webfonts Webfonts input. + * @param array $expected Expected urls. + */ + public function test_build_collection_api_urls( array $webfonts, array $expected ) { + $method = new ReflectionMethod( $this->provider, 'build_collection_api_urls' ); + $method->setAccessible( true ); + $actual = $method->invoke( $this->provider, $webfonts ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Data Provider. + * + * @return array + */ + public function data_build_collection_api_urls() { + return array( + 'single font-family + single variation' => array( + 'webfonts' => array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'expected' => array( + 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=fallback', + ), + ), + 'single font-family + multiple variations' => array( + 'webfonts' => array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'italic', + 'font-weight' => '700', + 'font-display' => 'fallback', + ), + ), + 'expected' => array( + 'https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;1,700&display=fallback', + ), + ), + 'multiple font-families and variations' => array( + 'webfonts' => array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'italic', + 'font-weight' => '700', + 'font-display' => 'fallback', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => '900', + 'font-display' => 'fallback', + ), + ), + 'expected' => array( + 'https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;1,700&family=Roboto:wght@900&display=fallback', + ), + ), + ); + } + + private function get_webfonts_property() { + $property = new ReflectionProperty( $this->provider, 'webfonts' ); + $property->setAccessible( true ); + + return $property->getValue( $this->provider ); + } +} diff --git a/tests/phpunit/tests/webfonts-api/providers/wpWebfontsLocalProvider.php b/tests/phpunit/tests/webfonts-api/providers/wpWebfontsLocalProvider.php new file mode 100644 index 0000000000000..830d0d0c2f8dc --- /dev/null +++ b/tests/phpunit/tests/webfonts-api/providers/wpWebfontsLocalProvider.php @@ -0,0 +1,244 @@ +provider = new WP_Webfonts_Local_Provider(); + + $this->set_up_theme(); + } + + /** + * Local `src` paths to need to be relative to the theme. This method sets up the + * `wp-content/themes/` directory to ensure consistency when running tests. + */ + private function set_up_theme() { + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $GLOBALS['wp_theme_directories'] = array( $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'] ); + } + + function tear_down() { + // 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(); + } + + /** + * @covers WP_Webfonts_Local_Provider::set_webfonts + */ + public function test_set_webfonts() { + $webfonts = array( + 'source-serif-pro.normal.200 900' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'source-serif-pro.italic.200 900' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + ), + ); + + $expected = array( + 'source-serif-pro.normal.200 900' => array( + 'provider' => 'local', + 'font-family' => '"Source Serif Pro"', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'format' => 'woff2', + ), + ), + ), + 'source-serif-pro.italic.200 900' => array( + 'provider' => 'local', + 'font-family' => '"Source Serif Pro"', + 'font-style' => 'italic', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'format' => 'woff2', + ), + ), + ), + ); + + $this->provider->set_webfonts( $webfonts ); + + $property = $this->get_webfonts_property(); + $this->assertSame( $expected, $property->getValue( $this->provider ) ); + } + + /** + * @covers WP_Webfonts_Local_Provider::get_css + * + * @dataProvider data_get_css + * + * @param array $webfonts Prepared webfonts (to store in WP_Webfonts_Local_Provider::$webfonts property) + * @param string $expected Expected CSS. + */ + public function test_get_css( array $webfonts, $expected ) { + $property = $this->get_webfonts_property(); + $property->setValue( $this->provider, $webfonts ); + + $this->assertSame( $expected, $this->provider->get_css() ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_css() { + return array( + 'URL to root assets dir' => array( + 'webfonts' => array( + 'open-sans.italic.400 900' => array( + 'provider' => 'local', + 'font-family' => '"Open Sans"', + 'font-style' => 'italic', + 'font-weight' => '400 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'http://example.org/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf', + 'format' => 'ttf', + ), + ), + ), + 'open-sans.normal.400 900' => array( + 'provider' => 'local', + 'font-family' => '"Open Sans"', + 'font-style' => 'normal', + 'font-weight' => '400 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'http://example.org/assets/fonts/OpenSans-VariableFont_wdth,wght.ttf', + 'format' => 'ttf', + ), + ), + ), + ), + 'expected' => << array( + 'webfonts' => array( + 'source-serif-pro.normal.200 900' => array( + 'provider' => 'local', + 'font-family' => '"Source Serif Pro"', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'format' => 'woff2', + ), + ), + ), + 'source-serif-pro.italic.200 900' => array( + 'provider' => 'local', + 'font-family' => '"Source Serif Pro"', + 'font-style' => 'italic', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => array( + array( + 'url' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'format' => 'woff2', + ), + ), + ), + ), + 'expected' => <<provider, 'webfonts' ); + $property->setAccessible( true ); + + return $property; + } +} diff --git a/tests/phpunit/tests/webfonts-api/wpWebfontsController.php b/tests/phpunit/tests/webfonts-api/wpWebfontsController.php index 831e9e9a7721d..97ef0cd8335c1 100644 --- a/tests/phpunit/tests/webfonts-api/wpWebfontsController.php +++ b/tests/phpunit/tests/webfonts-api/wpWebfontsController.php @@ -5,240 +5,340 @@ * @covers WP_Webfonts_Controller */ class Tests_Webfonts_API_wpWebfontsController extends WP_UnitTestCase { - private static $webfonts; + private $controller; + private $webfont_registry_mock; + private $provider_registry_mock; public static function set_up_before_class() { - require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-schema-validator.php'; require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-registry.php'; require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-provider-registry.php'; require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-controller.php'; require_once __DIR__ . '/mocks/class-my-custom-webfonts-provider-mock.php'; - - self::$webfonts = self::get_webfonts(); } - private function get_controller() { - $controller = new WP_Webfonts_Controller( - new WP_Webfonts_Registry( - new WP_Webfonts_Schema_Validator() - ), - new WP_Webfonts_Provider_Registry() - ); - $controller->init(); + public function set_up() { + parent::set_up(); - return $controller; + $this->webfont_registry_mock = $this->getMockBuilder( 'WP_Webfonts_Registry' ) + ->disableOriginalConstructor() + ->getMock(); + $this->provider_registry_mock = $this->getMockBuilder( 'WP_Webfonts_Provider_Registry' ) + ->getMock(); + $this->controller = new WP_Webfonts_Controller( + $this->webfont_registry_mock, + $this->provider_registry_mock + ); } /** - * @covers WP_Webfonts_Controller::get_webfonts + * @covers WP_Webfonts_Controller::init + * + * @dataProvider data_init + * + * @param string $hook Expected hook name. + * @param bool $did_action Whether the action fired or not. */ - public function test_get_webfonts_when_empty() { - $controller = $this->get_controller(); + public function test_init( $hook, $did_action ) { + $this->provider_registry_mock + ->expects( $this->once() ) + ->method( 'init' ); - $this->assertSame( array(), $controller->get_webfonts() ); + if ( $did_action ) { + do_action( 'wp_enqueue_scripts' ); + } + + $this->controller->init(); + + $this->assertSame( + 10, + has_action( $hook, array( $this->controller, 'generate_and_enqueue_styles' ) ) + ); + $this->assertSame( + 10, + has_action( 'admin_init', array( $this->controller, 'generate_and_enqueue_editor_styles' ) ) + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_init() { + return array( + 'did_action fired' => array( + 'hook' => 'wp_print_footer_scripts', + 'did_action' => true, + ), + 'did_action did not fire' => array( + 'hook' => 'wp_enqueue_scripts', + 'did_action' => false, + ), + ); } /** * @covers WP_Webfonts_Controller::register_webfonts */ public function test_register_webfonts_with_empty_schema() { - $controller = $this->get_controller(); - $webfonts = array(); + $webfonts = array(); - $controller->register_webfonts( $webfonts ); + $this->webfont_registry_mock + ->expects( $this->never() ) + ->method( 'register' ); - $this->assertSame( array(), $controller->get_webfonts() ); + $this->controller->register_webfonts( $webfonts ); } /** * @covers WP_Webfonts_Controller::register_webfonts * @covers WP_Webfonts_Controller::register_webfont - * - * @dataProvider data_register_webfonts_with_invalid_schema - * - * @param array $webfonts Webfonts input. - * @param array $expected Expected registered webfonts. */ - public function test_register_webfonts_with_invalid_schema( array $webfonts, $expected_message, array $expected ) { - $this->expectNotice(); - $this->expectNoticeMessage( $expected_message ); + public function test_register_webfonts() { + $webfonts = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'italic', + 'fontWeight' => '700', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'fontFamily' => 'Roboto', + 'fontStyle' => 'normal', + 'fontWeight' => '900', + ), + ); - $controller = $this->get_controller(); - $controller->register_webfonts( $webfonts ); + $this->mock_register_webfonts( $webfonts ); - $this->assertSame( $expected, $controller->get_webfonts() ); + $this->controller->register_webfonts( $webfonts ); } /** - * Data Provider. - * - * See Tests_Webfonts_API_wpWebfontsRegistry::data_register_with_invalid_schema() - * for more complete test coverage. - * - * return @array + * @covers WP_Webfonts_Controller::get_webfonts */ - public function data_register_webfonts_with_invalid_schema() { - return array( - 'provider: invalid type' => array( - 'webfonts' => array( - array( - 'provider' => null, - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - ), - array( - 'provider' => 'google', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'italic', - 'fontWeight' => '700', - ), - ), - 'expected_message' => 'Webfont provider must be a non-empty string.', - 'expected' => array( - 'open-sans.italic.700' => array( - 'provider' => 'google', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'italic', - 'fontWeight' => '700', - ), - ), + public function test_get_webfonts() { + $expected = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'italic', + 'fontWeight' => '700', ), - 'font family: invalid key' => array( - 'webfonts' => array( - array( - 'provider' => 'google', - 'fontFamily' => 'Roboto', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - array( - 'provider' => 'google', - 'font_family' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - ), - ), - 'expected_message' => 'Webfont font family must be a non-empty string.', - 'expected' => array( - 'roboto.normal.900' => array( - 'provider' => 'google', - 'fontFamily' => 'Roboto', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => '900', ), ); + + $this->webfont_registry_mock + ->expects( $this->once() ) + ->method( 'get_registry' ) + ->willReturn( $expected ); + + $this->assertSame( $expected, $this->controller->get_webfonts() ); } /** - * @covers WP_Webfonts_Controller::register_webfonts - * @covers WP_Webfonts_Controller::register_webfont + * @covers WP_Webfonts_Controller::get_webfonts_by_provider */ - public function test_register_webfonts_with_valid_schema() { - $controller = $this->get_controller(); + public function test_get_webfonts_by_provider() { + $provider_id = 'google'; + $expected = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + ), + 'open-sans.italic.700' => array( + 'provider' => 'google', + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'italic', + 'fontWeight' => '700', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'fontFamily' => 'Roboto', + 'fontStyle' => 'normal', + 'fontWeight' => '900', + ), + ); - $controller->register_webfonts( self::$webfonts ); + $this->webfont_registry_mock + ->expects( $this->once() ) + ->method( 'get_by_provider' ) + ->with( $this->equalTo( $provider_id ) ) + ->willReturn( $expected ); - $expected = array( - 'open-sans.normal.400' => self::$webfonts[0], - 'open-sans.italic.700' => self::$webfonts[1], - 'roboto.normal.900' => self::$webfonts[2], - ); - $this->assertSame( $expected, $controller->get_webfonts() ); + $this->assertSame( $expected, $this->controller->get_webfonts_by_provider( $provider_id ) ); } /** - * @covers WP_Webfonts_Controller::get_registered_providers + * @covers WP_Webfonts_Controller::get_webfonts_by_font_family */ - public function test_get_registered_providers_core_only() { - $controller = $this->get_controller(); + public function test_get_webfonts_by_font_family() { + $font_family = 'roboto'; + $expected = array( + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => '900', + ), + ); - $providers = $controller->get_registered_providers(); + $this->webfont_registry_mock + ->expects( $this->once() ) + ->method( 'get_by_font_family' ) + ->with( $this->equalTo( $font_family ) ) + ->willReturn( $expected ); - $expected = array( 'google', 'local' ); - $this->assertSame( $expected, array_keys( $providers ) ); - $this->assertInstanceOf( 'WP_Webfonts_Google_Provider', $providers['google'] ); - $this->assertInstanceOf( 'WP_Webfonts_Local_Provider', $providers['local'] ); + $this->assertSame( $expected, $this->controller->get_webfonts_by_font_family( $font_family ) ); } /** - * @covers WP_Webfonts_Controller::register_provider * @covers WP_Webfonts_Controller::get_registered_providers */ - public function test_register_provider() { - $controller = $this->get_controller(); + public function test_get_registered_providers() { + $expected = array( + 'my-custom-provider' => new My_Custom_Webfonts_Provider_Mock(), + ); - $controller->register_provider( My_Custom_Webfonts_Provider_Mock::class ); + $this->provider_registry_mock + ->expects( $this->once() ) + ->method( 'get_registry' ) + ->willReturn( $expected ); - $providers = $controller->get_registered_providers(); + $actual = $this->controller->get_registered_providers(); - $expected = array( 'google', 'local', 'my-custom-provider' ); - $this->assertSame( $expected, array_keys( $providers ) ); + $this->assertIsArray( $actual ); + $this->assertCount( 1, $actual ); + $this->assertArrayHasKey( 'my-custom-provider', $actual ); + $this->assertInstanceOf( 'My_Custom_Webfonts_Provider_Mock', $actual['my-custom-provider'] ); } /** - * @covers WP_Webfonts_Controller::get_webfonts_by_font_family + * @covers WP_Webfonts_Controller::register_provider */ - public function test_get_webfonts_by_font_family() { - $controller = $this->get_controller(); - $controller->register_webfonts( self::$webfonts ); + public function test_register_provider() { + $classname = My_Custom_Webfonts_Provider_Mock::class; - $expected = array( - 'roboto.normal.900' => self::$webfonts[2], - ); + $this->provider_registry_mock + ->expects( $this->once() ) + ->method( 'register' ) + ->with( $this->equalTo( $classname ) ) + ->willReturn( true ); - $this->assertSame( $expected, $controller->get_webfonts_by_font_family( 'roboto' ) ); + $this->assertTrue( $this->controller->register_provider( $classname ) ); } /** - * @covers WP_Webfonts_Controller::get_webfonts_by_font_family + * @covers WP_Webfonts_Controller::generate_and_enqueue_styles + * @covers WP_Webfonts_Controller::generate_and_enqueue_editor_styles * - * @dataProvider data_get_by_font_family_with_invalid_input + * @dataProvider data_generate_and_enqueue_editor_styles * - * @param mixed Font family input. + * @param string $stylestyle_handle Handle for the registered stylesheet. */ - public function test_get_webfonts_by_font_family_with_invalid_input( $font_family ) { - $controller = $this->get_controller(); - $controller->register_webfonts( self::$webfonts ); + public function test_generate_and_enqueue_editor_styles( $stylestyle_handle ) { + /* + * Set the stylesheet_handle property. + * This is set in WP_Webfonts_Controller::init(); however, init is not part + * of this test (as it has its own test). + */ + $property = new ReflectionProperty( $this->controller, 'stylesheet_handle' ); + $property->setAccessible( true ); + $property->setValue( $this->controller, $stylestyle_handle ); + + // Set up the provider mock. + $provider = new My_Custom_Webfonts_Provider_Mock(); + $providers = array( + 'my-custom-provider' => $provider, + ); + $this->provider_registry_mock + ->expects( $this->once() ) + ->method( 'get_registry' ) + ->willReturn( $providers ); - $this->assertSame( array(), $controller->get_webfonts_by_font_family( $font_family ) ); + // Set up the webfonts registry mock. + $webfonts = array( + 'source-serif-pro.normal.200 900' => array( + 'provider' => 'my-custom-provider', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + ), + 'source-serif-pro.italic.200 900' => array( + 'provider' => 'my-custom-provider', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '200 900', + ), + ); + $this->webfont_registry_mock + ->expects( $this->once() ) + ->method( 'get_by_provider' ) + ->with( $this->equalTo( 'my-custom-provider' ) ) + ->willReturn( $webfonts ); + + // Fire the method being tested. + $this->controller->generate_and_enqueue_styles(); + + /* + * As this method adds an inline style, the test needs to print it. + * Print the webfont styles and test the output matches expectation. + */ + $expected = "\n"; + $this->expectOutputString( $expected ); + wp_print_styles( $stylestyle_handle ); } /** - * Data Provider. + * Data provider. * - * return @array + * @return array */ - public function data_get_by_font_family_with_invalid_input() { + public function data_generate_and_enqueue_editor_styles() { return array( - 'not a string' => array( true ), - 'empty string' => array( '' ), - 'font family not registered' => array( 'Does not exist' ), + 'for wp_enqueue_scripts' => array( 'webfonts' ), + 'for wp_print_footer_scripts' => array( 'webfonts-footer' ), ); } - private static function get_webfonts() { - return array( - array( - 'provider' => 'google', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - ), - array( - 'provider' => 'google', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'italic', - 'fontWeight' => '700', - ), - array( - 'provider' => 'google', - 'fontFamily' => 'Roboto', - 'fontStyle' => 'normal', - 'fontWeight' => '900', - ), - ); + /** + * Mocks WP_Webfonts_Provider::register(). + * + * @since 5.9.0 + * + * @param array $webfonts Webfonts to register. + */ + private function mock_register_webfonts( array $webfonts ) { + $register_webfonts = array(); + foreach ( $webfonts as $webfont ) { + $register_webfonts[] = array( $this->equalTo( $webfont ) ); + } + + $this->webfont_registry_mock + ->expects( $this->exactly( count( $webfonts ) ) ) + ->method( 'register' ) + ->withConsecutive( ...$register_webfonts ); } } diff --git a/tests/phpunit/tests/webfonts-api/wpWebfontsRegistry.php b/tests/phpunit/tests/webfonts-api/wpWebfontsRegistry.php index b0824fcec5656..3fab2f46e2ab5 100644 --- a/tests/phpunit/tests/webfonts-api/wpWebfontsRegistry.php +++ b/tests/phpunit/tests/webfonts-api/wpWebfontsRegistry.php @@ -5,19 +5,53 @@ * @covers WP_Webfonts_Registry */ class Tests_Webfonts_API_wpWebfontsRegistry extends WP_UnitTestCase { - private static $webfonts; + private $registry; + private $validator_mock; - public static function wpSetUpBeforeClass() { + public static function set_up_before_class() { require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-schema-validator.php'; require_once ABSPATH . WPINC . '/webfonts-api/class-wp-webfonts-registry.php'; + } + + public function set_up() { + parent::set_up(); + + $this->validator_mock = $this->getMockBuilder( 'WP_Webfonts_Schema_Validator' ) + ->getMock(); - self::$webfonts = self::get_webfonts(); + $this->registry = new WP_Webfonts_Registry( $this->validator_mock ); } - private function get_registry() { - return new WP_Webfonts_Registry( - new WP_Webfonts_Schema_Validator() + /** + * @covers WP_Webfonts_Registry::get_registry + */ + public function test_get_registry() { + $expected = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Robot', + 'font-style' => 'normal', + 'font-weight' => '900', + 'font-display' => 'fallback', + ), ); + + /* + * Set the registry property. + * This is set in WP_Webfonts_Registry::register(), which not part of this test. + */ + $property = new ReflectionProperty( $this->registry, 'registry' ); + $property->setAccessible( true ); + $property->setValue( $this->registry, $expected ); + + $this->assertSame( $expected, $this->registry->get_registry() ); } /** @@ -27,135 +61,81 @@ private function get_registry() { * * @param array Webfonts input. */ - public function test_register_with_invalid_schema( array $webfont, $expected_message ) { - $this->expectNotice(); - $this->expectNoticeMessage( $expected_message ); - - $registry = $this->get_registry(); + public function test_register_with_invalid_schema( array $webfont ) { + $this->validator_mock + ->expects( $this->once() ) + ->method( 'is_valid_schema' ) + ->willReturn( false ); + $this->validator_mock + ->expects( $this->never() ) + ->method( 'set_valid_properties' ); - $this->assertSame( '', $registry->register( $webfont ) ); + $this->assertSame( '', $this->registry->register( $webfont ) ); } /** * Data Provider. * - * return @array + * @return array */ public function data_register_with_invalid_schema() { return array( 'empty array - no schema' => array( - 'webfont' => array(), - 'expected_message' => 'Webfont provider must be a non-empty string.', + array(), ), - 'provider: not set' => array( - 'webfont' => array( - 'fontFamily' => 'Open Sans', + 'provider: not defined' => array( + array( + 'fontFamily' => 'Some Font', 'fontStyle' => 'normal', 'fontWeight' => '400', ), - 'expected_message' => 'Webfont provider must be a non-empty string.', ), 'provider: empty string' => array( - 'webfont' => array( - 'provider' => '', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => '400', + array( + 'provider' => '', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', ), - 'expected_message' => 'Webfont provider must be a non-empty string.', ), - 'provider: invalid type' => array( - 'webfont' => array( - 'provider' => true, - 'fontFamily' => 'Open Sans', + 'provider: not a string' => array( + array( + 'provider' => null, + 'fontFamily' => 'Some Font', 'fontStyle' => 'normal', 'fontWeight' => '400', ), - 'expected_message' => 'Webfont provider must be a non-empty string.', ), - 'font family: not set' => array( - 'webfont' => array( + 'font family: not defined' => array( + array( 'provider' => 'local', 'fontStyle' => 'normal', 'fontWeight' => '400', ), - 'expected_message' => 'Webfont font family must be a non-empty string.', ), - 'font family: empty string' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => '', - 'fontStyle' => 'normal', - 'fontWeight' => '400', + 'font-family: not defined' => array( + array( + 'provider' => 'some-provider', + 'font-style' => 'normal', + 'font-weight' => '400', ), - 'expected_message' => 'Webfont font family must be a non-empty string.', ), - 'font family: invalid type' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => true, - 'fontStyle' => 'normal', - 'fontWeight' => '400', + 'font-family: empty string' => array( + array( + 'provider' => 'some-provider', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', ), - 'expected_message' => 'Webfont font family must be a non-empty string.', ), - 'font style: empty string' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => '', - 'fontWeight' => '400', + 'font-family: not a string' => array( + array( + 'provider' => 'some-provider', + 'font-family' => null, + 'font-style' => 'normal', + 'font-weight' => '400', ), - 'expected_message' => 'Webfont font style must be a non-empty string.', ), - 'font style: invalid type' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => true, - 'fontWeight' => '400', - ), - 'expected_message' => 'Webfont font style must be a non-empty string.', - ), - 'font style: invalid value' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'invalid', - 'fontWeight' => '400', - ), - 'expected_message' => 'Webfont font style must be normal, italic, oblique, or oblique . Given: invalid.', - ), - 'font weight: empty string' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => '', - ), - 'expected_message' => 'Webfont font weight must be a non-empty string.', - ), - 'font weight: invalid type' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => true, - ), - 'expected_message' => 'Webfont font weight must be a non-empty string.', - ), - // @todo uncomment once value validation is added. - /* - 'font weight: invalid value' => array( - 'webfont' => array( - 'provider' => 'local', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'normal', - 'fontWeight' => 'invalid', - ), - 'expected_message' => 'Webfont font weight must be a non-empty string.', - ), - */ ); } @@ -164,13 +144,21 @@ public function data_register_with_invalid_schema() { * * @dataProvider data_register_with_valid_schema * - * @param array Webfonts input. - * @param string Expected registration key. + * @param array $webfont Webfont input. + * @param array $validated_webfont Webfont after being processed by the validator. + * @param string $expected Expected return value. */ - public function test_register_with_valid_schema( array $webfont, $expected ) { - $registry = $this->get_registry(); + public function test_register_with_valid_schema( array $webfont, array $validated_webfont, $expected ) { + $this->validator_mock + ->expects( $this->once() ) + ->method( 'is_valid_schema' ) + ->willReturn( true ); + $this->validator_mock + ->expects( $this->once() ) + ->method( 'set_valid_properties' ) + ->willReturn( $validated_webfont ); - $this->assertSame( $expected, $registry->register( $webfont ) ); + $this->assertSame( $expected, $this->registry->register( $webfont ) ); } /** @@ -180,63 +168,129 @@ public function test_register_with_valid_schema( array $webfont, $expected ) { */ public function data_register_with_valid_schema() { return array( - 'Open Sans; normal; 400' => array( - 'webfonts' => array( + 'camelCase schema' => array( + 'webfont' => array( 'provider' => 'google', 'fontFamily' => 'Open Sans', 'fontStyle' => 'normal', 'fontWeight' => '400', ), - 'expected' => 'open-sans.normal.400', + 'validated_webfont' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'expected' => 'open-sans.normal.400', ), - 'Open Sans; italic; 900' => array( - 'webfonts' => array( - 'provider' => 'google', - 'fontFamily' => 'Open Sans', - 'fontStyle' => 'italic', - 'fontWeight' => '900', + 'kebab-case schema' => array( + 'webfont' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => 'normal', + ), + 'validated_webfont' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => 'normal', + 'font-display' => 'fallback', ), - 'expected' => 'open-sans.italic.900', + 'expected' => 'roboto.normal.normal', + ), + 'camelCase with src' => array( + 'webfont' => array( + 'provider' => 'local', + 'fontFamily' => 'Source Serif Pro', + 'fontStyle' => 'normal', + 'fontWeight' => '200 900', + 'fontStretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'validated_webfont' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'expected' => 'source-serif-pro.normal.200 900', + ), + 'kebab-case with src' => array( + 'webfont' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'validated_webfont' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'expected' => 'source-serif-pro.normal.200 900', ), ); } /** - * @covers WP_Webfonts_Registry::get_registry + * @covers WP_Webfonts_Registry::get_by_provider */ - public function test_get_registry() { - $registry = $this->get_registry(); - $this->register_webfonts( $registry ); + public function test_get_by_provider_when_does_not_exist() { + /* + * Set the `registry_by_provider` property. + * This is set in WP_Webfonts_Registry::register(), which not part of this test. + */ + $property = new ReflectionProperty( $this->registry, 'registry_by_provider' ); + $property->setAccessible( true ); + $property->setValue( $this->registry, array( 'google', 'local' ) ); - $this->assertSame( self::$webfonts, $registry->get_registry() ); + $this->assertSame( array(), $this->registry->get_by_provider( 'my-custom-provider' ) ); } /** - * @covers WP_Webfonts_Registry::get_by_font_family + * Data Provider. + * + * return @array */ - public function test_get_by_font_family() { - $registry = $this->get_registry(); - $this->register_webfonts( $registry ); - - $expected = array( - 'roboto.normal.400' => self::$webfonts['roboto.normal.400'], - 'roboto.normal.900' => self::$webfonts['roboto.normal.900'], + public function data_get_by_font_family_when_invalid_input() { + return array( + 'not a string' => array( null ), + 'empty string' => array( '' ), ); - $this->assertSame( $expected, $registry->get_by_font_family( 'Roboto' ) ); } /** - * @covers WP_Webfonts_Registry::get_by_font_family + * As there are many moving parts to getting by provider, this test is an integration + * test that does not mock. * - * @dataProvider data_get_by_font_family_with_invalid_input + * @covers WP_Webfonts_Registry::get_by_provider + * @covers WP_Webfonts_Registry::register * - * @param mixed Font family input. + * @dataProvider data_get_by_provider_integrated + * + * @param array $webfonts Given webfont to register. + * @param string $provider_id Provider ID to query. + * @param array $expected Expected return value. */ - public function test_get_by_font_family_with_invalid_input( $font_family ) { - $registry = $this->get_registry(); - $this->register_webfonts( $registry ); + public function test_get_by_provider_integrated( array $webfonts, $provider_id, $expected ) { + $registry = new WP_Webfonts_Registry( new WP_Webfonts_Schema_Validator() ); + + foreach ( $webfonts as $webfont ) { + $registry->register( $webfont ); + } - $this->assertSame( array(), $registry->get_by_font_family( $font_family ) ); + $this->assertSame( $expected, $registry->get_by_provider( $provider_id ) ); } /** @@ -244,61 +298,205 @@ public function test_get_by_font_family_with_invalid_input( $font_family ) { * * return @array */ - public function data_get_by_font_family_with_invalid_input() { + public function data_get_by_provider_integrated() { return array( - 'not a string' => array( true ), - 'empty string' => array( '' ), - 'font family not registered' => array( 'Does not exist' ), + 'no webfonts for requested provider' => array( + 'webfonts' => array( + array( + 'provider' => 'google', + 'fontFamily' => 'Lato', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + ), + 'provider_id' => 'local', + 'expected' => array(), + ), + 'with one provider' => array( + 'webfonts' => array( + array( + 'provider' => 'google', + 'fontFamily' => 'Lato', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + array( + 'provider' => 'google', + 'fontFamily' => 'Roboto', + 'fontStyle' => 'normal', + 'fontWeight' => '900', + ), + ), + 'provider_id' => 'google', + 'expected' => array( + 'lato.italic.400' => array( + 'provider' => 'google', + 'font-family' => 'Lato', + 'font-style' => 'italic', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'roboto.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Roboto', + 'font-style' => 'normal', + 'font-weight' => '900', + 'font-display' => 'fallback', + ), + ), + ), + 'with multiple providers' => array( + 'webfonts' => array( + array( + 'provider' => 'google', + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + ), + array( + 'provider' => 'local', + 'fontFamily' => 'Source Serif Pro', + 'fontStyle' => 'normal', + 'fontWeight' => '200 900', + 'fontStretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + array( + 'provider' => 'google', + 'fontFamily' => 'Roboto', + 'fontStyle' => 'normal', + 'fontWeight' => '900', + ), + ), + 'provider_id' => 'local', + 'expected' => array( + 'source-serif-pro.normal.200 900' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + ), + ), ); } /** - * Register the webfonts helper function. + * @covers WP_Webfonts_Registry::get_by_font_family + * + * @dataProvider data_get_by_font_family_when_invalid_input + * + * @param string $font_family Given font-family for the query. + */ + public function test_get_by_font_family_when_invalid_input( $font_family ) { + $this->assertSame( array(), $this->registry->get_by_font_family( $font_family ) ); + } + + /** + * As there are many moving parts to getting by font-family, this test is an integration + * test that does not mock. + * + * @covers WP_Webfonts_Registry::get_by_font_family + * @covers WP_Webfonts_Registry::register * - * @param WP_Webfonts_Registry $registry Instance of the registry. + * @dataProvider data_get_by_font_family_integrated + * + * @param array $webfonts Given webfont to register. + * @param string $font_family Font family to query. + * @param array $expected Expected return value. */ - private function register_webfonts( $registry ) { - foreach ( self::$webfonts as $webfont ) { + public function test_get_by_font_family_integrated( array $webfonts, $font_family, $expected ) { + $registry = new WP_Webfonts_Registry( new WP_Webfonts_Schema_Validator() ); + + foreach ( $webfonts as $webfont ) { $registry->register( $webfont ); } + + $this->assertSame( $expected, $registry->get_by_font_family( $font_family ) ); } /** - * Gets the webfonts collection. + * Data Provider. * - * @return string[][] + * return @array */ - private static function get_webfonts() { - return array( - 'open-sans.normal.400' => array( + public function data_get_by_font_family_integrated() { + $webfonts = array( + array( 'provider' => 'google', 'fontFamily' => 'Open Sans', 'fontStyle' => 'normal', 'fontWeight' => '400', ), - 'open-sans.normal.900' => array( + array( 'provider' => 'google', 'fontFamily' => 'Open Sans', 'fontStyle' => 'normal', 'fontWeight' => '900', ), - 'open-sans.italic.400' => array( + array( + 'provider' => 'google', + 'fontFamily' => 'Roboto', + 'fontStyle' => 'normal', + 'fontWeight' => '900', + ), + array( 'provider' => 'google', 'fontFamily' => 'Open Sans', 'fontStyle' => 'italic', 'fontWeight' => '400', ), - 'roboto.normal.400' => array( - 'provider' => 'google', - 'fontFamily' => 'Roboto', - 'fontStyle' => 'normal', - 'fontWeight' => '400', + ); + + $expected = array( + 'open-sans.normal.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', ), - 'roboto.normal.900' => array( - 'provider' => 'google', - 'fontFamily' => 'Roboto', - 'fontStyle' => 'normal', - 'fontWeight' => '900', + 'open-sans.normal.900' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '900', + 'font-display' => 'fallback', + ), + 'open-sans.italic.400' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'italic', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ); + + return array( + 'no webfonts for requested font-family' => array( + 'webfonts' => array( + array( + 'provider' => 'google', + 'fontFamily' => 'Lato', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + ), + 'font-family' => 'Open Sans', + 'expected' => array(), + ), + 'given proper font family' => array( + 'webfonts' => $webfonts, + 'font-family' => 'Open Sans', + 'expected' => $expected, + ), + 'given font family slug' => array( + 'webfonts' => $webfonts, + 'font-family' => 'open-sans', + 'expected' => $expected, ), ); } diff --git a/tests/phpunit/tests/webfonts-api/wpWebfontsSchemaValidator.php b/tests/phpunit/tests/webfonts-api/wpWebfontsSchemaValidator.php new file mode 100644 index 0000000000000..e4f1ca1171d9e --- /dev/null +++ b/tests/phpunit/tests/webfonts-api/wpWebfontsSchemaValidator.php @@ -0,0 +1,393 @@ +assertTrue( self::$validator->is_valid_schema( $webfont ) ); + } + + /** + * Data Provider. + * + * return @array + */ + public function data_is_valid_schema_with_valid() { + return array( + 'basic schema' => array( + array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + ), + 'only provider and font-family' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + ), + ), + ); + } + + /** + * @covers WP_Webfonts_Registry::is_valid_schema + * + * @dataProvider data_is_valid_schema_with_invalid + * + * @param array $webfont Webfont input. + * @param string $expected_message Expected notice message. + */ + public function test_is_valid_schema_with_invalid( array $webfont, $expected_message ) { + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + $this->assertFalse( self::$validator->is_valid_schema( $webfont ) ); + } + + /** + * Data Provider. + * + * return @array + */ + public function data_is_valid_schema_with_invalid() { + return array( + 'empty array - no schema' => array( + 'webfont' => array(), + 'expected_message' => 'Webfont provider must be a non-empty string.', + ), + 'provider: not defined' => array( + 'webfont' => array( + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont provider must be a non-empty string.', + ), + 'provider: empty string' => array( + 'webfont' => array( + 'provider' => '', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont provider must be a non-empty string.', + ), + 'provider: not a string' => array( + 'webfont' => array( + 'provider' => null, + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont provider must be a non-empty string.', + ), + 'font-family: not defined' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont font family must be a non-empty string.', + ), + 'font-family: empty string' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont font family must be a non-empty string.', + ), + 'font-family: not a string' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => null, + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected_message' => 'Webfont font family must be a non-empty string.', + ), + ); + } + + /** + * @covers WP_Webfonts_Registry::set_valid_properties + * + * @dataProvider data_set_valid_properties_with_valid_input + * + * @param array $webfont Webfont input. + * @param array $expected Expected updated webfont. + */ + public function test_set_valid_properties_with_valid_input( array $webfont, array $expected ) { + $this->assertSame( $expected, self::$validator->set_valid_properties( $webfont ) ); + } + + /** + * Data Provider. + * + * return @array + */ + public function data_set_valid_properties_with_valid_input() { + return array( + 'basic schema' => array( + 'webfont' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + ), + 'expected' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'basic schema in opposite order' => array( + 'webfont' => array( + 'font-weight' => '400', + 'font-style' => 'normal', + 'font-family' => 'Open Sans', + 'provider' => 'google', + ), + 'expected' => array( + 'provider' => 'google', + 'font-family' => 'Open Sans', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'with src' => array( + 'webfont' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + 'expected' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + ), + 'with font-stretch' => array( + 'webfont' => array( + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'provider' => 'local', + ), + 'expected' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'file:./assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + ), + ), + ); + } + + /** + * @covers WP_Webfonts_Registry::set_valid_properties + * + * @dataProvider data_set_valid_properties_with_invalid_input + * + * @param array $webfont Webfont input. + * @param array $expected Expected updated webfont. + */ + public function test_set_valid_properties_with_invalid_input( array $webfont, array $expected ) { + $this->assertSame( $expected, self::$validator->set_valid_properties( $webfont ) ); + } + + /** + * Data Provider. + * + * return @array + */ + public function data_set_valid_properties_with_invalid_input() { + return array( + 'empty array - no schema' => array( + 'webfont' => array(), + 'expected' => array( + 'provider' => '', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'with invalid @font-face property' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'invalid' => 'should remove it', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'font-style: invalid value' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'invalid', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'font-weight: invalid value' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-weight' => 'invalid', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + 'font-display: invalid value' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-display' => 'invalid', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + ), + ); + } + + /** + * @covers WP_Webfonts_Registry::set_valid_properties + * + * @dataProvider data_set_valid_properties_with_invalid_and_error + * + * @param array $webfont Webfont input. + * @param array $expected Expected updated webfont. + * @param string $expected_message Expected notice message. + */ + public function test_set_valid_properties_with_invalid_and_error( array $webfont, array $expected, $expected_message ) { + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + $this->assertSame( $expected, self::$validator->set_valid_properties( $webfont ) ); + } + + /** + * Data Provider. + * + * return @array + */ + public function data_set_valid_properties_with_invalid_and_error() { + return array( + 'font-style: empty value' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => '', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Webfont font style must be a non-empty string.', + ), + 'font-weight: not a string' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-weight' => null, + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Webfont font style must be a non-empty string.', + ), + 'font-weight: empty value' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-weight' => '', + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Webfont font weight must be a non-empty string.', + ), + 'font-weight: not a string' => array( + 'webfont' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-weight' => true, + ), + 'expected' => array( + 'provider' => 'some-provider', + 'font-family' => 'Some Font', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Webfont font weight must be a non-empty string.', + ), + ); + } +}