🔒 EXCLUSIVE: Changeset/ - Collection

Changeset 60704


Ignore:
Timestamp:
09/03/2025 10:15:31 PM (4 months ago)
Author:
westonruter
Message:

Script Loader: Introduce fetchpriority for Scripts and Script Modules.

  • Allow scripts and script modules to be registered with a fetchpriority of auto (default), high, low:
    • When registering a script, add a fetchpriority arg to go alongside the strategy arg which was added for loading scripts with the defer and async loading strategies. See #12009.
    • For script modules, introduce an $args array parameter with a fetchpriority key to the wp_register_script_module(), and wp_enqueue_script_module() functions (and their respective underlying WP_Script_Modules::register() and WP_Script_Modules::enqueue() methods). This $args parameter corresponds with the same parameter used when registering non-module scripts.
    • Also for script modules, introduce WP_Script_Modules::set_fetchpriority() to override the fetchpriority for what was previously registered.
    • Emit a _doing_it_wrong() warning when an invalid fetchpriority value is used, and when fetchpriority is added to a script alias.
    • Include fetchpriority as an attribute on printed SCRIPT tags as well as on preload LINK tags for static script module dependencies.
  • Use a fetchpriority of low by default for:
    • Script modules used with the Interactivity API. For overriding this default in blocks, see Gutenberg#71366.
    • The comment-reply script.
  • Improve type checks and type hints.

