diff --git a/src/wp-content/themes/twentytwentyone/functions.php b/src/wp-content/themes/twentytwentyone/functions.php index ffbae09ad88ea..fe9df27e8149b 100644 --- a/src/wp-content/themes/twentytwentyone/functions.php +++ b/src/wp-content/themes/twentytwentyone/functions.php @@ -425,7 +425,7 @@ function twenty_twenty_one_scripts() { get_template_directory_uri() . '/assets/js/polyfills.js', array(), wp_get_theme()->get( 'Version' ), - true + array( 'in_footer' => true ) ); // Register the IE11 polyfill loader. @@ -434,7 +434,7 @@ function twenty_twenty_one_scripts() { null, array(), wp_get_theme()->get( 'Version' ), - true + array( 'in_footer' => true ) ); wp_add_inline_script( 'twenty-twenty-one-ie11-polyfills', diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 9dcd1932147fd..1acdb67172ac3 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -153,7 +153,8 @@ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { $script_handle, $script_uri, $script_dependencies, - isset( $script_asset['version'] ) ? $script_asset['version'] : false + isset( $script_asset['version'] ) ? $script_asset['version'] : false, + array( 'in_footer' => false ) ); if ( ! $result ) { return false; diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 5374aa912a757..b90f6647ffe2e 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -133,6 +133,15 @@ class WP_Scripts extends WP_Dependencies { */ private $type_attr = ''; + /** + * Holds a mapping of dependents (as handles) for a given script handle. + * Used to optimize recursive dependency tree checks. + * + * @since 6.3.0 + * @var array + */ + private $dependents_map = array(); + /** * Constructor. * @@ -295,14 +304,34 @@ public function do_item( $handle, $group = false ) { } $before_handle = $this->print_inline_script( $handle, 'before', false ); - $after_handle = $this->print_inline_script( $handle, 'after', false ); if ( $before_handle ) { $before_handle = sprintf( "\n%s\n\n", $this->type_attr, esc_attr( $handle ), $before_handle ); } + // Eligible loading strategies will only be 'async', 'defer', or ''. + $strategy = $this->get_eligible_loading_strategy( $handle ); + $after = ( '' === $strategy ) ? 'after' : 'after-standalone'; + $after_handle = $this->print_inline_script( $handle, $after, false ); + if ( $after_handle ) { - $after_handle = sprintf( "\n%s\n\n", $this->type_attr, esc_attr( $handle ), $after_handle ); + $after_handle = sprintf( + "\n%3\$s\n\n", + $this->type_attr, + esc_attr( $handle ), + $after_handle + ); + } + if ( '' !== $strategy ) { + $after_non_standalone_handle = $this->print_inline_script( $handle, 'after-non-standalone', false ); + + if ( $after_non_standalone_handle ) { + $after_handle .= sprintf( + "\n", + esc_attr( $handle ), + $after_non_standalone_handle + ); + } } if ( $before_handle || $after_handle ) { @@ -333,7 +362,13 @@ public function do_item( $handle, $group = false ) { */ $srce = apply_filters( 'script_loader_src', $src, $handle ); - if ( $this->in_default_dir( $srce ) && ( $before_handle || $after_handle || $translations_stop_concat ) ) { + // Used as a conditional to prevent script concatenation. + $is_deferred_or_async_handle = $this->is_non_blocking_strategy( $strategy ); + + if ( + $this->in_default_dir( $srce ) + && ( $before_handle || $after_handle || $translations_stop_concat || $is_deferred_or_async_handle ) + ) { $this->do_concat = false; // Have to print the so-far concatenated scripts right away to maintain the right order. @@ -390,8 +425,20 @@ public function do_item( $handle, $group = false ) { return true; } + if ( '' !== $strategy ) { + $strategy = ' ' . $strategy; + if ( ! empty( $after_non_standalone_handle ) ) { + $strategy .= sprintf( " onload='wpLoadAfterScripts(%s)'", esc_attr( wp_json_encode( $handle ) ) ); + } + } $tag = $translations . $cond_before . $before_handle; - $tag .= sprintf( "\n", $this->type_attr, $src, esc_attr( $handle ) ); + $tag .= sprintf( + "\n", + $this->type_attr, + esc_url( $src ), + esc_attr( $handle ), + $strategy + ); $tag .= $after_handle . $cond_after; /** @@ -418,15 +465,17 @@ public function do_item( $handle, $group = false ) { * Adds extra code to a registered script. * * @since 4.5.0 + * @since 6.3.0 Addition of $standalone boolean parameter. * - * @param string $handle Name of the script to add the inline script to. - * Must be lowercase. - * @param string $data String containing the JavaScript to be added. - * @param string $position Optional. Whether to add the inline script - * before the handle or after. Default 'after'. + * @param string $handle Name of the script to add the inline script to. + * Must be lowercase. + * @param string $data String containing the JavaScript to be added. + * @param string $position Optional. Whether to add the inline script + * before the handle or after. Default 'after'. + * @param bool $standalone Optional. Inline script opted to be standalone or not. Default false. * @return bool True on success, false on failure. */ - public function add_inline_script( $handle, $data, $position = 'after' ) { + public function add_inline_script( $handle, $data, $position = 'after', $standalone = false ) { if ( ! $data ) { return false; } @@ -438,6 +487,12 @@ public function add_inline_script( $handle, $data, $position = 'after' ) { $script = (array) $this->get_data( $handle, $position ); $script[] = $data; + // Maintain a list of standalone and non-standalone before/after scripts. + $standalone_key = $standalone ? $position . '-standalone' : $position . '-non-standalone'; + $standalone_script = (array) $this->get_data( $handle, $standalone_key ); + $standalone_script[] = $data; + $this->add_data( $handle, $standalone_key, $standalone_script ); + return $this->add_data( $handle, $position, $script ); } @@ -464,7 +519,19 @@ public function print_inline_script( $handle, $position = 'after', $display = tr $output = trim( implode( "\n", $output ), "\n" ); if ( $display ) { - printf( "\n%s\n\n", $this->type_attr, esc_attr( $handle ), esc_attr( $position ), $output ); + if ( 'after-non-standalone' === $position ) { + $script_output = "\n%4\$s\n\n"; + } else { + $script_output = "\n%4\$s\n\n"; + } + + printf( + $script_output, + $this->type_attr, + esc_attr( $handle ), + esc_attr( $position ), + $output + ); } return $output; @@ -714,6 +781,262 @@ public function in_default_dir( $src ) { return false; } + /** + * This overrides the add_data method from WP_Dependencies, to support normalizing of $args. + * + * @since 6.3.0 + * + * @param string $handle Name of the item. Should be unique. + * @param string $key The data key. + * @param mixed $value The data value. + * @return bool True on success, false on failure. + */ + public function add_data( $handle, $key, $value ) { + if ( 'script_args' === $key ) { + $args = $this->get_normalized_script_args( $handle, $value ); + if ( $args['in_footer'] ) { + parent::add_data( $handle, 'group', 1 ); + } + return parent::add_data( $handle, $key, $args ); + } + return parent::add_data( $handle, $key, $value ); + } + + /** + * Checks all handles for any delayed inline scripts. + * + * @since 6.3.0 + * + * @return bool True if the inline script present, otherwise false. + */ + public function has_delayed_inline_script() { + foreach ( $this->registered as $handle => $script ) { + // Non standalone scripts in the after position, of type async or defer, are usually delayed. + $strategy = $this->get_intended_strategy( $handle ); + if ( $this->is_non_blocking_strategy( $strategy ) && + $this->has_non_standalone_inline_script( $handle, 'after' ) + ) { + return true; + } + } + return false; + } + + /** + * Normalize the data inside the $args parameter and support backward compatibility. + * + * @since 6.3.0 + * + * @param string $handle Name of the script. + * @param array $args { + * Optional. Additional script arguments. Default empty array. + * + * @type boolean $in_footer Optional. Default false. + * @type string $strategy Optional. Values blocking|defer|async. Default 'blocking'. + * } + * @return array Normalized $args array. + */ + private function get_normalized_script_args( $handle, $args = array() ) { + $default_args = array( + 'in_footer' => false, + 'strategy' => 'blocking', + ); + + // Handle backward compatibility for $in_footer. + if ( true === $args ) { + $args = array( 'in_footer' => true ); + } + + return wp_parse_args( $args, $default_args ); + } + + /** + * Get all dependents of a script. + * + * @since 6.3.0 + * + * @param string $handle The script handle. + * @return array Array of script handles. + */ + private function get_dependents( $handle ) { + // Check if dependents map for the handle in question is present. If so, use it. + if ( array_key_exists( $handle, $this->dependents_map ) ) { + return $this->dependents_map[ $handle ]; + } + + $dependents = array(); + + // Iterate over all registered scripts, finding dependents of the script passed to this method. + foreach ( $this->registered as $registered_handle => $args ) { + if ( in_array( $handle, $args->deps, true ) ) { + $dependents[] = $registered_handle; + } + } + + // Add the handles dependents to the map to ease future lookups. + $this->dependents_map[ $handle ] = $dependents; + + return $dependents; + } + + /** + * Determines if a script loading strategy is valid. + * + * @since 6.3.0 + * + * @param string $strategy A script loading strategy. + * @return bool True if the strategy is of an allowed type, false otherwise. + */ + private function is_valid_strategy( $strategy ) { + $allowed_strategies = array( 'blocking', 'defer', 'async' ); + + return in_array( $strategy, $allowed_strategies, true ); + } + + /** + * Get the strategy assigned during script registration. + * + * @since 6.3.0 + * + * @param string $handle The script handle. + * @return string Strategy set during script registration. Empty string if none was set. + */ + private function get_intended_strategy( $handle ) { + $script_args = $this->get_data( $handle, 'script_args' ); + $strategy = isset( $script_args['strategy'] ) ? $script_args['strategy'] : ''; + + if ( $strategy && ! $this->is_valid_strategy( $strategy ) ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: 1: $strategy, 2: $handle */ + __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ), + $strategy, + $handle + ), + '6.3.0' + ); + + return ''; + } + + return $strategy; + } + + /** + * Check if the strategy passed is a non-blocking strategy. + * + * @since 6.3.0 + * + * @param string $strategy The strategy to check. + * @return bool True if $strategy is one of the delayed strategy. + */ + private function is_non_blocking_strategy( $strategy ) { + $delayed_strategies = array( 'defer', 'async' ); + + return in_array( $strategy, $delayed_strategies, true ); + } + + /** + * Check if a script has a non standalone inline script associated with it. + * + * @since 6.3.0 + * + * @param string $handle The script handle. + * @param string $position Position of the inline script. + * + * @return bool True if script present. False if empty. + */ + private function has_non_standalone_inline_script( $handle, $position ) { + $non_standalone_script_key = $position . '-non-standalone'; + $non_standalone_script = $this->get_data( $handle, $non_standalone_script_key ); + + return ! empty( $non_standalone_script ); + } + + /** + * Check if all of a scripts dependents are deferrable, which is required to maintain execution order. + * + * @since 6.3.0 + * + * @param string $handle The script handle. + * @param array $checked Optional. An array of already checked script handles, used to avoid recursive loops. + * @return bool True if all dependents are deferrable, false otherwise. + */ + private function has_only_deferrable_dependents( $handle, $checked = array() ) { + // If this node was already checked, this script can be deferred and the branch ends. + if ( in_array( $handle, $checked, true ) ) { + return true; + } + + $checked[] = $handle; + $dependents = $this->get_dependents( $handle ); + + // If there are no dependents remaining to consider, the script can be deferred. + if ( empty( $dependents ) ) { + return true; + } + + // Consider each dependent and check if it is deferrable. + foreach ( $dependents as $dependent ) { + // If the dependent script is not using the defer or async strategy, no script in the chain is deferrable. + $strategy = $this->get_intended_strategy( $dependent ); + if ( ! $this->is_non_blocking_strategy( $strategy ) ) { + return false; + } + + // If the dependent script has a non-standalone inline script in the 'before' position associated with it, do not defer. + if ( $this->has_non_standalone_inline_script( $dependent, 'before' ) ) { + return false; + } + + // Recursively check all dependents. + if ( ! $this->has_only_deferrable_dependents( $dependent, $checked ) ) { + return false; + } + } + + return true; + } + + /** + * Get the best eligible loading strategy for a script. + * + * @since 6.3.0 + * + * @param string $handle The registered handle of the script. + * @return string $strategy return the final strategy. + */ + private function get_eligible_loading_strategy( $handle ) { + if ( ! isset( $this->registered[ $handle ] ) ) { + return ''; + } + + $intended_strategy = $this->get_intended_strategy( $handle ); + + /* + * Handle known blocking strategy scenarios. + * + * blocking if script args not set. + * blocking if explicitly set. + */ + if ( '' === $intended_strategy || 'blocking' === $intended_strategy ) { + return ''; + } + + // Handling async strategy scenarios. + if ( 'async' === $intended_strategy && empty( $this->registered[ $handle ]->deps ) && empty( $this->get_dependents( $handle ) ) ) { + return 'async'; + } + + // Handling defer strategy scenarios. + if ( $this->has_only_deferrable_dependents( $handle ) ) { + return 'defer'; + } + + return ''; + } + /** * Resets class properties. * diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index f10dc6f280b2b..5b77359496ef2 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -568,6 +568,7 @@ add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' ); add_filter( 'wp_print_scripts', 'wp_just_in_time_script_localization' ); +add_action( 'wp_print_scripts', 'wp_print_delayed_inline_script_loader' ); add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' ); add_filter( 'customize_controls_print_styles', 'wp_resource_hints', 1 ); add_action( 'admin_head', 'wp_check_widget_editor_deps' ); diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php index d6c8aaddd2ad5..4971c09ec3b11 100644 --- a/src/wp-includes/functions.wp-scripts.php +++ b/src/wp-includes/functions.wp-scripts.php @@ -121,13 +121,14 @@ function wp_print_scripts( $handles = false ) { * * @see WP_Scripts::add_inline_script() * - * @param string $handle Name of the script to add the inline script to. - * @param string $data String containing the JavaScript to be added. - * @param string $position Optional. Whether to add the inline script before the handle - * or after. Default 'after'. + * @param string $handle Name of the script to add the inline script to. + * @param string $data String containing the JavaScript to be added. + * @param string $position Optional. Whether to add the inline script before the handle + * or after. Default 'after'. + * @param bool $standalone Optional. Inline script opted to be standalone or not. Default false. * @return bool True on success, false on failure. */ -function wp_add_inline_script( $handle, $data, $position = 'after' ) { +function wp_add_inline_script( $handle, $data, $position = 'after', $standalone = false ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); if ( false !== stripos( $data, '' ) ) { @@ -144,7 +145,7 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) { $data = trim( preg_replace( '#]*>(.*)#is', '$1', $data ) ); } - return wp_scripts()->add_inline_script( $handle, $data, $position ); + return wp_scripts()->add_inline_script( $handle, $data, $position, $standalone ); } /** @@ -157,6 +158,7 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) { * * @since 2.1.0 * @since 4.3.0 A return value was added. + * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array. * * @param string $handle Name of the script. Should be unique. * @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory. @@ -166,20 +168,23 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) { * as a query string for cache busting purposes. If version is set to false, a version * number is automatically added equal to current installed WordPress version. * If set to null, no version is added. - * @param bool $in_footer Optional. Whether to enqueue the script before `` instead of in the ``. - * Default 'false'. + * @param array $args { + * Optional. An array of additional script loading strategies. Default empty array. + * + * @type boolean $in_footer Optional. Default false. + * @type string $strategy Optional. Values blocking|defer|async. Default 'blocking'. + * } * @return bool Whether the script has been registered. True on success, false on failure. */ -function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) { +function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args = array() ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); $wp_scripts = wp_scripts(); $registered = $wp_scripts->add( $handle, $src, $deps, $ver ); - if ( $in_footer ) { - $wp_scripts->add_data( $handle, 'group', 1 ); + if ( ! empty( $args ) ) { + $wp_scripts->add_data( $handle, 'script_args', $args ); } - return $registered; } @@ -331,6 +336,7 @@ function wp_deregister_script( $handle ) { * @see WP_Dependencies::enqueue() * * @since 2.1.0 + * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array. * * @param string $handle Name of the script. Should be unique. * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory. @@ -340,23 +346,29 @@ function wp_deregister_script( $handle ) { * as a query string for cache busting purposes. If version is set to false, a version * number is automatically added equal to current installed WordPress version. * If set to null, no version is added. + * @param array $args { + * Optional. An array of additional script loading strategies. Default empty array. + * + * @type boolean $in_footer Optional. Default false. + * @type string $strategy Optional. Values blocking|defer|async. Default 'blocking'. + * } + * * @param bool $in_footer Optional. Whether to enqueue the script before `` instead of in the ``. * Default 'false'. */ -function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $in_footer = false ) { +function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); $wp_scripts = wp_scripts(); - if ( $src || $in_footer ) { + if ( $src || ! empty( $args ) ) { $_handle = explode( '?', $handle ); if ( $src ) { $wp_scripts->add( $_handle[0], $src, $deps, $ver ); } - - if ( $in_footer ) { - $wp_scripts->add_data( $_handle[0], 'group', 1 ); + if ( ! empty( $args ) ) { + $wp_scripts->add_data( $_handle[0], 'script_args', $args ); } } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index e0e01299a097d..418bab09308d4 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1843,6 +1843,38 @@ function wp_just_in_time_script_localization() { ); } +/** + * Prints a loader script if there is text/template registered script. + * + * @since 6.3.0 + * + * When added to the DOM, this script converts any text/template scripts + * associated with a handle into type/javascript, and executes them. + */ +function wp_print_delayed_inline_script_loader() { + $wp_scripts = wp_scripts(); + + if ( $wp_scripts->has_delayed_inline_script() ) { + $output = << $type_attr ); + + wp_print_inline_script_tag( $output, array_merge( $script_type_array, array( 'id' => 'wp-executes-after-js' ) ) ); + } +} + /** * Localizes the jQuery UI datepicker. * @@ -2699,7 +2731,7 @@ function enqueue_editor_block_styles_assets() { $register_script_lines[] = '} )();'; $inline_script = implode( "\n", $register_script_lines ); - wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, true ); + wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, array( 'in_footer' => true ) ); wp_add_inline_script( 'wp-block-styles', $inline_script ); wp_enqueue_script( 'wp-block-styles' ); } diff --git a/tests/phpunit/includes/utils.php b/tests/phpunit/includes/utils.php index 30af6aa348490..dc18051e62149 100644 --- a/tests/phpunit/includes/utils.php +++ b/tests/phpunit/includes/utils.php @@ -645,3 +645,47 @@ function test_rest_expand_compact_links( $links ) { } return $links; } + +/** + * Removes select handles from $wp_scripts. + */ +function unregister_all_script_handles() { + global $wp_scripts; + + /** + * Do not deregister following library through this function. + */ + $libraries = array( + 'jquery', + 'jquery-core', + 'jquery-migrate', + 'jquery-ui-core', + 'jquery-ui-accordion', + 'jquery-ui-autocomplete', + 'jquery-ui-button', + 'jquery-ui-datepicker', + 'jquery-ui-dialog', + 'jquery-ui-draggable', + 'jquery-ui-droppable', + 'jquery-ui-menu', + 'jquery-ui-mouse', + 'jquery-ui-position', + 'jquery-ui-progressbar', + 'jquery-ui-resizable', + 'jquery-ui-selectable', + 'jquery-ui-slider', + 'jquery-ui-sortable', + 'jquery-ui-spinner', + 'jquery-ui-tabs', + 'jquery-ui-tooltip', + 'jquery-ui-widget', + 'backbone', + 'underscore', + ); + + foreach ( $wp_scripts->registered as $handle_name => $handle ) { + if ( ! in_array( $handle_name, $libraries, true ) ) { + wp_deregister_script( $handle_name ); + } + } +} diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index 10e447728a53b..dbb76a7d5a80b 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -14,6 +14,9 @@ class Tests_Dependencies_Scripts extends WP_UnitTestCase { protected $wp_scripts_print_translations_output; + // Stores a string reference to a default scripts directory name, utilised by certain tests. + protected $default_scripts_dir = '/directory/'; + public function set_up() { parent::set_up(); $this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; @@ -63,6 +66,699 @@ public function test_wp_enqueue_script() { $this->assertSame( '', get_echo( 'wp_print_scripts' ) ); } + /** + * Test standalone and non standalone inline scripts in the 'after' position of a single main script. + * + * @ticket 12009 + */ + public function test_non_standalone_and_standalone_after_script_combined() { + // If a main script containing a `defer` strategy has an `after` inline script, the expected script type is type='javascript', otherwise type='text/template'. + unregister_all_script_handles(); + wp_enqueue_script( 'ms-isinsa-1', 'http://example.org/ms-isinsa-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-isinsa-1', 'console.log("after one");', 'after', true ); + wp_add_inline_script( 'ms-isinsa-1', 'console.log("after two");', 'after' ); + $output = get_echo( 'wp_print_scripts' ); + $expected = << +function wpLoadAfterScripts( handle ) { + var scripts, newScript, i, len; + scripts = document.querySelectorAll( + '[type="text/template"][data-wp-executes-after="' + handle + '"]' + ); + for ( i = 0, len = scripts.length; i < len; i++ ) { + newScript = scripts[ i ].cloneNode( true ); + newScript.type = "text/javascript"; + scripts[ i ].parentNode.replaceChild( newScript, scripts[ i ] ); + } +} + + + + + +EXP; + $this->assertSame( $expected, $output ); + } + + /** + * Test `standalone` inline scripts in the `after` position with deferred main script. + * + * If the main script with a `defer` loading strategy has an `after` inline script, + * the inline script should not be affected. + * + * @ticket 12009 + */ + public function test_standalone_after_inline_script_with_defer_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-isa-1', 'http://example.org/ms-isa-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-isa-1', 'console.log("after one");', 'after', true ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $expected .= "\n"; + $this->assertSame( $expected, $output ); + } + + /** + * Test `standalone` inline scripts in the `after` position with async main script. + * + * If the main script with async strategy has a `after` inline script, + * the inline script should not be affected. + * + * @ticket 12009 + */ + public function test_standalone_after_inline_script_with_async_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-isa-2', 'http://example.org/ms-isa-2.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-isa-2', 'console.log("after one");', 'after', true ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $expected .= "\n"; + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone inline scripts in the `after` position with deferred main script. + * + * If a main script with a `defer` loading strategy has an `after` inline script, + * the inline script should be rendered as type='text/template'. + * The common loader script should also be injected in this case. + * + * @ticket 12009 + */ + public function test_non_standalone_after_inline_script_with_defer_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-insa-1', 'http://example.org/ms-insa-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-insa-1', 'console.log("after one");', 'after' ); + $output = get_echo( 'wp_print_scripts' ); + $expected = << +function wpLoadAfterScripts( handle ) { + var scripts, newScript, i, len; + scripts = document.querySelectorAll( + '[type="text/template"][data-wp-executes-after="' + handle + '"]' + ); + for ( i = 0, len = scripts.length; i < len; i++ ) { + newScript = scripts[ i ].cloneNode( true ); + newScript.type = "text/javascript"; + scripts[ i ].parentNode.replaceChild( newScript, scripts[ i ] ); + } +} + + + + +EXP; + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone inline scripts in the `after` position with async main script. + * + * If a main script with an `async` loading strategy has an `after` inline script, + * the inline script should be rendered as type='text/template'. + * The common loader script should also be injected in this case. + * + * @ticket 12009 + */ + public function test_non_standalone_after_inline_script_with_async_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-insa-2', 'http://example.org/ms-insa-2.js', array(), null, array( 'strategy' => 'async' ) ); + wp_add_inline_script( 'ms-insa-2', 'console.log("after one");', 'after' ); + $output = get_echo( 'wp_print_scripts' ); + $expected = << +function wpLoadAfterScripts( handle ) { + var scripts, newScript, i, len; + scripts = document.querySelectorAll( + '[type="text/template"][data-wp-executes-after="' + handle + '"]' + ); + for ( i = 0, len = scripts.length; i < len; i++ ) { + newScript = scripts[ i ].cloneNode( true ); + newScript.type = "text/javascript"; + scripts[ i ].parentNode.replaceChild( newScript, scripts[ i ] ); + } +} + + + + +EXP; + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone inline scripts in the `after` position with blocking main script. + * + * If a main script with a `blocking` strategy has an `after` inline script, + * the inline script should be rendered as type='text/javascript'. + * + * @ticket 12009 + */ + public function test_non_standalone_after_inline_script_with_blocking_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-insa-3', 'http://example.org/ms-insa-3.js', array(), null, array( 'strategy' => 'blocking' ) ); + wp_add_inline_script( 'ms-insa-3', 'console.log("after one");', 'after' ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone inline scripts in the `after` position with deferred main script. + * + * If a main script with no loading strategy has an `after` inline script, + * the inline script should be rendered as type='text/javascript'. + * + * @ticket 12009 + */ + public function test_non_standalone_after_inline_script_with_main_script_with_no_strategy() { + unregister_all_script_handles(); + wp_enqueue_script( 'ms-insa-4', 'http://example.org/ms-insa-4.js', array(), null ); + wp_add_inline_script( 'ms-insa-4', 'console.log("after one");', 'after' ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone `before` inline scripts attached to deferred main scripts. + * + * If the main script has a `before` inline script, all dependencies will be blocking. + * + * @ticket 12009 + */ + public function test_non_standalone_before_inline_script_with_defer_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ds-i1-1', 'http://example.org/ds-i1-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-i1-2', 'http://example.org/ds-i1-2.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-i1-3', 'http://example.org/ds-i1-3.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ms-i1-1', 'http://example.org/ms-i1-1.js', array( 'ds-i1-1', 'ds-i1-2', 'ds-i1-3' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-i1-1', 'console.log("before one");', 'before' ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone `before` inline scripts attached to a dependency scripts in a all scripts `defer` chain. + * + * If any of the dependencies in the chain have a `before` inline script, all scripts above it should be blocking. + * + * @ticket 12009 + */ + public function test_non_standalone_before_inline_script_on_dependency_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ds-i2-1', 'http://example.org/ds-i2-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-i2-2', 'http://example.org/ds-i2-2.js', array( 'ds-i2-1' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-i2-3', 'http://example.org/ds-i2-3.js', array( 'ds-i2-2' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ms-i2-1', 'http://example.org/ms-i2-1.js', array( 'ds-i2-3' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ds-i2-2', 'console.log("before one");', 'before' ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone `before` inline scripts attached to top most dependency in a all scripts `defer` chain. + * + * If the top most dependency in the chain has a `before` inline script, + * none of the scripts bellow it will be blocking. + * + * @ticket 12009 + */ + public function test_non_standalone_before_inline_script_on_top_most_dependency_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ds-i3-1', 'http://example.org/ds-i3-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-i3-2', 'http://example.org/ds-i3-2.js', array( 'ds-i3-1' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ms-i3-1', 'http://example.org/ms-i3-1.js', array( 'ds-i3-2' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ds-i3-1', 'console.log("before one");', 'before' ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test non standalone `before` inline scripts attached to one the chain, of the two all scripts `defer` chains. + * + * If there are two dependency chains, rules are applied to the scripts in the chain that contain a `before` inline script. + * + * @ticket 12009 + */ + public function test_non_standalone_before_inline_script_on_multiple_defer_script_chain() { + unregister_all_script_handles(); + wp_enqueue_script( 'ch1-ds-i4-1', 'http://example.org/ch1-ds-i4-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ch1-ds-i4-2', 'http://example.org/ch1-ds-i4-2.js', array( 'ch1-ds-i4-1' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ch2-ds-i4-1', 'http://example.org/ch2-ds-i4-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ch2-ds-i4-2', 'http://example.org/ch2-ds-i4-2.js', array( 'ch2-ds-i4-1' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ch2-ds-i4-2', 'console.log("before one");', 'before' ); + wp_enqueue_script( 'ms-i4-1', 'http://example.org/ms-i4-1.js', array( 'ch2-ds-i4-1', 'ch2-ds-i4-2' ), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test `standalone` inline scripts in the `before` position with deferred main script. + * + * If the main script has a `before` inline script, `standalone` doesn't apply to + * any inline script associated with the main script. + * + * @ticket 12009 + */ + public function test_standalone_before_inline_script_with_defer_main_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ds-is1-1', 'http://example.org/ds-is1-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-is1-2', 'http://example.org/ds-is1-2.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-is1-3', 'http://example.org/ds-is1-3.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ms-is1-1', 'http://example.org/ms-is1-1.js', array( 'ds-is1-1', 'ds-is1-2', 'ds-is1-3' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ms-is1-1', 'console.log("before one");', 'before', true ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test `standalone` inline scripts in the `before` position with defer main script. + * + * If one of the deferred dependencies in the chain has a `before` inline `standalone` script associated with it, + * strategy of the dependencies above it remains unchanged. + * + * @ticket 12009 + */ + public function test_standalone_before_inline_script_with_defer_dependency_script() { + unregister_all_script_handles(); + wp_enqueue_script( 'ds-is2-1', 'http://example.org/ds-is2-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-is2-2', 'http://example.org/ds-is2-2.js', array( 'ds-is2-1' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ds-is2-3', 'http://example.org/ds-is2-3.js', array( 'ds-is2-2' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'ms-is2-1', 'http://example.org/ms-is2-1.js', array( 'ds-is2-3' ), null, array( 'strategy' => 'defer' ) ); + wp_add_inline_script( 'ds-is2-2', 'console.log("before one");', 'before', true ); + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test valid async loading strategy case. + * + * @ticket 12009 + */ + public function test_loading_strategy_with_valid_async_registration() { + // No dependents, No dependencies then async. + wp_enqueue_script( 'main-script-a1', '/main-script-a1.js', array(), null, array( 'strategy' => 'async' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $this->assertSame( $expected, $output ); + } + + /** + * Test invalid async loading strategy cases. + * + * @ticket 12009 + */ + public function test_loading_strategy_with_invalid_async_registration() { + // If any dependencies then it's not async. Since dependency is blocking(/defer) final strategy will be defer. + wp_enqueue_script( 'dependency-script-a2', '/dependency-script-a2.js', array(), null ); + wp_enqueue_script( 'main-script-a2', '/main-script-a2.js', array( 'dependency-script-a2' ), null, array( 'strategy' => 'async' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = ""; + $this->assertStringContainsString( $expected, $output, 'Expected defer.' ); + + // If any dependent then it's not async. Since dependent is not set to defer the final strategy will be blocking. + wp_enqueue_script( 'main-script-a3', '/main-script-a3.js', array(), null, array( 'strategy' => 'async' ) ); + wp_enqueue_script( 'dependent-script-a3', '/dependent-script-a3.js', array( 'main-script-a3' ), null ); + $output = get_echo( 'wp_print_scripts' ); + $expected = ""; + $this->assertStringContainsString( $expected, $output, 'Expected blocking.' ); + } + + /** + * Test valid defer loading strategy cases. + * + * @ticket 12009 + * @dataProvider data_loading_strategy_with_valid_defer_registration + */ + public function test_loading_strategy_with_valid_defer_registration( $expected, $output, $message ) { + $this->assertStringContainsString( $expected, $output, $message ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_loading_strategy_with_valid_defer_registration() { + $data = array(); + + // No dependents, No dependencies and defer strategy. + wp_enqueue_script( 'main-script-d1', 'http://example.com/main-script-d1.js', array(), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + array_push( $data, array( $expected, $output, 'Expected defer, as there is no dependent or dependency' ) ); + + // Main script is defer and all dependencies are either defer/blocking. + wp_enqueue_script( 'dependency-script-d2-1', 'http://example.com/dependency-script-d2-1.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependency-script-d2-2', 'http://example.com/dependency-script-d2-2.js', array(), null, array( 'strategy' => 'blocking' ) ); + wp_enqueue_script( 'dependency-script-d2-3', 'http://example.com/dependency-script-d2-3.js', array( 'dependency-script-d2-2' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'main-script-d2', 'http://example.com/main-script-d2.js', array( 'dependency-script-d2-1', 'dependency-script-d2-3' ), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + array_push( $data, array( $expected, $output, 'Expected defer, as all dependencies are either deferred or blocking' ) ); + + // Main script is defer and all dependent are defer. + wp_enqueue_script( 'main-script-d3', 'http://example.com/main-script-d3.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d3-1', 'http://example.com/dependent-script-d3-1.js', array( 'main-script-d3' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d3-2', 'http://example.com/dependent-script-d3-2.js', array( 'dependent-script-d3-1' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d3-3', 'http://example.com/dependent-script-d3-3.js', array( 'dependent-script-d3-2' ), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + array_push( $data, array( $expected, $output, 'Expected defer, as all dependents have defer loading strategy' ) ); + + return $data; + } + + /** + * Test valid defer loading with async dependent. + * + * @ticket 12009 + */ + public function test_defer_with_async_dependent() { + // case with one async dependent. + wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'async' ) ); + wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test invalid defer loading strategy case. + * + * @ticket 12009 + */ + public function test_loading_strategy_with_invalid_defer_registration() { + // Main script is defer and all dependent are not defer. Then main script will have blocking(or no) strategy. + wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'blocking' ) ); + wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $this->assertStringContainsString( $expected, $output ); + } + + /** + * Test valid blocking loading strategy cases. + * + * @ticket 12009 + */ + public function test_loading_strategy_with_valid_blocking_registration() { + wp_enqueue_script( 'main-script-b1', '/main-script-b1.js', array(), null, array( 'strategy' => 'blocking' ) ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $this->assertSame( $expected, $output ); + + // strategy args not set. + wp_enqueue_script( 'main-script-b2', '/main-script-b2.js', array(), null, array() ); + $output = get_echo( 'wp_print_scripts' ); + $expected = "\n"; + $this->assertSame( $expected, $output ); + } + + /** + * Test old and new in_footer logic. + * + * @ticket 12009 + */ + public function test_old_and_new_in_footer_scripts() { + // Scripts in head. + wp_register_script( 'header-old', '/header-old.js', array(), null, false ); + wp_register_script( 'header-new', '/header-new.js', array( 'header-old' ), null, array( 'in_footer' => false ) ); + wp_enqueue_script( 'enqueue-header-old', '/enqueue-header-old.js', array( 'header-new' ), null, false ); + wp_enqueue_script( 'enqueue-header-new', '/enqueue-header-new.js', array( 'enqueue-header-old' ), null, array( 'in_footer' => false ) ); + + // Scripts in footer. + wp_register_script( 'footer-old', '/footer-old.js', array(), null, true ); + wp_register_script( 'footer-new', '/footer-new.js', array( 'footer-old' ), null, array( 'in_footer' => true ) ); + wp_enqueue_script( 'enqueue-footer-old', '/enqueue-footer-old.js', array( 'footer-new' ), null, true ); + wp_enqueue_script( 'enqueue-footer-new', '/enqueue-footer-new.js', array( 'enqueue-footer-old' ), null, array( 'in_footer' => true ) ); + + $header = get_echo( 'wp_print_head_scripts' ); + $footer = get_echo( 'wp_print_scripts' ); + + $expected_header = "\n"; + $expected_header .= "\n"; + $expected_header .= "\n"; + $expected_header .= "\n"; + + $expected_footer = "\n"; + $expected_footer .= "\n"; + $expected_footer .= "\n"; + $expected_footer .= "\n"; + + $this->assertSame( $expected_header, $header ); + $this->assertSame( $expected_footer, $footer ); + } + + /** + * Test normalized script args. + * + * @ticket 12009 + */ + public function test_get_normalized_script_args() { + global $wp_scripts; + $args = array( + 'in_footer' => true, + 'strategy' => 'async', + ); + wp_enqueue_script( 'footer-async', '/footer-async.js', array(), null, $args ); + $this->assertSame( $args, $wp_scripts->get_data( 'footer-async', 'script_args' ) ); + + // Test defaults. + $expected_args = array( + 'in_footer' => true, + 'strategy' => 'blocking', + ); + wp_register_script( 'defaults-strategy', '/defaults.js', array(), null, array( 'in_footer' => true ) ); + $this->assertSame( $expected_args, $wp_scripts->get_data( 'defaults-strategy', 'script_args' ) ); + + $expected_args = array( + 'in_footer' => false, + 'strategy' => 'async', + ); + wp_register_script( 'defaults-in-footer', '/defaults.js', array(), null, array( 'strategy' => 'async' ) ); + $this->assertSame( $expected_args, $wp_scripts->get_data( 'defaults-in-footer', 'script_args' ) ); + + // scripts_args not set of args parameter is empty. + wp_register_script( 'empty-args-array', '/defaults.js', array(), null, array() ); + $this->assertSame( false, $wp_scripts->get_data( 'defaults', 'script_args' ) ); + + wp_register_script( 'no-args', '/defaults.js', array(), null ); + $this->assertSame( false, $wp_scripts->get_data( 'defaults-no-args', 'script_args' ) ); + + // Test backward compatibility. + $expected_args = array( + 'in_footer' => true, + 'strategy' => 'blocking', + ); + wp_enqueue_script( 'footer-old', '/footer-async.js', array(), null, true ); + $this->assertSame( $expected_args, $wp_scripts->get_data( 'footer-old', 'script_args' ) ); + } + + /** + * Test script strategy doing it wrong. + * + * For an invalid strategy defined during script registration, default to a blocking strategy. + * + * @ticket 12009 + */ + public function test_script_strategy_doing_it_wrong() { + $this->setExpectedIncorrectUsage( 'WP_Scripts::get_intended_strategy' ); + + wp_register_script( 'invalid-strategy', '/defaults.js', array(), null, array( 'strategy' => 'random-strategy' ) ); + wp_enqueue_script( 'invalid-strategy' ); + + $output = get_echo( 'wp_print_scripts' ); + + $expected = "\n"; + + $this->assertSame( $expected, $output ); + } + + /** + * Test script concatenation with deferred main script. + * + * @ticket 12009 + */ + public function test_concatenate_with_defer_strategy() { + global $wp_scripts, $concatenate_scripts; + + $old_value = $concatenate_scripts; + $concatenate_scripts = true; + + $wp_scripts->do_concat = true; + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); + + wp_register_script( 'one-concat-dep', $this->default_scripts_dir . 'script.js' ); + wp_register_script( 'two-concat-dep', $this->default_scripts_dir . 'script.js' ); + wp_register_script( 'three-concat-dep', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'main-defer-script', '/main-script.js', array( 'one-concat-dep', 'two-concat-dep', 'three-concat-dep' ), null, array( 'strategy' => 'defer' ) ); + + wp_print_scripts(); + $print_scripts = get_echo( '_print_scripts' ); + + // reset global before asserting. + $concatenate_scripts = $old_value; + + $ver = get_bloginfo( 'version' ); + $expected = "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $print_scripts ); + } + + /** + * Test script concatenation with `async` main script. + * + * @ticket 12009 + */ + public function test_concatenate_with_async_strategy() { + global $wp_scripts, $concatenate_scripts; + + $old_value = $concatenate_scripts; + $concatenate_scripts = true; + + $wp_scripts->do_concat = true; + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); + + wp_enqueue_script( 'one-concat-dep-1', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'two-concat-dep-1', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'three-concat-dep-1', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'main-async-script-1', '/main-script.js', array(), null, array( 'strategy' => 'async' ) ); + + wp_print_scripts(); + $print_scripts = get_echo( '_print_scripts' ); + + // reset global before asserting. + $concatenate_scripts = $old_value; + + $ver = get_bloginfo( 'version' ); + $expected = "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $print_scripts ); + } + + /** + * Test script concatenation with blocking scripts before and after a `defer` script. + * + * @ticket 12009 + */ + public function test_concatenate_with_blocking_script_before_and_after_script_with_defer_strategy() { + global $wp_scripts, $concatenate_scripts; + + $old_value = $concatenate_scripts; + $concatenate_scripts = true; + + $wp_scripts->do_concat = true; + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); + + wp_enqueue_script( 'one-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'two-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'three-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'deferred-script-2', '/main-script.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'four-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'five-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'six-concat-dep-2', $this->default_scripts_dir . 'script.js' ); + + wp_print_scripts(); + $print_scripts = get_echo( '_print_scripts' ); + + // reset global before asserting. + $concatenate_scripts = $old_value; + + $ver = get_bloginfo( 'version' ); + $expected = "\n"; + $expected .= "\n"; + + $this->assertSame( $expected, $print_scripts ); + } + /** * @ticket 42804 */ @@ -132,11 +828,11 @@ public function test_script_concatenation() { global $wp_scripts; $wp_scripts->do_concat = true; - $wp_scripts->default_dirs = array( '/directory/' ); + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); - wp_enqueue_script( 'one', '/directory/script.js' ); - wp_enqueue_script( 'two', '/directory/script.js' ); - wp_enqueue_script( 'three', '/directory/script.js' ); + wp_enqueue_script( 'one', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'two', $this->default_scripts_dir . 'script.js' ); + wp_enqueue_script( 'three', $this->default_scripts_dir . 'script.js' ); wp_print_scripts(); $print_scripts = get_echo( '_print_scripts' ); @@ -529,21 +1225,21 @@ public function test_wp_add_inline_script_before_with_concat() { global $wp_scripts; $wp_scripts->do_concat = true; - $wp_scripts->default_dirs = array( '/directory/' ); + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); - wp_enqueue_script( 'one', '/directory/one.js' ); - wp_enqueue_script( 'two', '/directory/two.js' ); - wp_enqueue_script( 'three', '/directory/three.js' ); + wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' ); + wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' ); + wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' ); wp_add_inline_script( 'one', 'console.log("before one");', 'before' ); wp_add_inline_script( 'two', 'console.log("before two");', 'before' ); $ver = get_bloginfo( 'version' ); $expected = "\n"; - $expected .= "\n"; + $expected .= "\n"; $expected .= "\n"; - $expected .= "\n"; - $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) ); } @@ -555,19 +1251,19 @@ public function test_wp_add_inline_script_before_with_concat2() { global $wp_scripts; $wp_scripts->do_concat = true; - $wp_scripts->default_dirs = array( '/directory/' ); + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); - wp_enqueue_script( 'one', '/directory/one.js' ); - wp_enqueue_script( 'two', '/directory/two.js' ); - wp_enqueue_script( 'three', '/directory/three.js' ); + wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' ); + wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' ); + wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' ); wp_add_inline_script( 'one', 'console.log("before one");', 'before' ); $ver = get_bloginfo( 'version' ); $expected = "\n"; - $expected .= "\n"; - $expected .= "\n"; - $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; + $expected .= "\n"; $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) ); } @@ -579,23 +1275,23 @@ public function test_wp_add_inline_script_after_with_concat() { global $wp_scripts; $wp_scripts->do_concat = true; - $wp_scripts->default_dirs = array( '/directory/' ); + $wp_scripts->default_dirs = array( $this->default_scripts_dir ); - wp_enqueue_script( 'one', '/directory/one.js' ); - wp_enqueue_script( 'two', '/directory/two.js' ); - wp_enqueue_script( 'three', '/directory/three.js' ); - wp_enqueue_script( 'four', '/directory/four.js' ); + wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' ); + wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' ); + wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' ); + wp_enqueue_script( 'four', $this->default_scripts_dir . 'four.js' ); wp_add_inline_script( 'two', 'console.log("after two");' ); wp_add_inline_script( 'three', 'console.log("after three");' ); $ver = get_bloginfo( 'version' ); $expected = "\n"; - $expected .= "\n"; + $expected .= "\n"; $expected .= "\n"; - $expected .= "\n"; + $expected .= "\n"; $expected .= "\n"; - $expected .= "\n"; + $expected .= "\n"; $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) ); }