Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/wp-includes/block-supports/position.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,26 @@ function wp_render_position_support( $block_content, $block ) {

$viewport_settings = $global_settings['viewport'] ?? null;
$responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
$class_name = wp_unique_id( 'wp-container-' );
$selector = ".$class_name";
$position_styles = array();
$wrapper_classes = array();
$base_position = $style_attribute['position'] ?? null;

/*
* Derive the container class from the position styles rather than from a
* counter, so that blocks resolving to the same CSS share a single rule in
* the style engine store instead of emitting one rule per block instance.
*
* The hash covers the default state and every viewport state, since each
* contributes rules scoped to this class.
*/
$position_hash_input = array( $base_position );
foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
$position_hash_input[ $breakpoint ] = $style_attribute[ $breakpoint ]['position'] ?? null;
}

$class_name = wp_unique_id_from_values( $position_hash_input, 'wp-container-' );
$selector = ".$class_name";

// Default viewport (base) position styles.
$base_styles = wp_get_position_support_styles(
$selector,
Expand Down
201 changes: 193 additions & 8 deletions tests/phpunit/tests/block-supports/wpRenderPositionSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,191 @@ public function test_position_block_support( $theme_name, $block_name, $position
);
}

/**
* Tests that blocks resolving to the same position styles share a single CSS rule.
*
* The container class is derived from the position styles, so repeating a block
* with the same position does not add a rule per instance to the style engine store.
*
* @ticket 66200
*
* @covers ::wp_render_position_support
*/
public function test_identical_position_styles_share_one_rule() {
switch_theme( 'block-theme-child-with-fluid-typography' );
$this->test_block_name = 'test/position-rules-are-deduplicated';

register_block_type(
$this->test_block_name,
array(
'api_version' => 2,
'attributes' => array(
'style' => array(
'type' => 'object',
),
),
'supports' => array(
'position' => true,
),
)
);

$block = array(
'blockName' => $this->test_block_name,
'attrs' => array(
'style' => array(
'position' => array(
'type' => 'sticky',
'top' => '0px',
),
),
),
);

$first = wp_render_position_support( '<div>One</div>', $block );
$second = wp_render_position_support( '<div>Two</div>', $block );

$this->assertSame(
1,
preg_match( '/class="(wp-container-[0-9a-f]{8}) is-position-sticky"/', $first, $first_match ),
'First block should receive a hashed position container class.'
);
$this->assertSame(
1,
preg_match( '/class="(wp-container-[0-9a-f]{8}) is-position-sticky"/', $second, $second_match ),
'Second block should receive a hashed position container class.'
);
$this->assertSame(
$first_match[1],
$second_match[1],
'Blocks with identical position styles should share a container class.'
);

$stylesheet = wp_style_engine_get_stylesheet_from_context(
'block-supports',
array(
'prettify' => false,
)
);

$this->assertSame(
1,
substr_count( $stylesheet, '.' . $first_match[1] . '{' ),
'Identical position styles should produce a single CSS rule.'
);
}

/**
* Tests that blocks with different position styles do not share a CSS rule.
*
* @ticket 66200
*
* @covers ::wp_render_position_support
*/
public function test_differing_position_styles_do_not_share_a_rule() {
switch_theme( 'block-theme-child-with-fluid-typography' );
$this->test_block_name = 'test/position-rules-differ';

register_block_type(
$this->test_block_name,
array(
'api_version' => 2,
'attributes' => array(
'style' => array(
'type' => 'object',
),
),
'supports' => array(
'position' => true,
),
)
);

$make_block = static function ( $top ) {
return array(
'blockName' => 'test/position-rules-differ',
'attrs' => array(
'style' => array(
'position' => array(
'type' => 'sticky',
'top' => $top,
),
),
),
);
};

$first = wp_render_position_support( '<div>One</div>', $make_block( '0px' ) );
$second = wp_render_position_support( '<div>Two</div>', $make_block( '10px' ) );

preg_match( '/class="(wp-container-[0-9a-f]{8}) /', $first, $first_match );
preg_match( '/class="(wp-container-[0-9a-f]{8}) /', $second, $second_match );

$this->assertNotSame(
$first_match[1],
$second_match[1],
'Blocks with different position styles should not share a container class.'
);
}