Developed in GitHub PR, with companion for Gutenberg.

Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi.
Fixes #61734.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r60511 r60704  
    176176    $module_version      = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version;
    177177
     178    // Blocks using the Interactivity API are server-side rendered, so they are by design not in the critical rendering path and should be deprioritized.
     179    $args = array();
     180    if (
     181        ( isset( $metadata['supports']['interactivity'] ) && true === $metadata['supports']['interactivity'] ) ||
     182        ( isset( $metadata['supports']['interactivity']['interactive'] ) && true === $metadata['supports']['interactivity']['interactive'] )
     183    ) {
     184        $args['fetchpriority'] = 'low';
     185    }
     186
    178187    wp_register_script_module(
    179188        $module_id,
    180189        $module_uri,
    181190        $module_dependencies,
    182         $module_version
     191        $module_version,
     192        $args
    183193    );
    184194
  • trunk/src/wp-includes/class-wp-script-modules.php

    r60681 r60704  
    4747     *
    4848     * @since 6.5.0
     49     * @since 6.9.0 Added the $args parameter.
    4950     *
    5051     * @param string            $id       The identifier of the script module. Should be unique. It will be used in the
     
    7273     *                                    is set to false, the version number is the currently installed WordPress version.
    7374     *                                    If $version is set to null, no version is added.
    74      */
    75     public function register( string $id, string $src, array $deps = array(), $version = false ) {
     75     * @param array             $args     {
     76     *     Optional. An array of additional args. Default empty array.
     77     *
     78     *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
     79     * }
     80     */
     81    public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) {
    7682        if ( ! isset( $this->registered[ $id ] ) ) {
    7783            $dependencies = array();
    7884            foreach ( $deps as $dependency ) {
    7985                if ( is_array( $dependency ) ) {
    80                     if ( ! isset( $dependency['id'] ) ) {
     86                    if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) {
    8187                        _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
    8288                        continue;
     
    96102            }
    97103
     104            $fetchpriority = 'auto';
     105            if ( isset( $args['fetchpriority'] ) ) {
     106                if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) {
     107                    $fetchpriority = $args['fetchpriority'];
     108                } else {
     109                    _doing_it_wrong(
     110                        __METHOD__,
     111                        sprintf(
     112                            /* translators: 1: $fetchpriority, 2: $id */
     113                            __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
     114                            is_string( $args['fetchpriority'] ) ? $args['fetchpriority'] : gettype( $args['fetchpriority'] ),
     115                            $id
     116                        ),
     117                        '6.9.0'
     118                    );
     119                }
     120            }
     121
    98122            $this->registered[ $id ] = array(
    99                 'src'          => $src,
    100                 'version'      => $version,
    101                 'enqueue'      => isset( $this->enqueued_before_registered[ $id ] ),
    102                 'dependencies' => $dependencies,
     123                'src'           => $src,
     124                'version'       => $version,
     125                'enqueue'       => isset( $this->enqueued_before_registered[ $id ] ),
     126                'dependencies'  => $dependencies,
     127                'fetchpriority' => $fetchpriority,
    103128            );
    104129        }
     130    }
     131
     132    /**
     133     * Checks if the provided fetchpriority is valid.
     134     *
     135     * @since 6.9.0
     136     *
     137     * @param string|mixed $priority Fetch priority.
     138     * @return bool Whether valid fetchpriority.
     139     */
     140    private function is_valid_fetchpriority( $priority ): bool {
     141        return in_array( $priority, array( 'auto', 'low', 'high' ), true );
     142    }
     143
     144    /**
     145     * Sets the fetch priority for a script module.
     146     *
     147     * @since 6.9.0
     148     *
     149     * @param string              $id       Script module identifier.
     150     * @param 'auto'|'low'|'high' $priority Fetch priority for the script module.
     151     * @return bool Whether setting the fetchpriority was successful.
     152     */
     153    public function set_fetchpriority( string $id, string $priority ): bool {
     154        if ( ! isset( $this->registered[ $id ] ) ) {
     155            return false;
     156        }
     157
     158        if ( '' === $priority ) {
     159            $priority = 'auto';
     160        }
     161
     162        if ( ! $this->is_valid_fetchpriority( $priority ) ) {
     163            _doing_it_wrong(
     164                __METHOD__,
     165                /* translators: %s: Invalid fetchpriority. */
     166                sprintf( __( 'Invalid fetchpriority: %s' ), $priority ),
     167                '6.9.0'
     168            );
     169            return false;
     170        }
     171
     172        $this->registered[ $id ]['fetchpriority'] = $priority;
     173        return true;
    105174    }
    106175
     
    112181     *
    113182     * @since 6.5.0
     183     * @since 6.9.0 Added the $args parameter.
    114184     *
    115185     * @param string            $id       The identifier of the script module. Should be unique. It will be used in the
     
    137207     *                                    is set to false, the version number is the currently installed WordPress version.
    138208     *                                    If $version is set to null, no version is added.
    139      */
    140     public function enqueue( string $id, string $src = '', array $deps = array(), $version = false ) {
     209     * @param array             $args     {
     210     *     Optional. An array of additional args. Default empty array.
     211     *
     212     *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
     213     * }
     214     */
     215    public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
    141216        if ( isset( $this->registered[ $id ] ) ) {
    142217            $this->registered[ $id ]['enqueue'] = true;
    143218        } elseif ( $src ) {
    144             $this->register( $id, $src, $deps, $version );
     219            $this->register( $id, $src, $deps, $version, $args );
    145220            $this->registered[ $id ]['enqueue'] = true;
    146221        } else {
     
    209284    public function print_enqueued_script_modules() {
    210285        foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) {
    211             wp_print_script_tag(
    212                 array(
    213                     'type' => 'module',
    214                     'src'  => $this->get_src( $id ),
    215                     'id'   => $id . '-js-module',
    216                 )
     286            $args = array(
     287                'type' => 'module',
     288                'src'  => $this->get_src( $id ),
     289                'id'   => $id . '-js-module',
    217290            );
     291            if ( 'auto' !== $script_module['fetchpriority'] ) {
     292                $args['fetchpriority'] = $script_module['fetchpriority'];
     293            }
     294            wp_print_script_tag( $args );
    218295        }
    219296    }
     
    232309            if ( true !== $script_module['enqueue'] ) {
    233310                echo sprintf(
    234                     '<link rel="modulepreload" href="%s" id="%s">',
     311                    '<link rel="modulepreload" href="%s" id="%s"%s>',
    235312                    esc_url( $this->get_src( $id ) ),
    236                     esc_attr( $id . '-js-modulepreload' )
     313                    esc_attr( $id . '-js-modulepreload' ),
     314                    'auto' !== $script_module['fetchpriority'] ? sprintf( ' fetchpriority="%s"', esc_attr( $script_module['fetchpriority'] ) ) : ''
    237315                );
    238316            }
     
    279357     * @since 6.5.0
    280358     *
    281      * @return array[] Script modules marked for enqueue, keyed by script module identifier.
     359     * @return array<string, array> Script modules marked for enqueue, keyed by script module identifier.
    282360     */
    283361    private function get_marked_for_enqueue(): array {
  • trunk/src/wp-includes/class-wp-scripts.php

    r60690 r60704  
    425425        if ( $intended_strategy ) {
    426426            $attr['data-wp-strategy'] = $intended_strategy;
     427        }
     428        if ( isset( $obj->extra['fetchpriority'] ) && 'auto' !== $obj->extra['fetchpriority'] && $this->is_valid_fetchpriority( $obj->extra['fetchpriority'] ) ) {
     429            $attr['fetchpriority'] = $obj->extra['fetchpriority'];
    427430        }
    428431        $tag  = $translations . $ie_conditional_prefix . $before_script;
     
    832835                return false;
    833836            }
     837        } elseif ( 'fetchpriority' === $key ) {
     838            if ( empty( $value ) ) {
     839                $value = 'auto';
     840            }
     841            if ( ! $this->is_valid_fetchpriority( $value ) ) {
     842                _doing_it_wrong(
     843                    __METHOD__,
     844                    sprintf(
     845                        /* translators: 1: $fetchpriority, 2: $handle */
     846                        __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
     847                        is_string( $value ) ? $value : gettype( $value ),
     848                        $handle
     849                    ),
     850                    '6.9.0'
     851                );
     852                return false;
     853            } elseif ( ! $this->registered[ $handle ]->src ) {
     854                _doing_it_wrong(
     855                    __METHOD__,
     856                    sprintf(
     857                        /* translators: 1: $fetchpriority, 2: $handle */
     858                        __( 'Cannot supply a fetchpriority `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
     859                        is_string( $value ) ? $value : gettype( $value ),
     860                        $handle
     861                    ),
     862                    '6.9.0'
     863                );
     864                return false;
     865            }
    834866        }
    835867        return parent::add_data( $handle, $key, $value );
     
    870902     * @since 6.3.0
    871903     *
    872      * @param string $strategy The strategy to check.
     904     * @param string|mixed $strategy The strategy to check.
    873905     * @return bool True if $strategy is one of the delayed strategies, otherwise false.
    874906     */
    875     private function is_delayed_strategy( $strategy ) {
     907    private function is_delayed_strategy( $strategy ): bool {
    876908        return in_array(
    877909            $strategy,
     
    879911            true
    880912        );
     913    }
     914
     915    /**
     916     * Checks if the provided fetchpriority is valid.
     917     *
     918     * @since 6.9.0
     919     *
     920     * @param string|mixed $priority Fetch priority.
     921     * @return bool Whether valid fetchpriority.
     922     */
     923    private function is_valid_fetchpriority( $priority ): bool {
     924        return in_array( $priority, array( 'auto', 'low', 'high' ), true );
    881925    }
    882926
  • trunk/src/wp-includes/functions.wp-scripts.php

    r58200 r60704  
    159159 * @since 4.3.0 A return value was added.
    160160 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
     161 * @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
    161162 *
    162163 * @param string           $handle    Name of the script. Should be unique.
     
    172173 *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
    173174 *
    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'.
     175 *     @type string    $strategy      Optional. If provided, may be either 'defer' or 'async'.
     176 *     @type bool      $in_footer     Optional. Whether to print the script in the footer. Default 'false'.
     177 *     @type string    $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
    176178 * }
    177179 * @return bool Whether the script has been registered. True on success, false on failure.
     
    193195    if ( ! empty( $args['strategy'] ) ) {
    194196        $wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
     197    }
     198    if ( ! empty( $args['fetchpriority'] ) ) {
     199        $wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
    195200    }
    196201    return $registered;
     
    340345 * @since 2.1.0
    341346 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
     347 * @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
    342348 *
    343349 * @param string           $handle    Name of the script. Should be unique.
     
    353359 *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
    354360 *
    355  *     @type string    $strategy     Optional. If provided, may be either 'defer' or 'async'.
    356  *     @type bool      $in_footer    Optional. Whether to print the script in the footer. Default 'false'.
     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 *     @type string    $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
    357364 * }
    358365 */
     
    379386            $wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] );
    380387        }
     388        if ( ! empty( $args['fetchpriority'] ) ) {
     389            $wp_scripts->add_data( $_handle[0], 'fetchpriority', $args['fetchpriority'] );
     390        }
    381391    }
    382392
  • trunk/src/wp-includes/script-loader.php

    r60681 r60704  
    10481048
    10491049    $scripts->add( 'comment-reply', "/wp-includes/js/comment-reply$suffix.js", array(), false, 1 );
    1050     did_action( 'init' ) && $scripts->add_data( 'comment-reply', 'strategy', 'async' );
     1050    if ( did_action( 'init' ) ) {
     1051        $scripts->add_data( 'comment-reply', 'strategy', 'async' );
     1052        $scripts->add_data( 'comment-reply', 'fetchpriority', 'low' ); // In Chrome this is automatically low due to the async strategy, but in Firefox and Safari the priority is normal/medium.
     1053    }
    10511054
    10521055    $scripts->add( 'json2', "/wp-includes/js/json2$suffix.js", array(), '2015-05-03' );
  • trunk/src/wp-includes/script-modules.php

    r59223 r60704  
    3636 *
    3737 * @since 6.5.0
     38 * @since 6.9.0 Added the $args parameter.
    3839 *
    3940 * @param string            $id      The identifier of the script module. Should be unique. It will be used in the
     
    6162 *                                   is set to false, the version number is the currently installed WordPress version.
    6263 *                                   If $version is set to null, no version is added.
     64 * @param array             $args    {
     65 *     Optional. An array of additional args. Default empty array.
     66 *
     67 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
     68 * }
    6369 */
    64 function wp_register_script_module( string $id, string $src, array $deps = array(), $version = false ) {
    65     wp_script_modules()->register( $id, $src, $deps, $version );
     70function wp_register_script_module( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) {
     71    wp_script_modules()->register( $id, $src, $deps, $version, $args );
    6672}
    6773
     
    7379 *
    7480 * @since 6.5.0
     81 * @since 6.9.0 Added the $args parameter.
    7582 *
    7683 * @param string            $id      The identifier of the script module. Should be unique. It will be used in the
     
    98105 *                                   is set to false, the version number is the currently installed WordPress version.
    99106 *                                   If $version is set to null, no version is added.
     107 * @param array             $args    {
     108 *     Optional. An array of additional args. Default empty array.
     109 *
     110 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
     111 * }
    100112 */
    101 function wp_enqueue_script_module( string $id, string $src = '', array $deps = array(), $version = false ) {
    102     wp_script_modules()->enqueue( $id, $src, $deps, $version );
     113function wp_enqueue_script_module( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
     114    wp_script_modules()->enqueue( $id, $src, $deps, $version, $args );
    103115}
    104116
     
    170182        }
    171183
     184        // The Interactivity API is designed with server-side rendering as its primary goal, so all of its script modules should be loaded with low fetch priority since they should not be needed in the critical rendering path.
     185        $args = array();
     186        if ( str_starts_with( $script_module_id, '@wordpress/interactivity' ) || str_starts_with( $script_module_id, '@wordpress/block-library' ) ) {
     187            $args['fetchpriority'] = 'low';
     188        }
     189
    172190        $path = includes_url( "js/dist/script-modules/{$file_name}" );
    173         wp_register_script_module( $script_module_id, $path, $script_module_data['dependencies'], $script_module_data['version'] );
     191        wp_register_script_module( $script_module_id, $path, $script_module_data['dependencies'], $script_module_data['version'], $args );
    174192    }
    175193}
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r60690 r60704  
    11281128     * Tests that dependents that are async but attached to a deferred main script, print with defer as opposed to async.
    11291129     *
     1130     * Also tests that fetchpriority attributes are added as expected.
     1131     *
    11301132     * @ticket 12009
     1133     * @ticket 61734
    11311134     *
    11321135     * @covers WP_Scripts::do_item
    11331136     * @covers WP_Scripts::get_eligible_loading_strategy
     1137     * @covers ::wp_register_script
    11341138     * @covers ::wp_enqueue_script
    11351139     */
    11361140    public function test_defer_with_async_dependent() {
    11371141        // case with one async dependent.
    1138         wp_enqueue_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
    1139         wp_enqueue_script( 'dependent-script-d4-1', '/dependent-script-d4-1.js', array( 'main-script-d4' ), null, array( 'strategy' => 'defer' ) );
    1140         wp_enqueue_script( 'dependent-script-d4-2', '/dependent-script-d4-2.js', array( 'dependent-script-d4-1' ), null, array( 'strategy' => 'async' ) );
    1141         wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
     1142        wp_register_script( 'main-script-d4', '/main-script-d4.js', array(), null, array( 'strategy' => 'defer' ) );
     1143        wp_enqueue_script(
     1144            'dependent-script-d4-1',
     1145            '/dependent-script-d4-1.js',
     1146            array( 'main-script-d4' ),
     1147            null,
     1148            array(
     1149                'strategy'      => 'defer',
     1150                'fetchpriority' => 'auto',
     1151            )
     1152        );
     1153        wp_enqueue_script(
     1154            'dependent-script-d4-2',
     1155            '/dependent-script-d4-2.js',
     1156            array( 'dependent-script-d4-1' ),
     1157            null,
     1158            array(
     1159                'strategy'      => 'async',
     1160                'fetchpriority' => 'low',
     1161            )
     1162        );
     1163        wp_enqueue_script(
     1164            'dependent-script-d4-3',
     1165            '/dependent-script-d4-3.js',
     1166            array( 'dependent-script-d4-2' ),
     1167            null,
     1168            array(
     1169                'strategy'      => 'defer',
     1170                'fetchpriority' => 'high',
     1171            )
     1172        );
    11421173        $output    = get_echo( 'wp_print_scripts' );
    11431174        $expected  = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' defer='defer' data-wp-strategy='defer'></script>\n";
    11441175        $expected .= "<script type='text/javascript' src='/dependent-script-d4-1.js' id='dependent-script-d4-1-js' defer='defer' data-wp-strategy='defer'></script>\n";
    1145         $expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer='defer' data-wp-strategy='async'></script>\n";
    1146         $expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer='defer' data-wp-strategy='defer'></script>\n";
     1176        $expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer='defer' data-wp-strategy='async' fetchpriority='low'></script>\n";
     1177        $expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer='defer' data-wp-strategy='defer' fetchpriority='high'></script>\n";
    11471178
    11481179        $this->assertEqualHTML( $expected, $output, '<body>', 'Scripts registered as defer but that have dependents that are async are expected to have said dependents deferred.' );
     1180    }
     1181
     1182    /**
     1183     * Data provider for test_fetchpriority_values.
     1184     *
     1185     * @return array<string, array{fetchpriority: string}>
     1186     */
     1187    public function data_provider_fetchpriority_values(): array {
     1188        return array(
     1189            'auto' => array( 'fetchpriority' => 'auto' ),
     1190            'low'  => array( 'fetchpriority' => 'low' ),
     1191            'high' => array( 'fetchpriority' => 'high' ),
     1192        );
     1193    }
     1194
     1195    /**
     1196     * Tests that valid fetchpriority values are correctly added to script data.
     1197     *
     1198     * @ticket 61734
     1199     *
     1200     * @covers ::wp_register_script
     1201     * @covers WP_Scripts::add_data
     1202     * @covers ::wp_script_add_data
     1203     *
     1204     * @dataProvider data_provider_fetchpriority_values
     1205     *
     1206     * @param string $fetchpriority The fetchpriority value to test.
     1207     */
     1208    public function test_fetchpriority_values( string $fetchpriority ) {
     1209        wp_register_script( 'test-script', '/test-script.js', array(), null, array( 'fetchpriority' => $fetchpriority ) );
     1210        $this->assertArrayHasKey( 'fetchpriority', wp_scripts()->registered['test-script']->extra );
     1211        $this->assertSame( $fetchpriority, wp_scripts()->registered['test-script']->extra['fetchpriority'] );
     1212
     1213        wp_register_script( 'test-script-2', '/test-script-2.js' );
     1214        $this->assertTrue( wp_script_add_data( 'test-script-2', 'fetchpriority', $fetchpriority ) );
     1215        $this->assertArrayHasKey( 'fetchpriority', wp_scripts()->registered['test-script-2']->extra );
     1216        $this->assertSame( $fetchpriority, wp_scripts()->registered['test-script-2']->extra['fetchpriority'] );
     1217    }
     1218
     1219    /**
     1220     * Tests that an empty fetchpriority is treated the same as auto.
     1221     *
     1222     * @ticket 61734
     1223     *
     1224     * @covers ::wp_register_script
     1225     * @covers WP_Scripts::add_data
     1226     */
     1227    public function test_empty_fetchpriority_value() {
     1228        wp_register_script( 'unset', '/joke.js', array(), null, array( 'fetchpriority' => 'low' ) );
     1229        $this->assertSame( 'low', wp_scripts()->registered['unset']->extra['fetchpriority'] );
     1230        $this->assertTrue( wp_script_add_data( 'unset', 'fetchpriority', null ) );
     1231        $this->assertSame( 'auto', wp_scripts()->registered['unset']->extra['fetchpriority'] );
     1232    }
     1233
     1234    /**
     1235     * Tests that an invalid fetchpriority causes a _doing_it_wrong() warning.
     1236     *
     1237     * @ticket 61734
     1238     *
     1239     * @covers ::wp_register_script
     1240     * @covers WP_Scripts::add_data
     1241     *
     1242     * @expectedIncorrectUsage WP_Scripts::add_data
     1243     */
     1244    public function test_invalid_fetchpriority_value() {
     1245        wp_register_script( 'joke', '/joke.js', array(), null, array( 'fetchpriority' => 'silly' ) );
     1246        $this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['joke']->extra );
     1247        $this->assertArrayHasKey( 'WP_Scripts::add_data', $this->caught_doing_it_wrong );
     1248        $this->assertStringContainsString( 'Invalid fetchpriority `silly`', $this->caught_doing_it_wrong['WP_Scripts::add_data'] );
     1249    }
     1250
     1251    /**
     1252     * Tests that an invalid fetchpriority causes a _doing_it_wrong() warning.
     1253     *
     1254     * @ticket 61734
     1255     *
     1256     * @covers ::wp_register_script
     1257     * @covers WP_Scripts::add_data
     1258     *
     1259     * @expectedIncorrectUsage WP_Scripts::add_data
     1260     */
     1261    public function test_invalid_fetchpriority_value_type() {
     1262        wp_register_script( 'bad', '/bad.js' );
     1263        $this->assertFalse( wp_script_add_data( 'bad', 'fetchpriority', array( 'THIS IS SO WRONG!!!' ) ) );
     1264        $this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['bad']->extra );
     1265        $this->assertArrayHasKey( 'WP_Scripts::add_data', $this->caught_doing_it_wrong );
     1266        $this->assertStringContainsString( 'Invalid fetchpriority `array`', $this->caught_doing_it_wrong['WP_Scripts::add_data'] );
     1267    }
     1268
     1269    /**
     1270     * Tests that adding fetchpriority causes a _doing_it_wrong() warning on a script alias.
     1271     *
     1272     * @ticket 61734
     1273     *
     1274     * @covers ::wp_register_script
     1275     * @covers WP_Scripts::add_data
     1276     *
     1277     * @expectedIncorrectUsage WP_Scripts::add_data
     1278     */
     1279    public function test_invalid_fetchpriority_on_alias() {
     1280        wp_register_script( 'alias', false, array(), null, array( 'fetchpriority' => 'low' ) );
     1281        $this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['alias']->extra );
    11491282    }
    11501283
  • trunk/tests/phpunit/tests/script-modules/wpScriptModules.php

    r58952 r60704  
    99 *
    1010 * @group script-modules
    11  *
    12  * @coversDefaultClass WP_Script_Modules
    1311 */
    1412class Tests_Script_Modules_WpScriptModules extends WP_UnitTestCase {
    1513
    1614    /**
     15     * @var WP_Script_Modules
     16     */
     17    protected $original_script_modules;
     18
     19    /**
     20     * @var string
     21     */
     22    protected $original_wp_version;
     23
     24    /**
    1725     * Instance of WP_Script_Modules.
    1826     *
     
    2533     */
    2634    public function set_up() {
     35        global $wp_script_modules, $wp_version;
    2736        parent::set_up();
    28         // Set up the WP_Script_Modules instance.
    29         $this->script_modules = new WP_Script_Modules();
     37        $this->original_script_modules = $wp_script_modules;
     38        $this->original_wp_version     = $wp_version;
     39        $wp_script_modules             = null;
     40        $this->script_modules          = wp_script_modules();
     41    }
     42
     43    /**
     44     * Tear down.
     45     */
     46    public function tear_down() {
     47        global $wp_script_modules, $wp_version;
     48        parent::tear_down();
     49        $wp_script_modules = $this->original_script_modules;
     50        $wp_version        = $this->original_wp_version;
    3051    }
    3152
     
    3556     * @return array Enqueued script module URLs, keyed by script module identifier.
    3657     */
    37     public function get_enqueued_script_modules() {
    38         $script_modules_markup   = get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) );
    39         $p                       = new WP_HTML_Tag_Processor( $script_modules_markup );
    40         $enqueued_script_modules = array();
    41 
     58    public function get_enqueued_script_modules(): array {
     59        $modules = array();
     60
     61        $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) ) );
    4262        while ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
    43             if ( 'module' === $p->get_attribute( 'type' ) ) {
    44                 $id                             = preg_replace( '/-js-module$/', '', $p->get_attribute( 'id' ) );
    45                 $enqueued_script_modules[ $id ] = $p->get_attribute( 'src' );
     63            $this->assertSame( 'module', $p->get_attribute( 'type' ) );
     64            $this->assertIsString( $p->get_attribute( 'id' ) );
     65            $this->assertIsString( $p->get_attribute( 'src' ) );
     66            $this->assertStringEndsWith( '-js-module', $p->get_attribute( 'id' ) );
     67
     68            $id             = preg_replace( '/-js-module$/', '', (string) $p->get_attribute( 'id' ) );
     69            $fetchpriority  = $p->get_attribute( 'fetchpriority' );
     70            $modules[ $id ] = array(
     71                'url'           => $p->get_attribute( 'src' ),
     72                'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
     73            );
     74        }
     75
     76        return $modules;
     77    }
     78
     79    /**
     80     * Gets the script modules listed in the import map.
     81     *
     82     * @return array<string, string> Import map entry URLs, keyed by script module identifier.
     83     */
     84    public function get_import_map(): array {
     85        $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_import_map' ) ) );
     86        if ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
     87            $this->assertSame( 'importmap', $p->get_attribute( 'type' ) );
     88            $this->assertSame( 'wp-importmap', $p->get_attribute( 'id' ) );
     89            $data = json_decode( $p->get_modifiable_text(), true );
     90            $this->assertIsArray( $data );
     91            $this->assertArrayHasKey( 'imports', $data );
     92            return $data['imports'];
     93        } else {
     94            return array();
     95        }
     96    }
     97
     98    /**
     99     * Gets a list of preloaded script modules.
     100     *
     101     * @return array Preloaded script module URLs, keyed by script module identifier.
     102     */
     103    public function get_preloaded_script_modules(): array {
     104        $preloads = array();
     105
     106        $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_script_module_preloads' ) ) );
     107        while ( $p->next_tag( array( 'tag' => 'LINK' ) ) ) {
     108            $this->assertSame( 'modulepreload', $p->get_attribute( 'rel' ) );
     109            $this->assertIsString( $p->get_attribute( 'id' ) );
     110            $this->assertIsString( $p->get_attribute( 'href' ) );
     111            $this->assertStringEndsWith( '-js-modulepreload', $p->get_attribute( 'id' ) );
     112
     113            $id              = preg_replace( '/-js-modulepreload$/', '', $p->get_attribute( 'id' ) );
     114            $fetchpriority   = $p->get_attribute( 'fetchpriority' );
     115            $preloads[ $id ] = array(
     116                'url'           => $p->get_attribute( 'href' ),
     117                'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
     118            );
     119        }
     120
     121        return $preloads;
     122    }
     123
     124    /**
     125     * Test wp_script_modules().
     126     *
     127     * @covers ::wp_script_modules()
     128     */
     129    public function test_wp_script_modules() {
     130        $this->assertSame( $this->script_modules, wp_script_modules() );
     131    }
     132
     133    /**
     134     * Tests various ways of registering, enqueueing, dequeuing, and deregistering a script module.
     135     *
     136     * This ensures that the global function aliases pass all the same parameters as the class methods.
     137     *
     138     * @ticket 56313
     139     *
     140     * @dataProvider data_test_register_and_enqueue_script_module
     141     *
     142     * @covers ::wp_register_script_module()
     143     * @covers WP_Script_Modules::register()
     144     * @covers ::wp_enqueue_script_module()
     145     * @covers WP_Script_Modules::enqueue()
     146     * @covers ::wp_dequeue_script_module()
     147     * @covers WP_Script_Modules::dequeue()
     148     * @covers ::wp_deregister_script_module()
     149     * @covers WP_Script_Modules::deregister()
     150     * @covers WP_Script_Modules::set_fetchpriority()
     151     * @covers WP_Script_Modules::print_enqueued_script_modules()
     152     * @covers WP_Script_Modules::print_import_map()
     153     * @covers WP_Script_Modules::print_script_module_preloads()
     154     */
     155    public function test_comprehensive_methods( bool $use_global_function, bool $only_enqueue ) {
     156        global $wp_version;
     157        $wp_version = '99.9.9';
     158
     159        $register = static function ( ...$args ) use ( $use_global_function ) {
     160            if ( $use_global_function ) {
     161                wp_register_script_module( ...$args );
     162            } else {
     163                wp_script_modules()->register( ...$args );
     164            }
     165        };
     166
     167        $register_and_enqueue = static function ( ...$args ) use ( $use_global_function, $only_enqueue ) {
     168            if ( $use_global_function ) {
     169                if ( $only_enqueue ) {
     170                    wp_enqueue_script_module( ...$args );
     171                } else {
     172                    wp_register_script_module( ...$args );
     173                    wp_enqueue_script_module( $args[0] );
     174                }
     175            } else {
     176                if ( $only_enqueue ) {
     177                    wp_script_modules()->enqueue( ...$args );
     178                } else {
     179                    wp_script_modules()->register( ...$args );
     180                    wp_script_modules()->enqueue( $args[0] );
     181                }
     182            }
     183        };
     184
     185        // Minimal args.
     186        $register_and_enqueue( 'a', '/a.js' );
     187
     188        // One Dependency.
     189        $register( 'b-dep', '/b-dep.js' );
     190        $register_and_enqueue( 'b', '/b.js', array( 'b-dep' ) );
     191        $this->assertTrue( wp_script_modules()->set_fetchpriority( 'b', 'low' ) );
     192
     193        // Two dependencies with different formats and a false version.
     194        $register( 'c-dep', '/c-static.js', array(), false, array( 'fetchpriority' => 'low' ) );
     195        $register( 'c-static-dep', '/c-static-dep.js', array(), false, array( 'fetchpriority' => 'high' ) );
     196        $register_and_enqueue(
     197            'c',
     198            '/c.js',
     199            array(
     200                'c-dep',
     201                array(
     202                    'id'     => 'c-static-dep',
     203                    'import' => 'static',
     204                ),
     205            ),
     206            false
     207        );
     208
     209        // Two dependencies, one imported statically and the other dynamically, with a null version.
     210        $register( 'd-static-dep', '/d-static-dep.js', array(), false, array( 'fetchpriority' => 'auto' ) );
     211        $register( 'd-dynamic-dep', '/d-dynamic-dep.js', array(), false, array( 'fetchpriority' => 'high' ) ); // Because this is a dynamic import dependency, the fetchpriority will not be reflected in the markup since no SCRIPT tag and no preload LINK are printed, and the importmap SCRIPT does not support designating a priority.
     212        $register_and_enqueue(
     213            'd',
     214            '/d.js',
     215            array(
     216                array(
     217                    'id'     => 'd-static-dep',
     218                    'import' => 'static',
     219                ),
     220                array(
     221                    'id'     => 'd-dynamic-dep',
     222                    'import' => 'dynamic',
     223                ),
     224            ),
     225            null
     226        );
     227
     228        // No dependencies, with a string version version.
     229        $register_and_enqueue(
     230            'e',
     231            '/e.js',
     232            array(),
     233            '1.0.0'
     234        );
     235
     236        // No dependencies, with a string version and fetch priority.
     237        $register_and_enqueue(
     238            'f',
     239            '/f.js',
     240            array(),
     241            '2.0.0',
     242            array( 'fetchpriority' => 'auto' )
     243        );
     244
     245        // No dependencies, with a string version and fetch priority of low.
     246        $register_and_enqueue(
     247            'g',
     248            '/g.js',
     249            array(),
     250            '2.0.0',
     251            array( 'fetchpriority' => 'low' )
     252        );
     253
     254        // No dependencies, with a string version and fetch priority of high.
     255        $register_and_enqueue(
     256            'h',
     257            '/h.js',
     258            array(),
     259            '3.0.0',
     260            array( 'fetchpriority' => 'high' )
     261        );
     262
     263        $actual = array(
     264            'preload_links' => $this->get_preloaded_script_modules(),
     265            'script_tags'   => $this->get_enqueued_script_modules(),
     266            'import_map'    => $this->get_import_map(),
     267        );
     268
     269        $this->assertSame(
     270            array(
     271                'preload_links' => array(
     272                    'b-dep'        => array(
     273                        'url'           => '/b-dep.js?ver=99.9.9',
     274                        'fetchpriority' => 'auto',
     275                    ),
     276                    'c-dep'        => array(
     277                        'url'           => '/c-static.js?ver=99.9.9',
     278                        'fetchpriority' => 'low',
     279                    ),
     280                    'c-static-dep' => array(
     281                        'url'           => '/c-static-dep.js?ver=99.9.9',
     282                        'fetchpriority' => 'high',
     283                    ),
     284                    'd-static-dep' => array(
     285                        'url'           => '/d-static-dep.js?ver=99.9.9',
     286                        'fetchpriority' => 'auto',
     287                    ),
     288                ),
     289                'script_tags'   => array(
     290                    'a' => array(
     291                        'url'           => '/a.js?ver=99.9.9',
     292                        'fetchpriority' => 'auto',
     293                    ),
     294                    'b' => array(
     295                        'url'           => '/b.js?ver=99.9.9',
     296                        'fetchpriority' => 'low',
     297                    ),
     298                    'c' => array(
     299                        'url'           => '/c.js?ver=99.9.9',
     300                        'fetchpriority' => 'auto',
     301                    ),
     302                    'd' => array(
     303                        'url'           => '/d.js',
     304                        'fetchpriority' => 'auto',
     305                    ),
     306                    'e' => array(
     307                        'url'           => '/e.js?ver=1.0.0',
     308                        'fetchpriority' => 'auto',
     309                    ),
     310                    'f' => array(
     311                        'url'           => '/f.js?ver=2.0.0',
     312                        'fetchpriority' => 'auto',
     313                    ),
     314                    'g' => array(
     315                        'url'           => '/g.js?ver=2.0.0',
     316                        'fetchpriority' => 'low',
     317                    ),
     318                    'h' => array(
     319                        'url'           => '/h.js?ver=3.0.0',
     320                        'fetchpriority' => 'high',
     321                    ),
     322                ),
     323                'import_map'    => array(
     324                    'b-dep'         => '/b-dep.js?ver=99.9.9',
     325                    'c-dep'         => '/c-static.js?ver=99.9.9',
     326                    'c-static-dep'  => '/c-static-dep.js?ver=99.9.9',
     327                    'd-static-dep'  => '/d-static-dep.js?ver=99.9.9',
     328                    'd-dynamic-dep' => '/d-dynamic-dep.js?ver=99.9.9',
     329                ),
     330            ),
     331            $actual,
     332            "Snapshot:\n" . var_export( $actual, true )
     333        );
     334
     335        // Dequeue the first half of the scripts.
     336        foreach ( array( 'a', 'b', 'c', 'd' ) as $id ) {
     337            if ( $use_global_function ) {
     338                wp_dequeue_script_module( $id );
     339            } else {
     340                wp_script_modules()->dequeue( $id );
    46341            }
    47342        }
    48343
    49         return $enqueued_script_modules;
    50     }
    51 
    52     /**
    53      * Gets the script modules listed in the import map.
    54      *
    55      * @return array Import map entry URLs, keyed by script module identifier.
    56      */
    57     public function get_import_map() {
    58         $import_map_markup = get_echo( array( $this->script_modules, 'print_import_map' ) );
    59         preg_match( '/<script type="importmap" id="wp-importmap">.*?(\{.*\}).*?<\/script>/s', $import_map_markup, $import_map_string );
    60         return json_decode( $import_map_string[1], true )['imports'];
    61     }
    62 
    63     /**
    64      * Gets a list of preloaded script modules.
    65      *
    66      * @return array Preloaded script module URLs, keyed by script module identifier.
    67      */
    68     public function get_preloaded_script_modules() {
    69         $preloaded_markup         = get_echo( array( $this->script_modules, 'print_script_module_preloads' ) );
    70         $p                        = new WP_HTML_Tag_Processor( $preloaded_markup );
    71         $preloaded_script_modules = array();
    72 
    73         while ( $p->next_tag( array( 'tag' => 'LINK' ) ) ) {
    74             if ( 'modulepreload' === $p->get_attribute( 'rel' ) ) {
    75                 $id                              = preg_replace( '/-js-modulepreload$/', '', $p->get_attribute( 'id' ) );
    76                 $preloaded_script_modules[ $id ] = $p->get_attribute( 'href' );
     344        $actual = array(
     345            'preload_links' => $this->get_preloaded_script_modules(),
     346            'script_tags'   => $this->get_enqueued_script_modules(),
     347            'import_map'    => $this->get_import_map(),
     348        );
     349        $this->assertSame(
     350            array(
     351                'preload_links' => array(),
     352                'script_tags'   => array(
     353                    'e' => array(
     354                        'url'           => '/e.js?ver=1.0.0',
     355                        'fetchpriority' => 'auto',
     356                    ),
     357                    'f' => array(
     358                        'url'           => '/f.js?ver=2.0.0',
     359                        'fetchpriority' => 'auto',
     360                    ),
     361                    'g' => array(
     362                        'url'           => '/g.js?ver=2.0.0',
     363                        'fetchpriority' => 'low',
     364                    ),
     365                    'h' => array(
     366                        'url'           => '/h.js?ver=3.0.0',
     367                        'fetchpriority' => 'high',
     368                    ),
     369                ),
     370                'import_map'    => array(),
     371            ),
     372            $actual,
     373            "Snapshot:\n" . var_export( $actual, true )
     374        );
     375
     376        // Unregister the remaining scripts.
     377        foreach ( array( 'e', 'f', 'g', 'h' ) as $id ) {
     378            if ( $use_global_function ) {
     379                wp_dequeue_script_module( $id );
     380            } else {
     381                wp_script_modules()->dequeue( $id );
    77382            }
    78383        }
    79384
    80         return $preloaded_script_modules;
     385        $actual = array(
     386            'preload_links' => $this->get_preloaded_script_modules(),
     387            'script_tags'   => $this->get_enqueued_script_modules(),
     388            'import_map'    => $this->get_import_map(),
     389        );
     390        $this->assertSame(
     391            array(
     392                'preload_links' => array(),
     393                'script_tags'   => array(),
     394                'import_map'    => array(),
     395            ),
     396            $actual,
     397            "Snapshot:\n" . var_export( $actual, true )
     398        );
     399    }
     400
     401    /**
     402     * Data provider.
     403     *
     404     * @return array<string, array{ use_global_function: bool, only_enqueue: bool }>
     405     */
     406    public function data_test_register_and_enqueue_script_module(): array {
     407        $data = array();
     408
     409        foreach ( array( true, false ) as $use_global_function ) {
     410            foreach ( array( true, false ) as $only_enqueue ) {
     411                $test_case = compact( 'use_global_function', 'only_enqueue' );
     412                $key_parts = array();
     413                foreach ( $test_case as $param_name => $param_value ) {
     414                    $key_parts[] = sprintf( '%s_%s', $param_name, json_encode( $param_value ) );
     415                }
     416                $data[ join( '_', $key_parts ) ] = $test_case;
     417            }
     418        }
     419
     420        return $data;
    81421    }
    82422
     
    86426     * @ticket 56313
    87427     *
    88      * @covers ::register()
    89      * @covers ::enqueue()
    90      * @covers ::print_enqueued_script_modules()
     428     * @covers WP_Script_Modules::register()
     429     * @covers WP_Script_Modules::enqueue()
     430     * @covers WP_Script_Modules::print_enqueued_script_modules()
     431     * @covers WP_Script_Modules::set_fetchpriority()
    91432     */
    92433    public function test_wp_enqueue_script_module() {
    93434        $this->script_modules->register( 'foo', '/foo.js' );
    94         $this->script_modules->register( 'bar', '/bar.js' );
     435        $this->script_modules->register( 'bar', '/bar.js', array(), false, array( 'fetchpriority' => 'high' ) );
     436        $this->script_modules->register( 'baz', '/baz.js' );
     437        $this->assertTrue( $this->script_modules->set_fetchpriority( 'baz', 'low' ) );
    95438        $this->script_modules->enqueue( 'foo' );
    96439        $this->script_modules->enqueue( 'bar' );
     440        $this->script_modules->enqueue( 'baz' );
    97441
    98442        $enqueued_script_modules = $this->get_enqueued_script_modules();
    99443
    100         $this->assertCount( 2, $enqueued_script_modules );
    101         $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
    102         $this->assertStringStartsWith( '/bar.js', $enqueued_script_modules['bar'] );
     444        $this->assertCount( 3, $enqueued_script_modules );
     445        $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     446        $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
     447        $this->assertStringStartsWith( '/bar.js', $enqueued_script_modules['bar']['url'] );
     448        $this->assertSame( 'high', $enqueued_script_modules['bar']['fetchpriority'] );
     449        $this->assertStringStartsWith( '/baz.js', $enqueued_script_modules['baz']['url'] );
     450        $this->assertSame( 'low', $enqueued_script_modules['baz']['fetchpriority'] );
    103451    }
    104452
     
    108456    * @ticket 56313
    109457    *
    110     * @covers ::register()
    111     * @covers ::enqueue()
    112     * @covers ::dequeue()
    113     * @covers ::print_enqueued_script_modules()
     458    * @covers WP_Script_Modules::register()
     459    * @covers WP_Script_Modules::enqueue()
     460    * @covers WP_Script_Modules::dequeue()
     461    * @covers WP_Script_Modules::print_enqueued_script_modules()
    114462    */
    115463    public function test_wp_dequeue_script_module() {
     
    135483     * @ticket 60463
    136484     *
    137      * @covers ::register()
    138      * @covers ::enqueue()
    139      * @covers ::deregister()
    140      * @covers ::get_enqueued_script_modules()
     485     * @covers WP_Script_Modules::register()
     486     * @covers WP_Script_Modules::enqueue()
     487     * @covers WP_Script_Modules::deregister()
     488     * @covers WP_Script_Modules::get_enqueued_script_modules()
    141489     */
    142490    public function test_wp_deregister_script_module() {
     
    161509     * @ticket 60463
    162510     *
    163      * @covers ::deregister()
    164      * @covers ::get_enqueued_script_modules()
     511     * @covers WP_Script_Modules::deregister()
     512     * @covers WP_Script_Modules::get_enqueued_script_modules()
    165513     */
    166514    public function test_wp_deregister_unexistent_script_module() {
     
    179527     * @ticket 60463
    180528     *
    181      * @covers ::get_enqueued_script_modules()
    182      * @covers ::register()
    183      * @covers ::deregister()
    184      * @covers ::enqueue()
     529     * @covers WP_Script_Modules::get_enqueued_script_modules()
     530     * @covers WP_Script_Modules::register()
     531     * @covers WP_Script_Modules::deregister()
     532     * @covers WP_Script_Modules::enqueue()
    185533     */
    186534    public function test_wp_deregister_already_deregistered_script_module() {
     
    206554    * @ticket 56313
    207555    *
    208     * @covers ::register()
    209     * @covers ::enqueue()
    210     * @covers ::print_enqueued_script_modules()
     556    * @covers WP_Script_Modules::register()
     557    * @covers WP_Script_Modules::enqueue()
     558    * @covers WP_Script_Modules::print_enqueued_script_modules()
    211559    */
    212560    public function test_wp_enqueue_script_module_works_before_register() {
     
    218566
    219567        $this->assertCount( 1, $enqueued_script_modules );
    220         $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     568        $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     569        $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    221570        $this->assertArrayNotHasKey( 'bar', $enqueued_script_modules );
    222571    }
     
    228577     * @ticket 56313
    229578     *
    230      * @covers ::register()
    231      * @covers ::enqueue()
    232      * @covers ::dequeue()
    233      * @covers ::print_enqueued_script_modules()
     579     * @covers WP_Script_Modules::register()
     580     * @covers WP_Script_Modules::enqueue()
     581     * @covers WP_Script_Modules::dequeue()
     582     * @covers WP_Script_Modules::print_enqueued_script_modules()
    234583     */
    235584    public function test_wp_dequeue_script_module_works_before_register() {
     
    253602     * @ticket 56313
    254603     *
    255      * @covers ::register()
    256      * @covers ::enqueue()
    257      * @covers ::print_import_map()
     604     * @covers WP_Script_Modules::register()
     605     * @covers WP_Script_Modules::enqueue()
     606     * @covers WP_Script_Modules::print_import_map()
    258607     */
    259608    public function test_wp_import_map_dependencies() {
     
    276625     * @ticket 56313
    277626     *
    278      * @covers ::register()
    279      * @covers ::enqueue()
    280      * @covers ::print_import_map()
     627     * @covers WP_Script_Modules::register()
     628     * @covers WP_Script_Modules::enqueue()
     629     * @covers WP_Script_Modules::print_import_map()
    281630     */
    282631    public function test_wp_import_map_no_duplicate_dependencies() {
     
    299648     * @ticket 56313
    300649     *
    301      * @covers ::register()
    302      * @covers ::enqueue()
    303      * @covers ::print_import_map()
     650     * @covers WP_Script_Modules::register()
     651     * @covers WP_Script_Modules::enqueue()
     652     * @covers WP_Script_Modules::print_import_map()
    304653     */
    305654    public function test_wp_import_map_recursive_dependencies() {
     
    350699     * @ticket 56313
    351700     *
    352      * @covers ::register()
    353      * @covers ::enqueue()
    354      * @covers ::print_import_map()
     701     * @covers WP_Script_Modules::register()
     702     * @covers WP_Script_Modules::enqueue()
     703     * @covers WP_Script_Modules::print_import_map()
    355704     */
    356705    public function test_wp_import_map_doesnt_print_if_no_dependencies() {
     
    369718     * @ticket 56313
    370719     *
    371      * @covers ::register()
    372      * @covers ::enqueue()
    373      * @covers ::print_script_module_preloads()
     720     * @covers WP_Script_Modules::register()
     721     * @covers WP_Script_Modules::enqueue()
     722     * @covers WP_Script_Modules::print_script_module_preloads()
    374723     */
    375724    public function test_wp_enqueue_preloaded_static_dependencies() {
     
    397746                    'import' => 'dynamic',
    398747                ),
    399             )
     748            ),
     749            false,
     750            array( 'fetchpriority' => 'high' )
    400751        );
    401752        $this->script_modules->register( 'dynamic-dep', '/dynamic-dep.js' );
     
    408759
    409760        $this->assertCount( 2, $preloaded_script_modules );
    410         $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] );
    411         $this->assertStringStartsWith( '/nested-static-dep.js', $preloaded_script_modules['nested-static-dep'] );
     761        $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep']['url'] );
     762        $this->assertSame( 'high', $preloaded_script_modules['static-dep']['fetchpriority'] );
     763        $this->assertStringStartsWith( '/nested-static-dep.js', $preloaded_script_modules['nested-static-dep']['url'] );
     764        $this->assertSame( 'auto', $preloaded_script_modules['nested-static-dep']['fetchpriority'] );
    412765        $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules );
    413766        $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules );
     
    420773     * @ticket 56313
    421774     *
    422      * @covers ::register()
    423      * @covers ::enqueue()
    424      * @covers ::print_script_module_preloads()
     775     * @covers WP_Script_Modules::register()
     776     * @covers WP_Script_Modules::enqueue()
     777     * @covers WP_Script_Modules::print_script_module_preloads()
    425778     */
    426779    public function test_wp_dont_preload_static_dependencies_of_dynamic_dependencies() {
     
    445798
    446799        $this->assertCount( 1, $preloaded_script_modules );
    447         $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] );
     800        $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep']['url'] );
     801        $this->assertSame( 'auto', $preloaded_script_modules['static-dep']['fetchpriority'] );
    448802        $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules );
    449803        $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules );
     
    456810     * @ticket 56313
    457811     *
    458      * @covers ::register()
    459      * @covers ::enqueue()
    460      * @covers ::print_script_module_preloads()
     812     * @covers WP_Script_Modules::register()
     813     * @covers WP_Script_Modules::enqueue()
     814     * @covers WP_Script_Modules::print_script_module_preloads()
    461815     */
    462816    public function test_wp_preloaded_dependencies_filter_enqueued_script_modules() {
     
    487841     * @ticket 56313
    488842     *
    489      * @covers ::register()
    490      * @covers ::enqueue()
    491      * @covers ::print_import_map()
     843     * @covers WP_Script_Modules::register()
     844     * @covers WP_Script_Modules::enqueue()
     845     * @covers WP_Script_Modules::print_import_map()
    492846     */
    493847    public function test_wp_enqueued_script_modules_with_dependants_add_import_map() {
     
    518872     * @ticket 56313
    519873     *
    520      * @covers ::get_src()
     874     * @covers WP_Script_Modules::get_src()
    521875     */
    522876    public function test_get_src() {
     
    587941     * @ticket 56313
    588942     *
    589      * @covers ::register()
    590      * @covers ::enqueue()
    591      * @covers ::print_enqueued_script_modules()
    592      * @covers ::print_import_map()
    593      * @covers ::print_script_module_preloads()
    594      * @covers ::get_version_query_string()
     943     * @covers WP_Script_Modules::register()
     944     * @covers WP_Script_Modules::enqueue()
     945     * @covers WP_Script_Modules::print_enqueued_script_modules()
     946     * @covers WP_Script_Modules::print_import_map()
     947     * @covers WP_Script_Modules::print_script_module_preloads()
     948     * @covers WP_Script_Modules::get_version_query_string()
    595949     */
    596950    public function test_version_is_propagated_correctly() {
     
    601955                'dep',
    602956            ),
    603             '1.0'
    604         );
    605         $this->script_modules->register( 'dep', '/dep.js', array(), '2.0' );
     957            '1.0',
     958            array( 'fetchpriority' => 'auto' )
     959        );
     960        $this->script_modules->register( 'dep', '/dep.js', array(), '2.0', array( 'fetchpriority' => 'high' ) );
    606961        $this->script_modules->enqueue( 'foo' );
    607962
    608963        $enqueued_script_modules = $this->get_enqueued_script_modules();
    609         $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] );
     964        $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo']['url'] );
     965        $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    610966
    611967        $import_map = $this->get_import_map();
     
    613969
    614970        $preloaded_script_modules = $this->get_preloaded_script_modules();
    615         $this->assertSame( '/dep.js?ver=2.0', $preloaded_script_modules['dep'] );
     971        $this->assertSame( '/dep.js?ver=2.0', $preloaded_script_modules['dep']['url'] );
     972        $this->assertSame( 'high', $preloaded_script_modules['dep']['fetchpriority'] );
    616973    }
    617974
     
    622979     * @ticket 56313
    623980     *
    624      * @covers ::enqueue()
    625      * @covers ::print_enqueued_script_modules()
     981     * @covers WP_Script_Modules::enqueue()
     982     * @covers WP_Script_Modules::print_enqueued_script_modules()
    626983     */
    627984    public function test_wp_enqueue_script_module_doesnt_register_without_a_valid_src() {
     
    640997     * @ticket 56313
    641998     *
    642      * @covers ::enqueue()
    643      * @covers ::print_enqueued_script_modules()
     999     * @covers WP_Script_Modules::enqueue()
     1000     * @covers WP_Script_Modules::print_enqueued_script_modules()
    6441001     */
    6451002    public function test_wp_enqueue_script_module_registers_with_valid_src() {
     
    6491006
    6501007        $this->assertCount( 1, $enqueued_script_modules );
    651         $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     1008        $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     1009        $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    6521010    }
    6531011
     
    6581016     * @ticket 56313
    6591017     *
    660      * @covers ::enqueue()
    661      * @covers ::print_enqueued_script_modules()
     1018     * @covers WP_Script_Modules::enqueue()
     1019     * @covers WP_Script_Modules::print_enqueued_script_modules()
    6621020     */
    6631021    public function test_wp_enqueue_script_module_registers_with_valid_src_the_second_time() {
     
    6741032
    6751033        $this->assertCount( 1, $enqueued_script_modules );
    676         $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     1034        $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     1035        $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    6771036    }
    6781037
     
    6831042     * @ticket 56313
    6841043     *
    685      * @covers ::register()
    686      * @covers ::enqueue()
    687      * @covers ::print_enqueued_script_modules()
    688      * @covers ::print_import_map()
     1044     * @covers WP_Script_Modules::register()
     1045     * @covers WP_Script_Modules::enqueue()
     1046     * @covers WP_Script_Modules::print_enqueued_script_modules()
     1047     * @covers WP_Script_Modules::print_import_map()
    6891048     */
    6901049    public function test_wp_enqueue_script_module_registers_all_params() {
    691         $this->script_modules->enqueue( 'foo', '/foo.js', array( 'dep' ), '1.0' );
     1050        $this->script_modules->enqueue( 'foo', '/foo.js', array( 'dep' ), '1.0', array( 'fetchpriority' => 'low' ) );
    6921051        $this->script_modules->register( 'dep', '/dep.js' );
    6931052
     
    6961055
    6971056        $this->assertCount( 1, $enqueued_script_modules );
    698         $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] );
     1057        $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo']['url'] );
     1058        $this->assertSame( 'low', $enqueued_script_modules['foo']['fetchpriority'] );
    6991059        $this->assertCount( 1, $import_map );
    7001060        $this->assertStringStartsWith( '/dep.js', $import_map['dep'] );
     
    8531213
    8541214            // Non UTF-8
    855             'Solidus'                                => array( '/', '/', 'iso-8859-1' ),
    856             'Less than'                              => array( '<', '\u003C', 'iso-8859-1' ),
    857             'Greater than'                           => array( '>', '\u003E', 'iso-8859-1' ),
    858             'Ampersand'                              => array( '&', '&', 'iso-8859-1' ),
    859             'Newline'                                => array( "\n", "\\n", 'iso-8859-1' ),
    860             'Tab'                                    => array( "\t", "\\t", 'iso-8859-1' ),
    861             'Form feed'                              => array( "\f", "\\f", 'iso-8859-1' ),
    862             'Carriage return'                        => array( "\r", "\\r", 'iso-8859-1' ),
    863             'Line separator'                         => array( "\u{2028}", "\u2028", 'iso-8859-1' ),
    864             'Paragraph separator'                    => array( "\u{2029}", "\u2029", 'iso-8859-1' ),
     1215            'Solidus non-utf8'                       => array( '/', '/', 'iso-8859-1' ),
     1216            'Less than non-utf8'                     => array( '<', '\u003C', 'iso-8859-1' ),
     1217            'Greater than non-utf8'                  => array( '>', '\u003E', 'iso-8859-1' ),
     1218            'Ampersand non-utf8'                     => array( '&', '&', 'iso-8859-1' ),
     1219            'Newline non-utf8'                       => array( "\n", "\\n", 'iso-8859-1' ),
     1220            'Tab non-utf8'                           => array( "\t", "\\t", 'iso-8859-1' ),
     1221            'Form feed non-utf8'                     => array( "\f", "\\f", 'iso-8859-1' ),
     1222            'Carriage return non-utf8'               => array( "\r", "\\r", 'iso-8859-1' ),
     1223            'Line separator non-utf8'                => array( "\u{2028}", "\u2028", 'iso-8859-1' ),
     1224            'Paragraph separator non-utf8'           => array( "\u{2029}", "\u2029", 'iso-8859-1' ),
    8651225            /*
    8661226             * The following is the Flag of England emoji
    8671227             * PHP: "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}"
    8681228             */
    869             'Flag of england'                        => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", 'iso-8859-1' ),
    870             'Malicious script closer'                => array( '</script>', '\u003C/script\u003E', 'iso-8859-1' ),
    871             'Entity-encoded malicious script closer' => array( '&lt;/script&gt;', '&lt;/script&gt;', 'iso-8859-1' ),
     1229            'Flag of england non-utf8'               => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", 'iso-8859-1' ),
     1230            'Malicious script closer non-utf8'       => array( '</script>', '\u003C/script\u003E', 'iso-8859-1' ),
     1231            'Entity-encoded malicious script closer non-utf8' => array( '&lt;/script&gt;', '&lt;/script&gt;', 'iso-8859-1' ),
    8721232
    8731233        );
     
    8951255
    8961256    /**
     1257     * Data provider for test_fetchpriority_values.
     1258     *
     1259     * @return array<string, array{fetchpriority: string}>
     1260     */
     1261    public function data_provider_fetchpriority_values(): array {
     1262        return array(
     1263            'auto' => array( 'fetchpriority' => 'auto' ),
     1264            'low'  => array( 'fetchpriority' => 'low' ),
     1265            'high' => array( 'fetchpriority' => 'high' ),
     1266        );
     1267    }
     1268
     1269    /**
     1270     * Tests that valid fetchpriority values are correctly added to the registered module.
     1271     *
     1272     * @ticket 61734
     1273     *
     1274     * @covers WP_Script_Modules::register
     1275     * @covers WP_Script_Modules::set_fetchpriority
     1276     *
     1277     * @dataProvider data_provider_fetchpriority_values
     1278     *
     1279     * @param string $fetchpriority The fetchpriority value to test.
     1280     */
     1281    public function test_fetchpriority_values( string $fetchpriority ) {
     1282        $this->script_modules->register( 'test-script', '/test-script.js', array(), null, array( 'fetchpriority' => $fetchpriority ) );
     1283        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1284        $this->assertSame( $fetchpriority, $registered_modules['test-script']['fetchpriority'] );
     1285
     1286        $this->script_modules->register( 'test-script-2', '/test-script-2.js' );
     1287        $this->assertTrue( $this->script_modules->set_fetchpriority( 'test-script-2', $fetchpriority ) );
     1288        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1289        $this->assertSame( $fetchpriority, $registered_modules['test-script-2']['fetchpriority'] );
     1290
     1291        $this->assertTrue( $this->script_modules->set_fetchpriority( 'test-script-2', '' ) );
     1292        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1293        $this->assertSame( 'auto', $registered_modules['test-script-2']['fetchpriority'] );
     1294    }
     1295
     1296    /**
     1297     * Tests that a script module with an invalid fetchpriority value gets a value of auto.
     1298     *
     1299     * @ticket 61734
     1300     *
     1301     * @covers WP_Script_Modules::register
     1302     * @expectedIncorrectUsage WP_Script_Modules::register
     1303     */
     1304    public function test_register_script_module_having_fetchpriority_with_invalid_value() {
     1305        $this->script_modules->register( 'foo', '/foo.js', array(), false, array( 'fetchpriority' => 'silly' ) );
     1306        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1307        $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1308        $this->assertArrayHasKey( 'WP_Script_Modules::register', $this->caught_doing_it_wrong );
     1309        $this->assertStringContainsString( 'Invalid fetchpriority `silly`', $this->caught_doing_it_wrong['WP_Script_Modules::register'] );
     1310    }
     1311
     1312    /**
     1313     * Tests that a script module with an invalid fetchpriority value type gets a value of auto.
     1314     *
     1315     * @ticket 61734
     1316     *
     1317     * @covers WP_Script_Modules::register
     1318     * @expectedIncorrectUsage WP_Script_Modules::register
     1319     */
     1320    public function test_register_script_module_having_fetchpriority_with_invalid_value_type() {
     1321        $this->script_modules->register( 'foo', '/foo.js', array(), false, array( 'fetchpriority' => array( 'WHY AM I NOT A STRING???' ) ) );
     1322        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1323        $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1324        $this->assertArrayHasKey( 'WP_Script_Modules::register', $this->caught_doing_it_wrong );
     1325        $this->assertStringContainsString( 'Invalid fetchpriority `array`', $this->caught_doing_it_wrong['WP_Script_Modules::register'] );
     1326    }
     1327
     1328    /**
     1329     * Tests that a setting the fetchpriority for script module with an invalid value is ignored so that it remains auto.
     1330     *
     1331     * @ticket 61734
     1332     *
     1333     * @covers WP_Script_Modules::register
     1334     * @covers WP_Script_Modules::set_fetchpriority
     1335     * @expectedIncorrectUsage WP_Script_Modules::set_fetchpriority
     1336     */
     1337    public function test_set_fetchpriority_with_invalid_value() {
     1338        $this->script_modules->register( 'foo', '/foo.js' );
     1339        $this->script_modules->set_fetchpriority( 'foo', 'silly' );
     1340        $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1341        $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1342    }
     1343
     1344    /**
     1345     * Gets registered script modules.
     1346     *
     1347     * @param WP_Script_Modules $script_modules
     1348     * @return array<string, array> Registered modules.
     1349     */
     1350    private function get_registered_script_modules( WP_Script_Modules $script_modules ): array {
     1351        $reflection_class    = new ReflectionClass( $script_modules );
     1352        $registered_property = $reflection_class->getProperty( 'registered' );
     1353        $registered_property->setAccessible( true );
     1354        return $registered_property->getValue( $script_modules );
     1355    }
     1356
     1357    /**
    8971358     * Data provider.
    8981359     *
Note: See TracChangeset for help on using the changeset viewer.