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", $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", $this->type_attr, esc_attr( $handle ), $after_handle );
+ $after_handle = sprintf(
+ "\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", $this->type_attr, esc_attr( $handle ), esc_attr( $position ), $output );
+ if ( 'after-non-standalone' === $position ) {
+ $script_output = "\n";
+ } else {
+ $script_output = "\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' ) );
}