/**
* Tests that blocks differing only in a viewport state position do not share a CSS rule.
*
* @ticket 66200
*
* @covers ::wp_render_position_support
*/
public function test_differing_viewport_position_styles_do_not_share_a_rule() {
switch_theme( 'block-theme-child-with-fluid-typography' );
$this->test_block_name = 'test/position-viewport-rules-differ';

register_block_type(
$this->test_block_name,
array(
'api_version' => 2,
'attributes' => array(
'style' => array(
'type' => 'object',
),
),
'supports' => array(
'position' => true,
),
)
);

$make_block = static function ( $tablet_top ) {
return array(
'blockName' => 'test/position-viewport-rules-differ',
'attrs' => array(
'style' => array(
'position' => array(
'type' => 'sticky',
'top' => '0px',
),
'@tablet' => array(
'position' => array(
'top' => $tablet_top,
),
),
),
),
);
};

$first = wp_render_position_support( '<div>One</div>', $make_block( '1rem' ) );
$second = wp_render_position_support( '<div>Two</div>', $make_block( '2rem' ) );

preg_match( '/class="(wp-container-[0-9a-f]{8}) /', $first, $first_match );
preg_match( '/class="(wp-container-[0-9a-f]{8}) /', $second, $second_match );

$this->assertNotSame(
$first_match[1],
$second_match[1],
'Blocks with different viewport position styles should not share a container class.'
);
}

/**
* Data provider.
*
Expand All @@ -144,8 +329,8 @@ public function data_position_block_support() {
'top' => '0px',
),
),
'expected_wrapper' => '/^<div class="wp-container-\d+ is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^.wp-container-\d+' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . '$/',
'expected_wrapper' => '/^<div class="wp-container-[0-9a-f]{8} is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^.wp-container-[0-9a-f]{8}' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . '$/',
),
'sticky position style is not applied if theme does not support it' => array(
'theme_name' => 'default',
Expand Down Expand Up @@ -198,8 +383,8 @@ public function data_position_block_support() {
),
),
),
'expected_wrapper' => '/^<div class="wp-container-\d+ is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '@media (width <= 480px){.wp-container-' ) . '\d+' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}}' ) . '$/',
'expected_wrapper' => '/^<div class="wp-container-[0-9a-f]{8} is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '@media (width <= 480px){.wp-container-' ) . '[0-9a-f]{8}' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}}' ) . '$/',
),
'viewport position style inherits the default state position type' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
Expand All @@ -216,8 +401,8 @@ public function data_position_block_support() {
),
),
),
'expected_wrapper' => '/^<div class="wp-container-\d+ is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '.wp-container-' ) . '\d+' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . preg_quote( '@media (480px < width <= 782px){.wp-container-' ) . '\d+' . preg_quote( '{top:calc(2rem + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}}' ) . '$/',
'expected_wrapper' => '/^<div class="wp-container-[0-9a-f]{8} is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '.wp-container-' ) . '[0-9a-f]{8}' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . preg_quote( '@media (480px < width <= 782px){.wp-container-' ) . '[0-9a-f]{8}' . preg_quote( '{top:calc(2rem + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}}' ) . '$/',
),
'viewport position style is not applied if theme does not support it' => array(
'theme_name' => 'default',
Expand Down Expand Up @@ -249,8 +434,8 @@ public function data_position_block_support() {
),
),
),
'expected_wrapper' => '/^<div class="wp-container-\d+ is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '.wp-container-' ) . '\d+' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . preg_quote( '@media (width <= 480px){.wp-container-' ) . '\d+' . preg_quote( '{position:static;}}' ) . '$/',
'expected_wrapper' => '/^<div class="wp-container-[0-9a-f]{8} is-position-sticky">Content<\/div>$/',
'expected_styles' => '/^' . preg_quote( '.wp-container-' ) . '[0-9a-f]{8}' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . preg_quote( '@media (width <= 480px){.wp-container-' ) . '[0-9a-f]{8}' . preg_quote( '{position:static;}}' ) . '$/',
),
);
}
Expand Down
Loading