Make WordPress Core

Changeset 56033


Ignore:
Timestamp:
06/26/2023 01:40:31 PM (3 years 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.