diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php index e08bfa6e46ca8e..f3fde930383bc2 100644 --- a/lib/compat/wordpress-6.2/script-loader.php +++ b/lib/compat/wordpress-6.2/script-loader.php @@ -127,6 +127,30 @@ function gutenberg_resolve_assets_override() { $scripts = ob_get_clean(); + /* + * Generate web font @font-face styles for the site editor iframe. + * Use the registered font families for printing. + */ + if ( class_exists( 'WP_Web_Fonts' ) ) { + $wp_webfonts = wp_webfonts(); + $registered = $wp_webfonts->get_registered_font_families(); + if ( ! empty( $registered ) ) { + $queue = $wp_webfonts->queue; + $done = $wp_webfonts->done; + + $wp_webfonts->done = array(); + $wp_webfonts->queue = $registered; + + ob_start(); + $wp_webfonts->do_items(); + $styles .= ob_get_clean(); + + // Reset the Web Fonts API. + $wp_webfonts->done = $done; + $wp_webfonts->queue = $queue; + } + } + return array( 'styles' => $styles, 'scripts' => $scripts, diff --git a/lib/experimental/class-wp-web-fonts.php b/lib/experimental/class-wp-web-fonts.php new file mode 100644 index 00000000000000..15df7ecf47e5c5 --- /dev/null +++ b/lib/experimental/class-wp-web-fonts.php @@ -0,0 +1,744 @@ + 'local', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + ); + + /** + * Constructor. + * + * @since X.X.X + */ + public function __construct() { + /** + * Filters the web font variation's property defaults. + * + * @since X.X.X + * + * @param array $defaults { + * An array of required web font properties and defaults. + * + * @type string $provider The provider ID. Default 'local'. + * @type string $font-family The font-family property. Default empty string. + * @type string $font-style The font-style property. Default 'normal'. + * @type string $font-weight The font-weight property. Default '400'. + * @type string $font-display The font-display property. Default 'fallback'. + * } + */ + $this->variation_property_defaults = apply_filters( 'wp_webfont_variation_defaults', $this->variation_property_defaults ); + + /** + * Fires when the WP_Webfonts instance is initialized. + * + * @since X.X.X + * + * @param WP_Web_Fonts $wp_webfonts WP_Web_Fonts instance (passed by reference). + */ + do_action_ref_array( 'wp_default_webfonts', array( &$this ) ); + } + + /** + * Get the list of registered providers. + * + * @since X.X.X + * + * @return array $providers { + * An associative array of registered providers, keyed by their unique ID. + * + * @type string $provider_id => array { + * An associate array of provider's class name and fonts. + * + * @type string $class Fully qualified name of the provider's class. + * @type string[] $fonts An array of enqueued font handles for this provider. + * } + * } + */ + public function get_providers() { + return $this->providers; + } + + /** + * Register a provider. + * + * @since X.X.X + * + * @param string $provider_id The provider's unique ID. + * @param string $class The provider class name. + * @return bool True if successfully registered, else false. + */ + public function register_provider( $provider_id, $class ) { + if ( empty( $provider_id ) || empty( $class ) || ! class_exists( $class ) ) { + return false; + } + + $this->providers[ $provider_id ] = array( + 'class' => $class, + 'fonts' => array(), + ); + return true; + } + + /** + * Get the list of all registered font family handles. + * + * @since X.X.X + * + * @return string[] + */ + public function get_registered_font_families() { + $font_families = array(); + foreach ( $this->registered as $handle => $obj ) { + if ( $obj->extra['is_font_family'] ) { + $font_families[] = $handle; + } + } + return $font_families; + } + + /** + * Get the list of all registered font families and their variations. + * + * @since X.X.X + * + * @return string[] + */ + public function get_registered() { + return array_keys( $this->registered ); + } + + /** + * Get the list of enqueued font families and their variations. + * + * @since X.X.X + * + * @return array[] + */ + public function get_enqueued() { + return $this->queue; + } + + /** + * Registers a font family. + * + * @since X.X.X + * + * @param string $font_family Font family name to register. + * @return string|null Font family handle when registration successes. Null on failure. + */ + public function add_font_family( $font_family ) { + $font_family_handle = WP_Webfonts_Utils::convert_font_family_into_handle( $font_family ); + if ( ! $font_family_handle ) { + return null; + } + + if ( isset( $this->registered[ $font_family_handle ] ) ) { + return $font_family_handle; + } + + $registered = $this->add( $font_family_handle, false ); + if ( ! $registered ) { + return null; + } + + $this->add_data( $font_family_handle, 'font-properties', array( 'font-family' => $font_family ) ); + $this->add_data( $font_family_handle, 'is_font_family', true ); + + return $font_family_handle; + } + + /** + * Removes a font family and all registered variations. + * + * @since X.X.X + * + * @param string $font_family_handle The font family to remove. + */ + public function remove_font_family( $font_family_handle ) { + if ( ! isset( $this->registered[ $font_family_handle ] ) ) { + return; + } + + $variations = $this->registered[ $font_family_handle ]->deps; + + foreach ( $variations as $variation ) { + $this->remove( $variation ); + } + + $this->remove( $font_family_handle ); + } + + /** + * Add a variation to an existing family or register family if none exists. + * + * @since X.X.X + * + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + * @param string $variation_handle Optional. The variation's handle. When none is provided, the + * handle will be dynamically generated. + * Default empty string. + * @return string|null Variation handle on success. Else null. + */ + public function add_variation( $font_family_handle, array $variation, $variation_handle = '' ) { + if ( ! WP_Webfonts_Utils::is_defined( $font_family_handle ) ) { + trigger_error( 'Font family handle must be a non-empty string.' ); + return null; + } + + // When there is a variation handle, check it. + if ( '' !== $variation_handle && ! WP_Webfonts_Utils::is_defined( $variation_handle ) ) { + trigger_error( 'Variant handle must be a non-empty string.' ); + return null; + } + + // Register the font family when it does not yet exist. + if ( ! isset( $this->registered[ $font_family_handle ] ) ) { + if ( ! $this->add_font_family( $font_family_handle ) ) { + return null; + } + } + + $variation = $this->validate_variation( $variation ); + + // Variation validation failed. + if ( ! $variation ) { + return null; + } + + // When there's no variation handle, attempt to create one. + if ( '' === $variation_handle ) { + $variation_handle = WP_Webfonts_Utils::convert_variation_into_handle( $font_family_handle, $variation ); + if ( is_null( $variation_handle ) ) { + return null; + } + } + + // Bail out if the variant is already registered. + if ( $this->is_variation_registered( $font_family_handle, $variation_handle ) ) { + return $variation_handle; + } + + $variation_src = array_key_exists( 'src', $variation ) ? $variation['src'] : false; + $result = $this->add( $variation_handle, $variation_src ); + + // Bail out if the registration failed. + if ( ! $result ) { + return null; + } + + $this->add_data( $variation_handle, 'font-properties', $variation ); + $this->add_data( $variation_handle, 'is_font_family', false ); + + // Add the font variation as a dependency to the registered font family. + $this->add_dependency( $font_family_handle, $variation_handle ); + + $this->providers[ $variation['provider'] ]['fonts'][] = $variation_handle; + + return $variation_handle; + } + + /** + * Removes a variation. + * + * @since X.X.X + * + * @param string $font_family_handle The font family for this variation. + * @param string $variation_handle The variation's handle to remove. + */ + public function remove_variation( $font_family_handle, $variation_handle ) { + if ( isset( $this->registered[ $variation_handle ] ) ) { + $this->remove( $variation_handle ); + } + + if ( ! $this->is_variation_registered( $font_family_handle, $variation_handle ) ) { + return; + } + + // Remove the variation as a dependency from its font family. + $this->registered[ $font_family_handle ]->deps = array_values( + array_diff( + $this->registered[ $font_family_handle ]->deps, + array( $variation_handle ) + ) + ); + } + + /** + * Checks if the variation is registered. + * + * @since X.X.X + * + * @param string $font_family_handle The font family's handle for this variation. + * @param string $variation_handle Variation's handle. + * @return bool True when registered to the given font family. Else false. + */ + private function is_variation_registered( $font_family_handle, $variation_handle ) { + if ( ! isset( $this->registered[ $font_family_handle ] ) ) { + return false; + } + + return in_array( $variation_handle, $this->registered[ $font_family_handle ]->deps, true ); + } + + /** + * Adds a variation as a dependency to the given font family. + * + * @since X.X.X + * + * @param string $font_family_handle The font family's handle for this variation. + * @param string $variation_handle The variation's handle. + */ + private function add_dependency( $font_family_handle, $variation_handle ) { + $this->registered[ $font_family_handle ]->deps[] = $variation_handle; + } + + /** + * Validates and sanitizes a variation. + * + * @since X.X.X + * + * @param array $variation Variation properties to add. + * @return false|array Validated variation on success. Else, false. + */ + private function validate_variation( $variation ) { + $variation = wp_parse_args( $variation, $this->variation_property_defaults ); + + // Check the font-family. + if ( empty( $variation['font-family'] ) || ! is_string( $variation['font-family'] ) ) { + trigger_error( 'Webfont font-family must be a non-empty string.' ); + return false; + } + + // Local fonts need a "src". + if ( 'local' === $variation['provider'] ) { + // Make sure that local fonts have 'src' defined. + if ( empty( $variation['src'] ) || ( ! is_string( $variation['src'] ) && ! is_array( $variation['src'] ) ) ) { + trigger_error( 'Webfont src must be a non-empty string or an array of strings.' ); + return false; + } + } elseif ( ! isset( $this->providers[ $variation['provider'] ] ) ) { + trigger_error( sprintf( 'The provider "%s" is not registered', $variation['provider'] ) ); + return false; + } elseif ( ! class_exists( $this->providers[ $variation['provider'] ]['class'] ) ) { + trigger_error( sprintf( 'The provider class "%s" does not exist', $variation['provider'] ) ); + return false; + } + + // Validate the 'src' property. + if ( ! empty( $variation['src'] ) ) { + foreach ( (array) $variation['src'] as $src ) { + if ( empty( $src ) || ! is_string( $src ) ) { + trigger_error( 'Each webfont src must be a non-empty string.' ); + return false; + } + } + } + + // Check the font-weight. + if ( ! is_string( $variation['font-weight'] ) && ! is_int( $variation['font-weight'] ) ) { + trigger_error( 'Webfont font-weight must be a properly formatted string or integer.' ); + return false; + } + + // Check the font-display. + if ( ! in_array( $variation['font-display'], array( 'auto', 'block', 'fallback', 'swap', 'optional' ), true ) ) { + $variation['font-display'] = 'fallback'; + } + + $valid_props = array( + 'ascent-override', + 'descent-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', + + // Exceptions. + 'provider', + ); + + foreach ( $variation as $prop => $value ) { + if ( ! in_array( $prop, $valid_props, true ) ) { + unset( $variation[ $prop ] ); + } + } + + return $variation; + } + + /** + * Processes the items and dependencies. + * + * Processes the items passed to it or the queue, and their dependencies. + * + * @since X.X.X + * + * @param string|string[]|bool $handles Optional. Items to be processed: queue (false), + * single item (string), or multiple items (array of strings). + * Default false. + * @param int|false $group Optional. Group level: level (int), no group (false). + * + * @return array|string[] Array of web font handles that have been processed. + * An empty array if none were processed. + */ + public function do_items( $handles = false, $group = false ) { + $handles = $this->prepare_handles_for_printing( $handles ); + + if ( empty( $handles ) ) { + return $this->done; + } + + $this->all_deps( $handles ); + if ( empty( $this->to_do ) ) { + return $this->done; + } + + $this->to_do_keyed_handles = array_flip( $this->to_do ); + + foreach ( $this->get_providers() as $provider_id => $provider ) { + // Alert and skip if the provider class does not exist. + if ( ! class_exists( $provider['class'] ) ) { + /* translators: %s is the provider name. */ + trigger_error( + sprintf( + 'Class "%s" not found for "%s" web font provider', + $provider['class'], + $provider_id + ) + ); + continue; + } + + $this->do_item( $provider_id, $group ); + } + + $this->process_font_families_after_printing( $handles ); + + return $this->done; + } + + /** + * Prepares the given handles for printing. + * + * @since X.X.X + * + * @param string|string[]|bool $handles Optional. Handles to prepare. + * Default false. + * @return array Array of handles. + */ + private function prepare_handles_for_printing( $handles = false ) { + if ( false !== $handles ) { + $handles = $this->validate_handles( $handles ); + // Bail out when invalid. + if ( empty( $handles ) ) { + return array(); + } + } + + // Use the enqueued queue. + if ( empty( $handles ) ) { + if ( empty( $this->queue ) ) { + return array(); + } + $handles = $this->queue; + } + + return $handles; + } + + /** + * Validates handle(s) to ensure each is a non-empty string. + * + * @since X.X.X + * + * @param string|string[] $handles Handles to prepare. + * @return string[]|null Array of handles on success. Else null. + */ + private function validate_handles( $handles ) { + // Validate each element is a non-empty string handle. + $handles = array_filter( (array) $handles, array( WP_Webfonts_Utils::class, 'is_defined' ) ); + + if ( empty( $handles ) ) { + trigger_error( 'Handles must be a non-empty string or array of non-empty strings' ); + return null; + } + + return $handles; + } + + /** + * Invokes each provider to process and print its styles. + * + * @since X.X.X + * + * @see WP_Dependencies::do_item() + * + * @param string $provider_id The provider to process. + * @param int|false $group Not used. + * @return bool + */ + public function do_item( $provider_id, $group = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Bail out if the provider is not registered. + if ( ! isset( $this->providers[ $provider_id ] ) ) { + return false; + } + + $font_handles = $this->get_enqueued_fonts_for_provider( $provider_id ); + if ( empty( $font_handles ) ) { + return false; + } + + $properties_by_font = $this->get_font_properties_for_provider( $font_handles ); + if ( empty( $properties_by_font ) ) { + return false; + } + + // Invoke provider to print its styles. + $provider = $this->get_provider_instance( $provider_id ); + $provider->set_webfonts( $properties_by_font ); + $provider->print_styles(); + + // Clean up. + $this->update_queues_for_printed_fonts( $font_handles ); + + return true; + } + + /** + * Retrieves a list of enqueued web font variations for a provider. + * + * @since X.X.X + * + * @param string $provider_id The provider to process. + * @return array[] Webfonts organized by providers. + */ + private function get_enqueued_fonts_for_provider( $provider_id ) { + $providers = $this->get_providers(); + + if ( empty( $providers[ $provider_id ] ) ) { + return array(); + } + + return array_intersect( + $providers[ $provider_id ]['fonts'], + $this->to_do + ); + } + + /** + * Gets a list of font properties for each of the given font handles. + * + * @since X.X.X + * + * @param array $font_handles Font handles to get properties. + * @return array A list of fonts with each font's properties. + */ + private function get_font_properties_for_provider( array $font_handles ) { + $font_properties = array(); + + foreach ( $font_handles as $font_handle ) { + $properties = $this->get_data( $font_handle, 'font-properties' ); + if ( ! $properties ) { + continue; + } + $font_properties[ $font_handle ] = $properties; + } + + return $font_properties; + } + + /** + * Gets the instance of the provider from the WP_Webfonts::$provider_instance store. + * + * @since X.X.X + * + * @param string $provider_id The provider to get. + * @return object Instance of the provider. + */ + private function get_provider_instance( $provider_id ) { + if ( ! isset( $this->provider_instances[ $provider_id ] ) ) { + $this->provider_instances[ $provider_id ] = new $this->providers[ $provider_id ]['class'](); + } + return $this->provider_instances[ $provider_id ]; + } + + /** + * Update queues for the given printed fonts. + * + * @since X.X.X + * + * @param array $font_handles Font handles to get properties. + */ + private function update_queues_for_printed_fonts( array $font_handles ) { + foreach ( $font_handles as $font_handle ) { + $this->set_as_done( $font_handle ); + $this->remove_from_to_do_queues( $font_handle ); + } + } + + /** + * Processes the font families after printing the variations. + * + * For each queued font family: + * + * a. if any of their variations were printed, the font family is added to the `done` list. + * b. removes each from the to_do queues. + * + * @since X.X.X + * + * @param array $handles Handles to process. + */ + private function process_font_families_after_printing( array $handles ) { + foreach ( $handles as $handle ) { + if ( + ! $this->get_data( $handle, 'is_font_family' ) || + ! isset( $this->to_do_keyed_handles[ $handle ] ) + ) { + continue; + } + $font_family = $this->registered[ $handle ]; + + // Add the font family to `done` list if any of its variations were printed. + if ( ! empty( $font_family->deps ) ) { + $processed = array_intersect( $font_family->deps, $this->done ); + if ( ! empty( $processed ) ) { + $this->set_as_done( $handle ); + } + } + + $this->remove_from_to_do_queues( $handle ); + } + } + + /** + * Removes the handle from the `to_do` and `to_do_keyed_handles` lists. + * + * @since X.X.X + * + * @param string $handle Handle to remove. + */ + private function remove_from_to_do_queues( $handle ) { + unset( + $this->to_do[ $this->to_do_keyed_handles[ $handle ] ], + $this->to_do_keyed_handles[ $handle ] + ); + } + + /** + * Sets the given handle to done by adding it to the `done` list. + * + * @since X.X.X + * + * @param string $handle Handle to set as done. + */ + private function set_as_done( $handle ) { + if ( ! is_array( $this->done ) ) { + $this->done = array(); + } + $this->done[] = $handle; + } + + /** + * Converts the font family and its variations into theme.json structural format. + * + * @since X.X.X + * + * @param string $font_family_handle Font family to convert. + * @return array Webfonts in theme.json structural format. + */ + public function to_theme_json( $font_family_handle ) { + if ( ! isset( $this->registered[ $font_family_handle ] ) ) { + return array(); + } + + $font_family_name = $this->registered[ $font_family_handle ]->extra['font-properties']['font-family']; + $theme_json_format = array( + 'fontFamily' => str_contains( $font_family_name, ' ' ) ? "'{$font_family_name}'" : $font_family_name, + 'name' => $font_family_name, + 'slug' => $font_family_handle, + 'fontFace' => array(), + ); + + foreach ( $this->registered[ $font_family_handle ]->deps as $variation_handle ) { + if ( ! isset( $this->registered[ $variation_handle ] ) ) { + continue; + } + + $variation_obj = $this->registered[ $variation_handle ]; + $variation_properties = array( 'origin' => 'gutenberg_wp_webfonts_api' ); + foreach ( $variation_obj->extra['font-properties'] as $property_name => $property_value ) { + $property_in_camelcase = lcfirst( str_replace( '-', '', ucwords( $property_name, '-' ) ) ); + $variation_properties[ $property_in_camelcase ] = $property_value; + } + $theme_json_format['fontFace'][ $variation_obj->handle ] = $variation_properties; + } + + return $theme_json_format; + } +} diff --git a/lib/experimental/class-wp-webfonts-provider-local.php b/lib/experimental/class-wp-webfonts-provider-local.php index 9af27fa7c436a7..3950ebcd624c39 100644 --- a/lib/experimental/class-wp-webfonts-provider-local.php +++ b/lib/experimental/class-wp-webfonts-provider-local.php @@ -35,6 +35,21 @@ class WP_Webfonts_Provider_Local extends WP_Webfonts_Provider { */ protected $id = 'local'; + /** + * Constructor. + * + * @since 6.1.0 + */ + public function __construct() { + if ( + function_exists( 'is_admin' ) && ! is_admin() + && + function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'style' ) + ) { + $this->style_tag_atts = array( 'type' => 'text/css' ); + } + } + /** * Gets the `@font-face` CSS styles for locally-hosted font files. * @@ -81,7 +96,7 @@ class WP_Webfonts_Provider_Local extends WP_Webfonts_Provider { * } * * - * @since 6.0.0 + * @since X.X.X * * @return string The `@font-face` CSS. */ @@ -183,7 +198,8 @@ private function order_src( array $webfont ) { private function build_font_face_css( array $webfont ) { $css = ''; - // Wrap font-family in quotes if it contains spaces. + // Wrap font-family in quotes if it contains spaces + // and is not already wrapped in quotes. if ( str_contains( $webfont['font-family'], ' ' ) && ! str_contains( $webfont['font-family'], '"' ) && diff --git a/lib/experimental/class-wp-webfonts-provider.php b/lib/experimental/class-wp-webfonts-provider.php index a49b8a324b7f5f..8fcbd76a76d3cf 100644 --- a/lib/experimental/class-wp-webfonts-provider.php +++ b/lib/experimental/class-wp-webfonts-provider.php @@ -6,7 +6,7 @@ * * @package WordPress * @subpackage WebFonts - * @since 6.0.0 + * @since X.X.X */ if ( class_exists( 'WP_Webfonts_Provider' ) ) { @@ -30,26 +30,46 @@ * into styles (in a performant way for the provider service * it manages). * - * @since 6.0.0 + * @since X.X.X */ abstract class WP_Webfonts_Provider { + /** + * The provider's unique ID. + * + * @since X.X.X + * + * @var string + */ + protected $id = ''; + /** * Webfonts to be processed. * - * @since 6.0.0 + * @since X.X.X * * @var array[] */ protected $webfonts = array(); + /** + * Array of Font-face style tag's attribute(s) + * where the key is the attribute name and the + * value is its value. + * + * @since X.X.X + * + * @var string[] + */ + protected $style_tag_atts = array(); + /** * Sets this provider's webfonts property. * * The API's Controller passes this provider's webfonts * for processing here in the provider. * - * @since 6.0.0 + * @since X.X.X * * @param array[] $webfonts Registered webfonts. */ @@ -57,6 +77,18 @@ public function set_webfonts( array $webfonts ) { $this->webfonts = $webfonts; } + /** + * Prints the generated styles. + * + * @since X.X.X + */ + public function print_styles() { + printf( + $this->get_style_element(), + $this->get_css() + ); + } + /** * Gets the `@font-face` CSS for the provider's webfonts. * @@ -64,9 +96,37 @@ public function set_webfonts( array $webfonts ) { * needed `@font-face` CSS for all of its webfonts. Specifics of how * this processing is done is contained in each provider. * - * @since 6.0.0 + * @since X.X.X * * @return string The `@font-face` CSS. */ abstract public function get_css(); + + /** + * Gets the `\n"; + } + + /** + * Gets the defined \n", + $font_faces['merriweather-200-900-normal'] + ), + ), + 'print Source Serif Pro for local provider' => array( + 'setup' => array( + 'provider' => array( 'local' => $providers['local'] ), + 'provider_handles' => array( 'local' => $local_font_handles ), + 'registered' => $local_fonts, + 'enqueued' => array( 'source-serif-pro' ), + ), + 'expected_done' => array( 'source-serif-pro', 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ), + 'expected_output' => sprintf( + "\n", + $font_faces['Source Serif Pro-300-normal'], + $font_faces['Source Serif Pro-900-italic'] + ), + ), + 'print all fonts for local provider' => array( + 'setup' => array( + 'provider' => array( 'local' => $providers['local'] ), + 'provider_handles' => array( 'local' => $local_font_handles ), + 'registered' => $local_fonts, + 'enqueued' => array( 'merriweather', 'source-serif-pro' ), + ), + 'expected_done' => array( + 'merriweather', + 'merriweather-200-900-normal', + 'source-serif-pro', + 'Source Serif Pro-300-normal', + 'Source Serif Pro-900-italic', + ), + 'expected_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'], + $font_faces['Source Serif Pro-300-normal'], + $font_faces['Source Serif Pro-900-italic'] + ), + ), + + // All providers registered with multiple fonts. + + 'print font1 when all providers registered' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1' ) ) ), + 'expected_done' => array( 'font1', 'font1-300-normal', 'font1-300-italic', 'font1-900-normal' ), + 'expected_output' => sprintf( + '%s; %s; %s\n', + $font_faces['font1-300-normal'], + $font_faces['font1-300-italic'], + $font_faces['font1-900-normal'] + ), + ), + 'print all mock fonts when all providers registered' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1', 'font2', 'font3' ) ) ), + 'expected_done' => array( + 'font1', + 'font1-300-normal', + 'font1-300-italic', + 'font1-900-normal', + 'font2', + 'font2-200-900-normal', + 'font2-200-900-italic', + 'font3', + 'font3-bold-normal', + ), + 'expected_output' => sprintf( + '%s; %s; %s; %s; %s; %s\n', + $font_faces['font1-300-normal'], + $font_faces['font1-300-italic'], + $font_faces['font1-900-normal'], + $font_faces['font2-200-900-normal'], + $font_faces['font2-200-900-italic'], + $font_faces['font3-bold-normal'] + ), + ), + 'print merriweather when all providers registered' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather' ) ) ), + 'expected_done' => array( 'merriweather', 'merriweather-200-900-normal' ), + 'expected_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'] + ), + ), + 'print all local fonts when all providers registered' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather', 'source-serif-pro' ) ) ), + 'expected_done' => array( + 'merriweather', + 'merriweather-200-900-normal', + 'source-serif-pro', + 'Source Serif Pro-300-normal', + 'Source Serif Pro-900-italic', + ), + 'expected_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'], + $font_faces['Source Serif Pro-300-normal'], + $font_faces['Source Serif Pro-900-italic'] + ), + ), + + 'print all fonts for all providers' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => $all_variation_handles ) ), + 'expected_done' => $all_variation_handles, + 'expected_output' => + sprintf( + "\n", + $font_faces['merriweather-200-900-normal'], + $font_faces['Source Serif Pro-300-normal'], + $font_faces['Source Serif Pro-900-italic'] + ) . + sprintf( + '%s; %s; %s; %s; %s; %s\n', + $font_faces['font1-300-normal'], + $font_faces['font1-300-italic'], + $font_faces['font1-900-normal'], + $font_faces['font2-200-900-normal'], + $font_faces['font2-200-900-italic'], + $font_faces['font3-bold-normal'] + ), + ), + + // Specific variations enqueued. + // Validates that only these specific variations print once. + + 'specific variation: 1 local' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal' ) ) ), + 'expected_done' => array( 'merriweather-200-900-normal' ), + 'expected_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'] + ), + ), + 'specific variation: 1 local from different font families' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ) ), + 'expected_done' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ), + 'expected_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'], + $font_faces['Source Serif Pro-900-italic'] + ), + ), + 'specific variation: 1 local and 1 mock' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ) ) ), + 'expected_done' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ), + 'expected_output' => sprintf( + "\n" . + '%s\n', + $font_faces['merriweather-200-900-normal'], + $font_faces['font2-200-900-normal'] + ), + ), + 'specific variation: 1 mock and 1 local' => array( + 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ) ) ), + 'expected_done' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ), + 'expected_output' => sprintf( + "\n" . + '%s\n', + $font_faces['Source Serif Pro-300-normal'], + $font_faces['font2-200-900-normal'] + ), + ), + ); + } + + protected function get_data_registry() { + return array( + 'lato' => array(), + 'merriweather' => array( + 'merriweather-200-900-normal' => array( + 'font-family' => 'Merriweather', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + ), + 'Source Serif Pro' => array( + 'Source Serif Pro-300-normal' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'Source Serif Pro-900-italic' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'my-font' => array( + 'my-font-300-normal' => array( + 'font-family' => 'My Font', + 'font-weight' => '300', + 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', + ), + 'my-font-300-italic' => array( + 'font-family' => 'My Font', + 'font-weight' => '300', + 'font-style' => 'italic', + 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', + ), + 'my-font-900-normal' => array( + 'font-family' => 'My Font', + 'font-weight' => '900', + 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2', + ), + ), + ); + } + + /** + * Gets the provider definitions. + * + * @since X.X.X + * + * @param string $provider_id Optional. Provider ID to get. Default empty string. + * @return array + */ + protected function get_provider_definitions( $provider_id = '' ) { + $providers = array( + 'mock' => array( + 'id' => 'mock', + 'class' => Mock_Provider::class, + ), + 'local' => array( + 'id' => 'local', + 'class' => WP_Webfonts_Provider_Local::class, + ), + ); + + if ( '' === $provider_id ) { + return $providers; + } + + if ( isset( $providers[ $provider_id ] ) ) { + return $providers[ $provider_id ]; + } + + return array( + 'id' => $provider_id, + 'class' => '', + ); + } + + /** + * Gets web font definitions for both local and mock providers. + * + * @since X.X.X + * + * @return array|string[][][] + */ + protected function get_registered_fonts() { + return array_merge( + $this->get_registered_local_fonts(), + $this->get_registered_mock_fonts() + ); + } + + /** + * Returns an array of font-face styles that matches the font definitions + * in get_registered_local_fonts() and get_registered_mock_fonts(). + * + * @since X.X.X + * + * @return string[] + */ + protected function get_registered_fonts_css() { + return array( + 'merriweather-200-900-normal' => << << << 'font1-300-normal', + 'font1-300-italic' => 'font1-300-italic', + 'font1-900-normal' => 'font1-900-normal', + 'font2-200-900-normal' => 'font2-200-900-normal', + 'font2-200-900-italic' => 'font2-200-900-italic', + 'font3-bold-normal' => 'font3-bold-normal', + ); + } + + /** + * Gets web font definitions for local provider. + * + * @since X.X.X + * + * @return string[][][] + */ + protected function get_registered_local_fonts() { + return array( + 'lato' => array(), + 'merriweather' => array( + 'merriweather-200-900-normal' => array( + 'provider' => 'local', + 'font-family' => 'Merriweather', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + ), + 'Source Serif Pro' => array( + 'Source Serif Pro-300-normal' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'Source Serif Pro-900-italic' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + ); + } + + /** + * Gets web font definitions for mock provider. + * + * @since X.X.X + * + * @return string[][][] + */ + protected function get_registered_mock_fonts() { + return array( + 'font1' => array( + 'font1-300-normal' => array( + 'provider' => 'mock', + 'font-family' => 'Font 1', + 'font-weight' => '300', + 'font-style' => 'normal', + 'font-display' => 'fallback', + ), + 'font1-300-italic' => array( + 'provider' => 'mock', + 'font-family' => 'Font 1', + 'font-weight' => '300', + 'font-style' => 'italic', + 'font-display' => 'fallback', + ), + 'font1-900-normal' => array( + 'provider' => 'mock', + 'font-family' => 'Font 1', + 'font-weight' => '900', + 'font-style' => 'normal', + 'font-display' => 'fallback', + ), + ), + 'font2' => array( + 'font2-200-900-normal' => array( + 'provider' => 'mock', + 'font-family' => 'Font 2', + 'font-weight' => '200 900', + 'font-style' => 'normal', + 'font-display' => 'fallback', + ), + 'font2-200-900-italic' => array( + 'provider' => 'mock', + 'font-family' => 'Font 2', + 'font-weight' => '200 900', + 'font-style' => 'italic', + 'font-display' => 'fallback', + ), + ), + 'font3' => array( + 'font3-bold-normal' => array( + 'provider' => 'mock', + 'font-family' => 'Font 3', + 'font-weight' => 'bold', + 'font-style' => 'normal', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + ), + ), + ); + } +} diff --git a/phpunit/webfonts/wpDeregisterFontFamily-test.php b/phpunit/webfonts/wpDeregisterFontFamily-test.php new file mode 100644 index 00000000000000..2487053b6149c5 --- /dev/null +++ b/phpunit/webfonts/wpDeregisterFontFamily-test.php @@ -0,0 +1,74 @@ +set_up_mock( 'remove_font_family' ); + $mock->expects( $this->once() ) + ->method( 'remove_font_family' ) + ->with( + $this->identicalTo( $font_family_handle ) + ); + + wp_deregister_font_family( $font_family_handle ); + } + + /** + * Integration test for enqueuing before registering a font family and all of its variations. + * + * @dataProvider data_font_family_handles + * + * @param string $font_family Font family to test. + */ + public function test_should_deregister_before_registration( $font_family ) { + wp_deregister_font_family( $font_family ); + + $this->assertIsArray( $this->get_registered(), 'Registration queue should be an array' ); + $this->assertEmpty( $this->get_registered(), 'Registration queue should be empty after deregistering' ); + } + + /** + * Integration test for deregistering a font family and all of its variations. + * + * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations + * + * @param string $font_family Font family to test. + * @param array $inputs Font family(ies) and variations to pre-register. + * @param array $registered_handles Expected handles after registering. + * @param array $expected Array of expected handles. + */ + public function test_deregister_after_registration( $font_family, array $inputs, array $registered_handles, array $expected ) { + foreach ( $inputs as $handle => $variations ) { + $this->setup_register( $handle, $variations ); + } + // Test the before state, just to make sure. + $this->assertSame( $registered_handles, $this->get_registered_handles(), 'Font family and variations should be registered before deregistering' ); + + wp_deregister_font_family( $font_family ); + + // Test after deregistering. + $this->assertIsArray( $this->get_registered_handles(), 'Registration queue should be an array' ); + $this->assertSame( $expected, $this->get_registered_handles(), 'Registration queue should match after deregistering' ); + } +} diff --git a/phpunit/webfonts/wpDeregisterWebfontVariation-test.php b/phpunit/webfonts/wpDeregisterWebfontVariation-test.php new file mode 100644 index 00000000000000..a6a0ab67ad4480 --- /dev/null +++ b/phpunit/webfonts/wpDeregisterWebfontVariation-test.php @@ -0,0 +1,298 @@ +wp_webfonts = wp_webfonts(); + $this->fonts_to_register = $this->get_registered_local_fonts(); + } + + /** + * Sets up the unit test by mocking the WP_Dependencies object using stdClass and + * registering each font family directly to the WP_Webfonts::$registered property + * and its variations to the mocked $deps property. + */ + private function setup_unit_test() { + $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_webfonts ); + } + + /** + * Sets up the integration test by properly registering each font family and its variations + * by using the WP_Webfonts::add() and WP_Webfonts::add_variation() methods. + */ + private function setup_integration_test() { + foreach ( $this->fonts_to_register as $font_family_handle => $variations ) { + $this->setup_register( $font_family_handle, $variations, $this->wp_webfonts ); + } + } + + /** + * Testing the test setup to ensure it works. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + */ + public function test_mocked_setup( $font_family_handle, $variation_handle ) { + $this->setup_unit_test(); + + $this->assertArrayHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be in the registered queue before removal' ); + $this->assertContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' ); + } + + /** + * Unit test for deregistering a font-family's variation using mock of WP_Webfonts. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family to test. + * @param string $variation_handle Variation's handle to test. + */ + public function test_should_deregister_when_mocked( $font_family_handle, $variation_handle ) { + $mock = $this->set_up_mock( 'remove_variation' ); + $mock->expects( $this->once() ) + ->method( 'remove_variation' ) + ->with( + $this->identicalTo( $font_family_handle, $variation_handle ) + ); + + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + } + + /** + * Unit test. + * + * @dataProvider data_should_do_nothing + * + * @param string $font_family Font family name. + * @param string $font_family_handle Font family handle. + * @param string $variation_handle Variation handle to remove. + */ + public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { + // Set up the test. + unset( $this->fonts_to_register[ $font_family ] ); + $this->setup_unit_test(); + $registered_queue = $this->wp_webfonts->registered; + + // Run the tests. + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $font_family_handle, $this->wp_webfonts->registered, 'Font family should not be registered' ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertSame( $registered_queue, $this->wp_webfonts->registered, 'Registered queue should not have changed' ); + } + + /** + * Integration test. + * + * @dataProvider data_should_do_nothing + * + * @param string $font_family Font family name. + * @param string $font_family_handle Font family handle. + * @param string $variation_handle Variation handle to remove. + */ + public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { + // Set up the test. + unset( $this->fonts_to_register[ $font_family ] ); + $this->setup_integration_test(); + $registered_queue = $this->wp_webfonts->get_registered(); + + // Run the tests. + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $font_family_handle, $this->wp_webfonts->registered, 'Font family should not be registered' ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertSameSets( $registered_queue, $this->wp_webfonts->get_registered(), 'Registered queue should not have changed' ); + } + + /** + * Data provider for testing removal of variations. + * + * @return array + */ + public function data_should_do_nothing() { + return array( + 'Font with 1 variation' => array( + 'font_family' => 'merriweather', + 'font_family_handle' => 'merriweather', + 'variation_handle' => 'merriweather-200-900-normal', + ), + 'Font with multiple variations' => array( + 'font_family' => 'Source Serif Pro', + 'font_family_handle' => 'source-serif-pro', + 'variation_handle' => 'Source Serif Pro-300-normal', + ), + ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + $this->setup_remove_variation_from_registered( $variation_handle ); + + // Run the tests. + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + $this->setup_remove_variation_from_registered( $variation_handle ); + + // Run the tests. + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); + + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); + + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + + $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); + + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be not be in registered queue' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + + $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); + + wp_deregister_webfont_variation( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be not be in registered queue' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Remove the variation handle from the font family's deps. + * + * @param string $font_family_handle Font family. + * @param string $variation_handle The variation handle to remove. + */ + private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) { + foreach ( $this->wp_webfonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) { + if ( $variation_handle !== $vhandle ) { + continue; + } + unset( $this->wp_webfonts->registered[ $font_family_handle ]->deps[ $index ] ); + break; + } + } + + /** + * Removes the variation from the WP_Webfonts::$registered queue. + * + * @param string $variation_handle The variation handle to remove. + */ + private function setup_remove_variation_from_registered( $variation_handle ) { + unset( $this->wp_webfonts->registered[ $variation_handle ] ); + } +} diff --git a/phpunit/webfonts/wpEnqueueWebfontVariations-test.php b/phpunit/webfonts/wpEnqueueWebfontVariations-test.php new file mode 100644 index 00000000000000..f6ce62d3f47ee0 --- /dev/null +++ b/phpunit/webfonts/wpEnqueueWebfontVariations-test.php @@ -0,0 +1,82 @@ +set_up_mock( 'enqueue' ); + $mock->expects( $this->once() ) + ->method( 'enqueue' ) + ->with( + $this->identicalTo( $handles ) + ); + + wp_enqueue_webfont_variations( $handles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_variation_handles() { + return array( + '1 variation handle' => array( 'merriweather-200-900-normal' ), + 'multiple same font family handles' => array( array( 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ) ), + 'handles from different font families' => array( array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ), + ); + } + + /** + * Integration test for enqueuing one or more specific variations. + * + * @dataProvider data_enqueue_variations + * + * @param string|string[] $handles Variation handles to test. + * @param array $expected Expected queue. + */ + public function test_should_enqueue_after_registration( $handles, array $expected ) { + foreach ( $this->get_data_registry() as $handle => $variations ) { + $this->setup_register( $handle, $variations ); + } + + wp_enqueue_webfont_variations( $handles ); + $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' ); + $this->assertSame( $expected, $this->get_enqueued_handles(), 'Queue should contain the given handles' ); + } + + /** + * Integration test for enqueuing before registering one or more specific variations. + * + * @dataProvider data_enqueue_variations + * + * @param string|string[] $handles Variation handles to test. + * @param array $not_used Not used. + * @param array $expected Expected "queued_before_register" queue. + */ + public function test_should_enqueue_before_registration( $handles, array $not_used, array $expected ) { + wp_enqueue_webfont_variations( $handles ); + + $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given handles' ); + $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' ); + } +} diff --git a/phpunit/webfonts/wpEnqueueWebfonts-test.php b/phpunit/webfonts/wpEnqueueWebfonts-test.php new file mode 100644 index 00000000000000..a0ee6af5c1ddad --- /dev/null +++ b/phpunit/webfonts/wpEnqueueWebfonts-test.php @@ -0,0 +1,122 @@ +set_up_mock( 'enqueue' ); + $mock->expects( $this->once() ) + ->method( 'enqueue' ) + ->with( + $this->identicalTo( $expected_handles ) + ); + + wp_enqueue_webfonts( $font_families ); + } + + /** + * Integration test for enqueuing a font family and all of its variations. + * + * @dataProvider data_should_enqueue + * + * @param string[] $font_families Font families to test. + * @param string[] $expected_handles Expected handles passed to WP_Web_Fonts::enqueue(). + */ + public function test_should_enqueue_after_registration( $font_families, $expected_handles ) { + // Register the font-families. + foreach ( $this->get_data_registry() as $handle => $variations ) { + $this->setup_register( $handle, $variations ); + } + + wp_enqueue_webfonts( $font_families ); + + $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' ); + $this->assertSame( $expected_handles, $this->get_enqueued_handles(), 'Queue should contain the given font family(ies)' ); + } + + /** + * Integration test for enqueuing before registering a font family and all of its variations. + * + * @dataProvider data_should_enqueue + * + * @param string[] $font_families Font families to test. + * @param string[] $expected_handles Expected handles passed to WP_Web_Fonts::enqueue(). + */ + public function test_should_enqueue_before_registration( $font_families, $expected_handles ) { + wp_enqueue_webfonts( $font_families ); + + // Set up what "queued_before_register" queue should be. + $expected = array(); + foreach ( $expected_handles as $handle ) { + $expected[ $handle ] = null; + } + $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given font family(ies)' ); + $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_enqueue() { + return array( + '1: single word handle' => array( + 'font_families' => array( 'lato' ), + 'expected_handles' => array( 'lato' ), + ), + '1: multiple word handle' => array( + 'font_families' => array( 'source-serif-pro' ), + 'expected_handles' => array( 'source-serif-pro' ), + ), + '1: single word name' => array( + 'font_families' => array( 'Merriweather' ), + 'expected_handles' => array( 'merriweather' ), + ), + '1: multiple word name' => array( + 'font_families' => array( 'My Font' ), + 'expected_handles' => array( 'my-font' ), + ), + '>1: single word handle' => array( + 'font_families' => array( 'lato', 'merriweather' ), + 'expected_handles' => array( 'lato', 'merriweather' ), + ), + '>1: multiple word handle' => array( + 'font_families' => array( 'source-serif-pro', 'my-font' ), + 'expected_handles' => array( 'source-serif-pro', 'my-font' ), + ), + '>1: single word name' => array( + 'font_families' => array( 'Lato', 'Merriweather' ), + 'expected_handles' => array( 'lato', 'merriweather' ), + ), + '>1: multiple word name' => array( + 'font_families' => array( 'My Font', 'Source Serif Pro' ), + 'expected_handles' => array( 'my-font', 'source-serif-pro' ), + ), + '>1: mixture of word handles and names' => array( + 'font_families' => array( 'Source Serif Pro', 'Merriweather', 'my-font', 'Lato' ), + 'expected_handles' => array( 'source-serif-pro', 'merriweather', 'my-font', 'lato' ), + ), + ); + } +} diff --git a/phpunit/webfonts/wpPrintWebfonts-test.php b/phpunit/webfonts/wpPrintWebfonts-test.php new file mode 100644 index 00000000000000..3f782fec580fd7 --- /dev/null +++ b/phpunit/webfonts/wpPrintWebfonts-test.php @@ -0,0 +1,127 @@ +assertSame( array(), wp_print_webfonts() ); + $this->assertNotInstanceOf( WP_Webfonts::class, $wp_webfonts ); + } + + /** + * Unit test to mock WP_Webfonts::do_items(). + * + * @dataProvider data_mocked_handles + * + * @param string|string[]|false $handles Handles to test. + * @param array|string[] $expected Expected array of processed handles. + */ + public function test_should_return_mocked_handles( $handles, $expected ) { + $mock = $this->set_up_mock( 'do_items' ); + $mock->expects( $this->once() ) + ->method( 'do_items' ) + ->with( + $this->identicalTo( $handles ) + ) + ->will( $this->returnValue( $expected ) ); + + wp_print_webfonts( $handles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_mocked_handles() { + return array( + 'no handles' => array( + 'handles' => false, + 'expected' => array(), + ), + 'font family handles' => array( + 'handles' => array( 'my-custom-font' ), + 'expected' => array( 'my-custom-font' ), + ), + ); + } + + /** + * Integration test that registers providers and fonts and then enqueues before + * testing the printing functionality. + * + * @dataProvider data_print_enqueued + * + * @param array $setup Test set up information for provider, fonts, and enqueued. + * @param array $expected_done Expected array of printed handles. + * @param string $expected_output Expected printed output. + */ + public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) { + $wp_webfonts = wp_webfonts(); + + $this->setup_integrated_deps( $setup, $wp_webfonts ); + + $this->expectOutputString( $expected_output ); + $actual_done = wp_print_webfonts(); + $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); + } + + /** + * Integration test to validate printing given handles. Rather than mocking internal functionality, + * it registers providers and fonts but does not enqueue. + * + * @dataProvider data_print_enqueued + * + * @param array $setup Test set up information for provider, fonts, and enqueued. + * @param array $expected_done Expected array of printed handles. + * @param string $expected_output Expected printed output. + */ + public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) { + $wp_webfonts = wp_webfonts(); + + $this->setup_integrated_deps( $setup, $wp_webfonts, false ); + // Do not enqueue. Instead, pass the handles to wp_print_webfonts(). + $handles = $setup['enqueued']; + $this->assertEmpty( $wp_webfonts->queue, 'No fonts should be enqueued' ); + + $this->expectOutputString( $expected_output ); + $actual_done = wp_print_webfonts( $handles ); + $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); + } + + /** + * Sets up the dependencies for integration test. + * + * @param array $setup Dependencies to set up. + * @param WP_Web_Fonts $wp_webfonts Instance of WP_Web_Fonts. + * @param bool $enqueue Whether to enqueue. Default true. + */ + private function setup_integrated_deps( array $setup, $wp_webfonts, $enqueue = true ) { + foreach ( $setup['provider'] as $provider ) { + $wp_webfonts->register_provider( $provider['id'], $provider['class'] ); + } + foreach ( $setup['registered'] as $handle => $variations ) { + $this->setup_register( $handle, $variations, $wp_webfonts ); + } + + if ( $enqueue ) { + $wp_webfonts->enqueue( $setup['enqueued'] ); + } + } +} diff --git a/phpunit/webfonts/wpRegisterWebfontProvider-test.php b/phpunit/webfonts/wpRegisterWebfontProvider-test.php new file mode 100644 index 00000000000000..8f1367d6dbb23d --- /dev/null +++ b/phpunit/webfonts/wpRegisterWebfontProvider-test.php @@ -0,0 +1,96 @@ +set_up_mock( 'register_provider' ); + $mock->expects( $this->once() ) + ->method( 'register_provider' ) + ->with( + $this->identicalTo( $provider_id ), + $this->identicalTo( $class ) + ) + ->will( $this->returnValue( true ) ); + + $this->assertTrue( wp_register_webfont_provider( $provider_id, $class ), 'wp_register_webfont_provider() should return true' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_register_providers() { + return array( + 'mock' => array( + 'provider_id' => 'mock', + 'class' => Mock_Provider::class, + ), + 'local' => array( + 'provider_id' => 'local', + 'class' => WP_Webfonts_Provider_Local::class, + ), + ); + } + + /** + * @dataProvider data_invalid_providers + * + * @param string $provider_id Provider ID. + * @param string $class Provider class name. + */ + public function test_should_not_register( $provider_id, $class ) { + $mock = $this->set_up_mock( 'register_provider' ); + $mock->expects( $this->once() ) + ->method( 'register_provider' ) + ->with( + $this->identicalTo( $provider_id ), + $this->identicalTo( $class ) + ) + ->will( $this->returnValue( false ) ); + + $this->assertFalse( wp_register_webfont_provider( $provider_id, $class ), 'wp_register_webfont_provider() should return false' ); + + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_providers() { + return array( + 'provider_id is empty' => array( + 'provider_id' => '', + 'class' => Mock_Provider::class, + ), + 'class is empty' => array( + 'provider_id' => 'local', + 'class' => '', + ), + 'class does not exist' => array( + 'provider_id' => 'doesnotexist', + 'class' => 'Provider_Does_Not_Exist', + ), + ); + } +} diff --git a/phpunit/webfonts/wpRegisterWebfonts-test.php b/phpunit/webfonts/wpRegisterWebfonts-test.php new file mode 100644 index 00000000000000..11dbf9d73c2ea3 --- /dev/null +++ b/phpunit/webfonts/wpRegisterWebfonts-test.php @@ -0,0 +1,325 @@ +assertSame( $expected['wp_register_webfonts'], $actual, 'Font family handle(s) should be returned' ); + $this->assertSame( $expected['get_registered'], $this->get_registered_handles(), 'Web fonts should match registered queue' ); + } + + /** + * @dataProvider data_webfonts + * + * @param array $webfonts Array of webfonts to test. + */ + public function test_should_not_enqueue_on_registration( array $webfonts ) { + wp_register_webfonts( $webfonts ); + $this->assertEmpty( $this->get_enqueued_handles() ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_webfonts() { + return array( + 'font family keyed with slug' => array( + 'webfonts' => array( + 'source-serif-pro' => array( + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + ), + 'expected' => array( + 'wp_register_webfonts' => array( 'source-serif-pro' ), + 'get_registered' => array( + 'source-serif-pro', + 'source-serif-pro-200-900-normal', + ), + ), + ), + 'font family keyed with name' => array( + 'webfonts' => array( + 'Source Serif Pro' => array( + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + ), + 'expected' => array( + 'wp_register_webfonts' => array( 'source-serif-pro' ), + 'get_registered' => array( + 'source-serif-pro', + 'source-serif-pro-200-900-normal', + 'source-serif-pro-200-900-italic', + ), + ), + ), + ); + } + + /** + * @dataProvider data_deprecated_structure + * + * @param array $webfonts Webfonts to test. + */ + public function test_should_throw_deprecation_with_deprecated_structure( array $webfonts ) { + $this->expectDeprecation(); + $this->expectDeprecationMessage( + 'A deprecated web fonts array structure passed to wp_register_webfonts(). ' . + 'Variations must be grouped and keyed by their font family.' + ); + + wp_register_webfonts( $webfonts ); + } + + /** + * @dataProvider data_deprecated_structure + * + * @param array $webfonts Webfonts to test. + * @param array $expected Expected results. + */ + public function test_should_register_with_deprecated_structure( array $webfonts, array $expected ) { + $this->suppress_deprecations(); + + $actual = wp_register_webfonts( $webfonts ); + $this->assertSame( $expected['wp_register_webfonts'], $actual, 'Font family handle(s) should be returned' ); + $this->assertSame( $expected['get_registered'], $this->get_registered_handles(), 'Web fonts should match registered queue' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_deprecated_structure() { + return array( + '1 web font' => array( + 'webfonts' => array( + array( + 'provider' => 'local', + 'font-family' => 'Merriweather', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + ), + 'expected' => array( + 'wp_register_webfonts' => array( 'merriweather' ), + 'get_registered' => array( 'merriweather', 'merriweather-200-900-normal' ), + ), + ), + '2 web font in same font family' => array( + 'webfonts' => array( + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'expected' => array( + 'wp_register_webfonts' => array( 'source-serif-pro' ), + 'get_registered' => array( + 'source-serif-pro', + 'source-serif-pro-300-normal', + 'source-serif-pro-900-italic', + ), + ), + ), + 'Web fonts in different font families' => array( + 'webfonts' => array( + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + array( + 'provider' => 'local', + 'font-family' => 'Merriweather', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'expected' => array( + 'wp_register_webfonts' => array( 'source-serif-pro', 'merriweather' ), + 'get_registered' => array( + 'source-serif-pro', + 'source-serif-pro-300-normal', + 'source-serif-pro-900-italic', + 'merriweather', + 'merriweather-200-900-normal', + ), + ), + ), + ); + } + + /** + * @dataProvider data_invalid_font_family + * + * @param array $webfonts Web fonts to test. + * @param string $expected_message Expected notice message. + */ + public function test_should_not_register_with_undefined_font_family( array $webfonts, $expected_message ) { + $this->suppress_deprecations(); + + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + $actual = wp_register_webfonts( $webfonts ); + $this->assertSame( array(), $actual, 'Return value should be an empty array' ); + $this->assertEmpty( $this->get_registered_handles(), 'No Web fonts should have registered' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_font_family() { + return array( + 'non-string' => array( + 'webfonts' => array( + array( + 'provider' => 'local', + 'font-family' => null, + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + ), + 'expected_message' => 'Font family not defined in the variation.', + ), + 'empty string in deprecated structure' => array( + 'webfonts' => array( + '0' => array( + 'provider' => 'local', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'expected_message' => 'Font family not found.', + ), + 'incorrect parameter in deprecated structure' => array( + 'webfonts' => array( + array( + 'provider' => 'local', + 'FontFamily' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '300', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + array( + 'provider' => 'local', + 'font_family' => 'Merriweather', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + ), + array( + 'provider' => 'local', + 'font family' => 'Source Serif Pro', + 'font-style' => 'italic', + 'font-weight' => '900', + 'font-display' => 'fallback', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'expected_message' => 'Font family not found.', + ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfonts-test.php b/phpunit/webfonts/wpWebfonts-test.php new file mode 100644 index 00000000000000..17ab8c60583fae --- /dev/null +++ b/phpunit/webfonts/wpWebfonts-test.php @@ -0,0 +1,38 @@ +assertInstanceOf( WP_Web_Fonts::class, wp_webfonts() ); + } + + public function test_global_set() { + global $wp_webfonts; + $this->assertNull( $wp_webfonts ); + $instance = wp_webfonts(); + $this->assertInstanceOf( WP_Web_Fonts::class, $wp_webfonts ); + $this->assertSame( $instance, $wp_webfonts ); + } + + public function test_local_provider_is_automatically_registered() { + $expected = array( + 'local' => array( + 'class' => 'WP_Webfonts_Provider_Local', + 'fonts' => array(), + ), + ); + $this->assertSame( $expected, wp_webfonts()->get_providers() ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/add-test.php b/phpunit/webfonts/wpWebfonts/add-test.php new file mode 100644 index 00000000000000..a432c7e8572129 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/add-test.php @@ -0,0 +1,45 @@ +assertTrue( $wp_webfonts->add( $handle, false ), 'Registering a handle should return true' ); + $this->assertCount( 1, $wp_webfonts->registered ); + $this->assertArrayHasKey( $handle, $wp_webfonts->registered, 'Font family handle should be in the registry after registration' ); + + } + + /** + * Data provider. + * + * @return array + */ + public function data_handles() { + return array( + 'name: multiple' => array( 'Source Serif Pro' ), + 'handle: multiple' => array( 'source-serif-pro' ), + 'name: single' => array( 'Merriweather' ), + 'handle: single' => array( 'merriweather' ), + 'handle: variation' => array( 'my-custom-font-200-900-normal' ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/addFontFamily-test.php b/phpunit/webfonts/wpWebfonts/addFontFamily-test.php new file mode 100644 index 00000000000000..4aa5e55b1ef3cb --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/addFontFamily-test.php @@ -0,0 +1,66 @@ +add_font_family( $font_family ); + + $this->assertSame( $expected, $font_family_handle, 'Registering a font-family should return its handle' ); + $this->assertCount( 1, $wp_webfonts->registered ); + $this->assertArrayHasKey( $font_family_handle, $wp_webfonts->registered, 'Font family handle should be in the registry after registration' ); + + } + + /** + * Data provider. + * + * @return array + */ + public function data_handles() { + return array( + 'name: multiple' => array( + 'font_family' => 'Source Serif Pro', + 'expected' => 'source-serif-pro', + ), + 'handle: multiple' => array( + 'font_family' => 'source-serif-pro', + 'expected' => 'source-serif-pro', + ), + 'name: single' => array( + 'font_family' => 'Merriweather', + 'expected' => 'merriweather', + ), + 'handle: single' => array( + 'font_family' => 'merriweather', + 'expected' => 'merriweather', + ), + 'handle: variation' => array( + 'font_family' => 'my-custom-font-200-900-normal', + 'expected' => 'my-custom-font-200-900-normal', + ), + 'name: multiple font-families' => array( + 'font_family' => 'Source Serif Pro, Merriweather', + 'expected' => 'source-serif-pro', + ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/addVariation-test.php b/phpunit/webfonts/wpWebfonts/addVariation-test.php new file mode 100644 index 00000000000000..5f948b81fd146b --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/addVariation-test.php @@ -0,0 +1,149 @@ +add( $font_family_handle, false ); + + $variation_handle = $wp_webfonts->add_variation( $font_family_handle, $variation, $variation_handle ); + $this->assertSame( $expected, $variation_handle, 'Registering a variation should return its handle' ); + $this->assertArrayHasKey( $variation_handle, $wp_webfonts->registered, 'Variation handle should be in the registry after registration' ); + $this->assertSame( array( $expected ), $this->get_variations( $font_family_handle, $wp_webfonts ), 'Variation should be registered to font family' ); + } + + /** + * @dataProvider data_valid_variation + * + * @param string|bool $expected Expected results. + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + * @param string $variation_handle Optional. The variation's handle. + */ + public function test_should_not_reregister_font_family( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->add( $font_family_handle, false ); + + $variation_handle = $wp_webfonts->add_variation( $font_family_handle, $variation, $variation_handle ); + + // Font family should appear only once in the registered queue. + $expected = array( $font_family_handle, $variation_handle ); + $this->assertSame( $expected, array_keys( $wp_webfonts->registered ), 'Font family should not be re-registered after registering a variation' ); + } + + /** + * @dataProvider data_valid_variation + * + * @param string|bool $expected Expected results. + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + * @param string $variation_handle Optional. The variation's handle. + */ + public function test_should_not_reregister_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->add( $font_family_handle, false ); + + // Set up the test. + $variation_handle = $wp_webfonts->add_variation( $font_family_handle, $variation, $variation_handle ); + + // Run the test. + $variant_handle_on_reregister = $wp_webfonts->add_variation( $font_family_handle, $variation, $variation_handle ); + $this->assertSame( $expected, $variant_handle_on_reregister, 'Variation should be registered to font family' ); + $this->assertSame( $variation_handle, $variant_handle_on_reregister, 'Variation should return the previously registered variant handle' ); + $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_webfonts ), 'Variation should only be registered once' ); + + $this->assertCount( 2, $wp_webfonts->registered ); + $this->assertArrayHasKey( $variation_handle, $wp_webfonts->registered, 'Variation handle should be in the registry after registration' ); + } + + /** + * @dataProvider data_valid_variation + * + * @param string|bool $expected Expected results. + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + * @param string $variation_handle Optional. The variation's handle. + */ + public function test_should_register_font_family_and_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) { + $wp_webfonts = new WP_Web_Fonts(); + + $variation_handle = $wp_webfonts->add_variation( $font_family_handle, $variation, $variation_handle ); + $this->assertSame( $expected, $variation_handle, 'Variation should return its registered handle' ); + + // Extra checks to ensure both are registered. + $this->assertCount( 2, $wp_webfonts->registered ); + $this->assertArrayHasKey( $font_family_handle, $wp_webfonts->registered, 'Font family handle should be in the registry after registration' ); + $this->assertArrayHasKey( $variation_handle, $wp_webfonts->registered, 'Variation handle should be in the registry after registration' ); + $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_webfonts ), 'Variation should be registered to the font family' ); + } + + /** + * @dataProvider data_font_family_handle_undefined + * + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + */ + public function test_should_not_register_font_family_or_variant( $font_family_handle, array $variation ) { + $this->expectNotice(); + $this->expectNoticeMessage( 'Font family handle must be a non-empty string.' ); + + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->add_variation( $font_family_handle, $variation ); + + $this->assertEmpty( $wp_webfonts->registered, 'Registered queue should be empty' ); + $this->assertEmpty( $this->get_variations( $font_family_handle, $wp_webfonts ), 'Variation should not be registered to the font family' ); + } + + /** + * @dataProvider data_font_family_undefined_in_variation + * @dataProviders data_unable_determine_variation_handle + * + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + * @param string $expected_message Expected notice message. + */ + public function test_should_not_register_variation_when_font_family_not_defined( $font_family_handle, array $variation, $expected_message ) { + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + $wp_webfonts = new WP_Web_Fonts(); + $this->assertNull( $wp_webfonts->add_variation( $font_family_handle, $variation ) ); + } + + /** + * @dataProvider data_unable_determine_variation_handle + * + * @param string $font_family_handle The font family's handle for this variation. + * @param array $variation An array of variation properties to add. + */ + public function test_should_register_font_family_when_variant_fails_to_register( $font_family_handle, array $variation ) { + $this->expectNotice(); + $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' ); + + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->add_variation( $font_family_handle, $variation ); + + $this->assertCount( 1, $wp_webfonts->registered ); + $this->assertArrayHasKey( $font_family_handle, $wp_webfonts->registered ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/dequeue-test.php b/phpunit/webfonts/wpWebfonts/dequeue-test.php new file mode 100644 index 00000000000000..c4959958a2ccf4 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/dequeue-test.php @@ -0,0 +1,72 @@ +dequeue( $handles ); + $this->assertEmpty( $this->get_queued_before_register( $wp_webfonts ), 'Prequeue should be empty' ); + $this->assertEmpty( $wp_webfonts->queue, 'Queue should be empty' ); + } + + /** + * Integration test for dequeuing from queue. It first registers and then enqueues before dequeuing. + * + * @dataProvider data_enqueue + * @dataProvider data_enqueue_variations + * + * @param string|string[] $handles Handles to test. + */ + public function test_should_dequeue_from_queue( $handles ) { + $wp_webfonts = new WP_Web_Fonts(); + + // Register and enqueue. + foreach ( $this->get_data_registry() as $handle => $variations ) { + $this->setup_register( $handle, $variations, $wp_webfonts ); + } + $wp_webfonts->enqueue( $handles ); + + // To make sure the handles are in the queue before dequeuing. + $this->assertNotEmpty( $wp_webfonts->queue, 'Queue not be empty before dequeueing' ); + + // Run the test. + $wp_webfonts->dequeue( $handles ); + $this->assertEmpty( $wp_webfonts->queue, 'Queue should be empty after dequeueing' ); + } + + /** + * Integration test for dequeuing from prequeue. It enqueues first. + * + * @dataProvider data_enqueue + * @dataProvider data_enqueue_variations + * + * @param string|string[] $handles Handles to test. + */ + public function test_should_dequeue_from_prequeue( $handles ) { + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->enqueue( $handles ); + $this->assertNotEmpty( $this->get_queued_before_register( $wp_webfonts ), 'Prequeue not be empty before dequeueing' ); + + $wp_webfonts->dequeue( $handles ); + $this->assertEmpty( $this->get_queued_before_register( $wp_webfonts ), 'Prequeue should be empty after dequeueing' ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/doItem-test.php b/phpunit/webfonts/wpWebfonts/doItem-test.php new file mode 100644 index 00000000000000..e54b909f83429e --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/doItem-test.php @@ -0,0 +1,336 @@ +wp_webfonts = new WP_Web_Fonts; + } + + public function test_should_return_false_when_provider_not_registered() { + $this->assertFalse( $this->wp_webfonts->do_item( 'provider_not_registered' ) ); + } + + /** + * @dataProvider data_provider_definitions + * + * @param array $provider Provider to mock. + */ + public function test_should_return_false_when_no_fonts_enqueued_for_provider( array $provider ) { + $this->setup_provider_property_mock( $this->wp_webfonts, $provider ); + $this->assertFalse( $this->wp_webfonts->do_item( $provider['id'] ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_provider_definitions() { + $providers = $this->get_provider_definitions(); + + return array( + 'mock' => array( $providers['mock'] ), + 'local' => array( $providers['local'] ), + ); + } + + /** + * Test the test set up to ensure the `Tests_Webfonts_WpWebfonts_DoItem_::setup_provider_property_mock()` + * method works as expected. + */ + public function test_mocking_providers_property() { + $font_handles = array( 'font1', 'font2', 'font3' ); + $expected = array( + 'mock' => array( + 'class' => Mock_Provider::class, + 'fonts' => $font_handles, + ), + ); + + $this->setup_provider_property_mock( $this->wp_webfonts, $this->get_provider_definitions( 'mock' ), $font_handles ); + $actual = $this->property['WP_Web_Fonts::$providers']->getValue( $this->wp_webfonts ); + $this->assertSame( $expected, $actual ); + } + + /** + * Test the private method WP_Web_Fonts::get_enqueued_fonts_for_provider(). + * + * Why? This test validates the right fonts are returned for use within + * WP_Web_Fonts::do_item(). + * + * @dataProvider data_get_enqueued_fonts_for_provider + * + * @param array $font_handles Array of handles for the provider. + * @param array $to_do Handles to set for the WP_Web_Fonts::$to_do property. + * @param array $expected Expected result. + */ + public function test_get_enqueued_fonts_for_provider( $font_handles, $to_do, $expected ) { + // Set up the `to_do` property. + $this->wp_webfonts->to_do = $to_do; + + // Open the method's visibility for testing. + $get_enqueued_fonts_for_provider = $this->get_reflection_method( 'get_enqueued_fonts_for_provider' ); + + // Mock the WP_Web_Fonts::$property to set up the test. + $this->setup_provider_property_mock( $this->wp_webfonts, $this->get_provider_definitions( 'mock' ), $font_handles ); + + $actual = $get_enqueued_fonts_for_provider->invoke( $this->wp_webfonts, 'mock' ); + $this->assertSameSets( $expected, $actual ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_enqueued_fonts_for_provider() { + return array( + 'to_do queue is empty' => array( + 'font_handles ' => array( 'font1', 'font2', 'font3' ), + 'to_do' => array(), + 'expected' => array(), + ), + 'fonts not in to_do queue' => array( + 'font_handles ' => array( 'font1', 'font2', 'font3' ), + 'to_do' => array( 'font12', 'font13' ), + 'expected' => array(), + ), + '2 of the provider fonts in to_do queue' => array( + 'font_handles ' => array( 'font11', 'font12', 'font13' ), + 'to_do' => array( 'font11', 'font13' ), + 'expected' => array( 'font11', 'font13' ), + ), + 'do all of the provider fonts' => array( + 'font_handles ' => array( 'font21', 'font22', 'font23' ), + 'to_do' => array( 'font21', 'font22', 'font23' ), + 'expected' => array( 'font21', 'font22', 'font23' ), + ), + ); + } + + /** + * Test the private method WP_Web_Fonts::get_font_properties_for_provider(). + * + * Why? This test validates the right font properties are returned for use within + * WP_Web_Fonts::do_item(). + * + * @dataProvider data_get_font_properties_for_provider + * + * @param array $font_handles Web fonts for testing. + * @param array $expected Expected result. + */ + public function test_get_font_properties_for_provider( $font_handles, $expected ) { + // Set up the fonts for WP_Dependencies:get_data(). + $fonts = $this->get_registered_fonts(); + // Set all variations to 'mock' provider. + + // Mock the WP_Web_Fonts::$property to set up the test. + $this->setup_provider_property_mock( $this->wp_webfonts, $this->get_provider_definitions( 'mock' ), $font_handles ); + $this->setup_registration_mocks( $fonts, $this->wp_webfonts ); + + // Open the method's visibility for testing. + $method = $this->get_reflection_method( 'get_font_properties_for_provider' ); + + $actual = $method->invoke( $this->wp_webfonts, $font_handles ); + $this->assertSame( $expected, $actual ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_font_properties_for_provider() { + $fonts = $this->get_registered_fonts(); + + return array( + 'handles not registered' => array( + 'font_handles' => array( 'font-not-registered1', 'font-not-registered2', 'font-not-registered3' ), + 'expected' => array(), + ), + 'registered and non-registered handles' => array( + 'font_handles' => array( 'Source Serif Pro-300-normal', 'not-registered-handle', 'Source Serif Pro-900-italic' ), + 'expected' => array( + 'Source Serif Pro-300-normal' => $fonts['Source Serif Pro']['Source Serif Pro-300-normal'], + 'Source Serif Pro-900-italic' => $fonts['Source Serif Pro']['Source Serif Pro-900-italic'], + ), + ), + 'font-family handles, ie no "font-properties" extra data' => array( + 'font_handles' => array( 'font1', 'font2', 'merriweather' ), + 'expected' => array(), + ), + ); + } + + /** + * @dataProvider data_print_enqueued_fonts + * + * @param array $provider Define provider. + * @param array $fonts Fonts to register and enqueue. + * @param array $expected Expected results. + */ + public function test_should_trigger_provider_when_mocked( array $provider, array $fonts, array $expected ) { + $this->setup_print_deps( $provider, $fonts ); + + $provider_mock = $this->setup_object_mock( array( 'set_webfonts', 'print_styles' ), $provider['class'] ); + + // Test the provider's methods are invoked. + $provider_mock->expects( $this->once() )->method( 'set_webfonts' )->with( $this->identicalTo( $expected['set_webfonts'] ) ); + $provider_mock->expects( $this->once() )->method( 'print_styles' ); + + // Set up the WP_Web_Fonts::$provider_instances property. + $provider_instances = $this->get_reflection_property( 'provider_instances' ); + $provider_instances->setValue( $this->wp_webfonts, array( $provider['id'] => $provider_mock ) ); + + // Test the method successfully processes the provider. + $this->expectOutputString( '' ); + $this->assertTrue( $this->wp_webfonts->do_item( $provider['id'] ), 'WP_Web_Fonts::do_item() should return true' ); + } + + /** + * Integration test. + * + * @dataProvider data_print_enqueued_fonts + * + * @param array $provider Define provider. + * @param array $fonts Fonts to register and enqueue. + * @param array $expected Expected results. + */ + public function test_should_print( array $provider, array $fonts, array $expected ) { + $this->setup_print_deps( $provider, $fonts ); + + // Test the method successfully processes the provider. + $this->expectOutputString( $expected['printed_output'] ); + $this->assertTrue( $this->wp_webfonts->do_item( $provider['id'] ), 'WP_Web_Fonts::do_item() should return true' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_print_enqueued_fonts() { + $mock = $this->get_registered_mock_fonts(); + $local = $this->get_registered_local_fonts(); + $font_faces = $this->get_registered_fonts_css(); + + return array( + 'mock' => array( + 'provider' => $this->get_provider_definitions( 'mock' ), + 'fonts' => $mock, + 'expected' => array( + 'set_webfonts' => array_merge( $mock['font1'], $mock['font2'], $mock['font3'] ), + 'printed_output' => sprintf( + '%s; %s; %s; %s; %s; %s\n', + $font_faces['font1-300-normal'], + $font_faces['font1-300-italic'], + $font_faces['font1-900-normal'], + $font_faces['font2-200-900-normal'], + $font_faces['font2-200-900-italic'], + $font_faces['font3-bold-normal'] + ), + ), + ), + 'local' => array( + 'provider' => $this->get_provider_definitions( 'local' ), + 'fonts' => $local, + 'expected' => array( + 'set_webfonts' => array_merge( $local['merriweather'], $local['Source Serif Pro'] ), + 'printed_output' => sprintf( + "\n", + $font_faces['merriweather-200-900-normal'], + $font_faces['Source Serif Pro-300-normal'], + $font_faces['Source Serif Pro-900-italic'] + ), + ), + ), + ); + } + + /** + * Integration test. + * + * @dataProvider data_not_print_enqueued_fonts + * + * @param array $provider Define provider. + * @param array $fonts Fonts to register and enqueue. + * @param array $expected Not used. + * @param array $to_do_queue Value to set in the WP_Web_Fonts::$to_do queue. + */ + public function test_should_not_print_when_to_do_queue_empty( array $provider, array $fonts, $expected, $to_do_queue ) { + $this->setup_print_deps( $provider, $fonts, $to_do_queue ); + + // Test the method successfully processes the provider. + $this->expectOutputString( '' ); + $this->assertFalse( $this->wp_webfonts->do_item( $provider['id'] ), 'WP_Web_Fonts::do_item() should return false' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_not_print_enqueued_fonts() { + $mock = $this->get_registered_mock_fonts(); + $local = $this->get_registered_local_fonts(); + + return array( + 'mock provider when to_do queue is empty' => array( + 'provider' => $this->get_provider_definitions( 'mock' ), + 'fonts' => $mock, + 'expected' => array(), + 'to_do_queue' => array(), + ), + 'local provider when to_do queue is empty' => array( + 'provider' => $this->get_provider_definitions( 'local' ), + 'fonts' => $local, + 'expected' => array(), + 'to_do_queue' => array(), + ), + 'fonts not in to_do queue' => array( + 'provider' => $this->get_provider_definitions( 'mock' ), + 'fonts' => $mock, + 'expected' => array(), + 'to_do_queue' => array(), + ), + ); + } + + /** + * Sets up the print dependencies. + * + * @param array $provider Provider id and class. + * @param array $fonts Fonts to register and enqueue. + * @param array|null $to_do_queue Set the WP_Web_Fonts:$to_do queue. + */ + private function setup_print_deps( $provider, $fonts, $to_do_queue = null ) { + // Set up the fonts for WP_Dependencies:get_data(). + $mocks = $this->setup_registration_mocks( $fonts, $this->wp_webfonts ); + $handles = array_keys( $mocks ); + $this->setup_provider_property_mock( $this->wp_webfonts, $provider, $handles ); + + // Set up the `WP_Web_Fonts::$to_do` and `WP_Web_Fonts::$to_do_keyed_handles` properties. + if ( null === $to_do_queue ) { + $to_do_queue = $handles; + } + + $this->wp_webfonts->to_do = $to_do_queue; + $to_do_keyed_handles = $this->get_reflection_property( 'to_do_keyed_handles' ); + $to_do_keyed_handles->setValue( $this->wp_webfonts, array_flip( $to_do_queue ) ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/doItems-test.php b/phpunit/webfonts/wpWebfonts/doItems-test.php new file mode 100644 index 00000000000000..7efb8dff67b7ec --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/doItems-test.php @@ -0,0 +1,196 @@ +wp_webfonts = new WP_Web_Fonts; + } + + public function test_should_not_process_when_no_providers_registered() { + $this->setup_deps( array( 'enqueued' => 'font1' ) ); + + $done = $this->wp_webfonts->do_items(); + + $this->assertSame( array(), $done, 'WP_Web_Fonts::do_items() should return an empty array' ); + $this->assertSame( array(), $this->wp_webfonts->to_do, 'WP_Web_Fonts::$to_do should be an empty array' ); + } + + /** + * @dataProvider data_invalid_handles + * + * @param mixed $handles Handles to test. + */ + public function test_should_throw_notice_when_invalid_handles( $handles ) { + $this->expectNotice(); + $this->expectNoticeMessage( 'Handles must be a non-empty string or array of non-empty strings' ); + + $this->wp_webfonts->do_items( $handles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_handles() { + return array( + 'null' => array( null ), + 'empty array' => array( array() ), + 'empty string' => array( '' ), + 'array of empty strings' => array( array( '', '' ) ), + 'array of mixed falsey values' => array( array( '', false, null, array() ) ), + ); + } + + public function test_should_throw_notice_when_provider_class_not_found() { + $this->expectNotice(); + $this->expectNoticeMessage( 'Class "Provider_Does_Not_Exist" not found for "doesnotexist" web font provider' ); + + $setup = array( + 'provider' => array( + 'doesnotexist' => array( + 'id' => 'doesnotexist', + 'class' => 'Provider_Does_Not_Exist', + ), + ), + 'provider_handles' => array( 'doesnotexist' => array( 'font1' ) ), + 'registered' => array( + 'doesnotexist' => array( + 'font1' => array( + 'font1-300-normal' => array( + 'provider' => 'doesnotexist', + 'font-weight' => '300', + 'font-style' => 'normal', + 'font-display' => 'fallback', + ), + ), + ), + ), + 'enqueued' => array( 'font1', 'font1-300-normal' ), + ); + $this->setup_deps( $setup ); + + $this->wp_webfonts->do_items(); + } + + /** + * @dataProvider data_print_enqueued + * + * @param array $setup Test set up information for provider, fonts, and enqueued. + * @param array $expected_done Expected array of printed handles. + * @param string $expected_output Expected printed output. + */ + public function test_should_print_mocked_enqueued( $setup, $expected_done, $expected_output ) { + $this->setup_deps( $setup ); + + $this->expectOutputString( $expected_output ); + $actual_done = $this->wp_webfonts->do_items(); + $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); + } + + /** + * Integration test that registers providers and fonts and then enqueues before + * testing the printing functionality. + * + * @dataProvider data_print_enqueued + * + * @param array $setup Test set up information for provider, fonts, and enqueued. + * @param array $expected_done Expected array of printed handles. + * @param string $expected_output Expected printed output. + */ + public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) { + $this->setup_integrated_deps( $setup ); + + $this->expectOutputString( $expected_output, 'Printed @font-face styles should match' ); + $actual_done = $this->wp_webfonts->do_items(); + $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); + } + + /** + * Integration test to validate printing given handles. Rather than mocking internal functionality, + * it registers providers and fonts but does not enqueue. + * + * @dataProvider data_print_enqueued + * + * @param array $setup Test set up information for provider, fonts, and enqueued. + * @param array $expected_done Expected array of printed handles. + * @param string $expected_output Expected printed output. + */ + public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) { + $this->setup_integrated_deps( $setup, false ); + // Do not enqueue. Instead, pass the handles to WP_Web_Fonts::do_items(). + $handles = $setup['enqueued']; + $this->assertEmpty( $this->wp_webfonts->queue, 'No fonts should be enqueued' ); + + $this->expectOutputString( $expected_output ); + $actual_done = $this->wp_webfonts->do_items( $handles ); + $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' ); + } + + /** + * Sets up the dependencies for the mocked test. + * + * @param array $setup Dependencies to set up. + */ + private function setup_deps( array $setup ) { + $setup = array_merge( + array( + 'provider' => array(), + 'provider_handles' => array(), + 'registered' => array(), + 'enqueued' => array(), + ), + $setup + ); + + if ( ! empty( $setup['provider'] ) ) { + foreach ( $setup['provider'] as $provider_id => $provider ) { + $this->setup_provider_property_mock( $this->wp_webfonts, $provider, $setup['provider_handles'][ $provider_id ] ); + } + } + + if ( ! empty( $setup['registered'] ) ) { + $this->setup_registration_mocks( $setup['registered'], $this->wp_webfonts ); + } + + if ( ! empty( $setup['enqueued'] ) ) { + $queue = $this->get_reflection_property( 'queue' ); + $queue->setValue( $this->wp_webfonts, $setup['enqueued'] ); + } + } + + /** + * Sets up the dependencies for integration test. + * + * @param array $setup Dependencies to set up. + * @param bool $enqueue Whether to enqueue. Default true. + */ + private function setup_integrated_deps( array $setup, $enqueue = true ) { + foreach ( $setup['provider'] as $provider ) { + $this->wp_webfonts->register_provider( $provider['id'], $provider['class'] ); + } + foreach ( $setup['registered'] as $handle => $variations ) { + $this->setup_register( $handle, $variations, $this->wp_webfonts ); + } + + if ( $enqueue ) { + $this->wp_webfonts->enqueue( $setup['enqueued'] ); + } + } +} diff --git a/phpunit/webfonts/wpWebfonts/enqueue-test.php b/phpunit/webfonts/wpWebfonts/enqueue-test.php new file mode 100644 index 00000000000000..dde2d92c6df377 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/enqueue-test.php @@ -0,0 +1,53 @@ +enqueue( $handles ); + + $this->assertSame( $expected, $this->get_queued_before_register( $wp_webfonts ), 'Handles should be added to before registered queue' ); + $this->assertEmpty( $wp_webfonts->queue, 'Handles should not be added to the enqueue queue when not registered' ); + } + + /** + * Integration test for enqueuing (a) a font family and all of its variations or (b) specific variations. + * + * @dataProvider data_enqueue + * @dataProviders data_enqueue_variations + * + * @param string|string[] $handles Handles to test. + * @param array $expected Expected queue. + */ + public function test_should_enqueue_when_registered( $handles, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + foreach ( $this->get_data_registry() as $font_family => $variations ) { + $this->setup_register( $font_family, $variations, $wp_webfonts ); + } + + $wp_webfonts->enqueue( $handles ); + + $this->assertEmpty( $this->get_queued_before_register( $wp_webfonts ), '"queued_before_register" queue should be empty' ); + $this->assertSame( $expected, $wp_webfonts->queue, 'Queue should contain the given handles' ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/getEnqueued-test.php b/phpunit/webfonts/wpWebfonts/getEnqueued-test.php new file mode 100644 index 00000000000000..63ea923c085f54 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/getEnqueued-test.php @@ -0,0 +1,57 @@ +assertEmpty( $wp_webfonts->get_enqueued() ); + } + + /** + * Unit test for when font families are enqueued. + * + * @dataProvider data_enqueue + * + * @param string|string[] $not_used Not used. + * @param array $expected Expected queue. + */ + public function test_should_return_queue_when_property_has_font_families( $not_used, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->queue = $expected; + + $this->assertSame( $expected, $wp_webfonts->get_enqueued() ); + } + + /** + * Full integration test that registers and enqueues the queue + * is properly wired for "get_enqueued()". + * + * @dataProvider data_enqueue + * + * @param string|string[] $font_family Font family to test. + * @param array $expected Expected queue. + */ + public function test_should_return_queue_when_font_families_registered_and_enqueued( $font_family, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + + // Register and enqueue. + foreach ( $this->get_data_registry() as $handle => $variations ) { + $this->setup_register( $handle, $variations, $wp_webfonts ); + } + $wp_webfonts->enqueue( $font_family ); + + $this->assertSame( $expected, $wp_webfonts->get_enqueued() ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/getProviders-test.php b/phpunit/webfonts/wpWebfonts/getProviders-test.php new file mode 100644 index 00000000000000..7aa758582cb7c7 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/getProviders-test.php @@ -0,0 +1,62 @@ +wp_webfonts = new WP_Web_Fonts(); + + $this->providers_property = new ReflectionProperty( WP_Web_Fonts::class, 'providers' ); + $this->providers_property->setAccessible( true ); + } + + public function test_should_be_empty() { + $actual = $this->wp_webfonts->get_providers(); + $this->assertIsArray( $actual, 'Should return an empty array' ); + $this->assertEmpty( $actual, 'Should return an empty array when no providers are registered' ); + } + + /** + * @dataProvider data_get_providers + * + * @param array $providers Array of providers to test. + * @param array $expected Expected results. + */ + public function test_get_providers( array $providers, array $expected ) { + $this->setup_providers( $providers ); + $this->assertSame( $expected, $this->wp_webfonts->get_providers() ); + } + + /** + * Sets up the given providers and stores them in the `WP_Web_Fonts::providers` property. + * + * @param array $providers Array of providers to set up. + */ + private function setup_providers( array $providers ) { + $data = array(); + + foreach ( $providers as $provider_id => $class ) { + $data[ $provider_id ] = array( + 'class' => $class, + 'fonts' => array(), + ); + } + + $this->providers_property->setValue( $this->wp_webfonts, $data ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/getRegistered-test.php b/phpunit/webfonts/wpWebfonts/getRegistered-test.php new file mode 100644 index 00000000000000..76b0ab3a5d097c --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/getRegistered-test.php @@ -0,0 +1,90 @@ +assertEmpty( $wp_webfonts->get_registered() ); + } + + /** + * Unit test for when font families are enqueued. + * + * @dataProvider data_get_registered + * + * @param array $inputs Font family(ies) and variations to register. + */ + public function test_should_return_queue_when_mocking_registered_property( array $inputs ) { + $wp_webfonts = new WP_Web_Fonts(); + $mocks = $this->setup_registration_mocks( $inputs, $wp_webfonts ); + $expected = array_keys( $mocks ); + + $this->assertSame( $expected, $wp_webfonts->get_registered() ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_registered() { + return array( + 'no variations' => array( + 'inputs' => array( + 'lato' => array(), + ), + ), + 'with 1 variation' => array( + 'inputs' => array( + 'Source Serif Pro' => array( 'variation-1' ), + ), + ), + 'with 2 variations' => array( + 'inputs' => array( + 'my-cool-font' => array( 'cool-1', 'cool-2' ), + ), + ), + 'when multiple font families registered' => array( + 'inputs' => array( + 'font-family-1' => array( 'variation-11', 'variation-12' ), + 'font-family-2' => array( 'variation-21', 'variation-22' ), + 'font-family-3' => array( 'variation-31', 'variation-32' ), + ), + ), + ); + } + + /** + * Full integration test that registers varying number of font families and variations + * to validate if "get_registered()" internals is property wired to the registered queue. + * + * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations + * + * @param string $font_family Not used. + * @param array $inputs Font family(ies) and variations to register. + * @param array $expected Expected results. + */ + public function test_should_return_queue_when_items_are_registered( $font_family, array $inputs, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + + // Register before testing. + foreach ( $inputs as $handle => $variations ) { + $this->setup_register( $handle, $variations, $wp_webfonts ); + } + + $this->assertSame( $expected, $wp_webfonts->get_registered() ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/query-test.php b/phpunit/webfonts/wpWebfonts/query-test.php new file mode 100644 index 00000000000000..75b44b2717f2b9 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/query-test.php @@ -0,0 +1,151 @@ +wp_webfonts = new WP_Web_Fonts(); + } + + /** + * @dataProvider data_invalid_query + * @dataProvider data_valid_query + * + * @param string $query_handle Handle to test. + */ + public function test_should_fail_when_handles_not_registered( $query_handle ) { + $this->assertFalse( $this->wp_webfonts->query( $query_handle, 'registered' ) ); + } + + /** + * @dataProvider data_invalid_query + * @dataProvider data_valid_query + * + * @param string $query_handle Handle to test. + */ + public function test_should_fail_when_handles_not_registered_or_enqueued( $query_handle ) { + $this->assertFalse( $this->wp_webfonts->query( $query_handle, 'queue' ) ); + } + + /** + * @dataProvider data_valid_query + * + * @param string $query_handle Handle to test. + */ + public function test_registered_query_should_succeed_when_registered( $query_handle ) { + $this->setup_registry(); + + $actual = $this->wp_webfonts->query( $query_handle, 'registered' ); + $this->assertInstanceOf( '_WP_Dependency', $actual, 'Query should return an instance of _WP_Dependency' ); + $this->assertSame( $query_handle, $actual->handle, 'Query object handle should match the given handle to query' ); + } + + /** + * @dataProvider data_valid_query + * + * @param string $query_handle Handle to test. + */ + public function test_enqueued_query_should_succeed_when_registered_and_enqueued( $query_handle ) { + $this->setup_registry(); + $this->wp_webfonts->enqueue( $query_handle ); + + $this->assertTrue( $this->wp_webfonts->query( $query_handle, 'enqueued' ) ); + } + + /** + * @dataProvider data_valid_query + * + * @param string $query_handle Handle to test. + */ + public function test_enqueued_query_should_fail_when_not_registered_but_enqueued( $query_handle ) { + $this->wp_webfonts->enqueue( $query_handle ); + + $this->assertFalse( $this->wp_webfonts->query( $query_handle, 'enqueued' ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_query() { + return array( + 'DM Sans' => array( 'DM Sans' ), + 'roboto' => array( 'roboto' ), + 'my-font' => array( 'my-font' ), + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_valid_query() { + return array( + 'lato' => array( 'lato' ), + 'merriweather' => array( 'merriweather' ), + 'Source Serif Pro' => array( 'source-serif-pro' ), + ); + } + + public function test_done_query_should_fail_when_no_variations() { + $this->wp_webfonts->register_provider( 'local', WP_Webfonts_Provider_Local::class ); + $this->setup_registry(); + $this->wp_webfonts->enqueue( 'lato' ); + + $this->wp_webfonts->do_items( 'lato' ); + + $this->assertFalse( $this->wp_webfonts->query( 'lato', 'done' ) ); + } + + /** + * @dataProvider data_done_query + * + * @param string $query_handle Handle to test. + */ + public function test_done_query_should_succeed_when_registered_and_enqueued( $query_handle ) { + $this->wp_webfonts->register_provider( 'local', WP_Webfonts_Provider_Local::class ); + $this->setup_registry(); + $this->wp_webfonts->enqueue( $query_handle ); + + // Process the web fonts while ignoring all the printed output. + $this->expectOutputRegex( '`.`' ); + $this->wp_webfonts->do_items( $query_handle ); + $this->getActualOutput(); + + $this->assertTrue( $this->wp_webfonts->query( $query_handle, 'done' ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_done_query() { + return array( + 'merriweather' => array( 'merriweather' ), + 'Source Serif Pro' => array( 'source-serif-pro' ), + ); + } + + private function setup_registry() { + foreach ( $this->get_registered_local_fonts() as $handle => $variations ) { + $this->setup_register( $handle, $variations, $this->wp_webfonts ); + } + } +} diff --git a/phpunit/webfonts/wpWebfonts/registerProvider-test.php b/phpunit/webfonts/wpWebfonts/registerProvider-test.php new file mode 100644 index 00000000000000..b73623259fcf48 --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/registerProvider-test.php @@ -0,0 +1,116 @@ +assertTrue( $wp_webfonts->register_provider( $provider_id, $class ), 'WP_Web_Fonts::register_provider() should return true' ); + $this->assertSame( $expected, $wp_webfonts->get_providers(), 'Provider "' . $provider_id . '" should be registered in providers queue' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_register_providers() { + return array( + 'mock' => array( + 'provider_id' => 'mock', + 'class' => Mock_Provider::class, + 'expected' => array( + 'mock' => array( + 'class' => Mock_Provider::class, + 'fonts' => array(), + ), + ), + ), + 'local' => array( + 'provider_id' => 'local', + 'class' => WP_Webfonts_Provider_Local::class, + 'expected' => array( + 'local' => array( + 'class' => WP_Webfonts_Provider_Local::class, + 'fonts' => array(), + ), + ), + ), + ); + } + + public function test_should_register_mutliple_providers() { + $wp_webfonts = new WP_Web_Fonts(); + $providers = $this->get_provider_definitions(); + foreach ( $providers as $provider ) { + $this->assertTrue( $wp_webfonts->register_provider( $provider['id'], $provider['class'] ), 'WP_Web_Fonts::register_provider() should return true for provider ' . $provider['id'] ); + } + + $expected = array( + 'mock' => array( + 'class' => $providers['mock']['class'], + 'fonts' => array(), + ), + 'local' => array( + 'class' => $providers['local']['class'], + 'fonts' => array(), + ), + ); + + $this->assertSame( $expected, $wp_webfonts->get_providers(), 'Both local and mock providers should be registered' ); + } + + /** + * @dataProvider data_invalid_providers + * + * @param string $provider_id Provider ID. + * @param string $class Provider class name. + */ + public function test_should_not_register( $provider_id, $class ) { + $wp_webfonts = new WP_Web_Fonts(); + + $this->assertFalse( $wp_webfonts->register_provider( $provider_id, $class ), 'WP_Web_Fonts::register_provider() should return false' ); + $this->assertArrayNotHasKey( $provider_id, $wp_webfonts->get_providers(), 'Both local and mock providers should be registered' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_providers() { + return array( + 'provider_id is empty' => array( + 'provider_id' => '', + 'class' => Mock_Provider::class, + ), + 'class is empty' => array( + 'provider_id' => 'local', + 'class' => '', + ), + 'class does not exist' => array( + 'provider_id' => 'doesnotexist', + 'class' => 'Provider_Does_Not_Exist', + ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/remove-test.php b/phpunit/webfonts/wpWebfonts/remove-test.php new file mode 100644 index 00000000000000..86cf9fa6ad1bac --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/remove-test.php @@ -0,0 +1,118 @@ +remove( array( 'handle-1', 'handle2' ) ); + + $this->assertEmpty( $wp_webfonts->registered ); + } + + /** + * @dataProvider data_remove_when_registered + * + * @param array $handles Handles to remove. + * @param array $expected Expected handles are running test. + */ + public function test_should_remove_when_registered( array $handles, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + $wp_webfonts->registered = $this->generate_registered_queue(); + + $wp_webfonts->remove( $handles ); + + $this->assertSameSets( $expected, array_keys( $wp_webfonts->registered ), 'Registered queue should match after removing handles' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_remove_when_registered() { + $all = array( + 'handle-1', + 'handle-2', + 'handle-3', + 'handle-4', + 'handle-5', + 'handle-6', + 'handle-7', + 'handle-8', + 'handle-9', + 'handle-10', + ); + + return array( + 'remove none' => array( + 'handles' => array(), + 'expected' => $all, + ), + 'remove handle-5' => array( + 'handles' => array( 'handle-5' ), + 'expected' => array( + 'handle-1', + 'handle-2', + 'handle-3', + 'handle-4', + 'handle-6', + 'handle-7', + 'handle-8', + 'handle-9', + 'handle-10', + ), + ), + 'remove 2 from start and end' => array( + 'handles' => array( 'handle-1', 'handle-2', 'handle-9', 'handle-10' ), + 'expected' => array( + 'handle-3', + 'handle-4', + 'handle-5', + 'handle-6', + 'handle-7', + 'handle-8', + ), + ), + 'remove all' => array( + 'handles' => $all, + 'expected' => array(), + ), + 'remove only registered' => array( + 'handles' => array( 'handle-1', 'handle-10', 'handle-abc', 'handle-5' ), + 'expected' => array( + 'handle-2', + 'handle-3', + 'handle-4', + 'handle-6', + 'handle-7', + 'handle-8', + 'handle-9', + ), + ), + ); + } + + private function generate_registered_queue() { + $queue = array(); + for ( $num = 1; $num <= 10; $num++ ) { + $handle = "handle-{$num}"; + $queue[ $handle ] = $num; + } + + return $queue; + } +} diff --git a/phpunit/webfonts/wpWebfonts/removeFontFamily-test.php b/phpunit/webfonts/wpWebfonts/removeFontFamily-test.php new file mode 100644 index 00000000000000..959744e555202f --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/removeFontFamily-test.php @@ -0,0 +1,89 @@ +setup_registration_mocks( $inputs, $wp_webfonts ); + // Test the before state, just to make sure. + $this->assertArrayHasKey( $font_family, $wp_webfonts->registered, 'Registered queue should contain the font family before remove' ); + $this->assertSame( $registered_handles, array_keys( $wp_webfonts->registered ), 'Font family and variations should be registered before remove' ); + + $wp_webfonts->remove_font_family( $font_family ); + + $this->assertArrayNotHasKey( $font_family, $wp_webfonts->registered, 'Registered queue should not contain the font family' ); + $this->assertSame( $expected, array_keys( $wp_webfonts->registered ), 'Registered queue should match after removing font family' ); + } + + /** + * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations + * + * @param string $font_family Font family to test. + * @param array $inputs Font family(ies) and variations to pre-register. + * @param array $registered_handles Not used. + * @param array $expected Array of expected handles. + */ + public function test_should_bail_out_when_not_registered( $font_family, array $inputs, array $registered_handles, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + unset( $inputs[ $font_family ] ); + $this->setup_registration_mocks( $inputs, $wp_webfonts ); + + $wp_webfonts->remove_font_family( $font_family ); + + $this->assertArrayNotHasKey( $font_family, $wp_webfonts->registered, 'Registered queue should not contain the font family' ); + $this->assertSame( $expected, array_keys( $wp_webfonts->registered ), 'Registered queue should match after removing font family' ); + } + + /** + * Integration test for removing a font family and all of its variation when font family is registered. + * + * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations + * + * @param string $font_family Font family to test. + * @param array $inputs Font family(ies) and variations to pre-register. + * @param array $registered_handles Expected handles after registering. + * @param array $expected Array of expected handles. + */ + public function test_should_deregister_when_registered( $font_family, array $inputs, array $registered_handles, array $expected ) { + $wp_webfonts = new WP_Web_Fonts(); + // Register all font families and their variations. + foreach ( $inputs as $input_font_family => $variations ) { + $handle = $wp_webfonts->add_font_family( $input_font_family ); + foreach ( $variations as $variation_handle => $variation ) { + if ( ! is_string( $variation_handle ) ) { + $variation_handle = ''; + } + $wp_webfonts->add_variation( $handle, $variation, $variation_handle ); + } + } + // Test the before state, just to make sure. + $this->assertArrayHasKey( $font_family, $wp_webfonts->registered, 'Registered queue should contain the font family before remove' ); + $this->assertSame( $registered_handles, array_keys( $wp_webfonts->registered ), 'Font family and variations should be registered before remove' ); + + $wp_webfonts->remove_font_family( $font_family ); + + $this->assertArrayNotHasKey( $font_family, $wp_webfonts->registered, 'Registered queue should not contain the font family' ); + $this->assertSame( $expected, array_keys( $wp_webfonts->registered ), 'Registered queue should match after removing font family' ); + } +} diff --git a/phpunit/webfonts/wpWebfonts/removeVariation-test.php b/phpunit/webfonts/wpWebfonts/removeVariation-test.php new file mode 100644 index 00000000000000..dd3b21a9573eaa --- /dev/null +++ b/phpunit/webfonts/wpWebfonts/removeVariation-test.php @@ -0,0 +1,278 @@ +wp_webfonts = new WP_Web_Fonts(); + $this->fonts_to_register = $this->get_registered_local_fonts(); + } + + /** + * Sets up the unit test by mocking the WP_Dependencies object using stdClass and + * registering each font family directly to the WP_Web_Fonts::$registered property + * and its variations to the mocked $deps property. + */ + private function setup_unit_test() { + $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_webfonts ); + } + + /** + * Sets up the integration test by properly registering each font family and its variations + * by using the WP_Web_Fonts::add() and WP_Web_Fonts::add_variation() methods. + */ + private function setup_integration_test() { + foreach ( $this->fonts_to_register as $font_family_handle => $variations ) { + $this->setup_register( $font_family_handle, $variations, $this->wp_webfonts ); + } + } + + /** + * Testing the test setup to ensure it works. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + */ + public function test_mocked_setup( $font_family_handle, $variation_handle ) { + $this->setup_unit_test(); + + $this->assertArrayHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be in the registered queue before remval' ); + $this->assertContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' ); + } + + /** + * Unit test. + * + * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered + * + * @param string $font_family Font family name. + * @param string $font_family_handle Font family handle. + * @param string $variation_handle Variation handle to remove. + */ + public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { + // Set up the test. + unset( $this->fonts_to_register[ $font_family ] ); + $this->setup_unit_test(); + $registered_queue = $this->wp_webfonts->registered; + + // Run the tests. + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $font_family_handle, $this->wp_webfonts->registered, 'Font family should not be registered' ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertSame( $registered_queue, $this->wp_webfonts->registered, 'Registered queue should not have changed' ); + } + + /** + * Integration test. + * + * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered + * + * @param string $font_family Font family name. + * @param string $font_family_handle Font family handle. + * @param string $variation_handle Variation handle to remove. + */ + public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) { + // Set up the test. + unset( $this->fonts_to_register[ $font_family ] ); + $this->setup_integration_test(); + $registered_queue = $this->wp_webfonts->get_registered(); + + // Run the tests. + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $font_family_handle, $this->wp_webfonts->registered, 'Font family should not be registered' ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertSameSets( $registered_queue, $this->wp_webfonts->get_registered(), 'Registered queue should not have changed' ); + } + + /** + * Data provider for testing removal of variations. + * + * @return array + */ + public function data_should_do_nothing_when_variation_and_font_family_not_registered() { + return array( + 'Font with 1 variation' => array( + 'font_family' => 'merriweather', + 'font_family_handle' => 'merriweather', + 'variation_handle' => 'merriweather-200-900-normal', + ), + 'Font with multiple variations' => array( + 'font_family' => 'Source Serif Pro', + 'font_family_handle' => 'source-serif-pro', + 'variation_handle' => 'Source Serif Pro-300-normal', + ), + ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + $this->setup_remove_variation_from_registered( $variation_handle ); + + // Run the tests. + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + $this->setup_remove_variation_from_registered( $variation_handle ); + + // Run the tests. + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variant should not be registered' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); + + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' ); + + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Unit test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_unit_test(); + + $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); + + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be not be in registered queue' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Integration test. + * + * @dataProvider data_remove_variations + * + * @param string $font_family_handle Font family for the variation. + * @param string $variation_handle Variation handle to remove. + * @param array $expected Expected results. + */ + public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) { + // Set up the test. + $this->setup_integration_test(); + + $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' ); + + $this->wp_webfonts->remove_variation( $font_family_handle, $variation_handle ); + + $this->assertArrayNotHasKey( $variation_handle, $this->wp_webfonts->registered, 'Variation should be not be in registered queue' ); + $this->assertNotContains( $variation_handle, $this->wp_webfonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' ); + $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_webfonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' ); + } + + /** + * Remove the variation handle from the font family's deps. + * + * @param string $font_family_handle Font family. + * @param string $variation_handle The variation handle to remove. + */ + private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) { + foreach ( $this->wp_webfonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) { + if ( $variation_handle !== $vhandle ) { + continue; + } + unset( $this->wp_webfonts->registered[ $font_family_handle ]->deps[ $index ] ); + break; + } + } + + /** + * Removes the variation from the WP_Web_Fonts::$registered queue. + * + * @param string $variation_handle The variation handle to remove. + */ + private function setup_remove_variation_from_registered( $variation_handle ) { + unset( $this->wp_webfonts->registered[ $variation_handle ] ); + } +} diff --git a/phpunit/class-wp-webfonts-local-provider-test.php b/phpunit/webfonts/wpWebfontsProviderLocal-test.php similarity index 78% rename from phpunit/class-wp-webfonts-local-provider-test.php rename to phpunit/webfonts/wpWebfontsProviderLocal-test.php index 43ef4a84951c84..08f9f18c3cd6a2 100644 --- a/phpunit/class-wp-webfonts-local-provider-test.php +++ b/phpunit/webfonts/wpWebfontsProviderLocal-test.php @@ -1,10 +1,15 @@ 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'] ); - } - public function tear_down() { // Restore the original theme directory setup. $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; @@ -79,7 +63,7 @@ public function test_set_webfonts() { /** * @covers WP_Webfonts_Provider_Local::get_css * - * @dataProvider data_get_css + * @dataProvider data_get_css_print_styles * * @param array $webfonts Prepared webfonts (to store in WP_Webfonts_Provider_Local::$webfonts property). * @param string $expected Expected CSS. @@ -88,7 +72,24 @@ 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() ); + $this->assertSame( $expected['font-face-css'], $this->provider->get_css() ); + } + + /** + * @covers WP_Webfonts_Provider_Local::print_styles + * + * @dataProvider data_get_css_print_styles + * + * @param array $webfonts Prepared webfonts (to store in WP_Webfonts_Provider_Local::$webfonts property). + * @param string $expected Expected CSS. + */ + public function test_print_styles( array $webfonts, $expected ) { + $property = $this->get_webfonts_property(); + $property->setValue( $this->provider, $webfonts ); + + $expected_output = sprintf( $expected['style-element'], $expected['font-face-css'] ); + $this->expectOutputString( $expected_output ); + $this->provider->print_styles(); } /** @@ -96,7 +97,7 @@ public function test_get_css( array $webfonts, $expected ) { * * @return array */ - public function data_get_css() { + public function data_get_css_print_styles() { return array( 'truetype format' => array( 'webfonts' => array( @@ -108,10 +109,13 @@ public function data_get_css() { 'src' => 'http://example.org/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf', ), ), - 'expected' => << array( + 'style-element' => "\n", + 'font-face-css' => << array( 'webfonts' => array( @@ -132,14 +136,39 @@ public function data_get_css() { 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2', ), ), - 'expected' => << array( + 'style-element' => "\n", + 'font-face-css' => <<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'] ); + } + private function get_webfonts_property() { $property = new ReflectionProperty( $this->provider, 'webfonts' ); $property->setAccessible( true ); diff --git a/phpunit/webfonts/wpWebfontsUtils/convertFontFamilyIntoHandle-test.php b/phpunit/webfonts/wpWebfontsUtils/convertFontFamilyIntoHandle-test.php new file mode 100644 index 00000000000000..d258f1e5c435df --- /dev/null +++ b/phpunit/webfonts/wpWebfontsUtils/convertFontFamilyIntoHandle-test.php @@ -0,0 +1,84 @@ +assertSame( $expected, WP_Webfonts_Utils::convert_font_family_into_handle( $font_family ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_valid_input() { + return array( + 'font family single word name' => array( + 'font_family' => 'Merriweather', + 'expected' => 'merriweather', + ), + 'font family multiword name' => array( + 'font_family' => 'Source Sans Pro', + 'expected' => 'source-sans-pro', + ), + 'font family handle delimited by hyphens' => array( + 'font_family' => 'source-serif-pro', + 'expected' => 'source-serif-pro', + ), + 'font family handle delimited by underscore' => array( + 'font_family' => 'source_serif_pro', + 'expected' => 'source_serif_pro', + ), + 'font family handle delimited by hyphens and underscore' => array( + 'font_family' => 'my-custom_font_family', + 'expected' => 'my-custom_font_family', + ), + 'font family handle delimited mixture' => array( + 'font_family' => 'My custom_font-family', + 'expected' => 'my-custom_font-family', + ), + ); + } + + /** + * @dataProvider data_with_invalid_input + * + * @covers WP_Webfonts_Utils::convert_font_family_into_handle + * + * @param mixed $invalid_input Invalid input. + */ + public function test_should_not_convert_with_invalid_input( $invalid_input ) { + $this->assertNull( WP_Webfonts_Utils::convert_font_family_into_handle( $invalid_input ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_invalid_input() { + return array( + 'empty string' => array( '' ), + 'integer' => array( 10 ), + 'font family wrapped in an array' => array( array( 'source-serif-pro' ) ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfontsUtils/convertVariationIntoHandle-test.php b/phpunit/webfonts/wpWebfontsUtils/convertVariationIntoHandle-test.php new file mode 100644 index 00000000000000..9e2a3462ed7a82 --- /dev/null +++ b/phpunit/webfonts/wpWebfontsUtils/convertVariationIntoHandle-test.php @@ -0,0 +1,122 @@ +assertSame( $expected, WP_Webfonts_Utils::convert_variation_into_handle( $font_family, $variation ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_valid_input() { + return array( + 'with only font-weight' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'font-weight' => '400', + ), + 'expected' => 'merriweather-400', + ), + 'with no font-style' => array( + 'font_family' => 'source-sans-pro', + 'variation' => array( + 'font-weight' => '200 900', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'provider' => 'local', + ), + 'expected' => 'source-sans-pro-200-900', + ), + 'with font family name and full variant' => array( + 'font_family' => 'source-sans-pro', + 'variation' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'expected' => 'source-sans-pro-200-900-normal', + ), + ); + } + + /** + * @dataProvider data_with_invalid_input + * + * @param string $font_family Font family to test. + * @param array $invalid_input Variation to test. + */ + public function tests_should_convert_with_invalid_input( $font_family, $invalid_input ) { + $this->expectNotice(); + $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' ); + + $this->assertNull( WP_Webfonts_Utils::convert_variation_into_handle( $font_family, $invalid_input ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_invalid_input() { + return array( + 'with no font-weight or font-style' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'provider' => 'local', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + ), + 'with non-string font-weight' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'font-weight' => 400, + ), + ), + 'with non-string font-style' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'font-style' => 0, + ), + ), + 'with empty string font-weight' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'font-weight' => '', + ), + ), + 'with empty string font-style' => array( + 'font_family' => 'merriweather', + 'variation' => array( + 'font-style' => '', + ), + ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfontsUtils/getFontFamilyFromVariation-test.php b/phpunit/webfonts/wpWebfontsUtils/getFontFamilyFromVariation-test.php new file mode 100644 index 00000000000000..f370e1d043222c --- /dev/null +++ b/phpunit/webfonts/wpWebfontsUtils/getFontFamilyFromVariation-test.php @@ -0,0 +1,134 @@ +assertSame( $expected, WP_Webfonts_Utils::get_font_family_from_variation( $variation ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_valid_variation() { + return array( + 'keyed by font-family' => array( + 'variation' => array( + 'provider' => 'local', + 'font-family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'expected' => 'Source Serif Pro', + ), + 'keyed by fontFamily and as a handle' => array( + 'variation' => array( + 'fontFamily' => 'source-sans-pro', + 'font-weight' => '200 900', + 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', + 'provider' => 'local', + ), + 'expected' => 'source-sans-pro', + ), + 'with font family name and full variant' => array( + 'variation' => array( + 'provider' => 'local', + 'font-family' => 'Merriweather', + 'font-style' => 'normal', + 'font-weight' => '400 600', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2', + 'font-display' => 'fallback', + ), + 'expected' => 'Merriweather', + ), + ); + } + + /** + * @dataProvider data_with_invalid_input + * + * @param array $invalid_variation Variation to test. + * @param string $expected_message Expected notice message. + */ + public function test_with_invalid_input( array $invalid_variation, $expected_message ) { + $this->expectNotice(); + $this->expectNoticeMessage( $expected_message ); + + $this->assertNull( WP_Webfonts_Utils::get_font_family_from_variation( $invalid_variation ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_with_invalid_input() { + return array( + 'keyed with underscore' => array( + 'variation' => array( + 'provider' => 'local', + 'font_family' => 'Source Serif Pro', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Font family not found.', + ), + 'keyed with space' => array( + 'variation' => array( + 'font family' => 'Source Sans Pro', + 'font-weight' => '200 900', + 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', + 'provider' => 'local', + ), + 'expected_message' => 'Font family not found.', + ), + 'fontFamily => empty string' => array( + 'variation' => array( + 'fontFamily' => '', + 'font-weight' => '200 900', + 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2', + 'provider' => 'local', + ), + 'expected_message' => 'Font family not defined in the variation.', + ), + 'font-family => empty string' => array( + 'variation' => array( + 'provider' => 'local', + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '200 900', + 'font-stretch' => 'normal', + 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2', + 'font-display' => 'fallback', + ), + 'expected_message' => 'Font family not defined in the variation.', + ), + ); + } +} diff --git a/phpunit/webfonts/wpWebfontsUtils/isDefined-test.php b/phpunit/webfonts/wpWebfontsUtils/isDefined-test.php new file mode 100644 index 00000000000000..c5b2a1608269c8 --- /dev/null +++ b/phpunit/webfonts/wpWebfontsUtils/isDefined-test.php @@ -0,0 +1,61 @@ +assertTrue( WP_Webfonts_Utils::is_defined( $input ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_when_defined() { + return array( + 'name: non empty string' => array( 'Some Font Family' ), + 'handle: non empty string' => array( 'some-font-family' ), + ); + } + + /** + * @dataProvider data_when_not_defined + * + * @param mixed $invalid_input Input to test. + */ + public function test_should_return_false_when_not_defined( $invalid_input ) { + $this->assertFalse( WP_Webfonts_Utils::is_defined( $invalid_input ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_when_not_defined() { + return array( + 'empty string' => array( '' ), + 'string 0' => array( '0' ), + 'integer' => array( 10 ), + 'name wrapped in an array' => array( array( 'Some Font Family' ) ), + 'handle wrapped in an array' => array( array( 'some-font-family' ) ), + ); + } +}