Changeset 61174
- Timestamp:
- 11/07/2025 04:27:45 AM (5 weeks ago)
- Location:
- trunk
- Files:
-
- 3 edited
-
phpcompat.xml.dist (modified) (1 diff)
-
src/wp-includes/script-loader.php (modified) (4 diffs)
-
tests/phpunit/tests/template.php (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/phpcompat.xml.dist
r60800 r61174 115 115 </rule> 116 116 117 <!-- 118 Excluded while waiting for PHPCompatibility v10. 119 See <https://github.com/PHPCompatibility/PHPCompatibility/issues/1481>. 120 --> 121 <rule ref="PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundInStatic"> 122 <exclude-pattern>/src/wp-includes/script-loader\.php$</exclude-pattern> 123 </rule> 124 117 125 </ruleset> -
trunk/src/wp-includes/script-loader.php
r61142 r61174 2265 2265 * Private, for use in *_footer_scripts hooks 2266 2266 * 2267 * In classic themes, when block styles are loaded on demand via {@see wp_load_classic_theme_block_styles_on_demand()}, 2268 * this function is replaced by a closure in {@see wp_hoist_late_printed_styles()} which will capture the output of 2269 * {@see print_late_styles()} before printing footer scripts as usual. The captured late-printed styles are then hoisted 2270 * to the HEAD by means of the template enhancement output buffer. 2267 * In classic themes, when block styles are loaded on demand via wp_load_classic_theme_block_styles_on_demand(), 2268 * this function is replaced by a closure in wp_hoist_late_printed_styles() which will capture the printing of 2269 * two sets of "late" styles to be hoisted to the HEAD by means of the template enhancement output buffer: 2270 * 2271 * 1. Styles related to blocks are inserted right after the wp-block-library stylesheet. 2272 * 2. All other styles are appended to the end of the HEAD. 2273 * 2274 * The closure calls print_footer_scripts() to print scripts in the footer as usual. 2271 2275 * 2272 2276 * @since 3.3.0 … … 3602 3606 3603 3607 /* 3604 * Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally, 3605 * and so that block-specific styles will only be enqueued when they are used on the page. 3606 * A priority of zero allows for this to be easily overridden by themes which wish to opt out. 3608 * Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally, and so 3609 * that block-specific styles will only be enqueued when they are used on the page. A priority of zero allows for 3610 * this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading 3611 * separate block styles, then abort. 3607 3612 */ 3608 3613 add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 ); 3614 if ( ! wp_should_load_separate_core_block_assets() ) { 3615 return; 3616 } 3609 3617 3610 3618 /* 3611 3619 * Also ensure that block assets are loaded on demand (although the default value is from should_load_separate_core_block_assets). 3612 * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. 3620 * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site 3621 * has explicitly opted out of loading block styles on demand, then abort. 3613 3622 */ 3614 3623 add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 ); 3615 3616 // If a site has explicitly opted out of loading block styles on demand via filters with priorities higher than above, then abort. 3617 if ( ! wp_should_load_separate_core_block_assets() || ! wp_should_load_block_assets_on_demand() ) { 3624 if ( ! wp_should_load_block_assets_on_demand() ) { 3618 3625 return; 3619 3626 } … … 3638 3645 3639 3646 /* 3640 * While normally late styles are printed, there is a filter to disable prevent this, so this makes sure they are 3641 * printed. Note that this filter was intended to control whether to print the styles queued too late for the HTML 3642 * head. This filter was introduced in <https://core.trac.wordpress.org/ticket/9346>. However, with the template 3643 * enhancement output buffer, essentially no style can be enqueued too late, because an output buffer filter can 3644 * always hoist it to the HEAD. 3645 */ 3646 add_filter( 'print_late_styles', '__return_true', PHP_INT_MAX ); 3647 * Add a placeholder comment into the inline styles for wp-block-library, after which where the late block styles 3648 * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement 3649 * output buffer. The `wp_print_styles` action is used to ensure that if the inline style gets replaced at 3650 * `enqueue_block_assets` or `wp_enqueue_scripts` that the placeholder will be sure to be present. 3651 */ 3652 $placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) ); 3653 add_action( 3654 'wp_print_styles', 3655 static function () use ( $placeholder ) { 3656 wp_add_inline_style( 'wp-block-library', $placeholder ); 3657 } 3658 ); 3647 3659 3648 3660 /* 3649 * Print a placeholder comment where the late styles can be hoisted from the footer to be printed in the header 3650 * by means of a filter below on the template enhancement output buffer. 3651 */ 3652 $placeholder = sprintf( '/*%s*/', uniqid( 'wp_late_styles_placeholder:' ) ); 3653 3654 wp_add_inline_style( 'wp-block-library', $placeholder ); 3655 3656 // Wrap print_late_styles() with a closure that captures the late-printed styles. 3657 $printed_late_styles = ''; 3658 $capture_late_styles = static function () use ( &$printed_late_styles ) { 3661 * Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print 3662 * the styles, but it captures what would be printed for block styles and non-block styles so that they can be 3663 * later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts` 3664 * before `print_footer_scripts()` is called. 3665 */ 3666 $printed_block_styles = ''; 3667 $printed_late_styles = ''; 3668 $capture_late_styles = static function () use ( &$printed_block_styles, &$printed_late_styles ) { 3669 // Gather the styles related to on-demand block enqueues. 3670 $all_block_style_handles = array(); 3671 foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) { 3672 foreach ( $block_type->style_handles as $style_handle ) { 3673 $all_block_style_handles[] = $style_handle; 3674 } 3675 } 3676 $all_block_style_handles = array_merge( 3677 $all_block_style_handles, 3678 array( 3679 'global-styles', 3680 'block-style-variation-styles', 3681 'core-block-supports', 3682 'core-block-supports-duotone', 3683 ) 3684 ); 3685 3686 /* 3687 * First print all styles related to blocks which should inserted right after the wp-block-library stylesheet 3688 * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`. 3689 */ 3690 $enqueued_block_styles = array_values( array_intersect( $all_block_style_handles, wp_styles()->queue ) ); 3691 if ( count( $enqueued_block_styles ) > 0 ) { 3692 ob_start(); 3693 wp_styles()->do_items( $enqueued_block_styles ); 3694 $printed_block_styles = ob_get_clean(); 3695 } 3696 3697 /* 3698 * Print all remaining styles not related to blocks. This contains a subset of the logic from 3699 * `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether 3700 * late styles are printed (since they are being hoisted anyway). 3701 */ 3659 3702 ob_start(); 3660 print_late_styles();3703 wp_styles()->do_footer_items(); 3661 3704 $printed_late_styles = ob_get_clean(); 3662 3705 }; 3663 3706 3664 3707 /* 3665 * If _wp_footer_scripts() was unhooked from the wp_print_footer_scripts action, or if wp_print_footer_scripts()3666 * was unhooked from running at the wp_footer action, then only add a callback to wp_footerwhich will capture the3708 * If `_wp_footer_scripts()` was unhooked from the `wp_print_footer_scripts` action, or if `wp_print_footer_scripts()` 3709 * was unhooked from running at the `wp_footer` action, then only add a callback to `wp_footer` which will capture the 3667 3710 * late-printed styles. 3668 3711 * 3669 * Otherwise, in the normal case where _wp_footer_scripts() will run at the wp_print_footer_scriptsaction, then3670 * swap out _wp_footer_scripts()with an alternative which captures the printed styles (for hoisting to HEAD) before3712 * Otherwise, in the normal case where `_wp_footer_scripts()` will run at the `wp_print_footer_scripts` action, then 3713 * swap out `_wp_footer_scripts()` with an alternative which captures the printed styles (for hoisting to HEAD) before 3671 3714 * proceeding with printing the footer scripts. 3672 3715 */ … … 3690 3733 add_filter( 3691 3734 'wp_template_enhancement_output_buffer', 3692 function ( $buffer ) use ( $placeholder, &$printed_late_styles ) {3735 static function ( $buffer ) use ( $placeholder, &$printed_block_styles, &$printed_late_styles ) { 3693 3736 3694 3737 // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans. 3695 3738 $processor = new class( $buffer ) extends WP_HTML_Tag_Processor { 3696 public function get_span(): WP_HTML_Span { 3697 $instance = $this; // phpcs:ignore PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundOutsideClass -- It is inside an anonymous class. 3698 $instance->set_bookmark( 'here' ); 3699 return $instance->bookmarks['here']; 3739 /** 3740 * Gets the span for the current token. 3741 * 3742 * @return WP_HTML_Span Current token span. 3743 */ 3744 private function get_span(): WP_HTML_Span { 3745 // Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true. 3746 $this->set_bookmark( 'here' ); 3747 return $this->bookmarks['here']; 3748 } 3749 3750 /** 3751 * Inserts text before the current token. 3752 * 3753 * @param string $text Text to insert. 3754 */ 3755 public function insert_before( string $text ) { 3756 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); 3757 } 3758 3759 /** 3760 * Inserts text after the current token. 3761 * 3762 * @param string $text Text to insert. 3763 */ 3764 public function insert_after( string $text ) { 3765 $span = $this->get_span(); 3766 3767 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text ); 3768 } 3769 3770 /** 3771 * Removes the current token. 3772 */ 3773 public function remove() { 3774 $span = $this->get_span(); 3775 3776 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' ); 3700 3777 } 3701 3778 }; 3702 3779 3703 // Loop over STYLE tags. 3780 /* 3781 * Insert block styles right after wp-block-library (if it is present), and then insert any remaining styles 3782 * at </head> (or else print everything there). The placeholder CSS comment will always be added to the 3783 * wp-block-library inline style since it gets printed at `wp_head` before the blocks are rendered. 3784 * This means that there may not actually be any block styles to hoist from the footer to insert after this 3785 * inline style. The placeholder CSS comment needs to be added so that the inline style gets printed, but 3786 * if the resulting inline style is empty after the placeholder is removed, then the inline style is 3787 * removed. 3788 */ 3704 3789 while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) { 3705 3706 // We've encountered the inline style for the 'wp-block-library' stylesheet which probably has the placeholder comment.3707 3790 if ( 3708 ! $processor->is_tag_closer() &&3709 3791 'STYLE' === $processor->get_tag() && 3710 3792 'wp-block-library-inline-css' === $processor->get_attribute( 'id' ) 3711 3793 ) { 3712 // If the inline style lacks the placeholder comment, then we have to continue until we get to </HEAD> to append the styles there.3713 3794 $css_text = $processor->get_modifiable_text(); 3714 if ( ! str_contains( $css_text, $placeholder ) ) { 3715 continue; 3795 3796 /* 3797 * A placeholder CSS comment is added to the inline style in order to force an inline STYLE tag to 3798 * be printed. Now that we've located the inline style, the placeholder comment can be removed. If 3799 * there is no CSS left in the STYLE tag after removing the placeholder (aside from the sourceURL 3800 * comment, then remove the STYLE entirely.) 3801 */ 3802 $css_text = str_replace( $placeholder, '', $css_text ); 3803 if ( preg_match( ':^/\*# sourceURL=\S+? \*/$:', trim( $css_text ) ) ) { 3804 $processor->remove(); 3805 } else { 3806 $processor->set_modifiable_text( $css_text ); 3716 3807 } 3717 3808 3718 // Remove the placeholder now that we've located the inline style.3719 $processor->set_modifiable_text( str_replace( $placeholder, '', $css_text ) );3720 $buffer = $processor->get_updated_html();3721 3722 3809 // Insert the $printed_late_styles immediately after the closing inline STYLE tag. This preserves the CSS cascade. 3723 $span = $processor->get_span(); 3724 $buffer = implode( 3725 '', 3726 array( 3727 substr( $buffer, 0, $span->start + $span->length ), 3728 $printed_late_styles, 3729 substr( $buffer, $span->start + $span->length ), 3730 ) 3731 ); 3732 break; 3733 } 3734 3735 // As a fallback, append the hoisted late styles to the end of the HEAD. 3736 if ( $processor->is_tag_closer() && 'HEAD' === $processor->get_tag() ) { 3737 $span = $processor->get_span(); 3738 $buffer = implode( 3739 '', 3740 array( 3741 substr( $buffer, 0, $span->start ), 3742 $printed_late_styles, 3743 substr( $buffer, $span->start ), 3744 ) 3745 ); 3810 if ( '' !== $printed_block_styles ) { 3811 $processor->insert_after( $printed_block_styles ); 3812 3813 // Prevent printing them again at </head>. 3814 $printed_block_styles = ''; 3815 } 3816 3817 // If there aren't any late styles, there's no need to continue to finding </head>. 3818 if ( '' === $printed_late_styles ) { 3819 break; 3820 } 3821 } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { 3822 $processor->insert_before( $printed_block_styles . $printed_late_styles ); 3746 3823 break; 3747 3824 } 3748 3825 } 3749 3826 3750 return $ buffer;3827 return $processor->get_updated_html(); 3751 3828 } 3752 3829 ); -
trunk/tests/phpunit/tests/template.php
r61140 r61174 128 128 $wp_scripts = null; 129 129 $wp_styles = null; 130 wp_scripts(); 131 wp_styles(); 132 133 $this->original_theme_features = $GLOBALS['_wp_theme_features']; 130 134 131 foreach ( self::RESTORED_CONFIG_OPTIONS as $option ) { 135 132 $this->original_ini_config[ $option ] = ini_get( $option ); … … 142 139 $wp_styles = $this->original_wp_styles; 143 140 144 $GLOBALS['_wp_theme_features'] = $this->original_theme_features;145 141 foreach ( $this->original_ini_config as $option => $value ) { 146 142 ini_set( $option, $value ); … … 150 146 unregister_taxonomy( 'taxo' ); 151 147 $this->set_permalink_structure( '' ); 148 152 149 parent::tear_down(); 153 150 } … … 631 628 'wp_template_enhancement_output_buffer', 632 629 static function () { 633 return '<html >Hey!</html>';630 return '<html lang="en"><head><meta charset="utf-8"></head><body>Hey!</body></html>'; 634 631 } 635 632 ); … … 1423 1420 }, 1424 1421 'expected_load_separate' => false, 1425 'expected_on_demand' => true,1422 'expected_on_demand' => false, 1426 1423 'expected_buffer_started' => false, 1427 1424 ), … … 1476 1473 * Data provider. 1477 1474 * 1478 * @return array<string, array{set_up: Closure|null }>1475 * @return array<string, array{set_up: Closure|null, inline_size_limit: int, expected_styles: array{ HEAD: string[], BODY: string[] }}> 1479 1476 */ 1480 1477 public function data_wp_hoist_late_printed_styles(): array { 1478 $common_expected_head_styles = array( 1479 'wp-img-auto-sizes-contain-inline-css', 1480 'early-css', 1481 'early-inline-css', 1482 'wp-emoji-styles-inline-css', 1483 'wp-block-library-css', 1484 'wp-block-separator-css', 1485 'global-styles-inline-css', 1486 'core-block-supports-inline-css', 1487 'classic-theme-styles-css', 1488 'normal-css', 1489 'normal-inline-css', 1490 'wp-custom-css', 1491 'late-css', 1492 'late-inline-css', 1493 ); 1494 1481 1495 return array( 1482 'no_actions_removed' => array( 1483 'set_up' => null, 1484 ), 1485 '_wp_footer_scripts_removed' => array( 1486 'set_up' => static function () { 1496 'standard_classic_theme_config_with_min_styles_inlined' => array( 1497 'set_up' => null, 1498 'inline_size_limit' => 0, 1499 'expected_styles' => array( 1500 'HEAD' => $common_expected_head_styles, 1501 'BODY' => array(), 1502 ), 1503 ), 1504 'standard_classic_theme_config_with_max_styles_inlined' => array( 1505 'set_up' => null, 1506 'inline_size_limit' => PHP_INT_MAX, 1507 'expected_styles' => array( 1508 'HEAD' => array( 1509 'wp-img-auto-sizes-contain-inline-css', 1510 'early-css', 1511 'early-inline-css', 1512 'wp-emoji-styles-inline-css', 1513 'wp-block-library-inline-css', 1514 'wp-block-separator-inline-css', 1515 'global-styles-inline-css', 1516 'core-block-supports-inline-css', 1517 'classic-theme-styles-inline-css', 1518 'normal-css', 1519 'normal-inline-css', 1520 'wp-custom-css', 1521 'late-css', 1522 'late-inline-css', 1523 ), 1524 'BODY' => array(), 1525 ), 1526 ), 1527 'standard_classic_theme_config_extra_block_library_inline_style' => array( 1528 'set_up' => static function () { 1529 add_action( 1530 'enqueue_block_assets', 1531 static function () { 1532 wp_add_inline_style( 'wp-block-library', '/* Extra CSS which prevents empty inline style containing placeholder from being removed. */' ); 1533 } 1534 ); 1535 }, 1536 'inline_size_limit' => 0, 1537 'expected_styles' => array( 1538 'HEAD' => ( function ( $expected_styles ) { 1539 // Insert 'wp-block-library-inline-css' right after 'wp-block-library-css'. 1540 $i = array_search( 'wp-block-library-css', $expected_styles, true ); 1541 $this->assertIsInt( $i, 'Expected wp-block-library-css to be among the styles.' ); 1542 array_splice( $expected_styles, $i + 1, 0, 'wp-block-library-inline-css' ); 1543 return $expected_styles; 1544 } )( $common_expected_head_styles ), 1545 'BODY' => array(), 1546 ), 1547 ), 1548 'classic_theme_opt_out_separate_block_styles' => array( 1549 'set_up' => static function () { 1550 add_filter( 'should_load_separate_core_block_assets', '__return_false' ); 1551 }, 1552 'inline_size_limit' => 0, 1553 'expected_styles' => array( 1554 'HEAD' => array( 1555 'wp-img-auto-sizes-contain-inline-css', 1556 'early-css', 1557 'early-inline-css', 1558 'wp-emoji-styles-inline-css', 1559 'wp-block-library-css', 1560 'classic-theme-styles-css', 1561 'global-styles-inline-css', 1562 'normal-css', 1563 'normal-inline-css', 1564 'wp-custom-css', 1565 ), 1566 'BODY' => array( 1567 'late-css', 1568 'late-inline-css', 1569 'core-block-supports-inline-css', 1570 ), 1571 ), 1572 ), 1573 '_wp_footer_scripts_removed' => array( 1574 'set_up' => static function () { 1487 1575 remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); 1488 1576 }, 1489 ), 1490 'wp_print_footer_scripts_removed' => array( 1491 'set_up' => static function () { 1577 'inline_size_limit' => 0, 1578 'expected_styles' => array( 1579 'HEAD' => $common_expected_head_styles, 1580 'BODY' => array(), 1581 ), 1582 ), 1583 'wp_print_footer_scripts_removed' => array( 1584 'set_up' => static function () { 1492 1585 remove_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); 1493 1586 }, 1494 ), 1495 'both_actions_removed' => array( 1496 'set_up' => static function () { 1587 'inline_size_limit' => 0, 1588 'expected_styles' => array( 1589 'HEAD' => $common_expected_head_styles, 1590 'BODY' => array(), 1591 ), 1592 ), 1593 'both_actions_removed' => array( 1594 'set_up' => static function () { 1497 1595 remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); 1498 1596 remove_action( 'wp_footer', 'wp_print_footer_scripts' ); 1499 1597 }, 1500 ), 1501 'block_library_removed' => array( 1502 'set_up' => static function () { 1503 wp_deregister_style( 'wp-block-library' ); 1504 }, 1598 'inline_size_limit' => 0, 1599 'expected_styles' => array( 1600 'HEAD' => $common_expected_head_styles, 1601 'BODY' => array(), 1602 ), 1603 ), 1604 'disable_block_library' => array( 1605 'set_up' => static function () { 1606 add_action( 1607 'enqueue_block_assets', 1608 function (): void { 1609 wp_deregister_style( 'wp-block-library' ); 1610 wp_register_style( 'wp-block-library', '' ); 1611 } 1612 ); 1613 add_filter( 'should_load_separate_core_block_assets', '__return_false' ); 1614 }, 1615 'inline_size_limit' => 0, 1616 'expected_styles' => array( 1617 'HEAD' => array( 1618 'wp-img-auto-sizes-contain-inline-css', 1619 'early-css', 1620 'early-inline-css', 1621 'wp-emoji-styles-inline-css', 1622 'classic-theme-styles-css', 1623 'global-styles-inline-css', 1624 'normal-css', 1625 'normal-inline-css', 1626 'wp-custom-css', 1627 ), 1628 'BODY' => array( 1629 'late-css', 1630 'late-inline-css', 1631 'core-block-supports-inline-css', 1632 ), 1633 ), 1634 ), 1635 'override_block_library_inline_style_late' => array( 1636 'set_up' => static function () { 1637 add_action( 1638 'enqueue_block_assets', 1639 function (): void { 1640 // This tests what happens when the placeholder comment gets replaced unexpectedly. 1641 wp_styles()->registered['wp-block-library']->extra['after'] = array( '/* OVERRIDDEN! */' ); 1642 } 1643 ); 1644 }, 1645 'inline_size_limit' => 0, 1646 'expected_styles' => array( 1647 'HEAD' => array( 1648 'wp-img-auto-sizes-contain-inline-css', 1649 'early-css', 1650 'early-inline-css', 1651 'wp-emoji-styles-inline-css', 1652 'wp-block-library-css', 1653 'wp-block-library-inline-css', // This contains the "OVERRIDDEN" text. 1654 'wp-block-separator-css', 1655 'global-styles-inline-css', 1656 'core-block-supports-inline-css', 1657 'classic-theme-styles-css', 1658 'normal-css', 1659 'normal-inline-css', 1660 'wp-custom-css', 1661 'late-css', 1662 'late-inline-css', 1663 ), 1664 'BODY' => array(), 1665 ), 1505 1666 ), 1506 1667 ); … … 1511 1672 * 1512 1673 * @ticket 64099 1674 * @covers ::wp_load_classic_theme_block_styles_on_demand 1513 1675 * @covers ::wp_hoist_late_printed_styles 1514 1676 * 1515 1677 * @dataProvider data_wp_hoist_late_printed_styles 1516 1678 */ 1517 public function test_wp_hoist_late_printed_styles( ?Closure $set_up ): void { 1679 public function test_wp_hoist_late_printed_styles( ?Closure $set_up, int $inline_size_limit, array $expected_styles ): void { 1680 switch_theme( 'default' ); 1681 global $wp_styles; 1682 $wp_styles = null; 1683 1684 // Disable the styles_inline_size_limit in order to prevent changes from invalidating the snapshots. 1685 add_filter( 1686 'styles_inline_size_limit', 1687 static function () use ( $inline_size_limit ): int { 1688 return $inline_size_limit; 1689 } 1690 ); 1691 1692 add_filter( 1693 'wp_get_custom_css', 1694 static function () { 1695 return '/* CUSTOM CSS from Customizer */'; 1696 } 1697 ); 1698 1518 1699 if ( $set_up ) { 1519 1700 $set_up(); 1520 1701 } 1521 1702 1522 switch_theme( 'default' ); 1523 1524 // Enqueue a style 1525 wp_enqueue_style( 'early', 'http://example.com/style.css' ); 1703 wp_load_classic_theme_block_styles_on_demand(); 1704 1705 // Ensure that separate core block assets get registered. 1706 register_core_block_style_handles(); 1707 $this->assertTrue( WP_Block_Type_Registry::get_instance()->is_registered( 'core/separator' ), 'Expected the core/separator block to be registered.' ); 1708 1709 // Ensure stylesheet files exist on the filesystem since a build may not have been done. 1710 $this->ensure_style_asset_file_created( 1711 'wp-block-library', 1712 wp_should_load_separate_core_block_assets() ? 'css/dist/block-library/common.css' : 'css/dist/block-library/style.css' 1713 ); 1714 if ( wp_should_load_separate_core_block_assets() ) { 1715 $this->ensure_style_asset_file_created( 'wp-block-separator', 'blocks/separator/style.css' ); 1716 } 1717 $this->assertFalse( wp_is_block_theme(), 'Test is not relevant to block themes (only classic themes).' ); 1718 1719 // Enqueue a style early, before wp_enqueue_scripts. 1720 wp_enqueue_style( 'early', 'https://example.com/style.css' ); 1526 1721 wp_add_inline_style( 'early', '/* EARLY */' ); 1527 1722 1528 wp_hoist_late_printed_styles(); 1529 1530 // Ensure late styles are printed. 1531 add_filter( 'print_late_styles', '__return_false', 1000 ); 1532 $this->assertTrue( apply_filters( 'print_late_styles', true ), 'Expected late style printing to be forced.' ); 1723 // Enqueue a style at the normal spot. 1724 add_action( 1725 'wp_enqueue_scripts', 1726 static function () { 1727 wp_enqueue_style( 'normal', 'https://example.com/normal.css' ); 1728 wp_add_inline_style( 'normal', '/* NORMAL */' ); 1729 } 1730 ); 1731 1732 // Call wp_hoist_late_printed_styles() if wp_load_classic_theme_block_styles_on_demand() queued it up. 1733 if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { 1734 wp_hoist_late_printed_styles(); 1735 } 1533 1736 1534 1737 // Simulate wp_head. … … 1538 1741 1539 1742 // Enqueue a late style (after wp_head). 1540 wp_enqueue_style( 'late', 'http://example.com/late-style.css', array(), null ); 1541 wp_add_inline_style( 'late', '/* EARLY */' ); 1743 wp_enqueue_style( 'late', 'https://example.com/late-style.css', array(), null ); 1744 wp_add_inline_style( 'late', '/* LATE */' ); 1745 1746 // Simulate the_content(). 1747 $content = apply_filters( 1748 'the_content', 1749 '<!-- wp:separator --><hr class="wp-block-separator has-alpha-channel-opacity"/><!-- /wp:separator -->' 1750 ); 1542 1751 1543 1752 // Simulate footer scripts. … … 1545 1754 1546 1755 // Create a simulated output buffer. 1547 $buffer = '<html><head>' . $head_output . '</head><body><main>Content</main>' . $footer_output . '</body></html>'; 1756 $buffer = '<html lang="en"><head><meta charset="utf-8">' . $head_output . '</head><body><main>' . $content . '</main>' . $footer_output . '</body></html>'; 1757 1758 $placeholder_regexp = '#/\*wp_block_styles_on_demand_placeholder:[a-f0-9]+\*/#'; 1759 if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { 1760 $this->assertMatchesRegularExpression( $placeholder_regexp, $buffer, 'Expected the placeholder to be present in the buffer.' ); 1761 } 1548 1762 1549 1763 // Apply the output buffer filter. 1550 1764 $filtered_buffer = apply_filters( 'wp_template_enhancement_output_buffer', $buffer ); 1551 1765 1552 $this->assertStringContainsString( '</head>', $ buffer, 'Expected the closing HEAD tag to be in the response.' );1553 1554 $this->assertDoesNotMatchRegularExpression( '#/\*wp_late_styles_placeholder:[a-f0-9-]+\*/#', $filtered_buffer, 'Expected the placeholder to be removed.' );1766 $this->assertStringContainsString( '</head>', $filtered_buffer, 'Expected the closing HEAD tag to be in the response.' ); 1767 1768 $this->assertDoesNotMatchRegularExpression( $placeholder_regexp, $filtered_buffer, 'Expected the placeholder to be removed.' ); 1555 1769 $found_styles = array( 1556 1770 'HEAD' => array(), … … 1570 1784 } 1571 1785 1572 $expected = array( 1573 'early-css', 1574 'early-inline-css', 1575 'late-css', 1576 'late-inline-css', 1577 ); 1578 foreach ( $expected as $style_id ) { 1579 $this->assertContains( $style_id, $found_styles['HEAD'], 'Expected stylesheet with ID to be in the HEAD.' ); 1580 } 1786 /* 1787 * Since new styles could appear at any time and since certain styles leak in from the global scope not being 1788 * properly reset somewhere else in the test suite, we only check that the expected styles are at least present 1789 * and in the same order. When new styles are introduced in core, they may be added to this array as opposed to 1790 * updating the arrays in the data provider, if appropriate. 1791 */ 1792 $ignored_styles = array( 1793 'core-block-supports-duotone-inline-css', 1794 'wp-block-library-theme-css', 1795 'wp-block-template-skip-link-inline-css', 1796 ); 1797 1798 $found_subset_styles = array(); 1799 foreach ( array( 'HEAD', 'BODY' ) as $group ) { 1800 $found_subset_styles[ $group ] = array_values( array_diff( $found_styles[ $group ], $ignored_styles ) ); 1801 } 1802 1581 1803 $this->assertSame( 1582 $expected, 1583 array_values( array_intersect( $found_styles['HEAD'], $expected ) ), 1584 'Expected styles to be printed in the same order.' 1585 ); 1586 $this->assertCount( 0, $found_styles['BODY'], 'Expected no styles to be present in the footer.' ); 1804 $expected_styles, 1805 $found_subset_styles, 1806 'Expected the same styles. Snapshot: ' . self::get_array_snapshot_export( $found_subset_styles ) 1807 ); 1808 } 1809 1810 /** 1811 * Ensures a CSS file is on the filesystem. 1812 * 1813 * This is needed because unit tests may be run without a build step having been done. Something similar can be seen 1814 * elsewhere in tests for the `wp-emoji-loader.js` script: 1815 * 1816 * self::touch( ABSPATH . WPINC . '/js/wp-emoji-loader.js' ); 1817 * 1818 * @param string $handle Style handle. 1819 * @param string $relative_path Relative path to the CSS file in wp-includes. 1820 * 1821 * @throws Exception If the supplied style handle is not registered as expected. 1822 */ 1823 private function ensure_style_asset_file_created( string $handle, string $relative_path ) { 1824 $dependency = wp_styles()->query( $handle ); 1825 if ( ! $dependency ) { 1826 throw new Exception( "The stylesheet for $handle is not registered." ); 1827 } 1828 $dependency->src = includes_url( $relative_path ); 1829 $path = ABSPATH . WPINC . '/' . $relative_path; 1830 if ( ! file_exists( $path ) ) { 1831 $dir = dirname( $path ); 1832 if ( ! file_exists( $dir ) ) { 1833 mkdir( $dir, 0777, true ); 1834 } 1835 file_put_contents( $path, "/* CSS for $handle */" ); 1836 } 1837 wp_style_add_data( $handle, 'path', $path ); 1587 1838 } 1588 1839 … … 1592 1843 1593 1844 $this->assertSame( $expected, $hierarchy, $message ); 1845 } 1846 1847 /** 1848 * Exports PHP array as string formatted as a snapshot for pasting into a data provider. 1849 * 1850 * Unfortunately, `var_export()` always includes array indices even for lists. For example: 1851 * 1852 * var_export( array( 'a', 'b', 'c' ) ); 1853 * 1854 * Results in: 1855 * 1856 * array ( 1857 * 0 => 'a', 1858 * 1 => 'b', 1859 * 2 => 'c', 1860 * ) 1861 * 1862 * This makes it unhelpful when outputting a snapshot to update a unit test. So this function strips out the indices 1863 * to facilitate copy/pasting the snapshot from an assertion error message into the data provider. For example: 1864 * 1865 * array( 1866 * 'a', 1867 * 'b', 1868 * 'c', 1869 * ) 1870 * 1871 * 1872 * @param array $snapshot Snapshot. 1873 * @return string Snapshot export. 1874 */ 1875 private static function get_array_snapshot_export( array $snapshot ): string { 1876 $export = var_export( $snapshot, true ); 1877 $export = preg_replace( '/\barray \($/m', 'array(', $export ); 1878 $export = preg_replace( '/^(\s+)\d+\s+=>\s+/m', '$1', $export ); 1879 $export = preg_replace( '/=> *\n +/', '=> ', $export ); 1880 $export = preg_replace( '/array\(\n\s+\)/', 'array()', $export ); 1881 return preg_replace_callback( 1882 '/(^ +)/m', 1883 static function ( $matches ) { 1884 return str_repeat( "\t", strlen( $matches[0] ) / 2 ); 1885 }, 1886 $export 1887 ); 1594 1888 } 1595 1889
Note: See TracChangeset
for help on using the changeset viewer.