diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 1517605bf4aa7..0440ec7206b0f 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -818,6 +818,9 @@ private static function get_viewport_breakpoint_value_in_pixels( $value ) { * uses a single max-width media query. When `tablet` is not larger than * `mobile`, it is removed. * + * `tablet` is also removed when the two breakpoints are measured against + * different bases, since their order cannot be determined. + * * @since 7.1.0 * * @param mixed $viewport_settings Viewport settings from theme.json. @@ -836,6 +839,7 @@ private static function sanitize_viewport_settings( $viewport_settings ) { $breakpoints[ $breakpoint ] = array( 'value' => trim( $value ), 'px' => $px, + 'is_px' => str_ends_with( trim( $value ), 'px' ), ); } } @@ -851,7 +855,11 @@ private static function sanitize_viewport_settings( $viewport_settings ) { $sanitized = array( 'mobile' => $breakpoints['mobile']['value'] ); - if ( isset( $breakpoints['tablet'] ) && $breakpoints['mobile']['px'] < $breakpoints['tablet']['px'] ) { + if ( + isset( $breakpoints['tablet'] ) + && $breakpoints['mobile']['is_px'] === $breakpoints['tablet']['is_px'] + && $breakpoints['mobile']['px'] < $breakpoints['tablet']['px'] + ) { $sanitized['tablet'] = $breakpoints['tablet']['value']; } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index c2cda7bb158d0..e6d5605d93f69 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -1248,6 +1248,138 @@ public function test_get_viewport_media_queries_omits_tablet_when_its_breakpoint ); } + /** + * A `px` breakpoint cannot be ordered against a font-relative one, so the + * tablet range is dropped rather than emitted unordered. + * + * @ticket 65833 + * + * @dataProvider data_viewport_breakpoints_without_a_shared_base + * + * @param array $viewport_settings Viewport settings to sanitize. + * @param array $expected Expected media queries. + */ + public function test_get_viewport_media_queries_omits_tablet_when_breakpoints_do_not_share_a_base( $viewport_settings, $expected ) { + $this->assertSame( + $expected, + WP_Theme_JSON::get_viewport_media_queries( + $viewport_settings, + array( + 'include_desktop' => true, + ) + ) + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_viewport_breakpoints_without_a_shared_base() { + return array( + 'font-relative mobile, pixel tablet' => array( + 'viewport_settings' => array( + 'mobile' => '30em', + 'tablet' => '500px', + ), + 'expected' => array( + '@mobile' => '@media (width <= 30em)', + '@desktop' => '@media (width > 30em)', + ), + ), + 'pixel mobile, font-relative tablet' => array( + 'viewport_settings' => array( + 'mobile' => '400px', + 'tablet' => '30em', + ), + 'expected' => array( + '@mobile' => '@media (width <= 400px)', + '@desktop' => '@media (width > 400px)', + ), + ), + ); + } + + /** + * `em` and `rem` resolve against the same base in a media query, so they can + * be ordered against each other. + * + * @ticket 65833 + * + * @dataProvider data_viewport_breakpoints_with_a_shared_base + * + * @param array $viewport_settings Viewport settings to sanitize. + * @param array $expected Expected media queries. + */ + public function test_get_viewport_media_queries_keeps_tablet_when_breakpoints_share_a_base( $viewport_settings, $expected ) { + $this->assertSame( + $expected, + WP_Theme_JSON::get_viewport_media_queries( + $viewport_settings, + array( + 'include_desktop' => true, + ) + ) + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_viewport_breakpoints_with_a_shared_base() { + return array( + 'em mobile, rem tablet' => array( + 'viewport_settings' => array( + 'mobile' => '30em', + 'tablet' => '40rem', + ), + 'expected' => array( + '@mobile' => '@media (width <= 30em)', + '@tablet' => '@media (30em < width <= 40rem)', + '@desktop' => '@media (width > 40rem)', + ), + ), + 'rem mobile, em tablet' => array( + 'viewport_settings' => array( + 'mobile' => '30rem', + 'tablet' => '40em', + ), + 'expected' => array( + '@mobile' => '@media (width <= 30rem)', + '@tablet' => '@media (30rem < width <= 40em)', + '@desktop' => '@media (width > 40em)', + ), + ), + ); + } + + /** + * @ticket 65833 + */ + public function test_viewport_settings_omit_tablet_when_breakpoints_do_not_share_a_base() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'viewport' => array( + 'mobile' => '30em', + 'tablet' => '500px', + ), + ), + ) + ); + + $this->assertSame( + array( + 'mobile' => '30em', + ), + $theme_json->get_raw_data()['settings']['viewport'] + ); + } + /** * @ticket 65596 */