From de2852ab08d6efb60b1c9b4758c002f714b460d1 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Fri, 8 Aug 2025 03:37:42 -0700 Subject: [PATCH] Reland: Add tests for DisplayMetricsHolder Summary: This is a re-land of D78981753 Those tests were OOM-ing because we were using a old version of robolectric. I've bumped it and this should fix it. Changelog: [Internal] [Changed] - Differential Revision: D79883742 --- .../react/uimanager/DisplayMetricsHolder.kt | 2 +- .../uimanager/DisplayMetricsHolderTest.kt | 168 ++++++++++++++++++ .../react-native/gradle/libs.versions.toml | 2 +- 3 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt index 055312c2a995..a66129c8b98c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt @@ -139,6 +139,6 @@ public object DisplayMetricsHolder { (checkNotNull(screenDisplayMetrics).heightPixels - verticalInsets).toFloat().pxToDp()) } - private fun encodeFloatsToLong(width: Float, height: Float): Long = + internal fun encodeFloatsToLong(width: Float, height: Float): Long = (width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong()) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt new file mode 100644 index 000000000000..d9c3ea02325f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager + +import android.annotation.TargetApi +import android.app.Activity +import android.content.Context +import android.util.DisplayMetrics +import android.view.View +import android.view.Window +import android.view.WindowInsets +import com.facebook.react.bridge.WritableMap +import com.facebook.testutils.shadows.ShadowNativeLoader +import com.facebook.testutils.shadows.ShadowNativeMap +import com.facebook.testutils.shadows.ShadowReadableNativeMap +import com.facebook.testutils.shadows.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowWritableNativeMap +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config( + shadows = + [ + ShadowSoLoader::class, + ShadowNativeLoader::class, + ShadowNativeMap::class, + ShadowWritableNativeMap::class, + ShadowReadableNativeMap::class, + ]) +class DisplayMetricsHolderTest { + + private lateinit var context: Context + private lateinit var displayMetrics: DisplayMetrics + + @Before + fun setUp() { + context = RuntimeEnvironment.getApplication() + displayMetrics = context.resources.displayMetrics + DisplayMetricsHolder.setWindowDisplayMetrics(null) + DisplayMetricsHolder.setScreenDisplayMetrics(null) + } + + @After + fun tearDown() { + DisplayMetricsHolder.setWindowDisplayMetrics(null) + DisplayMetricsHolder.setScreenDisplayMetrics(null) + } + + @Test(expected = IllegalStateException::class) + fun getWindowDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() { + DisplayMetricsHolder.getWindowDisplayMetrics() + } + + @Test(expected = IllegalStateException::class) + fun getScreenDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() { + DisplayMetricsHolder.getScreenDisplayMetrics() + } + + @Test + fun setAndGetWindowDisplayMetrics_returnsSetValue() { + DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics) + val result = DisplayMetricsHolder.getWindowDisplayMetrics() + assertThat(result).isEqualTo(displayMetrics) + } + + @Test + fun setAndGetScreenDisplayMetrics_returnsSetValue() { + DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics) + val result = DisplayMetricsHolder.getScreenDisplayMetrics() + assertThat(result).isEqualTo(displayMetrics) + } + + @Test + fun initDisplayMetrics_setsMetrics() { + DisplayMetricsHolder.initDisplayMetrics(context) + assertThat(DisplayMetricsHolder.getWindowDisplayMetrics()).isNotNull() + assertThat(DisplayMetricsHolder.getScreenDisplayMetrics()).isNotNull() + } + + @Test + fun initDisplayMetricsIfNotInitialized_onlyInitializesOnce() { + DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context) + val firstWindow = DisplayMetricsHolder.getWindowDisplayMetrics() + val firstScreen = DisplayMetricsHolder.getScreenDisplayMetrics() + // Should not reinitialize + DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context) + val secondWindow = DisplayMetricsHolder.getWindowDisplayMetrics() + val secondScreen = DisplayMetricsHolder.getScreenDisplayMetrics() + assertThat(secondWindow).isEqualTo(firstWindow) + assertThat(secondScreen).isEqualTo(firstScreen) + } + + @Test(expected = IllegalStateException::class) + fun getDisplayMetricsWritableMap_failsIfNotInitialized() { + DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0) + } + + @Test + fun getDisplayMetricsWritableMap_returnsCorrectMap() { + // Use the official initialization method to ensure both metrics are set + DisplayMetricsHolder.initDisplayMetrics(context) + val map: WritableMap = DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0) + assertThat(map.hasKey("windowPhysicalPixels")).isTrue() + assertThat(map.hasKey("screenPhysicalPixels")).isTrue() + val windowMap = map.getMap("windowPhysicalPixels") + val screenMap = map.getMap("screenPhysicalPixels") + checkNotNull(windowMap) + checkNotNull(screenMap) + assertThat(windowMap.hasKey("width")).isTrue() + assertThat(windowMap.hasKey("height")).isTrue() + assertThat(windowMap.hasKey("scale")).isTrue() + assertThat(windowMap.hasKey("fontScale")).isTrue() + assertThat(windowMap.hasKey("densityDpi")).isTrue() + } + + @Test + @TargetApi(30) + fun getEncodedScreenSizeWithoutVerticalInsets_returnsEncodedValue() { + DisplayMetricsHolder.initDisplayMetrics(context) + + val activity: Activity = mock() + val window: Window = mock() + val decorView: View = mock() + val windowInsets: WindowInsets = mock() + whenever(activity.window).thenReturn(window) + whenever(window.decorView).thenReturn(decorView) + + whenever(decorView.rootWindowInsets).thenReturn(windowInsets) + whenever(windowInsets.getInsets(anyInt())) + .thenReturn(android.graphics.Insets.of(10, 20, 10, 20)) + + // Should return a non-zero encoded value + val encoded = DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(activity) + assertThat(encoded).isNotZero() + } + + @Test + fun getEncodedScreenSizeWithoutVerticalInsets_returnsZeroIfActivityIsNull() { + val encoded = DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(null) + assertThat(encoded).isZero() + } + + @Test + fun encodeFloatsToLong_encodesWidthAndHeightCorrectly() { + val width = 123.45f + val height = 67.89f + val encoded = DisplayMetricsHolder.encodeFloatsToLong(width, height) + val decodedWidth = Float.fromBits((encoded shr 32).toInt()) + val decodedHeight = Float.fromBits(encoded.toInt()) + assertThat(decodedWidth).isEqualTo(width) + assertThat(decodedHeight).isEqualTo(height) + } +} diff --git a/packages/react-native/gradle/libs.versions.toml b/packages/react-native/gradle/libs.versions.toml index 58ec8b5535e0..f21668ffff84 100644 --- a/packages/react-native/gradle/libs.versions.toml +++ b/packages/react-native/gradle/libs.versions.toml @@ -35,7 +35,7 @@ mockito-kotlin = "3.2.0" nexus-publish = "2.0.0" okhttp = "4.9.2" okio = "2.9.0" -robolectric = "4.9.2" +robolectric = "4.15.1" soloader = "0.12.1" uiautomator = "2.3.0" xstream = "1.4.20"