Make WordPress Core

Changeset 56033


Ignore:
Timestamp:
06/26/2023 01:40:31 PM (11 months ago)
Author:
joemcgill
Message:

Script Loader: Add support for HTML 5 "async" and "defer" attributes.

This allows developers to register scripts with an intended loading strategy by changing the $in_footer parameter of wp_register_script and wp_enqueue_script to an array that accepts both an in_footer and strategy argument. If present, the loading strategy attribute will be added to the script tag when that script is printed to the page as long as it is not a dependency of any blocking scripts, including any inline scripts attached to the script or any of its dependents.

Props 10upsimon, thekt12, westonruter, costdev, flixos90, spacedmonkey, adamsilverstein, azaozz, mukeshpanchal27, mor10, scep, wpnook, vanaf1979, Otto42.
Fixes #12009.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/.jshintrc

    r46587 r56033  
    2020        "_": false,
    2121        "Backbone": false,
     22        "console": false,
    2223        "jQuery": false,
    2324        "JSON": false,
     
    2526        "export": false,
    2627        "module": false,
    27         "require": false
     28        "require": false,
     29        "Set": false
    2830    }
    2931}
  • trunk/phpcs.xml.dist

    r54703 r56033  
    6969                <!-- From DOMDocument. -->
    7070                <element value="childNodes"/>
     71                <element value="firstChild"/>
    7172                <element value="formatOutput"/>
     73                <element value="lastChild"/>
    7274                <element value="nodeName"/>
    7375                <element value="nodeType"/>
  • trunk/src/wp-includes/class-wp-scripts.php

    r55703 r56033  
    135135
    136136    /**
     137     * Holds a mapping of dependents (as handles) for a given script handle.
     138     * Used to optimize recursive dependency tree checks.
     139     *
     140     * @since 6.3.0
     141     * @var array
     142     */
     143    private $dependents_map = array();
     144
     145    /**
     146     * Holds a reference to the delayed (non-blocking) script loading strategies.
     147     * Used by methods that validate loading strategies.
     148     *
     149     * @since 6.3.0
     150     * @var string[]
     151     */
     152    private $delayed_strategies = array( 'defer', 'async' );
     153
     154    /**
    137155     * Constructor.
    138156     *
     
    285303        }
    286304
    287         $src         = $obj->src;
    288         $cond_before = '';
    289         $cond_after  = '';
    290         $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : '';
     305        $src               = $obj->src;
     306        $strategy          = $this->get_eligible_loading_strategy( $handle );
     307        $intended_strategy = (string) $this->get_data( $handle, 'strategy' );
     308        $cond_before       = '';
     309        $cond_after        = '';
     310        $conditional       = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : '';
     311
     312        if ( ! $this->is_delayed_strategy( $intended_strategy ) ) {
     313            $intended_strategy = '';
     314        }
    291315
    292316        if ( $conditional ) {
     
    295319        }
    296320
    297         $before_handle = $this->print_inline_script( $handle, 'before', false );
    298         $after_handle  = $this->print_inline_script( $handle, 'after', false );
    299 
    300         if ( $before_handle ) {
    301             $before_handle = sprintf( "<script%s id='%s-js-before'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $before_handle );
    302         }
    303 
    304         if ( $after_handle ) {
    305             $after_handle = sprintf( "<script%s id='%s-js-after'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $after_handle );
    306         }
    307 
    308         if ( $before_handle || $after_handle ) {
    309             $inline_script_tag = $cond_before . $before_handle . $after_handle . $cond_after;
     321        $before_script = $this->get_inline_script_tag( $handle, 'before' );
     322        $after_script  = $this->get_inline_script_tag( $handle, 'after' );
     323
     324        if ( $before_script || $after_script ) {
     325            $inline_script_tag = $cond_before . $before_script . $after_script . $cond_after;
    310326        } else {
    311327            $inline_script_tag = '';
     
    334350            $srce = apply_filters( 'script_loader_src', $src, $handle );
    335351
    336             if ( $this->in_default_dir( $srce ) && ( $before_handle || $after_handle || $translations_stop_concat ) ) {
     352            if (
     353                $this->in_default_dir( $srce )
     354                && ( $before_script || $after_script || $translations_stop_concat || $this->is_delayed_strategy( $strategy ) )
     355            ) {
    337356                $this->do_concat = false;
    338357
     
    391410        }
    392411
    393         $tag  = $translations . $cond_before . $before_handle;
    394         $tag .= sprintf( "<script%s src='%s' id='%s-js'></script>\n", $this->type_attr, $src, esc_attr( $handle ) );
    395         $tag .= $after_handle . $cond_after;
     412        $tag  = $translations . $cond_before . $before_script;
     413        $tag .= sprintf(
     414            "<script%s src='%s' id='%s-js'%s%s></script>\n",
     415            $this->type_attr,
     416            $src, // Value is escaped above.
     417            esc_attr( $handle ),
     418            $strategy ? " {$strategy}" : '',
     419            $intended_strategy ? " data-wp-strategy='{$intended_strategy}'" : ''
     420        );
     421        $tag .= $after_script . $cond_after;
    396422
    397423        /**
     
    446472     *
    447473     * @since 4.5.0
    448      *
    449      * @param string $handle   Name of the script to add the inline script to.
     474     * @deprecated 6.3.0 Use methods get_inline_script_tag() or get_inline_script_data() instead.
     475     *
     476     * @param string $handle   Name of the script to print inline scripts for.
    450477     *                         Must be lowercase.
    451478     * @param string $position Optional. Whether to add the inline script
    452479     *                         before the handle or after. Default 'after'.
    453      * @param bool   $display  Optional. Whether to print the script
    454      *                         instead of just returning it. Default true.
    455      * @return string|false Script on success, false otherwise.
     480     * @param bool   $display  Optional. Whether to print the script tag
     481     *                         instead of just returning the script data. Default true.
     482     * @return string|false Script data on success, false otherwise.
    456483     */
    457484    public function print_inline_script( $handle, $position = 'after', $display = true ) {
    458         $output = $this->get_data( $handle, $position );
    459 
     485        _deprecated_function( __METHOD__, '6.3.0', 'WP_Scripts::get_inline_script_data() or WP_Scripts::get_inline_script_tag()' );
     486
     487        $output = $this->get_inline_script_data( $handle, $position );
    460488        if ( empty( $output ) ) {
    461489            return false;
    462490        }
    463491
    464         $output = trim( implode( "\n", $output ), "\n" );
    465 
    466492        if ( $display ) {
    467             printf( "<script%s id='%s-js-%s'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), esc_attr( $position ), $output );
    468         }
    469 
     493            echo $this->get_inline_script_tag( $handle, $position );
     494        }
    470495        return $output;
     496    }
     497
     498    /**
     499     * Gets data for inline scripts registered for a specific handle.
     500     *
     501     * @since 6.3.0
     502     *
     503     * @param string $handle   Name of the script to get data for.
     504     *                         Must be lowercase.
     505     * @param string $position Optional. Whether to add the inline script
     506     *                         before the handle or after. Default 'after'.
     507     * @return string Inline script, which may be empty string.
     508     */
     509    public function get_inline_script_data( $handle, $position = 'after' ) {
     510        $data = $this->get_data( $handle, $position );
     511        if ( empty( $data ) || ! is_array( $data ) ) {
     512            return '';
     513        }
     514
     515        return trim( implode( "\n", $data ), "\n" );
     516    }
     517
     518    /**
     519     * Gets unaliased dependencies.
     520     *
     521     * An alias is a dependency whose src is false. It is used as a way to bundle multiple dependencies in a single
     522     * handle. This in effect flattens an alias dependency tree.
     523     *
     524     * @since 6.3.0
     525     *
     526     * @param string[] $deps Dependency handles.
     527     * @return string[] Unaliased handles.
     528     */
     529    private function get_unaliased_deps( array $deps ) {
     530        $flattened = array();
     531        foreach ( $deps as $dep ) {
     532            if ( ! isset( $this->registered[ $dep ] ) ) {
     533                continue;
     534            }
     535
     536            if ( $this->registered[ $dep ]->src ) {
     537                $flattened[] = $dep;
     538            } elseif ( $this->registered[ $dep ]->deps ) {
     539                array_push( $flattened, ...$this->get_unaliased_deps( $this->registered[ $dep ]->deps ) );
     540            }
     541        }
     542        return $flattened;
     543    }
     544
     545    /**
     546     * Gets tags for inline scripts registered for a specific handle.
     547     *
     548     * @since 6.3.0
     549     *
     550     * @param string $handle   Name of the script to get associated inline script tag for.
     551     *                         Must be lowercase.
     552     * @param string $position Optional. Whether to get tag for inline
     553     *                         scripts in the before or after position. Default 'after'.
     554     * @return string Inline script, which may be empty string.
     555     */
     556    public function get_inline_script_tag( $handle, $position = 'after' ) {
     557        $js = $this->get_inline_script_data( $handle, $position );
     558        if ( empty( $js ) ) {
     559            return '';
     560        }
     561
     562        $id = "{$handle}-js-{$position}";
     563
     564        return wp_get_inline_script_tag( $js, compact( 'id' ) );
    471565    }
    472566
     
    716810
    717811    /**
     812     * This overrides the add_data method from WP_Dependencies, to support normalizing of $args.
     813     *
     814     * @since 6.3.0
     815     *
     816     * @param string $handle Name of the item. Should be unique.
     817     * @param string $key    The data key.
     818     * @param mixed  $value  The data value.
     819     * @return bool True on success, false on failure.
     820     */
     821    public function add_data( $handle, $key, $value ) {
     822        if ( ! isset( $this->registered[ $handle ] ) ) {
     823            return false;
     824        }
     825
     826        if ( 'strategy' === $key ) {
     827            if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) {
     828                _doing_it_wrong(
     829                    __METHOD__,
     830                    sprintf(
     831                        /* translators: 1: $strategy, 2: $handle */
     832                        __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
     833                        $value,
     834                        $handle
     835                    ),
     836                    '6.3.0'
     837                );
     838                return false;
     839            } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) {
     840                _doing_it_wrong(
     841                    __METHOD__,
     842                    sprintf(
     843                        /* translators: 1: $strategy, 2: $handle */
     844                        __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
     845                        $value,
     846                        $handle
     847                    ),
     848                    '6.3.0'
     849                );
     850                return false;
     851            }
     852        }
     853        return parent::add_data( $handle, $key, $value );
     854    }
     855
     856    /**
     857     * Gets all dependents of a script.
     858     *
     859     * @since 6.3.0
     860     *
     861     * @param string $handle The script handle.
     862     * @return string[] Script handles.
     863     */
     864    private function get_dependents( $handle ) {
     865        // Check if dependents map for the handle in question is present. If so, use it.
     866        if ( isset( $this->dependents_map[ $handle ] ) ) {
     867            return $this->dependents_map[ $handle ];
     868        }
     869
     870        $dependents = array();
     871
     872        // Iterate over all registered scripts, finding dependents of the script passed to this method.
     873        foreach ( $this->registered as $registered_handle => $args ) {
     874            if ( in_array( $handle, $args->deps, true ) ) {
     875                $dependents[] = $registered_handle;
     876            }
     877        }
     878
     879        // Add the handles dependents to the map to ease future lookups.
     880        $this->dependents_map[ $handle ] = $dependents;
     881
     882        return $dependents;
     883    }
     884
     885    /**
     886     * Checks if the strategy passed is a valid delayed (non-blocking) strategy.
     887     *
     888     * @since 6.3.0
     889     *
     890     * @param string $strategy The strategy to check.
     891     * @return bool True if $strategy is one of the delayed strategies, otherwise false.
     892     */
     893    private function is_delayed_strategy( $strategy ) {
     894        return in_array(
     895            $strategy,
     896            $this->delayed_strategies,
     897            true
     898        );
     899    }
     900
     901    /**
     902     * Gets the best eligible loading strategy for a script.
     903     *
     904     * @since 6.3.0
     905     *
     906     * @param string $handle The script handle.
     907     * @return string The best eligible loading strategy.
     908     */
     909    private function get_eligible_loading_strategy( $handle ) {
     910        $eligible = $this->filter_eligible_strategies( $handle );
     911
     912        // Bail early once we know the eligible strategy is blocking.
     913        if ( empty( $eligible ) ) {
     914            return '';
     915        }
     916
     917        return in_array( 'async', $eligible, true ) ? 'async' : 'defer';
     918    }
     919
     920    /**
     921     * Filter the list of eligible loading strategies for a script.
     922     *
     923     * @since 6.3.0
     924     *
     925     * @param string              $handle   The script handle.
     926     * @param string[]|null       $eligible Optional. The list of strategies to filter. Default null.
     927     * @param array<string, true> $checked  Optional. An array of already checked script handles, used to avoid recursive loops.
     928     * @return string[] A list of eligible loading strategies that could be used.
     929     */
     930    private function filter_eligible_strategies( $handle, $eligible = null, $checked = array() ) {
     931        // If no strategies are being passed, all strategies are eligible.
     932        if ( null === $eligible ) {
     933            $eligible = $this->delayed_strategies;
     934        }
     935
     936        // If this handle was already checked, return early.
     937        if ( isset( $checked[ $handle ] ) ) {
     938            return $eligible;
     939        }
     940
     941        // Mark this handle as checked.
     942        $checked[ $handle ] = true;
     943
     944        // If this handle isn't registered, don't filter anything and return.
     945        if ( ! isset( $this->registered[ $handle ] ) ) {
     946            return $eligible;
     947        }
     948
     949        // If the handle is not enqueued, don't filter anything and return.
     950        if ( ! $this->query( $handle, 'enqueued' ) ) {
     951            return $eligible;
     952        }
     953
     954        $is_alias = (bool) ! $this->registered[ $handle ]->src;
     955        $intended_strategy = $this->get_data( $handle, 'strategy' );
     956
     957        // For non-alias handles, an empty intended strategy filters all strategies.
     958        if ( ! $is_alias && empty( $intended_strategy ) ) {
     959            return array();
     960        }
     961
     962        // Handles with inline scripts attached in the 'after' position cannot be delayed.
     963        if ( $this->has_inline_script( $handle, 'after' ) ) {
     964            return array();
     965        }
     966
     967        // If the intended strategy is 'defer', filter out 'async'.
     968        if ( 'defer' === $intended_strategy ) {
     969            $eligible = array( 'defer' );
     970        }
     971
     972        $dependents = $this->get_dependents( $handle );
     973
     974        // Recursively filter eligible strategies for dependents.
     975        foreach ( $dependents as $dependent ) {
     976            // Bail early once we know the eligible strategy is blocking.
     977            if ( empty( $eligible ) ) {
     978                return array();
     979            }
     980
     981            $eligible = $this->filter_eligible_strategies( $dependent, $eligible, $checked );
     982        }
     983
     984        return $eligible;
     985    }
     986
     987    /**
     988     * Gets data for inline scripts registered for a specific handle.
     989     *
     990     * @since 6.3.0
     991     *
     992     * @param string $handle   Name of the script to get data for. Must be lowercase.
     993     * @param string $position The position of the inline script.
     994     * @return bool Whether the handle has an inline script (either before or after).
     995     */
     996    private function has_inline_script( $handle, $position = null ) {
     997        if ( $position && in_array( $position, array( 'before', 'after' ), true ) ) {
     998            return (bool) $this->get_data( $handle, $position );
     999        }
     1000
     1001        return (bool) ( $this->get_data( $handle, 'before' ) || $this->get_data( $handle, 'after' ) );
     1002    }
     1003
     1004    /**
    7181005     * Resets class properties.
    7191006     *
  • trunk/src/wp-includes/functions.wp-scripts.php

    r55732 r56033  
    158158 * @since 2.1.0
    159159 * @since 4.3.0 A return value was added.
     160 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
    160161 *
    161162 * @param string           $handle    Name of the script. Should be unique.
     
    167168 *                                    number is automatically added equal to current installed WordPress version.
    168169 *                                    If set to null, no version is added.
    169  * @param bool             $in_footer Optional. Whether to enqueue the script before `</body>` instead of in the `<head>`.
    170  *                                    Default 'false'.
     170 * @param array|bool       $args     {
     171 *      Optional. An array of additional script loading strategies. Default empty array.
     172 *      Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
     173 *
     174 *      @type string    $strategy     Optional. If provided, may be either 'defer' or 'async'.
     175 *      @type bool      $in_footer    Optional. Whether to print the script in the footer. Default 'false'.
     176 * }
    171177 * @return bool Whether the script has been registered. True on success, false on failure.
    172178 */
    173 function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
     179function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args = array() ) {
     180    if ( ! is_array( $args ) ) {
     181        $args = array(
     182            'in_footer' => (bool) $args,
     183        );
     184    }
    174185    _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle );
    175186
     
    177188
    178189    $registered = $wp_scripts->add( $handle, $src, $deps, $ver );
    179     if ( $in_footer ) {
     190    if ( ! empty( $args['in_footer'] ) ) {
    180191        $wp_scripts->add_data( $handle, 'group', 1 );
    181192    }
    182 
     193    if ( ! empty( $args['strategy'] ) ) {
     194        $wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
     195    }
    183196    return $registered;
    184197}
     
    332345 *
    333346 * @since 2.1.0
     347 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
    334348 *
    335349 * @param string           $handle    Name of the script. Should be unique.
     
    341355 *                                    number is automatically added equal to current installed WordPress version.
    342356 *                                    If set to null, no version is added.
    343  * @param bool             $in_footer Optional. Whether to enqueue the script before `</body>` instead of in the `<head>`.
    344  *                                    Default 'false'.
    345  */
    346 function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $in_footer = false ) {
     357 * @param array|bool       $args     {
     358 *      Optional. An array of additional script loading strategies. Default empty array.
     359 *      Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
     360 *
     361 *      @type string    $strategy     Optional. If provided, may be either 'defer' or 'async'.
     362 *      @type bool      $in_footer    Optional. Whether to print the script in the footer. Default 'false'.
     363 * }
     364 */
     365function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) {
    347366    _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle );
    348367
    349368    $wp_scripts = wp_scripts();
    350369
    351     if ( $src || $in_footer ) {
     370    if ( $src || ! empty( $args ) ) {
    352371        $_handle = explode( '?', $handle );
     372        if ( ! is_array( $args ) ) {
     373            $args = array(
     374                'in_footer' => (bool) $args,
     375            );
     376        }
    353377
    354378        if ( $src ) {
    355379            $wp_scripts->add( $_handle[0], $src, $deps, $ver );
    356380        }
    357 
    358         if ( $in_footer ) {
     381        if ( ! empty( $args['in_footer'] ) ) {
    359382            $wp_scripts->add_data( $_handle[0], 'group', 1 );
     383        }
     384        if ( ! empty( $args['strategy'] ) ) {
     385            $wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] );
    360386        }
    361387    }
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r55822 r56033  
    1111 */
    1212class Tests_Dependencies_Scripts extends WP_UnitTestCase {
     13
     14    /**
     15     * @var WP_Scripts
     16     */
    1317    protected $old_wp_scripts;
    1418
     19    /**
     20     * @var WP_Styles
     21     */
     22    protected $old_wp_styles;
     23
    1524    protected $wp_scripts_print_translations_output;
     25
     26    /**
     27     * Stores a string reference to a default scripts directory name, utilised by certain tests.
     28     *
     29     * @var string
     30     */
     31    protected $default_scripts_dir = '/directory/';
    1632
    1733    public function set_up() {
    1834        parent::set_up();
    1935        $this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null;
     36        $this->old_wp_styles  = isset( $GLOBALS['wp_styles'] ) ? $GLOBALS['wp_styles'] : null;
    2037        remove_action( 'wp_default_scripts', 'wp_default_scripts' );
    2138        remove_action( 'wp_default_scripts', 'wp_default_packages' );
    2239        $GLOBALS['wp_scripts']                  = new WP_Scripts();
    2340        $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' );
     41        $GLOBALS['wp_styles']                   = new WP_Styles();
    2442
    2543        $this->wp_scripts_print_translations_output  = <<<JS
     
    3755    public function tear_down() {
    3856        $GLOBALS['wp_scripts'] = $this->old_wp_scripts;
     57        $GLOBALS['wp_styles']  = $this->old_wp_styles;
    3958        add_action( 'wp_default_scripts', 'wp_default_scripts' );
    4059        parent::tear_down();
     
    4766     */
    4867    public function test_wp_enqueue_script() {
     68        global $wp_version;
     69
    4970        wp_enqueue_script( 'no-deps-no-version', 'example.com', array() );
    5071        wp_enqueue_script( 'empty-deps-no-version', 'example.com' );
     
    5273        wp_enqueue_script( 'empty-deps-null-version', 'example.com', array(), null );
    5374
    54         $ver       = get_bloginfo( 'version' );
    55         $expected  = "<script type='text/javascript' src='http://example.com?ver=$ver' id='no-deps-no-version-js'></script>\n";
    56         $expected .= "<script type='text/javascript' src='http://example.com?ver=$ver' id='empty-deps-no-version-js'></script>\n";
     75        $expected  = "<script type='text/javascript' src='http://example.com?ver={$wp_version}' id='no-deps-no-version-js'></script>\n";
     76        $expected .= "<script type='text/javascript' src='http://example.com?ver={$wp_version}' id='empty-deps-no-version-js'></script>\n";
    5777        $expected .= "<script type='text/javascript' src='http://example.com?ver=1.2' id='empty-deps-version-js'></script>\n";
    5878        $expected .= "<script type='text/javascript' src='http://example.com' id='empty-deps-null-version-js'></script>\n";
     
    6585
    6686    /**
     87     * Gets delayed strategies as a data provider.
     88     *
     89     * @return array[] Delayed strategies.
     90     */
     91    public function data_provider_delayed_strategies() {
     92        return array(
     93            'defer' => array( 'defer' ),
     94            'async' => array( 'async' ),
     95        );
     96    }
     97
     98    /**
     99     * Tests that inline scripts in the `after` position, attached to delayed main scripts, remain unaffected.
     100     *
     101     * If the main script with delayed loading strategy has an `after` inline script,
     102     * the inline script should not be affected.
     103     *
     104     * @ticket 12009
     105     *
     106     * @covers WP_Scripts::do_item
     107     * @covers WP_Scripts::get_inline_script_tag
     108     * @covers ::wp_add_inline_script
     109     * @covers ::wp_enqueue_script
     110     *
     111     * @dataProvider data_provider_delayed_strategies
     112     *
     113     * @param string $strategy Strategy.
     114     */
     115    public function test_after_inline_script_with_delayed_main_script( $strategy ) {
     116        wp_enqueue_script( 'ms-isa-1', 'http://example.org/ms-isa-1.js', array(), null, compact( 'strategy' ) );
     117        wp_add_inline_script( 'ms-isa-1', 'console.log("after one");', 'after' );
     118        $output    = get_echo( 'wp_print_scripts' );
     119        $expected  = "<script type='text/javascript' src='http://example.org/ms-isa-1.js' id='ms-isa-1-js' data-wp-strategy='{$strategy}'></script>\n";
     120        $expected .= wp_get_inline_script_tag(
     121            "console.log(\"after one\");\n",
     122            array(
     123                'id' => 'ms-isa-1-js-after',
     124            )
     125        );
     126        $this->assertSame( $expected, $output, 'Inline scripts in the "after" position, that are attached to a deferred main script, are failing to print/execute.' );
     127    }
     128
     129    /**
     130     * Tests that inline scripts in the `after` position, attached to a blocking main script, are rendered as javascript.
     131     *
     132     * If a main script with a `blocking` strategy has an `after` inline script,
     133     * the inline script should be rendered as type='text/javascript'.
     134     *
     135     * @ticket 12009
     136     *
     137     * @covers WP_Scripts::do_item
     138     * @covers WP_Scripts::get_inline_script_tag
     139     * @covers ::wp_add_inline_script
     140     * @covers ::wp_enqueue_script
     141     */
     142    public function test_after_inline_script_with_blocking_main_script() {
     143        wp_enqueue_script( 'ms-insa-3', 'http://example.org/ms-insa-3.js', array(), null );
     144        wp_add_inline_script( 'ms-insa-3', 'console.log("after one");', 'after' );
     145        $output = get_echo( 'wp_print_scripts' );
     146
     147        $expected  = "<script type='text/javascript' src='http://example.org/ms-insa-3.js' id='ms-insa-3-js'></script>\n";
     148        $expected .= wp_get_inline_script_tag(
     149            "console.log(\"after one\");\n",
     150            array(
     151                'id' => 'ms-insa-3-js-after',
     152            )
     153        );
     154
     155        $this->assertSame( $expected, $output, 'Inline scripts in the "after" position, that are attached to a blocking main script, are failing to print/execute.' );
     156    }
     157
     158    /**
     159     * Tests that inline scripts in the `before` position, attached to a delayed inline main script, results in all
     160     * dependents being delayed.
     161     *
     162     * @ticket 12009
     163     *
     164     * @covers WP_Scripts::do_item
     165     * @covers WP_Scripts::get_inline_script_tag
     166     * @covers ::wp_add_inline_script
     167     * @covers ::wp_enqueue_script
     168     *
     169     * @dataProvider data_provider_delayed_strategies
     170     *
     171     * @param string $strategy
     172     */
     173    public function test_before_inline_scripts_with_delayed_main_script( $strategy ) {
     174        wp_enqueue_script( 'ds-i1-1', 'http://example.org/ds-i1-1.js', array(), null, compact( 'strategy' ) );
     175        wp_add_inline_script( 'ds-i1-1', 'console.log("before first");', 'before' );
     176        wp_enqueue_script( 'ds-i1-2', 'http://example.org/ds-i1-2.js', array(), null, compact( 'strategy' ) );
     177        wp_enqueue_script( 'ds-i1-3', 'http://example.org/ds-i1-3.js', array(), null, compact( 'strategy' ) );
     178        wp_enqueue_script( 'ms-i1-1', 'http://example.org/ms-i1-1.js', array( 'ds-i1-1', 'ds-i1-2', 'ds-i1-3' ), null, compact( 'strategy' ) );
     179        wp_add_inline_script( 'ms-i1-1', 'console.log("before last");', 'before' );
     180        $output = get_echo( 'wp_print_scripts' );
     181
     182        $expected  = wp_get_inline_script_tag(
     183            "console.log(\"before first\");\n",
     184            array(
     185                'id' => 'ds-i1-1-js-before',
     186            )
     187        );
     188        $expected .= "<script type='text/javascript' src='http://example.org/ds-i1-1.js' id='ds-i1-1-js' $strategy data-wp-strategy='{$strategy}'></script>\n";
     189        $expected .= "<script type='text/javascript' src='http://example.org/ds-i1-2.js' id='ds-i1-2-js' $strategy data-wp-strategy='{$strategy}'></script>\n";
     190        $expected .= "<script type='text/javascript' src='http://example.org/ds-i1-3.js' id='ds-i1-3-js' $strategy data-wp-strategy='{$strategy}'></script>\n";
     191        $expected .= wp_get_inline_script_tag(
     192            "console.log(\"before last\");\n",
     193            array(
     194                'id'   => 'ms-i1-1-js-before',
     195                'type' => 'text/javascript',
     196            )
     197        );
     198        $expected .= "<script type='text/javascript' src='http://example.org/ms-i1-1.js' id='ms-i1-1-js' {$strategy} data-wp-strategy='{$strategy}'></script>\n";
     199
     200        $this->assertSame( $expected, $output, 'Inline scripts in the "before" position, that are attached to a deferred main script, are failing to print/execute.' );
     201    }
     202
     203    /**
     204     * Tests that scripts registered with an async strategy print with the async attribute.
     205     *
     206     * @ticket 12009
     207     *
     208     * @covers WP_Scripts::do_item
     209     * @covers WP_Scripts::get_eligible_loading_strategy
     210     * @covers WP_Scripts::filter_eligible_strategies
     211     * @covers ::wp_enqueue_script
     212     */
     213    public function test_loading_strategy_with_valid_async_registration() {
     214        // No dependents, No dependencies then async.
     215        wp_enqueue_script( 'main-script-a1', '/main-script-a1.js', array(), null, array( 'strategy' => 'async' ) );
     216        $output   = get_echo( 'wp_print_scripts' );
     217        $expected = "<script type='text/javascript' src='/main-script-a1.js' id='main-script-a1-js' async data-wp-strategy='async'></script>\n";
     218        $this->assertSame( $expected, $output, 'Scripts enqueued with an async loading strategy are failing to have the async attribute applied to the script handle when being printed.' );
     219    }
     220
     221    /**
     222     * Tests that dependents of a blocking dependency script are free to contain any strategy.
     223     *
     224     * @ticket 12009
     225     *
     226     * @covers WP_Scripts::do_item
     227     * @covers WP_Scripts::get_eligible_loading_strategy
     228     * @covers WP_Scripts::filter_eligible_strategies
     229     * @covers ::wp_enqueue_script
     230     *
     231     * @dataProvider data_provider_delayed_strategies
     232     *
     233     * @param string $strategy Strategy.
     234     */
     235    public function test_delayed_dependent_with_blocking_dependency( $strategy ) {
     236        wp_enqueue_script( 'dependency-script-a2', '/dependency-script-a2.js', array(), null );
     237        wp_enqueue_script( 'main-script-a2', '/main-script-a2.js', array( 'dependency-script-a2' ), null, compact( 'strategy' ) );
     238        $output   = get_echo( 'wp_print_scripts' );
     239        $expected = "<script type='text/javascript' src='/main-script-a2.js' id='main-script-a2-js' {$strategy} data-wp-strategy='{$strategy}'></script>";
     240        $this->assertStringContainsString( $expected, $output, 'Dependents of a blocking dependency are free to have any strategy.' );
     241    }
     242
     243    /**
     244     * Tests that blocking dependents force delayed dependencies to become blocking.
     245     *
     246     * @ticket 12009
     247     *
     248     * @covers WP_Scripts::do_item
     249     * @covers WP_Scripts::get_eligible_loading_strategy
     250     * @covers WP_Scripts::filter_eligible_strategies
     251     * @covers ::wp_enqueue_script
     252     *
     253     * @dataProvider data_provider_delayed_strategies
     254     * @param string $strategy Strategy.
     255     */
     256    public function test_blocking_dependent_with_delayed_dependency( $strategy ) {
     257        wp_enqueue_script( 'main-script-a3', '/main-script-a3.js', array(), null, compact( 'strategy' ) );
     258        wp_enqueue_script( 'dependent-script-a3', '/dependent-script-a3.js', array( 'main-script-a3' ), null );
     259        $output   = get_echo( 'wp_print_scripts' );
     260        $expected = "<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js' data-wp-strategy='{$strategy}'></script>";
     261        $this->assertStringContainsString( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
     262    }
     263
     264    /**
     265     * Tests that only enqueued dependents effect the eligible loading strategy.
     266     *
     267     * @ticket 12009
     268     *
     269     * @covers WP_Scripts::do_item
     270     * @covers WP_Scripts::get_eligible_loading_strategy
     271     * @covers WP_Scripts::filter_eligible_strategies
     272     * @covers ::wp_enqueue_script
     273     *
     274     * @dataProvider data_provider_delayed_strategies
     275     * @param string $strategy Strategy.
     276     */
     277    public function test_delayed_dependent_with_blocking_dependency_not_enqueued( $strategy ) {
     278        wp_enqueue_script( 'main-script-a4', '/main-script-a4.js', array(), null, compact( 'strategy' ) );
     279        // This dependent is registered but not enqueued, so it should not factor into the eligible loading strategy.
     280        wp_register_script( 'dependent-script-a4', '/dependent-script-a4.js', array( 'main-script-a4' ), null );
     281        $output   = get_echo( 'wp_print_scripts' );
     282        $expected = "<script type='text/javascript' src='/main-script-a4.js' id='main-script-a4-js' {$strategy} data-wp-strategy='{$strategy}'></script>";
     283        $this->assertStringContainsString( $expected, $output, 'Only enqueued dependents should affect the eligible strategy.' );
     284    }
     285
     286    /**
     287     * Data provider for test_filter_eligible_strategies.
     288     *
     289     * @return array
     290     */
     291    public function get_data_to_filter_eligible_strategies() {
     292        return array(
     293            'no_dependents'                       => array(
     294                'set_up'   => static function () {
     295                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     296                    return 'foo';
     297                },
     298                'expected' => array( 'defer' ),
     299            ),
     300            'one_delayed_dependent'               => array(
     301                'set_up'   => static function () {
     302                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     303                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     304                    return 'foo';
     305                },
     306                'expected' => array( 'defer' ),
     307            ),
     308            'one_blocking_dependent'              => array(
     309                'set_up'   => static function () {
     310                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     311                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null );
     312                    return 'foo';
     313                },
     314                'expected' => array(),
     315            ),
     316            'one_blocking_dependent_not_enqueued' => array(
     317                'set_up'   => static function () {
     318                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     319                    wp_register_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null );
     320                    return 'foo';
     321                },
     322                'expected' => array( 'defer' ), // Because bar was not enqueued, only foo was.
     323            ),
     324            'two_delayed_dependents'              => array(
     325                'set_up'   => static function () {
     326                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     327                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     328                    wp_enqueue_script( 'baz', 'https://example.com/baz.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     329                    return 'foo';
     330                },
     331                'expected' => array( 'defer' ),
     332            ),
     333            'recursion_not_delayed'               => array(
     334                'set_up'   => static function () {
     335                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array( 'foo' ), null );
     336                    return 'foo';
     337                },
     338                'expected' => array(),
     339            ),
     340            'recursion_yes_delayed'               => array(
     341                'set_up'   => static function () {
     342                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     343                    return 'foo';
     344                },
     345                'expected' => array( 'defer' ),
     346            ),
     347            'recursion_triple_level'              => array(
     348                'set_up'   => static function () {
     349                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array( 'baz' ), null, array( 'strategy' => 'defer' ) );
     350                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     351                    wp_enqueue_script( 'baz', 'https://example.com/bar.js', array( 'bar' ), null, array( 'strategy' => 'defer' ) );
     352                    return 'foo';
     353                },
     354                'expected' => array( 'defer' ),
     355            ),
     356            'async_only_with_async_dependency'    => array(
     357                'set_up'   => static function () {
     358                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'async' ) );
     359                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null, array( 'strategy' => 'async' ) );
     360                    return 'foo';
     361                },
     362                'expected' => array( 'defer', 'async' ),
     363            ),
     364            'async_only_with_defer_dependency'    => array(
     365                'set_up'   => static function () {
     366                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'async' ) );
     367                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null, array( 'strategy' => 'defer' ) );
     368                    return 'foo';
     369                },
     370                'expected' => array( 'defer' ),
     371            ),
     372            'async_only_with_blocking_dependency' => array(
     373                'set_up'   => static function () {
     374                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'async' ) );
     375                    wp_enqueue_script( 'bar', 'https://example.com/bar.js', array( 'foo' ), null );
     376                    return 'foo';
     377                },
     378                'expected' => array(),
     379            ),
     380            'defer_with_inline_after_script'      => array(
     381                'set_up'   => static function () {
     382                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     383                    wp_add_inline_script( 'foo', 'console.log("foo")', 'after' );
     384                    return 'foo';
     385                },
     386                'expected' => array(),
     387            ),
     388            'defer_with_inline_before_script'     => array(
     389                'set_up'   => static function () {
     390                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'defer' ) );
     391                    wp_add_inline_script( 'foo', 'console.log("foo")', 'before' );
     392                    return 'foo';
     393                },
     394                'expected' => array( 'defer' ),
     395            ),
     396            'async_with_inline_after_script'      => array(
     397                'set_up'   => static function () {
     398                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'async' ) );
     399                    wp_add_inline_script( 'foo', 'console.log("foo")', 'after' );
     400                    return 'foo';
     401                },
     402                'expected' => array(),
     403            ),
     404            'async_with_inline_before_script'     => array(
     405                'set_up'   => static function () {
     406                    wp_enqueue_script( 'foo', 'https://example.com/foo.js', array(), null, array( 'strategy' => 'async' ) );
     407                    wp_add_inline_script( 'foo', 'console.log("foo")', 'before' );
     408                    return 'foo';
     409                },
     410                'expected' => array( 'defer', 'async' ),
     411            ),
     412        );
     413    }
     414
     415    /**
     416     * Tests that the filter_eligible_strategies method works as expected and returns the correct value.
     417     *
     418     * @ticket 12009
     419     *
     420     * @covers WP_Scripts::filter_eligible_strategies
     421     *
     422     * @dataProvider get_data_to_filter_eligible_strategies
     423     *
     424     * @param callable $set_up     Set up.
     425     * @param bool     $async_only Async only.
     426     * @param bool     $expected   Expected return value.
     427     */
     428    public function test_filter_eligible_strategies( $set_up, $expected ) {
     429        $handle = $set_up();
     430
     431        $wp_scripts_reflection      = new ReflectionClass( WP_Scripts::class );
     432        $filter_eligible_strategies = $wp_scripts_reflection->getMethod( 'filter_eligible_strategies' );
     433        $filter_eligible_strategies->setAccessible( true );
     434        $this->assertSame( $expected, $filter_eligible_strategies->invokeArgs( wp_scripts(), array( $handle ) ), 'Expected return value of WP_Scripts::filter_eligible_strategies to match.' );
     435    }
     436
     437    /**
     438     * Register test script.
     439     *
     440     * @param string   $handle    Dependency handle to enqueue.
     441     * @param string   $strategy  Strategy to use for dependency.
     442     * @param string[] $deps      Dependencies for the script.
     443     * @param bool     $in_footer Whether to print the script in the footer.
     444     */
     445    protected function register_test_script( $handle, $strategy, $deps = array(), $in_footer = false ) {
     446        wp_register_script(
     447            $handle,
     448            add_query_arg(
     449                array(
     450                    'script_event_log' => "$handle: script",
     451                ),
     452                'https://example.com/external.js'
     453            ),
     454            $deps,
     455            null
     456        );
     457        if ( 'blocking' !== $strategy ) {
     458            wp_script_add_data( $handle, 'strategy', $strategy );
     459        }
     460    }
     461
     462    /**
     463     * Enqueue test script.
     464     *
     465     * @param string   $handle    Dependency handle to enqueue.
     466     * @param string   $strategy  Strategy to use for dependency.
     467     * @param string[] $deps      Dependencies for the script.
     468     * @param bool     $in_footer Whether to print the script in the footer.
     469     */
     470    protected function enqueue_test_script( $handle, $strategy, $deps = array(), $in_footer = false ) {
     471        $this->register_test_script( $handle, $strategy, $deps, $in_footer );
     472        wp_enqueue_script( $handle );
     473    }
     474
     475    /**
     476     * Adds test inline script.
     477     *
     478     * @param string $handle   Dependency handle to enqueue.
     479     * @param string $position Position.
     480     */
     481    protected function add_test_inline_script( $handle, $position ) {
     482        wp_add_inline_script( $handle, sprintf( 'scriptEventLog.push( %s )', wp_json_encode( "{$handle}: {$position} inline" ) ), $position );
     483    }
     484
     485    /**
     486     * Data provider to test various strategy dependency chains.
     487     *
     488     * @return array[]
     489     */
     490    public function data_provider_to_test_various_strategy_dependency_chains() {
     491        return array(
     492            'async-dependent-with-one-blocking-dependency' => array(
     493                'set_up'          => function () {
     494                    $handle1 = 'blocking-not-async-without-dependency';
     495                    $handle2 = 'async-with-blocking-dependency';
     496                    $this->enqueue_test_script( $handle1, 'blocking', array() );
     497                    $this->enqueue_test_script( $handle2, 'async', array( $handle1 ) );
     498                    foreach ( array( $handle1, $handle2 ) as $handle ) {
     499                        $this->add_test_inline_script( $handle, 'before' );
     500                        $this->add_test_inline_script( $handle, 'after' );
     501                    }
     502                },
     503                'expected_markup' => <<<HTML
     504<script id="blocking-not-async-without-dependency-js-before" type="text/javascript">
     505scriptEventLog.push( "blocking-not-async-without-dependency: before inline" )
     506</script>
     507<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-not-async-without-dependency:%20script' id='blocking-not-async-without-dependency-js'></script>
     508<script id="blocking-not-async-without-dependency-js-after" type="text/javascript">
     509scriptEventLog.push( "blocking-not-async-without-dependency: after inline" )
     510</script>
     511<script id="async-with-blocking-dependency-js-before" type="text/javascript">
     512scriptEventLog.push( "async-with-blocking-dependency: before inline" )
     513</script>
     514<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-with-blocking-dependency:%20script' id='async-with-blocking-dependency-js' data-wp-strategy='async'></script>
     515<script id="async-with-blocking-dependency-js-after" type="text/javascript">
     516scriptEventLog.push( "async-with-blocking-dependency: after inline" )
     517</script>
     518HTML
     519                ,
     520                /*
     521                 * Note: The above comma must be on its own line in PHP<7.3 and not after the `HTML` identifier
     522                 * terminating the heredoc. Otherwise, a syntax error is raised with the line number being wildly wrong:
     523                 *
     524                 * PHP Parse error:  syntax error, unexpected '' (T_ENCAPSED_AND_WHITESPACE), expecting '-' or identifier (T_STRING) or variable (T_VARIABLE) or number (T_NUM_STRING)
     525                 */
     526            ),
     527            'async-with-async-dependencies'                => array(
     528                'set_up'          => function () {
     529                    $handle1 = 'async-no-dependency';
     530                    $handle2 = 'async-one-async-dependency';
     531                    $handle3 = 'async-two-async-dependencies';
     532                    $this->enqueue_test_script( $handle1, 'async', array() );
     533                    $this->enqueue_test_script( $handle2, 'async', array( $handle1 ) );
     534                    $this->enqueue_test_script( $handle3, 'async', array( $handle1, $handle2 ) );
     535                    foreach ( array( $handle1, $handle2, $handle3 ) as $handle ) {
     536                        $this->add_test_inline_script( $handle, 'before' );
     537                        $this->add_test_inline_script( $handle, 'after' );
     538                    }
     539                },
     540                'expected_markup' => <<<HTML
     541<script id="async-no-dependency-js-before" type="text/javascript">
     542scriptEventLog.push( "async-no-dependency: before inline" )
     543</script>
     544<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-no-dependency:%20script' id='async-no-dependency-js' data-wp-strategy='async'></script>
     545<script id="async-no-dependency-js-after" type="text/javascript">
     546scriptEventLog.push( "async-no-dependency: after inline" )
     547</script>
     548<script id="async-one-async-dependency-js-before" type="text/javascript">
     549scriptEventLog.push( "async-one-async-dependency: before inline" )
     550</script>
     551<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-one-async-dependency:%20script' id='async-one-async-dependency-js' data-wp-strategy='async'></script>
     552<script id="async-one-async-dependency-js-after" type="text/javascript">
     553scriptEventLog.push( "async-one-async-dependency: after inline" )
     554</script>
     555<script id="async-two-async-dependencies-js-before" type="text/javascript">
     556scriptEventLog.push( "async-two-async-dependencies: before inline" )
     557</script>
     558<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-two-async-dependencies:%20script' id='async-two-async-dependencies-js' data-wp-strategy='async'></script>
     559<script id="async-two-async-dependencies-js-after" type="text/javascript">
     560scriptEventLog.push( "async-two-async-dependencies: after inline" )
     561</script>
     562HTML
     563                ,
     564            ),
     565            'async-with-blocking-dependency'               => array(
     566                'set_up'          => function () {
     567                    $handle1 = 'async-with-blocking-dependent';
     568                    $handle2 = 'blocking-dependent-of-async';
     569                    $this->enqueue_test_script( $handle1, 'async', array() );
     570                    $this->enqueue_test_script( $handle2, 'blocking', array( $handle1 ) );
     571                    foreach ( array( $handle1, $handle2 ) as $handle ) {
     572                        $this->add_test_inline_script( $handle, 'before' );
     573                        $this->add_test_inline_script( $handle, 'after' );
     574                    }
     575                },
     576                'expected_markup' => <<<HTML
     577<script id="async-with-blocking-dependent-js-before" type="text/javascript">
     578scriptEventLog.push( "async-with-blocking-dependent: before inline" )
     579</script>
     580<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-with-blocking-dependent:%20script' id='async-with-blocking-dependent-js' data-wp-strategy='async'></script>
     581<script id="async-with-blocking-dependent-js-after" type="text/javascript">
     582scriptEventLog.push( "async-with-blocking-dependent: after inline" )
     583</script>
     584<script id="blocking-dependent-of-async-js-before" type="text/javascript">
     585scriptEventLog.push( "blocking-dependent-of-async: before inline" )
     586</script>
     587<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-dependent-of-async:%20script' id='blocking-dependent-of-async-js'></script>
     588<script id="blocking-dependent-of-async-js-after" type="text/javascript">
     589scriptEventLog.push( "blocking-dependent-of-async: after inline" )
     590</script>
     591HTML
     592                ,
     593            ),
     594            'defer-with-async-dependency'                  => array(
     595                'set_up'          => function () {
     596                    $handle1 = 'async-with-defer-dependent';
     597                    $handle2 = 'defer-dependent-of-async';
     598                    $this->enqueue_test_script( $handle1, 'async', array() );
     599                    $this->enqueue_test_script( $handle2, 'defer', array( $handle1 ) );
     600                    foreach ( array( $handle1, $handle2 ) as $handle ) {
     601                        $this->add_test_inline_script( $handle, 'before' );
     602                        $this->add_test_inline_script( $handle, 'after' );
     603                    }
     604                },
     605                'expected_markup' => <<<HTML
     606<script id="async-with-defer-dependent-js-before" type="text/javascript">
     607scriptEventLog.push( "async-with-defer-dependent: before inline" )
     608</script>
     609<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-with-defer-dependent:%20script' id='async-with-defer-dependent-js' data-wp-strategy='async'></script>
     610<script id="async-with-defer-dependent-js-after" type="text/javascript">
     611scriptEventLog.push( "async-with-defer-dependent: after inline" )
     612</script>
     613<script id="defer-dependent-of-async-js-before" type="text/javascript">
     614scriptEventLog.push( "defer-dependent-of-async: before inline" )
     615</script>
     616<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-async:%20script' id='defer-dependent-of-async-js' data-wp-strategy='defer'></script>
     617<script id="defer-dependent-of-async-js-after" type="text/javascript">
     618scriptEventLog.push( "defer-dependent-of-async: after inline" )
     619</script>
     620HTML
     621                ,
     622            ),
     623            'blocking-bundle-of-none-with-inline-scripts-and-defer-dependent' => array(
     624                'set_up'          => function () {
     625                    $handle1 = 'blocking-bundle-of-none';
     626                    $handle2 = 'defer-dependent-of-blocking-bundle-of-none';
     627
     628                    wp_register_script( $handle1, false, array(), null );
     629                    $this->add_test_inline_script( $handle1, 'before' );
     630                    $this->add_test_inline_script( $handle1, 'after' );
     631
     632                    // Note: the before script for this will be blocking because the dependency is blocking.
     633                    $this->enqueue_test_script( $handle2, 'defer', array( $handle1 ) );
     634                    $this->add_test_inline_script( $handle2, 'before' );
     635                    $this->add_test_inline_script( $handle2, 'after' );
     636                },
     637                'expected_markup' => <<<HTML
     638<script id="blocking-bundle-of-none-js-before" type="text/javascript">
     639scriptEventLog.push( "blocking-bundle-of-none: before inline" )
     640</script>
     641<script id="blocking-bundle-of-none-js-after" type="text/javascript">
     642scriptEventLog.push( "blocking-bundle-of-none: after inline" )
     643</script>
     644<script id="defer-dependent-of-blocking-bundle-of-none-js-before" type="text/javascript">
     645scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-none: before inline" )
     646</script>
     647<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-blocking-bundle-of-none:%20script' id='defer-dependent-of-blocking-bundle-of-none-js' data-wp-strategy='defer'></script>
     648<script id="defer-dependent-of-blocking-bundle-of-none-js-after" type="text/javascript">
     649scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-none: after inline" )
     650</script>
     651HTML
     652                ,
     653            ),
     654            'blocking-bundle-of-two-with-defer-dependent'  => array(
     655                'set_up'          => function () {
     656                    $handle1 = 'blocking-bundle-of-two';
     657                    $handle2 = 'blocking-bundle-member-one';
     658                    $handle3 = 'blocking-bundle-member-two';
     659                    $handle4 = 'defer-dependent-of-blocking-bundle-of-two';
     660
     661                    wp_register_script( $handle1, false, array( $handle2, $handle3 ), null );
     662                    $this->enqueue_test_script( $handle2, 'blocking' );
     663                    $this->enqueue_test_script( $handle3, 'blocking' );
     664                    $this->enqueue_test_script( $handle4, 'defer', array( $handle1 ) );
     665
     666                    foreach ( array( $handle2, $handle3, $handle4 ) as $handle ) {
     667                        $this->add_test_inline_script( $handle, 'before' );
     668                        $this->add_test_inline_script( $handle, 'after' );
     669                    }
     670                },
     671                'expected_markup' => <<<HTML
     672<script id="blocking-bundle-member-one-js-before" type="text/javascript">
     673scriptEventLog.push( "blocking-bundle-member-one: before inline" )
     674</script>
     675<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-bundle-member-one:%20script' id='blocking-bundle-member-one-js'></script>
     676<script id="blocking-bundle-member-one-js-after" type="text/javascript">
     677scriptEventLog.push( "blocking-bundle-member-one: after inline" )
     678</script>
     679<script id="blocking-bundle-member-two-js-before" type="text/javascript">
     680scriptEventLog.push( "blocking-bundle-member-two: before inline" )
     681</script>
     682<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-bundle-member-two:%20script' id='blocking-bundle-member-two-js'></script>
     683<script id="blocking-bundle-member-two-js-after" type="text/javascript">
     684scriptEventLog.push( "blocking-bundle-member-two: after inline" )
     685</script>
     686<script id="defer-dependent-of-blocking-bundle-of-two-js-before" type="text/javascript">
     687scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-two: before inline" )
     688</script>
     689<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-blocking-bundle-of-two:%20script' id='defer-dependent-of-blocking-bundle-of-two-js' data-wp-strategy='defer'></script>
     690<script id="defer-dependent-of-blocking-bundle-of-two-js-after" type="text/javascript">
     691scriptEventLog.push( "defer-dependent-of-blocking-bundle-of-two: after inline" )
     692</script>
     693HTML
     694                ,
     695            ),
     696            'defer-bundle-of-none-with-inline-scripts-and-defer-dependents' => array(
     697                'set_up'          => function () {
     698                    $handle1 = 'defer-bundle-of-none';
     699                    $handle2 = 'defer-dependent-of-defer-bundle-of-none';
     700
     701                    // The eligible loading strategy for this will be forced to be blocking when rendered since $src = false.
     702                    wp_register_script( $handle1, false, array(), null );
     703                    wp_scripts()->registered[ $handle1 ]->extra['strategy'] = 'defer'; // Bypass wp_script_add_data() which should no-op with _doing_it_wrong() because of $src=false.
     704                    $this->add_test_inline_script( $handle1, 'before' );
     705                    $this->add_test_inline_script( $handle1, 'after' );
     706
     707                    // Note: the before script for this will be blocking because the dependency is blocking.
     708                    $this->enqueue_test_script( $handle2, 'defer', array( $handle1 ) );
     709                    $this->add_test_inline_script( $handle2, 'before' );
     710                    $this->add_test_inline_script( $handle2, 'after' );
     711                },
     712                'expected_markup' => <<<HTML
     713<script id="defer-bundle-of-none-js-before" type="text/javascript">
     714scriptEventLog.push( "defer-bundle-of-none: before inline" )
     715</script>
     716<script id="defer-bundle-of-none-js-after" type="text/javascript">
     717scriptEventLog.push( "defer-bundle-of-none: after inline" )
     718</script>
     719<script id="defer-dependent-of-defer-bundle-of-none-js-before" type="text/javascript">
     720scriptEventLog.push( "defer-dependent-of-defer-bundle-of-none: before inline" )
     721</script>
     722<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-defer-bundle-of-none:%20script' id='defer-dependent-of-defer-bundle-of-none-js' data-wp-strategy='defer'></script>
     723<script id="defer-dependent-of-defer-bundle-of-none-js-after" type="text/javascript">
     724scriptEventLog.push( "defer-dependent-of-defer-bundle-of-none: after inline" )
     725</script>
     726HTML
     727                ,
     728            ),
     729            'defer-dependent-with-blocking-and-defer-dependencies' => array(
     730                'set_up'          => function () {
     731                    $handle1 = 'blocking-dependency-with-defer-following-dependency';
     732                    $handle2 = 'defer-dependency-with-blocking-preceding-dependency';
     733                    $handle3 = 'defer-dependent-of-blocking-and-defer-dependencies';
     734                    $this->enqueue_test_script( $handle1, 'blocking', array() );
     735                    $this->enqueue_test_script( $handle2, 'defer', array() );
     736                    $this->enqueue_test_script( $handle3, 'defer', array( $handle1, $handle2 ) );
     737
     738                    foreach ( array( $handle1, $handle2, $handle3 ) as $dep ) {
     739                        $this->add_test_inline_script( $dep, 'before' );
     740                        $this->add_test_inline_script( $dep, 'after' );
     741                    }
     742                },
     743                'expected_markup' => <<<HTML
     744<script id="blocking-dependency-with-defer-following-dependency-js-before" type="text/javascript">
     745scriptEventLog.push( "blocking-dependency-with-defer-following-dependency: before inline" )
     746</script>
     747<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-dependency-with-defer-following-dependency:%20script' id='blocking-dependency-with-defer-following-dependency-js'></script>
     748<script id="blocking-dependency-with-defer-following-dependency-js-after" type="text/javascript">
     749scriptEventLog.push( "blocking-dependency-with-defer-following-dependency: after inline" )
     750</script>
     751<script id="defer-dependency-with-blocking-preceding-dependency-js-before" type="text/javascript">
     752scriptEventLog.push( "defer-dependency-with-blocking-preceding-dependency: before inline" )
     753</script>
     754<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependency-with-blocking-preceding-dependency:%20script' id='defer-dependency-with-blocking-preceding-dependency-js' data-wp-strategy='defer'></script>
     755<script id="defer-dependency-with-blocking-preceding-dependency-js-after" type="text/javascript">
     756scriptEventLog.push( "defer-dependency-with-blocking-preceding-dependency: after inline" )
     757</script>
     758<script id="defer-dependent-of-blocking-and-defer-dependencies-js-before" type="text/javascript">
     759scriptEventLog.push( "defer-dependent-of-blocking-and-defer-dependencies: before inline" )
     760</script>
     761<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-blocking-and-defer-dependencies:%20script' id='defer-dependent-of-blocking-and-defer-dependencies-js' data-wp-strategy='defer'></script>
     762<script id="defer-dependent-of-blocking-and-defer-dependencies-js-after" type="text/javascript">
     763scriptEventLog.push( "defer-dependent-of-blocking-and-defer-dependencies: after inline" )
     764</script>
     765HTML
     766                ,
     767            ),
     768            'defer-dependent-with-defer-and-blocking-dependencies' => array(
     769                'set_up'          => function () {
     770                    $handle1 = 'defer-dependency-with-blocking-following-dependency';
     771                    $handle2 = 'blocking-dependency-with-defer-preceding-dependency';
     772                    $handle3 = 'defer-dependent-of-defer-and-blocking-dependencies';
     773                    $this->enqueue_test_script( $handle1, 'defer', array() );
     774                    $this->enqueue_test_script( $handle2, 'blocking', array() );
     775                    $this->enqueue_test_script( $handle3, 'defer', array( $handle1, $handle2 ) );
     776
     777                    foreach ( array( $handle1, $handle2, $handle3 ) as $dep ) {
     778                        $this->add_test_inline_script( $dep, 'before' );
     779                        $this->add_test_inline_script( $dep, 'after' );
     780                    }
     781                },
     782                'expected_markup' => <<<HTML
     783<script id="defer-dependency-with-blocking-following-dependency-js-before" type="text/javascript">
     784scriptEventLog.push( "defer-dependency-with-blocking-following-dependency: before inline" )
     785</script>
     786<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependency-with-blocking-following-dependency:%20script' id='defer-dependency-with-blocking-following-dependency-js' data-wp-strategy='defer'></script>
     787<script id="defer-dependency-with-blocking-following-dependency-js-after" type="text/javascript">
     788scriptEventLog.push( "defer-dependency-with-blocking-following-dependency: after inline" )
     789</script>
     790<script id="blocking-dependency-with-defer-preceding-dependency-js-before" type="text/javascript">
     791scriptEventLog.push( "blocking-dependency-with-defer-preceding-dependency: before inline" )
     792</script>
     793<script type='text/javascript' src='https://example.com/external.js?script_event_log=blocking-dependency-with-defer-preceding-dependency:%20script' id='blocking-dependency-with-defer-preceding-dependency-js'></script>
     794<script id="blocking-dependency-with-defer-preceding-dependency-js-after" type="text/javascript">
     795scriptEventLog.push( "blocking-dependency-with-defer-preceding-dependency: after inline" )
     796</script>
     797<script id="defer-dependent-of-defer-and-blocking-dependencies-js-before" type="text/javascript">
     798scriptEventLog.push( "defer-dependent-of-defer-and-blocking-dependencies: before inline" )
     799</script>
     800<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-defer-and-blocking-dependencies:%20script' id='defer-dependent-of-defer-and-blocking-dependencies-js' data-wp-strategy='defer'></script>
     801<script id="defer-dependent-of-defer-and-blocking-dependencies-js-after" type="text/javascript">
     802scriptEventLog.push( "defer-dependent-of-defer-and-blocking-dependencies: after inline" )
     803</script>
     804HTML
     805                ,
     806            ),
     807            'async-with-defer-dependency'                  => array(
     808                'set_up'          => function () {
     809                    $handle1 = 'defer-with-async-dependent';
     810                    $handle2 = 'async-dependent-of-defer';
     811                    $this->enqueue_test_script( $handle1, 'defer', array() );
     812                    $this->enqueue_test_script( $handle2, 'async', array( $handle1 ) );
     813                    foreach ( array( $handle1, $handle2 ) as $handle ) {
     814                        $this->add_test_inline_script( $handle, 'before' );
     815                        $this->add_test_inline_script( $handle, 'after' );
     816                    }
     817                },
     818                'expected_markup' => <<<HTML
     819<script id="defer-with-async-dependent-js-before" type="text/javascript">
     820scriptEventLog.push( "defer-with-async-dependent: before inline" )
     821</script>
     822<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-with-async-dependent:%20script' id='defer-with-async-dependent-js' data-wp-strategy='defer'></script>
     823<script id="defer-with-async-dependent-js-after" type="text/javascript">
     824scriptEventLog.push( "defer-with-async-dependent: after inline" )
     825</script>
     826<script id="async-dependent-of-defer-js-before" type="text/javascript">
     827scriptEventLog.push( "async-dependent-of-defer: before inline" )
     828</script>
     829<script type='text/javascript' src='https://example.com/external.js?script_event_log=async-dependent-of-defer:%20script' id='async-dependent-of-defer-js' data-wp-strategy='async'></script>
     830<script id="async-dependent-of-defer-js-after" type="text/javascript">
     831scriptEventLog.push( "async-dependent-of-defer: after inline" )
     832</script>
     833HTML
     834                ,
     835            ),
     836            'defer-with-before-inline-script'              => array(
     837                'set_up'          => function () {
     838                    // Note this should NOT result in no delayed-inline-script-loader script being added.
     839                    $handle = 'defer-with-before-inline';
     840                    $this->enqueue_test_script( $handle, 'defer', array() );
     841                    $this->add_test_inline_script( $handle, 'before' );
     842                },
     843                'expected_markup' => <<<HTML
     844<script id="defer-with-before-inline-js-before" type="text/javascript">
     845scriptEventLog.push( "defer-with-before-inline: before inline" )
     846</script>
     847<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-with-before-inline:%20script' id='defer-with-before-inline-js' defer data-wp-strategy='defer'></script>
     848HTML
     849                ,
     850            ),
     851            'defer-with-after-inline-script'               => array(
     852                'set_up'          => function () {
     853                    // Note this SHOULD result in delayed-inline-script-loader script being added.
     854                    $handle = 'defer-with-after-inline';
     855                    $this->enqueue_test_script( $handle, 'defer', array() );
     856                    $this->add_test_inline_script( $handle, 'after' );
     857                },
     858                'expected_markup' => <<<HTML
     859<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-with-after-inline:%20script' id='defer-with-after-inline-js' data-wp-strategy='defer'></script>
     860<script id="defer-with-after-inline-js-after" type="text/javascript">
     861scriptEventLog.push( "defer-with-after-inline: after inline" )
     862</script>
     863HTML
     864                ,
     865            ),
     866            'jquery-deferred'                              => array(
     867                'set_up'          => function () {
     868                    $wp_scripts = wp_scripts();
     869                    wp_default_scripts( $wp_scripts );
     870                    foreach ( $wp_scripts->registered['jquery']->deps as $jquery_dep ) {
     871                        $wp_scripts->registered[ $jquery_dep ]->add_data( 'strategy', 'defer' );
     872                        $wp_scripts->registered[ $jquery_dep ]->ver = null; // Just to avoid markup changes in the test when jQuery is upgraded.
     873                    }
     874                    wp_enqueue_script( 'theme-functions', 'https://example.com/theme-functions.js', array( 'jquery' ), null, array( 'strategy' => 'defer' ) );
     875                },
     876                'expected_markup' => <<<HTML
     877<script type='text/javascript' src='http://example.org/wp-includes/js/jquery/jquery.js' id='jquery-core-js' defer data-wp-strategy='defer'></script>
     878<script type='text/javascript' src='http://example.org/wp-includes/js/jquery/jquery-migrate.js' id='jquery-migrate-js' defer data-wp-strategy='defer'></script>
     879<script type='text/javascript' src='https://example.com/theme-functions.js' id='theme-functions-js' defer data-wp-strategy='defer'></script>
     880HTML
     881                ,
     882            ),
     883            'nested-aliases'                               => array(
     884                'set_up'          => function () {
     885                    $outer_alias_handle = 'outer-bundle-of-two';
     886                    $inner_alias_handle = 'inner-bundle-of-two';
     887
     888                    // The outer alias contains a blocking member, as well as a nested alias that contains defer scripts.
     889                    wp_register_script( $outer_alias_handle, false, array( $inner_alias_handle, 'outer-bundle-leaf-member' ), null );
     890                    $this->register_test_script( 'outer-bundle-leaf-member', 'blocking', array() );
     891
     892                    // Inner alias only contains delay scripts.
     893                    wp_register_script( $inner_alias_handle, false, array( 'inner-bundle-member-one', 'inner-bundle-member-two' ), null );
     894                    $this->register_test_script( 'inner-bundle-member-one', 'defer', array() );
     895                    $this->register_test_script( 'inner-bundle-member-two', 'defer', array() );
     896
     897                    $this->enqueue_test_script( 'defer-dependent-of-nested-aliases', 'defer', array( $outer_alias_handle ) );
     898                    $this->add_test_inline_script( 'defer-dependent-of-nested-aliases', 'before' );
     899                    $this->add_test_inline_script( 'defer-dependent-of-nested-aliases', 'after' );
     900                },
     901                'expected_markup' => <<<HTML
     902<script type='text/javascript' src='https://example.com/external.js?script_event_log=inner-bundle-member-one:%20script' id='inner-bundle-member-one-js' data-wp-strategy='defer'></script>
     903<script type='text/javascript' src='https://example.com/external.js?script_event_log=inner-bundle-member-two:%20script' id='inner-bundle-member-two-js' data-wp-strategy='defer'></script>
     904<script type='text/javascript' src='https://example.com/external.js?script_event_log=outer-bundle-leaf-member:%20script' id='outer-bundle-leaf-member-js'></script>
     905<script id="defer-dependent-of-nested-aliases-js-before" type="text/javascript">
     906scriptEventLog.push( "defer-dependent-of-nested-aliases: before inline" )
     907</script>
     908<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-nested-aliases:%20script' id='defer-dependent-of-nested-aliases-js' data-wp-strategy='defer'></script>
     909<script id="defer-dependent-of-nested-aliases-js-after" type="text/javascript">
     910scriptEventLog.push( "defer-dependent-of-nested-aliases: after inline" )
     911</script>
     912HTML
     913                ,
     914            ),
     915
     916            'async-alias-members-with-defer-dependency'    => array(
     917                'set_up'          => function () {
     918                    $alias_handle = 'async-alias';
     919                    $async_handle1 = 'async1';
     920                    $async_handle2 = 'async2';
     921
     922                    wp_register_script( $alias_handle, false, array( $async_handle1, $async_handle2 ), null );
     923                    $this->register_test_script( $async_handle1, 'async', array() );
     924                    $this->register_test_script( $async_handle2, 'async', array() );
     925
     926                    $this->enqueue_test_script( 'defer-dependent-of-async-aliases', 'defer', array( $alias_handle ) );
     927                },
     928                'expected_markup' => <<<HTML
     929<script type='text/javascript' src='https://example.com/external.js?script_event_log=async1:%20script' id='async1-js' defer data-wp-strategy='async'></script>
     930<script type='text/javascript' src='https://example.com/external.js?script_event_log=async2:%20script' id='async2-js' defer data-wp-strategy='async'></script>
     931<script type='text/javascript' src='https://example.com/external.js?script_event_log=defer-dependent-of-async-aliases:%20script' id='defer-dependent-of-async-aliases-js' defer data-wp-strategy='defer'></script>
     932HTML
     933                ,
     934            ),
     935        );
     936    }
     937
     938    /**
     939     * Tests that various loading strategy dependency chains function as expected.
     940     *
     941     * @covers ::wp_enqueue_script()
     942     * @covers ::wp_add_inline_script()
     943     * @covers ::wp_print_scripts()
     944     * @covers WP_Scripts::get_inline_script_tag
     945     *
     946     * @dataProvider data_provider_to_test_various_strategy_dependency_chains
     947     *
     948     * @param callable $set_up          Set up.
     949     * @param string   $expected_markup Expected markup.
     950     */
     951    public function test_various_strategy_dependency_chains( $set_up, $expected_markup ) {
     952        $set_up();
     953        $actual_markup = get_echo( 'wp_print_scripts' );
     954        $this->assertEqualMarkup( trim( $expected_markup ), trim( $actual_markup ), "Actual markup:\n{$actual_markup}" );
     955    }
     956
     957    /**
     958     * Tests that defer is the final strategy when registering a script using defer, that has no dependents/dependencies.
     959     *
     960     * @ticket 12009
     961     *
     962     * @covers WP_Scripts::do_item
     963     * @covers WP_Scripts::get_eligible_loading_strategy
     964     * @covers ::wp_enqueue_script
     965     */
     966    public function test_loading_strategy_with_defer_having_no_dependents_nor_dependencies() {
     967        wp_enqueue_script( 'main-script-d1', 'http://example.com/main-script-d1.js', array(), null, array( 'strategy' => 'defer' ) );
     968        $output   = get_echo( 'wp_print_scripts' );
     969        $expected = "<script type='text/javascript' src='http://example.com/main-script-d1.js' id='main-script-d1-js' defer data-wp-strategy='defer'></script>\n";
     970        $this->assertStringContainsString( $expected, $output, 'Expected defer, as there is no dependent or dependency' );
     971    }
     972
     973    /**
     974     * Tests that a script registered with defer remains deferred when all dependencies are either deferred or blocking.
     975     *
     976     * @ticket 12009
     977     *
     978     * @covers WP_Scripts::do_item
     979     * @covers WP_Scripts::get_eligible_loading_strategy
     980     * @covers ::wp_enqueue_script
     981     */
     982    public function test_loading_strategy_with_defer_dependent_and_varied_dependencies() {
     983        wp_enqueue_script( 'dependency-script-d2-1', 'http://example.com/dependency-script-d2-1.js', array(), null, array( 'strategy' => 'defer' ) );
     984        wp_enqueue_script( 'dependency-script-d2-2', 'http://example.com/dependency-script-d2-2.js', array(), null );
     985        wp_enqueue_script( 'dependency-script-d2-3', 'http://example.com/dependency-script-d2-3.js', array( 'dependency-script-d2-2' ), null, array( 'strategy' => 'defer' ) );
     986        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' ) );
     987        $output   = get_echo( 'wp_print_scripts' );
     988        $expected = "<script type='text/javascript' src='http://example.com/main-script-d2.js' id='main-script-d2-js' defer data-wp-strategy='defer'></script>\n";
     989        $this->assertStringContainsString( $expected, $output, 'Expected defer, as all dependencies are either deferred or blocking' );
     990    }
     991
     992    /**
     993     * Tests that scripts registered with defer remain deferred when all dependents are also deferred.
     994     *
     995     * @ticket 12009
     996     *
     997     * @covers WP_Scripts::do_item
     998     * @covers WP_Scripts::get_eligible_loading_strategy
     999     * @covers ::wp_enqueue_script
     1000     */
     1001    public function test_loading_strategy_with_all_defer_dependencies() {
     1002        wp_enqueue_script( 'main-script-d3', 'http://example.com/main-script-d3.js', array(), null, array( 'strategy' => 'defer' ) );
     1003        wp_enqueue_script( 'dependent-script-d3-1', 'http://example.com/dependent-script-d3-1.js', array( 'main-script-d3' ), null, array( 'strategy' => 'defer' ) );
     1004        wp_enqueue_script( 'dependent-script-d3-2', 'http://example.com/dependent-script-d3-2.js', array( 'dependent-script-d3-1' ), null, array( 'strategy' => 'defer' ) );
     1005        wp_enqueue_script( 'dependent-script-d3-3', 'http://example.com/dependent-script-d3-3.js', array( 'dependent-script-d3-2' ), null, array( 'strategy' => 'defer' ) );
     1006        $output   = get_echo( 'wp_print_scripts' );
     1007        $expected = "<script type='text/javascript' src='http://example.com/main-script-d3.js' id='main-script-d3-js' defer data-wp-strategy='defer'></script>\n";
     1008        $this->assertStringContainsString( $expected, $output, 'Expected defer, as all dependents have defer loading strategy' );
     1009    }
     1010
     1011    /**
     1012     * Tests that dependents that are async but attached to a deferred main script, print with defer as opposed to async.
     1013     *
     1014     * @ticket 12009
     1015     *
     1016     * @covers WP_Scripts::do_item
     1017     * @covers WP_Scripts::get_eligible_loading_strategy
     1018     * @covers ::wp_enqueue_script
     1019     */
     1020    public function test_defer_with_async_dependent() {
     1021        // case with one async dependent.
     1022        wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
     1023        wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) );
     1024        wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'async' ) );
     1025        wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
     1026        $output    = get_echo( 'wp_print_scripts' );
     1027        $expected  = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' defer data-wp-strategy='defer'></script>\n";
     1028        $expected .= "<script type='text/javascript' src='/dependent-script-d4-1.js' id='dependent-script-d4-1-js' defer data-wp-strategy='defer'></script>\n";
     1029        $expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer data-wp-strategy='async'></script>\n";
     1030        $expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer data-wp-strategy='defer'></script>\n";
     1031
     1032        $this->assertSame( $expected, $output, 'Scripts registered as defer but that have dependents that are async are expected to have said dependents deferred.' );
     1033    }
     1034
     1035    /**
     1036     * Tests that scripts registered as defer become blocking when their dependents chain are all blocking.
     1037     *
     1038     * @ticket 12009
     1039     *
     1040     * @covers WP_Scripts::do_item
     1041     * @covers WP_Scripts::get_eligible_loading_strategy
     1042     * @covers WP_Scripts::filter_eligible_strategies
     1043     * @covers ::wp_enqueue_script
     1044     */
     1045    public function test_loading_strategy_with_invalid_defer_registration() {
     1046        // Main script is defer and all dependent are not defer. Then main script will have blocking(or no) strategy.
     1047        wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
     1048        wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) );
     1049        wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null );
     1050        wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
     1051        $output   = get_echo( 'wp_print_scripts' );
     1052        $expected = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' data-wp-strategy='defer'></script>\n";
     1053        $this->assertStringContainsString( $expected, $output, 'Scripts registered as defer but that have all dependents with no strategy, should become blocking (no strategy).' );
     1054    }
     1055
     1056    /**
     1057     * Tests that scripts registered as default/blocking remain as such when they have no dependencies.
     1058     *
     1059     * @ticket 12009
     1060     *
     1061     * @covers WP_Scripts::do_item
     1062     * @covers WP_Scripts::get_eligible_loading_strategy
     1063     * @covers WP_Scripts::filter_eligible_strategies
     1064     * @covers ::wp_enqueue_script
     1065     */
     1066    public function test_loading_strategy_with_valid_blocking_registration() {
     1067        wp_enqueue_script( 'main-script-b1', '/main-script-b1.js', array(), null );
     1068        $output   = get_echo( 'wp_print_scripts' );
     1069        $expected = "<script type='text/javascript' src='/main-script-b1.js' id='main-script-b1-js'></script>\n";
     1070        $this->assertSame( $expected, $output, 'Scripts registered with a "blocking" strategy, and who have no dependencies, should have no loading strategy attributes printed.' );
     1071
     1072        // strategy args not set.
     1073        wp_enqueue_script( 'main-script-b2', '/main-script-b2.js', array(), null, array() );
     1074        $output   = get_echo( 'wp_print_scripts' );
     1075        $expected = "<script type='text/javascript' src='/main-script-b2.js' id='main-script-b2-js'></script>\n";
     1076        $this->assertSame( $expected, $output, 'Scripts registered with no strategy assigned, and who have no dependencies, should have no loading strategy attributes printed.' );
     1077    }
     1078
     1079    /**
     1080     * Tests that scripts registered for the head do indeed end up there.
     1081     *
     1082     * @ticket 12009
     1083     *
     1084     * @covers WP_Scripts::do_item
     1085     * @covers ::wp_enqueue_script
     1086     * @covers ::wp_register_script
     1087     */
     1088    public function test_scripts_targeting_head() {
     1089        wp_register_script( 'header-old', '/header-old.js', array(), null, false );
     1090        wp_register_script( 'header-new', '/header-new.js', array( 'header-old' ), null, array( 'in_footer' => false ) );
     1091        wp_enqueue_script( 'enqueue-header-old', '/enqueue-header-old.js', array( 'header-new' ), null, false );
     1092        wp_enqueue_script( 'enqueue-header-new', '/enqueue-header-new.js', array( 'enqueue-header-old' ), null, array( 'in_footer' => false ) );
     1093
     1094        $actual_header = get_echo( 'wp_print_head_scripts' );
     1095        $actual_footer = get_echo( 'wp_print_scripts' );
     1096
     1097        $expected_header  = "<script type='text/javascript' src='/header-old.js' id='header-old-js'></script>\n";
     1098        $expected_header .= "<script type='text/javascript' src='/header-new.js' id='header-new-js'></script>\n";
     1099        $expected_header .= "<script type='text/javascript' src='/enqueue-header-old.js' id='enqueue-header-old-js'></script>\n";
     1100        $expected_header .= "<script type='text/javascript' src='/enqueue-header-new.js' id='enqueue-header-new-js'></script>\n";
     1101
     1102        $this->assertSame( $expected_header, $actual_header, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
     1103        $this->assertEmpty( $actual_footer, 'Expected footer to be empty since all scripts were for head.' );
     1104    }
     1105
     1106    /**
     1107     * Test that scripts registered for the footer do indeed end up there.
     1108     *
     1109     * @ticket 12009
     1110     *
     1111     * @covers WP_Scripts::do_item
     1112     * @covers ::wp_enqueue_script
     1113     * @covers ::wp_register_script
     1114     */
     1115    public function test_scripts_targeting_footer() {
     1116        wp_register_script( 'footer-old', '/footer-old.js', array(), null, true );
     1117        wp_register_script( 'footer-new', '/footer-new.js', array( 'footer-old' ), null, array( 'in_footer' => true ) );
     1118        wp_enqueue_script( 'enqueue-footer-old', '/enqueue-footer-old.js', array( 'footer-new' ), null, true );
     1119        wp_enqueue_script( 'enqueue-footer-new', '/enqueue-footer-new.js', array( 'enqueue-footer-old' ), null, array( 'in_footer' => true ) );
     1120
     1121        $actual_header = get_echo( 'wp_print_head_scripts' );
     1122        $actual_footer = get_echo( 'wp_print_scripts' );
     1123
     1124        $expected_footer  = "<script type='text/javascript' src='/footer-old.js' id='footer-old-js'></script>\n";
     1125        $expected_footer .= "<script type='text/javascript' src='/footer-new.js' id='footer-new-js'></script>\n";
     1126        $expected_footer .= "<script type='text/javascript' src='/enqueue-footer-old.js' id='enqueue-footer-old-js'></script>\n";
     1127        $expected_footer .= "<script type='text/javascript' src='/enqueue-footer-new.js' id='enqueue-footer-new-js'></script>\n";
     1128
     1129        $this->assertEmpty( $actual_header, 'Expected header to be empty since all scripts targeted footer.' );
     1130        $this->assertSame( $expected_footer, $actual_footer, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
     1131    }
     1132
     1133    /**
     1134     * Data provider for test_setting_in_footer_and_strategy.
     1135     *
     1136     * @return array[]
     1137     */
     1138    public function get_data_for_test_setting_in_footer_and_strategy() {
     1139        return array(
     1140            // Passing in_footer and strategy via args array.
     1141            'async_footer_in_args_array'    => array(
     1142                'set_up'   => static function ( $handle ) {
     1143                    $args = array(
     1144                        'in_footer' => true,
     1145                        'strategy'  => 'async',
     1146                    );
     1147                    wp_enqueue_script( $handle, '/footer-async.js', array(), null, $args );
     1148                },
     1149                'group'    => 1,
     1150                'strategy' => 'async',
     1151            ),
     1152
     1153            // Passing in_footer=true but no strategy.
     1154            'blocking_footer_in_args_array' => array(
     1155                'set_up'   => static function ( $handle ) {
     1156                    wp_register_script( $handle, '/defaults.js', array(), null, array( 'in_footer' => true ) );
     1157                },
     1158                'group'    => 1,
     1159                'strategy' => false,
     1160            ),
     1161
     1162            // Passing async strategy in script args array.
     1163            'async_in_args_array'           => array(
     1164                'set_up'   => static function ( $handle ) {
     1165                    wp_register_script( $handle, '/defaults.js', array(), null, array( 'strategy' => 'async' ) );
     1166                },
     1167                'group'    => false,
     1168                'strategy' => 'async',
     1169            ),
     1170
     1171            // Passing empty array as 5th arg.
     1172            'empty_args_array'              => array(
     1173                'set_up'   => static function ( $handle ) {
     1174                    wp_register_script( $handle, '/defaults.js', array(), null, array() );
     1175                },
     1176                'group'    => false,
     1177                'strategy' => false,
     1178            ),
     1179
     1180            // Passing no value as 5th arg.
     1181            'undefined_args_param'          => array(
     1182                'set_up'   => static function ( $handle ) {
     1183                    wp_register_script( $handle, '/defaults.js', array(), null );
     1184                },
     1185                'group'    => false,
     1186                'strategy' => false,
     1187            ),
     1188
     1189            // Test backward compatibility, passing $in_footer=true as 5th arg.
     1190            'passing_bool_as_args_param'    => array(
     1191                'set_up'   => static function ( $handle ) {
     1192                    wp_enqueue_script( $handle, '/footer-async.js', array(), null, true );
     1193                },
     1194                'group'    => 1,
     1195                'strategy' => false,
     1196            ),
     1197
     1198            // Test backward compatibility, passing $in_footer=true as 5th arg and setting strategy via wp_script_add_data().
     1199            'bool_as_args_and_add_data'     => array(
     1200                'set_up'   => static function ( $handle ) {
     1201                    wp_register_script( $handle, '/footer-async.js', array(), null, true );
     1202                    wp_script_add_data( $handle, 'strategy', 'defer' );
     1203                },
     1204                'group'    => 1,
     1205                'strategy' => 'defer',
     1206            ),
     1207        );
     1208    }
     1209
     1210    /**
     1211     * Tests that scripts print in the correct group (head/footer) when using in_footer and assigning a strategy.
     1212     *
     1213     * @ticket 12009
     1214     *
     1215     * @covers ::wp_register_script
     1216     * @covers ::wp_enqueue_script
     1217     * @covers ::wp_script_add_data
     1218     *
     1219     * @dataProvider get_data_for_test_setting_in_footer_and_strategy
     1220     *
     1221     * @param callable     $set_up            Set up.
     1222     * @param int|false    $expected_group    Expected group.
     1223     * @param string|false $expected_strategy Expected strategy.
     1224     */
     1225    public function test_setting_in_footer_and_strategy( $set_up, $expected_group, $expected_strategy ) {
     1226        $handle = 'foo';
     1227        $set_up( $handle );
     1228        $this->assertSame( $expected_group, wp_scripts()->get_data( $handle, 'group' ) );
     1229        $this->assertSame( $expected_strategy, wp_scripts()->get_data( $handle, 'strategy' ) );
     1230    }
     1231
     1232    /**
     1233     * Tests that scripts print with no strategy when an incorrect strategy is passed during wp_register_script.
     1234     *
     1235     * For an invalid strategy defined during script registration, default to a blocking strategy.
     1236     *
     1237     * @ticket 12009
     1238     *
     1239     * @covers WP_Scripts::add_data
     1240     * @covers ::wp_register_script
     1241     * @covers ::wp_enqueue_script
     1242     *
     1243     * @expectedIncorrectUsage WP_Scripts::add_data
     1244     */
     1245    public function test_script_strategy_doing_it_wrong_via_register() {
     1246        wp_register_script( 'invalid-strategy', '/defaults.js', array(), null, array( 'strategy' => 'random-strategy' ) );
     1247        wp_enqueue_script( 'invalid-strategy' );
     1248
     1249        $this->assertSame(
     1250            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
     1251            get_echo( 'wp_print_scripts' )
     1252        );
     1253    }
     1254
     1255    /**
     1256     * Tests that scripts print with no strategy when an incorrect strategy is passed via wp_script_add_data().
     1257     *
     1258     * For an invalid strategy defined during script registration, default to a blocking strategy.
     1259     *
     1260     * @ticket 12009
     1261     *
     1262     * @covers WP_Scripts::add_data
     1263     * @covers ::wp_script_add_data
     1264     * @covers ::wp_register_script
     1265     * @covers ::wp_enqueue_script
     1266     *
     1267     * @expectedIncorrectUsage WP_Scripts::add_data
     1268     */
     1269    public function test_script_strategy_doing_it_wrong_via_add_data() {
     1270        wp_register_script( 'invalid-strategy', '/defaults.js', array(), null );
     1271        wp_script_add_data( 'invalid-strategy', 'strategy', 'random-strategy' );
     1272        wp_enqueue_script( 'invalid-strategy' );
     1273
     1274        $this->assertSame(
     1275            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
     1276            get_echo( 'wp_print_scripts' )
     1277        );
     1278    }
     1279
     1280    /**
     1281     * Tests that scripts print with no strategy when an incorrect strategy is passed during wp_enqueue_script.
     1282     *
     1283     * For an invalid strategy defined during script registration, default to a blocking strategy.
     1284     *
     1285     * @ticket 12009
     1286     *
     1287     * @covers WP_Scripts::add_data
     1288     * @covers ::wp_enqueue_script
     1289     *
     1290     * @expectedIncorrectUsage WP_Scripts::add_data
     1291     */
     1292    public function test_script_strategy_doing_it_wrong_via_enqueue() {
     1293        wp_enqueue_script( 'invalid-strategy', '/defaults.js', array(), null, array( 'strategy' => 'random-strategy' ) );
     1294
     1295        $this->assertSame(
     1296            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
     1297            get_echo( 'wp_print_scripts' )
     1298        );
     1299    }
     1300
     1301    /**
     1302     * Tests that scripts registered with a deferred strategy are not included in the script concat loading query.
     1303     *
     1304     * @ticket 12009
     1305     *
     1306     * @covers WP_Scripts::do_item
     1307     * @covers ::wp_enqueue_script
     1308     * @covers ::wp_register_script
     1309     */
     1310    public function test_concatenate_with_defer_strategy() {
     1311        global $wp_scripts, $concatenate_scripts, $wp_version;
     1312
     1313        $old_value           = $concatenate_scripts;
     1314        $concatenate_scripts = true;
     1315
     1316        $wp_scripts->do_concat    = true;
     1317        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1318
     1319        wp_register_script( 'one-concat-dep', $this->default_scripts_dir . 'script.js' );
     1320        wp_register_script( 'two-concat-dep', $this->default_scripts_dir . 'script.js' );
     1321        wp_register_script( 'three-concat-dep', $this->default_scripts_dir . 'script.js' );
     1322        wp_enqueue_script( 'main-defer-script', '/main-script.js', array( 'one-concat-dep', 'two-concat-dep', 'three-concat-dep' ), null, array( 'strategy' => 'defer' ) );
     1323
     1324        wp_print_scripts();
     1325        $print_scripts = get_echo( '_print_scripts' );
     1326
     1327        // Reset global before asserting.
     1328        $concatenate_scripts = $old_value;
     1329
     1330        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one-concat-dep,two-concat-dep,three-concat-dep&amp;ver={$wp_version}'></script>\n";
     1331        $expected .= "<script type='text/javascript' src='/main-script.js' id='main-defer-script-js' defer data-wp-strategy='defer'></script>\n";
     1332
     1333        $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with a "defer" loading strategy. Deferred scripts should not be part of the script concat loading query.' );
     1334    }
     1335
     1336    /**
     1337     * Test script concatenation with `async` main script.
     1338     *
     1339     * @ticket 12009
     1340     *
     1341     * @covers WP_Scripts::do_item
     1342     * @covers ::wp_enqueue_script
     1343     * @covers ::wp_register_script
     1344     */
     1345    public function test_concatenate_with_async_strategy() {
     1346        global $wp_scripts, $concatenate_scripts, $wp_version;
     1347
     1348        $old_value           = $concatenate_scripts;
     1349        $concatenate_scripts = true;
     1350
     1351        $wp_scripts->do_concat    = true;
     1352        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1353
     1354        wp_enqueue_script( 'one-concat-dep-1', $this->default_scripts_dir . 'script.js' );
     1355        wp_enqueue_script( 'two-concat-dep-1', $this->default_scripts_dir . 'script.js' );
     1356        wp_enqueue_script( 'three-concat-dep-1', $this->default_scripts_dir . 'script.js' );
     1357        wp_enqueue_script( 'main-async-script-1', '/main-script.js', array(), null, array( 'strategy' => 'async' ) );
     1358
     1359        wp_print_scripts();
     1360        $print_scripts = get_echo( '_print_scripts' );
     1361
     1362        // Reset global before asserting.
     1363        $concatenate_scripts = $old_value;
     1364
     1365        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one-concat-dep-1,two-concat-dep-1,three-concat-dep-1&amp;ver={$wp_version}'></script>\n";
     1366        $expected .= "<script type='text/javascript' src='/main-script.js' id='main-async-script-1-js' async data-wp-strategy='async'></script>\n";
     1367
     1368        $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with an "async" loading strategy. Async scripts should not be part of the script concat loading query.' );
     1369    }
     1370
     1371    /**
     1372     * Tests that script concatenation remains correct when a main script is registered as deferred after other blocking
     1373     * scripts are registered.
     1374     *
     1375     * @ticket 12009
     1376     *
     1377     * @covers WP_Scripts::do_item
     1378     * @covers ::wp_enqueue_script
     1379     * @covers ::wp_register_script
     1380     */
     1381    public function test_concatenate_with_blocking_script_before_and_after_script_with_defer_strategy() {
     1382        global $wp_scripts, $concatenate_scripts, $wp_version;
     1383
     1384        $old_value           = $concatenate_scripts;
     1385        $concatenate_scripts = true;
     1386
     1387        $wp_scripts->do_concat    = true;
     1388        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1389
     1390        wp_enqueue_script( 'one-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1391        wp_enqueue_script( 'two-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1392        wp_enqueue_script( 'three-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1393        wp_enqueue_script( 'deferred-script-2', '/main-script.js', array(), null, array( 'strategy' => 'defer' ) );
     1394        wp_enqueue_script( 'four-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1395        wp_enqueue_script( 'five-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1396        wp_enqueue_script( 'six-concat-dep-2', $this->default_scripts_dir . 'script.js' );
     1397
     1398        wp_print_scripts();
     1399        $print_scripts = get_echo( '_print_scripts' );
     1400
     1401        // Reset global before asserting.
     1402        $concatenate_scripts = $old_value;
     1403
     1404        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one-concat-dep-2,two-concat-dep-2,three-concat-dep-2,four-concat-dep-2,five-concat-dep-2,six-concat-dep-2&amp;ver={$wp_version}'></script>\n";
     1405        $expected .= "<script type='text/javascript' src='/main-script.js' id='deferred-script-2-js' defer data-wp-strategy='defer'></script>\n";
     1406
     1407        $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered as deferred after other blocking scripts are registered. Deferred scripts should not be part of the script concat loader query string. ' );
     1408    }
     1409
     1410    /**
    671411     * @ticket 42804
    681412     */
    691413    public function test_wp_enqueue_script_with_html5_support_does_not_contain_type_attribute() {
     1414        global $wp_version;
    701415        add_theme_support( 'html5', array( 'script' ) );
    711416
     
    751420        wp_enqueue_script( 'empty-deps-no-version', 'example.com' );
    761421
    77         $ver      = get_bloginfo( 'version' );
    78         $expected = "<script src='http://example.com?ver=$ver' id='empty-deps-no-version-js'></script>\n";
     1422        $expected = "<script src='http://example.com?ver={$wp_version}' id='empty-deps-no-version-js'></script>\n";
    791423
    801424        $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     
    841428     * Test the different protocol references in wp_enqueue_script
    851429     *
     1430     * @ticket 16560
     1431     *
    861432     * @global WP_Scripts $wp_scripts
    87      * @ticket 16560
    881433     */
    891434    public function test_protocols() {
    901435        // Init.
    91         global $wp_scripts;
     1436        global $wp_scripts, $wp_version;
    921437        $base_url_backup      = $wp_scripts->base_url;
    931438        $wp_scripts->base_url = 'http://example.com/wordpress';
    941439        $expected             = '';
    95         $ver                  = get_bloginfo( 'version' );
    961440
    971441        // Try with an HTTP reference.
    981442        wp_enqueue_script( 'jquery-http', 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' );
    99         $expected .= "<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver=$ver' id='jquery-http-js'></script>\n";
     1443        $expected .= "<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver={$wp_version}' id='jquery-http-js'></script>\n";
    1001444
    1011445        // Try with an HTTPS reference.
    1021446        wp_enqueue_script( 'jquery-https', 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' );
    103         $expected .= "<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver=$ver' id='jquery-https-js'></script>\n";
     1447        $expected .= "<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver={$wp_version}' id='jquery-https-js'></script>\n";
    1041448
    1051449        // Try with an automatic protocol reference (//).
    1061450        wp_enqueue_script( 'jquery-doubleslash', '//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' );
    107         $expected .= "<script type='text/javascript' src='//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver=$ver' id='jquery-doubleslash-js'></script>\n";
     1451        $expected .= "<script type='text/javascript' src='//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver={$wp_version}' id='jquery-doubleslash-js'></script>\n";
    1081452
    1091453        // Try with a local resource and an automatic protocol reference (//).
    1101454        $url = '//my_plugin/script.js';
    1111455        wp_enqueue_script( 'plugin-script', $url );
    112         $expected .= "<script type='text/javascript' src='$url?ver=$ver' id='plugin-script-js'></script>\n";
     1456        $expected .= "<script type='text/javascript' src='$url?ver={$wp_version}' id='plugin-script-js'></script>\n";
    1131457
    1141458        // Try with a bad protocol.
    1151459        wp_enqueue_script( 'jquery-ftp', 'ftp://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' );
    116         $expected .= "<script type='text/javascript' src='{$wp_scripts->base_url}ftp://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver=$ver' id='jquery-ftp-js'></script>\n";
     1460        $expected .= "<script type='text/javascript' src='{$wp_scripts->base_url}ftp://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver={$wp_version}' id='jquery-ftp-js'></script>\n";
    1171461
    1181462        // Go!
     
    1301474     */
    1311475    public function test_script_concatenation() {
    132         global $wp_scripts;
     1476        global $wp_scripts, $wp_version;
    1331477
    1341478        $wp_scripts->do_concat    = true;
    135         $wp_scripts->default_dirs = array( '/directory/' );
    136 
    137         wp_enqueue_script( 'one', '/directory/script.js' );
    138         wp_enqueue_script( 'two', '/directory/script.js' );
    139         wp_enqueue_script( 'three', '/directory/script.js' );
     1479        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1480
     1481        wp_enqueue_script( 'one', $this->default_scripts_dir . 'script.js' );
     1482        wp_enqueue_script( 'two', $this->default_scripts_dir . 'script.js' );
     1483        wp_enqueue_script( 'three', $this->default_scripts_dir . 'script.js' );
    1401484
    1411485        wp_print_scripts();
    1421486        $print_scripts = get_echo( '_print_scripts' );
    1431487
    144         $ver      = get_bloginfo( 'version' );
    145         $expected = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one,two,three&amp;ver={$ver}'></script>\n";
     1488        $expected = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one,two,three&amp;ver={$wp_version}'></script>\n";
    1461489
    1471490        $this->assertSame( $expected, $print_scripts );
     
    2061549
    2071550    /**
    208      * Testing `wp_script_add_data` with an anvalid key.
     1551     * Testing `wp_script_add_data` with an invalid key.
    2091552     *
    2101553     * @ticket 16024
     
    3341677        $expected_footer  = "<script type='text/javascript' src='/parent.js' id='parent-js'></script>\n";
    3351678
    336         $this->assertSame( $expected_header, $header );
    337         $this->assertSame( $expected_footer, $footer );
     1679        $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
     1680        $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
    3381681    }
    3391682
     
    3551698        $expected_footer .= "<script type='text/javascript' src='/parent.js' id='parent-js'></script>\n";
    3561699
    357         $this->assertSame( $expected_header, $header );
    358         $this->assertSame( $expected_footer, $footer );
     1700        $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
     1701        $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
    3591702    }
    3601703
     
    3861729        $expected_footer .= "<script type='text/javascript' src='/parent-footer.js' id='parent-footer-js'></script>\n";
    3871730
    388         $this->assertSame( $expected_header, $header );
    389         $this->assertSame( $expected_footer, $footer );
     1731        $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
     1732        $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
    3901733    }
    3911734
     
    4171760        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
    4181761
    419         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1762        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4201763    }
    4211764
     
    4301773        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    4311774
    432         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1775        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4331776    }
    4341777
     
    4451788        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    4461789
    447         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1790        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4481791    }
    4491792
     
    4581801        $expected = "<script type='text/javascript' id='test-example-js-before'>\nconsole.log(\"before\");\n</script>\n";
    4591802
    460         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1803        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4611804    }
    4621805
     
    4711814        $expected = "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    4721815
    473         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1816        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4741817    }
    4751818
     
    4861829        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    4871830
    488         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1831        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    4891832    }
    4901833
     
    5031846        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\nconsole.log(\"after\");\n</script>\n";
    5041847
    505         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1848        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    5061849    }
    5071850
     
    5201863        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    5211864
    522         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1865        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    5231866    }
    5241867
     
    5271870     */
    5281871    public function test_wp_add_inline_script_before_with_concat() {
    529         global $wp_scripts;
     1872        global $wp_scripts, $wp_version;
    5301873
    5311874        $wp_scripts->do_concat    = true;
    532         $wp_scripts->default_dirs = array( '/directory/' );
    533 
    534         wp_enqueue_script( 'one', '/directory/one.js' );
    535         wp_enqueue_script( 'two', '/directory/two.js' );
    536         wp_enqueue_script( 'three', '/directory/three.js' );
     1875        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1876
     1877        wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' );
     1878        wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' );
     1879        wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' );
    5371880
    5381881        wp_add_inline_script( 'one', 'console.log("before one");', 'before' );
    5391882        wp_add_inline_script( 'two', 'console.log("before two");', 'before' );
    5401883
    541         $ver       = get_bloginfo( 'version' );
    5421884        $expected  = "<script type='text/javascript' id='one-js-before'>\nconsole.log(\"before one\");\n</script>\n";
    543         $expected .= "<script type='text/javascript' src='/directory/one.js?ver={$ver}' id='one-js'></script>\n";
     1885        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}one.js?ver={$wp_version}' id='one-js'></script>\n";
    5441886        $expected .= "<script type='text/javascript' id='two-js-before'>\nconsole.log(\"before two\");\n</script>\n";
    545         $expected .= "<script type='text/javascript' src='/directory/two.js?ver={$ver}' id='two-js'></script>\n";
    546         $expected .= "<script type='text/javascript' src='/directory/three.js?ver={$ver}' id='three-js'></script>\n";
    547 
    548         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1887        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}two.js?ver={$wp_version}' id='two-js'></script>\n";
     1888        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}three.js?ver={$wp_version}' id='three-js'></script>\n";
     1889
     1890        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    5491891    }
    5501892
     
    5531895     */
    5541896    public function test_wp_add_inline_script_before_with_concat2() {
    555         global $wp_scripts;
     1897        global $wp_scripts, $wp_version;
    5561898
    5571899        $wp_scripts->do_concat    = true;
    558         $wp_scripts->default_dirs = array( '/directory/' );
    559 
    560         wp_enqueue_script( 'one', '/directory/one.js' );
    561         wp_enqueue_script( 'two', '/directory/two.js' );
    562         wp_enqueue_script( 'three', '/directory/three.js' );
     1900        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1901
     1902        wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' );
     1903        wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' );
     1904        wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' );
    5631905
    5641906        wp_add_inline_script( 'one', 'console.log("before one");', 'before' );
    5651907
    566         $ver       = get_bloginfo( 'version' );
    5671908        $expected  = "<script type='text/javascript' id='one-js-before'>\nconsole.log(\"before one\");\n</script>\n";
    568         $expected .= "<script type='text/javascript' src='/directory/one.js?ver={$ver}' id='one-js'></script>\n";
    569         $expected .= "<script type='text/javascript' src='/directory/two.js?ver={$ver}' id='two-js'></script>\n";
    570         $expected .= "<script type='text/javascript' src='/directory/three.js?ver={$ver}' id='three-js'></script>\n";
    571 
    572         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1909        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}one.js?ver={$wp_version}' id='one-js'></script>\n";
     1910        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}two.js?ver={$wp_version}' id='two-js'></script>\n";
     1911        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}three.js?ver={$wp_version}' id='three-js'></script>\n";
     1912
     1913        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    5731914    }
    5741915
     
    5771918     */
    5781919    public function test_wp_add_inline_script_after_with_concat() {
    579         global $wp_scripts;
     1920        global $wp_scripts, $wp_version;
    5801921
    5811922        $wp_scripts->do_concat    = true;
    582         $wp_scripts->default_dirs = array( '/directory/' );
    583 
    584         wp_enqueue_script( 'one', '/directory/one.js' );
    585         wp_enqueue_script( 'two', '/directory/two.js' );
    586         wp_enqueue_script( 'three', '/directory/three.js' );
    587         wp_enqueue_script( 'four', '/directory/four.js' );
     1923        $wp_scripts->default_dirs = array( $this->default_scripts_dir );
     1924
     1925        wp_enqueue_script( 'one', $this->default_scripts_dir . 'one.js' );
     1926        wp_enqueue_script( 'two', $this->default_scripts_dir . 'two.js' );
     1927        wp_enqueue_script( 'three', $this->default_scripts_dir . 'three.js' );
     1928        wp_enqueue_script( 'four', $this->default_scripts_dir . 'four.js' );
    5881929
    5891930        wp_add_inline_script( 'two', 'console.log("after two");' );
    5901931        wp_add_inline_script( 'three', 'console.log("after three");' );
    5911932
    592         $ver       = get_bloginfo( 'version' );
    593         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one&amp;ver={$ver}'></script>\n";
    594         $expected .= "<script type='text/javascript' src='/directory/two.js?ver={$ver}' id='two-js'></script>\n";
     1933        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one&amp;ver={$wp_version}'></script>\n";
     1934        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}two.js?ver={$wp_version}' id='two-js'></script>\n";
    5951935        $expected .= "<script type='text/javascript' id='two-js-after'>\nconsole.log(\"after two\");\n</script>\n";
    596         $expected .= "<script type='text/javascript' src='/directory/three.js?ver={$ver}' id='three-js'></script>\n";
     1936        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}three.js?ver={$wp_version}' id='three-js'></script>\n";
    5971937        $expected .= "<script type='text/javascript' id='three-js-after'>\nconsole.log(\"after three\");\n</script>\n";
    598         $expected .= "<script type='text/javascript' src='/directory/four.js?ver={$ver}' id='four-js'></script>\n";
    599 
    600         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1938        $expected .= "<script type='text/javascript' src='{$this->default_scripts_dir}four.js?ver={$wp_version}' id='four-js'></script>\n";
     1939
     1940        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    6011941    }
    6021942
     
    6271967
    6281968        $this->assertSame( $expected_localized, get_echo( 'wp_print_scripts' ) );
    629         $this->assertSame( $expected, $wp_scripts->print_html );
     1969        $this->assertEqualMarkup( $expected, $wp_scripts->print_html );
    6301970        $this->assertTrue( $wp_scripts->do_concat );
    6311971    }
     
    6351975     */
    6361976    public function test_wp_add_inline_script_after_with_concat_and_core_dependency() {
    637         global $wp_scripts;
     1977        global $wp_scripts, $wp_version;
    6381978
    6391979        wp_default_scripts( $wp_scripts );
     
    6421982        $wp_scripts->do_concat = true;
    6431983
    644         $ver       = get_bloginfo( 'version' );
    645         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$ver}'></script>\n";
     1984        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$wp_version}'></script>\n";
    6461985        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
    6471986        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
     
    6531992        $print_scripts = get_echo( '_print_scripts' );
    6541993
    655         $this->assertSame( $expected, $print_scripts );
     1994        $this->assertEqualMarkup( $expected, $print_scripts );
    6561995    }
    6571996
     
    6601999     */
    6612000    public function test_wp_add_inline_script_after_with_concat_and_conditional_and_core_dependency() {
    662         global $wp_scripts;
     2001        global $wp_scripts, $wp_version;
    6632002
    6642003        wp_default_scripts( $wp_scripts );
     
    6672006        $wp_scripts->do_concat = true;
    6682007
    669         $ver       = get_bloginfo( 'version' );
    670         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$ver}'></script>\n";
     2008        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$wp_version}'></script>\n";
    6712009        $expected .= "<!--[if gte IE 9]>\n";
    6722010        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
     
    6812019        $print_scripts = get_echo( '_print_scripts' );
    6822020
    683         $this->assertSame( $expected, $print_scripts );
     2021        $this->assertEqualMarkup( $expected, $print_scripts );
    6842022    }
    6852023
     
    6882026     */
    6892027    public function test_wp_add_inline_script_before_with_concat_and_core_dependency() {
    690         global $wp_scripts;
     2028        global $wp_scripts, $wp_version;
    6912029
    6922030        wp_default_scripts( $wp_scripts );
     
    6962034        $wp_scripts->do_concat = true;
    6972035
    698         $ver       = get_bloginfo( 'version' );
    699         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$ver}'></script>\n";
     2036        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate&amp;ver={$wp_version}'></script>\n";
    7002037        $expected .= "<script type='text/javascript' id='test-example-js-before'>\nconsole.log(\"before\");\n</script>\n";
    7012038        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
     
    7072044        $print_scripts = get_echo( '_print_scripts' );
    7082045
    709         $this->assertSame( $expected, $print_scripts );
     2046        $this->assertEqualMarkup( $expected, $print_scripts );
    7102047    }
    7112048
     
    7142051     */
    7152052    public function test_wp_add_inline_script_before_after_concat_with_core_dependency() {
    716         global $wp_scripts;
     2053        global $wp_scripts, $wp_version;
    7172054
    7182055        wp_default_scripts( $wp_scripts );
     
    7222059        $wp_scripts->do_concat = true;
    7232060
    724         $ver       = get_bloginfo( 'version' );
    725         $suffix    = wp_scripts_get_suffix();
    726         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate,wp-polyfill-inert,regenerator-runtime,wp-polyfill,wp-dom-ready,wp-hooks&amp;ver={$ver}'></script>\n";
     2061        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core,jquery-migrate,wp-polyfill-inert,regenerator-runtime,wp-polyfill,wp-dom-ready,wp-hooks&amp;ver={$wp_version}'></script>\n";
    7272062        $expected .= "<script type='text/javascript' id='test-example-js-before'>\nconsole.log(\"before\");\n</script>\n";
    7282063        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
     
    7592094        );
    7602095
    761         $this->assertSameIgnoreEOL( $expected, $print_scripts );
     2096        $this->assertEqualMarkup( $expected, $print_scripts );
    7622097    }
    7632098
     
    7912126
    7922127        $tail = substr( $print_scripts, strrpos( $print_scripts, "<script type='text/javascript' src='/customize-dependency.js' id='customize-dependency-js'>" ) );
    793         $this->assertSame( $expected_tail, $tail );
     2128        $this->assertEqualMarkup( $expected_tail, $tail );
    7942129    }
    7952130
     
    7982133     */
    7992134    public function test_wp_add_inline_script_after_for_core_scripts_with_concat_is_limited_and_falls_back_to_no_concat() {
    800         global $wp_scripts;
     2135        global $wp_scripts, $wp_version;
    8012136
    8022137        $wp_scripts->do_concat    = true;
     
    8092144        wp_enqueue_script( 'four', '/wp-includes/js/script4.js' );
    8102145
    811         $ver       = get_bloginfo( 'version' );
    812         $expected  = "<script type='text/javascript' src='/wp-includes/js/script.js?ver={$ver}' id='one-js'></script>\n";
     2146        $expected  = "<script type='text/javascript' src='/wp-includes/js/script.js?ver={$wp_version}' id='one-js'></script>\n";
    8132147        $expected .= "<script type='text/javascript' id='one-js-after'>\nconsole.log(\"after one\");\n</script>\n";
    814         $expected .= "<script type='text/javascript' src='/wp-includes/js/script2.js?ver={$ver}' id='two-js'></script>\n";
    815         $expected .= "<script type='text/javascript' src='/wp-includes/js/script3.js?ver={$ver}' id='three-js'></script>\n";
    816         $expected .= "<script type='text/javascript' src='/wp-includes/js/script4.js?ver={$ver}' id='four-js'></script>\n";
    817 
    818         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     2148        $expected .= "<script type='text/javascript' src='/wp-includes/js/script2.js?ver={$wp_version}' id='two-js'></script>\n";
     2149        $expected .= "<script type='text/javascript' src='/wp-includes/js/script3.js?ver={$wp_version}' id='three-js'></script>\n";
     2150        $expected .= "<script type='text/javascript' src='/wp-includes/js/script4.js?ver={$wp_version}' id='four-js'></script>\n";
     2151
     2152        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    8192153    }
    8202154
     
    8232157     */
    8242158    public function test_wp_add_inline_script_before_third_core_script_prints_two_concat_scripts() {
    825         global $wp_scripts;
     2159        global $wp_scripts, $wp_version;
    8262160
    8272161        $wp_scripts->do_concat    = true;
     
    8342168        wp_enqueue_script( 'four', '/wp-includes/js/script4.js' );
    8352169
    836         $ver       = get_bloginfo( 'version' );
    837         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one,two&amp;ver={$ver}'></script>\n";
     2170        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=one,two&amp;ver={$wp_version}'></script>\n";
    8382171        $expected .= "<script type='text/javascript' id='three-js-before'>\nconsole.log(\"before three\");\n</script>\n";
    839         $expected .= "<script type='text/javascript' src='/wp-includes/js/script3.js?ver={$ver}' id='three-js'></script>\n";
    840         $expected .= "<script type='text/javascript' src='/wp-includes/js/script4.js?ver={$ver}' id='four-js'></script>\n";
    841 
    842         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     2172        $expected .= "<script type='text/javascript' src='/wp-includes/js/script3.js?ver={$wp_version}' id='three-js'></script>\n";
     2173        $expected .= "<script type='text/javascript' src='/wp-includes/js/script4.js?ver={$wp_version}' id='four-js'></script>\n";
     2174
     2175        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
     2176    }
     2177
     2178    /**
     2179     * Data provider to test get_inline_script_data and get_inline_script_tag.
     2180     *
     2181     * @return array[]
     2182     */
     2183    public function data_provider_to_test_get_inline_script() {
     2184        return array(
     2185            'before-blocking' => array(
     2186                'position'       => 'before',
     2187                'inline_scripts' => array(
     2188                    '/*before foo 1*/',
     2189                ),
     2190                'delayed'        => false,
     2191                'expected_data'  => '/*before foo 1*/',
     2192                'expected_tag'   => "<script id='foo-js-before' type='text/javascript'>\n/*before foo 1*/\n</script>\n",
     2193            ),
     2194            'after-blocking'  => array(
     2195                'position'       => 'after',
     2196                'inline_scripts' => array(
     2197                    '/*after foo 1*/',
     2198                    '/*after foo 2*/',
     2199                ),
     2200                'delayed'        => false,
     2201                'expected_data'  => "/*after foo 1*/\n/*after foo 2*/",
     2202                'expected_tag'   => "<script id='foo-js-after' type='text/javascript'>\n/*after foo 1*/\n/*after foo 2*/\n</script>\n",
     2203            ),
     2204            'before-delayed'  => array(
     2205                'position'       => 'before',
     2206                'inline_scripts' => array(
     2207                    '/*before foo 1*/',
     2208                ),
     2209                'delayed'        => true,
     2210                'expected_data'  => '/*before foo 1*/',
     2211                'expected_tag'   => "<script id='foo-js-before' type='text/javascript'>\n/*before foo 1*/\n</script>\n",
     2212            ),
     2213            'after-delayed'   => array(
     2214                'position'       => 'after',
     2215                'inline_scripts' => array(
     2216                    '/*after foo 1*/',
     2217                    '/*after foo 2*/',
     2218                ),
     2219                'delayed'        => true,
     2220                'expected_data'  => "/*after foo 1*/\n/*after foo 2*/",
     2221                'expected_tag'   => "<script id='foo-js-after' type='text/javascript'>\n/*after foo 1*/\n/*after foo 2*/\n</script>\n",
     2222            ),
     2223        );
     2224    }
     2225
     2226    /**
     2227     * Test getting inline scripts.
     2228     *
     2229     * @covers WP_Scripts::get_inline_script_data
     2230     * @covers WP_Scripts::get_inline_script_tag
     2231     * @covers WP_Scripts::print_inline_script
     2232     *
     2233     * @expectedDeprecated WP_Scripts::print_inline_script
     2234     *
     2235     * @dataProvider data_provider_to_test_get_inline_script
     2236     *
     2237     * @param string   $position       Position.
     2238     * @param string[] $inline_scripts Inline scripts.
     2239     * @param bool     $delayed        Delayed.
     2240     * @param string   $expected_data  Expected data.
     2241     * @param string   $expected_tag   Expected tag.
     2242     */
     2243    public function test_get_inline_script( $position, $inline_scripts, $delayed, $expected_data, $expected_tag ) {
     2244        global $wp_scripts;
     2245
     2246        $deps = array();
     2247        if ( $delayed ) {
     2248            $wp_scripts->add( 'dep', 'https://example.com/dependency.js', array(), false ); // TODO: Cannot pass strategy to $args e.g. array( 'strategy' => 'defer' )
     2249            $wp_scripts->add_data( 'dep', 'strategy', 'defer' );
     2250            $deps[] = 'dep';
     2251        }
     2252
     2253        $handle = 'foo';
     2254        $wp_scripts->add( $handle, 'https://example.com/foo.js', $deps );
     2255        if ( $delayed ) {
     2256            $wp_scripts->add_data( $handle, 'strategy', 'defer' );
     2257        }
     2258
     2259        $this->assertSame( '', $wp_scripts->get_inline_script_data( $handle, $position ) );
     2260        $this->assertSame( '', $wp_scripts->get_inline_script_tag( $handle, $position ) );
     2261        $this->assertFalse( $wp_scripts->print_inline_script( $handle, $position, false ) );
     2262        ob_start();
     2263        $output = $wp_scripts->print_inline_script( $handle, $position, true );
     2264        $this->assertSame( '', ob_get_clean() );
     2265        $this->assertFalse( $output );
     2266
     2267        foreach ( $inline_scripts as $inline_script ) {
     2268            $wp_scripts->add_inline_script( $handle, $inline_script, $position );
     2269        }
     2270
     2271        $this->assertSame( $expected_data, $wp_scripts->get_inline_script_data( $handle, $position ) );
     2272        $this->assertSame( $expected_data, $wp_scripts->print_inline_script( $handle, $position, false ) );
     2273        $this->assertEqualMarkup(
     2274            $expected_tag,
     2275            $wp_scripts->get_inline_script_tag( $handle, $position )
     2276        );
     2277        ob_start();
     2278        $output = $wp_scripts->print_inline_script( $handle, $position, true );
     2279        $this->assertEqualMarkup( $expected_tag, ob_get_clean() );
     2280        $this->assertEquals( $expected_data, $output );
    8432281    }
    8442282
     
    10442482     *
    10452483     * @ticket 41871
     2484     *
    10462485     * @covers ::wp_enqueue_code_editor
    10472486     */
     
    11312570     *
    11322571     * @ticket 41871
     2572     *
    11332573     * @covers ::wp_enqueue_code_editor
    11342574     */
     
    12142654     *
    12152655     * @ticket 41871
     2656     *
    12162657     * @covers ::wp_enqueue_code_editor
    12172658     */
     
    13112752     *
    13122753     * @ticket 41871
     2754     *
    13132755     * @covers ::wp_enqueue_code_editor
    13142756     */
     
    14032845    /**
    14042846     * @ticket 52534
     2847     *
    14052848     * @covers ::wp_localize_script
    14062849     *
     
    14572900    /**
    14582901     * @ticket 55628
     2902     *
    14592903     * @covers ::wp_set_script_translations
    14602904     */
    14612905    public function test_wp_external_wp_i18n_print_order() {
    1462         global $wp_scripts;
     2906        global $wp_scripts, $wp_version;
    14632907
    14642908        $wp_scripts->do_concat    = true;
     
    14812925
    14822926        // The non-default script should end concatenation and maintain order.
    1483         $ver       = get_bloginfo( 'version' );
    1484         $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core&amp;ver={$ver}'></script>\n";
     2927        $expected  = "<script type='text/javascript' src='/wp-admin/load-scripts.php?c=0&amp;load%5Bchunk_0%5D=jquery-core&amp;ver={$wp_version}'></script>\n";
    14852928        $expected .= "<script type='text/javascript' src='/plugins/wp-i18n.js' id='wp-i18n-js'></script>\n";
    14862929        $expected .= "<script type='text/javascript' src='/default/common.js' id='common-js'></script>\n";
     
    14882931        $this->assertSame( $expected, $print_scripts );
    14892932    }
     2933
     2934    /**
     2935     * Parse an HTML markup fragment.
     2936     *
     2937     * @param string $markup Markup.
     2938     * @return DOMElement Body element wrapping supplied markup fragment.
     2939     */
     2940    protected function parse_markup_fragment( $markup ) {
     2941        $dom = new DOMDocument();
     2942        $dom->loadHTML(
     2943            "<!DOCTYPE html><html><head><meta charset=utf8></head><body>{$markup}</body></html>"
     2944        );
     2945
     2946        /** @var DOMElement $body */
     2947        $body = $dom->getElementsByTagName( 'body' )->item( 0 );
     2948
     2949        // Trim whitespace nodes added before/after which can be added when parsing.
     2950        foreach ( array( $body->firstChild, $body->lastChild ) as $node ) {
     2951            if ( $node instanceof DOMText && '' === trim( $node->data ) ) {
     2952                $body->removeChild( $node );
     2953            }
     2954        }
     2955
     2956        return $body;
     2957    }
     2958
     2959    /**
     2960     * Assert markup is equal.
     2961     *
     2962     * @param string $expected Expected markup.
     2963     * @param string $actual   Actual markup.
     2964     * @param string $message  Message.
     2965     */
     2966    protected function assertEqualMarkup( $expected, $actual, $message = '' ) {
     2967        $this->assertEquals(
     2968            $this->parse_markup_fragment( $expected ),
     2969            $this->parse_markup_fragment( $actual ),
     2970            $message
     2971        );
     2972    }
    14902973}
Note: See TracChangeset for help on using the changeset viewer.