Skip to content

Commit ef598d9

Browse files
zoontekmeta-codesync[bot]
authored andcommitted
Add tests for DeviceInfoModule.getWindowDisplayMetrics edge-to-edge branches (facebook#56632)
Summary: This PR adds two focused unit tests for `DeviceInfoModule.getWindowDisplayMetrics()` that attach a mocked `Activity`, swap in a fake `WindowMetricsCalculator` via `WindowMetricsCalculator.overrideDecorator()` and verify both branches of `isEdgeToEdgeFeatureFlagOn`: - edge-to-edge on -> `WindowMetrics.bounds` are used directly. - edge-to-edge off -> bounds minus `systemBars | displayCutout` insets. To make this testable without reflection or `SuppressLint("RestrictedApi")` outside the production code, three small visibility tweaks: - `DeviceInfoModule.getWindowDisplayMetrics()` and `getDisplayMetricsWritableMap()`: `private` → `internal` + `VisibleForTesting` - `WindowUtilKt.isEdgeToEdgeFeatureFlagOn` setter: `private set` → `VisibleForTesting internal set` ## Changelog: [INTERNAL] [ADDED] - Add unit tests for `DeviceInfoModule.getWindowDisplayMetrics` covering both edge-to-edge branches Pull Request resolved: facebook#56632 Test Plan: ``` ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest \ --tests com.facebook.react.modules.deviceinfo.DeviceInfoModuleTest ``` Reviewed By: javache Differential Revision: D102801090 Pulled By: alanleedev fbshipit-source-id: 5e103404ef822d782c87117d77916221c512eaa2
1 parent 7128482 commit ef598d9

3 files changed

Lines changed: 98 additions & 3 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.facebook.react.modules.deviceinfo
99

1010
import android.util.DisplayMetrics
11+
import androidx.annotation.VisibleForTesting
1112
import androidx.core.view.ViewCompat
1213
import androidx.core.view.WindowInsetsCompat
1314
import androidx.window.layout.WindowMetricsCalculator
@@ -36,7 +37,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
3637
reactContext.addLifecycleEventListener(this)
3738
}
3839

39-
private fun getWindowDisplayMetrics(): DisplayMetrics {
40+
@VisibleForTesting
41+
internal fun getWindowDisplayMetrics(): DisplayMetrics {
4042
val windowDisplayMetrics = DisplayMetrics()
4143
windowDisplayMetrics.setTo(reactApplicationContext.resources.displayMetrics)
4244

@@ -64,7 +66,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
6466
return windowDisplayMetrics
6567
}
6668

67-
fun getDisplayMetricsWritableMap(): WritableMap =
69+
@VisibleForTesting
70+
internal fun getDisplayMetricsWritableMap(): WritableMap =
6871
WritableNativeMap().apply {
6972
putMap(
7073
"windowPhysicalPixels",

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import android.view.View
1414
import android.view.Window
1515
import android.view.WindowInsetsController
1616
import android.view.WindowManager
17+
import androidx.annotation.VisibleForTesting
1718
import androidx.core.view.ViewCompat
1819
import androidx.core.view.WindowCompat
1920
import androidx.core.view.WindowInsetsCompat
@@ -35,7 +36,7 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b)
3536
* as enabled elsewhere in the application.
3637
*/
3738
public var isEdgeToEdgeFeatureFlagOn: Boolean = false
38-
private set
39+
@VisibleForTesting internal set
3940

4041
public fun setEdgeToEdgeFeatureFlagOn() {
4142
isEdgeToEdgeFeatureFlagOn = true

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,25 @@
99

1010
package com.facebook.react.modules.deviceinfo
1111

12+
import android.app.Activity
13+
import android.graphics.Rect
1214
import android.util.DisplayMetrics
15+
import android.view.View
16+
import android.view.Window
17+
import androidx.core.graphics.Insets
18+
import androidx.core.view.ViewCompat
19+
import androidx.core.view.WindowInsetsCompat
20+
import androidx.window.layout.WindowMetrics
21+
import androidx.window.layout.WindowMetricsCalculator
22+
import androidx.window.layout.WindowMetricsCalculatorDecorator
1323
import com.facebook.react.bridge.BridgeReactContext
1424
import com.facebook.react.bridge.JavaOnlyMap
1525
import com.facebook.react.bridge.ReactContext
1626
import com.facebook.react.bridge.ReactTestHelper
1727
import com.facebook.react.bridge.WritableMap
1828
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
1929
import com.facebook.react.uimanager.DisplayMetricsHolder
30+
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2031
import com.facebook.testutils.shadows.ShadowNativeLoader
2132
import com.facebook.testutils.shadows.ShadowNativeMap
2233
import com.facebook.testutils.shadows.ShadowReadableNativeMap
@@ -33,6 +44,7 @@ import org.mockito.ArgumentMatchers
3344
import org.mockito.MockedStatic
3445
import org.mockito.Mockito.mockStatic
3546
import org.mockito.kotlin.doReturn
47+
import org.mockito.kotlin.mock
3648
import org.mockito.kotlin.spy
3749
import org.mockito.kotlin.times
3850
import org.mockito.kotlin.verify
@@ -80,6 +92,7 @@ class DeviceInfoModuleTest : TestCase() {
8092
@After
8193
fun teardown() {
8294
displayMetricsHolder.close()
95+
isEdgeToEdgeFeatureFlagOn = false
8396
}
8497

8598
@Test
@@ -150,10 +163,88 @@ class DeviceInfoModuleTest : TestCase() {
150163
assertThat(windowMap.hasKey("densityDpi")).isTrue()
151164
}
152165

166+
@Test
167+
fun getWindowDisplayMetrics_usesBoundsWhenEdgeToEdgeOn() {
168+
isEdgeToEdgeFeatureFlagOn = true
169+
170+
val activity = mock<Activity>()
171+
doReturn(activity).whenever(reactContext).currentActivity
172+
173+
val bounds = Rect(0, 0, 1080, 2400)
174+
val calculator = mockCalculator(activity, bounds)
175+
176+
withWindowMetricsCalculator(calculator) {
177+
val metrics = deviceInfoModule.getWindowDisplayMetrics()
178+
assertThat(metrics.widthPixels).isEqualTo(bounds.width())
179+
assertThat(metrics.heightPixels).isEqualTo(bounds.height())
180+
}
181+
}
182+
183+
@Test
184+
fun getWindowDisplayMetrics_subtractsSystemBarsWhenEdgeToEdgeOff() {
185+
isEdgeToEdgeFeatureFlagOn = false
186+
187+
val window = mock<Window>()
188+
val decorView = mock<View>()
189+
whenever(window.decorView).thenReturn(decorView)
190+
val activity = mock<Activity>()
191+
whenever(activity.window).thenReturn(window)
192+
doReturn(activity).whenever(reactContext).currentActivity
193+
194+
val bounds = Rect(0, 0, 1080, 2400)
195+
val calculator = mockCalculator(activity, bounds)
196+
val rootInsets = mockRootInsets(Insets.of(20, 80, 30, 100))
197+
198+
withWindowMetricsCalculator(calculator) {
199+
mockStatic(ViewCompat::class.java).use { viewCompatStatic ->
200+
viewCompatStatic
201+
.`when`<WindowInsetsCompat?> { ViewCompat.getRootWindowInsets(decorView) }
202+
.thenReturn(rootInsets)
203+
204+
val metrics = deviceInfoModule.getWindowDisplayMetrics()
205+
assertThat(metrics.widthPixels).isEqualTo(bounds.width() - (20 + 30))
206+
assertThat(metrics.heightPixels).isEqualTo(bounds.height() - (80 + 100))
207+
}
208+
}
209+
}
210+
153211
private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) {
154212
doReturn(fakeDisplayMetrics).whenever(deviceInfoModule).getDisplayMetricsWritableMap()
155213
}
156214

215+
private fun mockCalculator(activity: Activity, bounds: Rect): WindowMetricsCalculator {
216+
val windowMetrics = mock<WindowMetrics>()
217+
whenever(windowMetrics.bounds).thenReturn(bounds)
218+
val calculator = mock<WindowMetricsCalculator>()
219+
whenever(calculator.computeCurrentWindowMetrics(activity)).thenReturn(windowMetrics)
220+
return calculator
221+
}
222+
223+
private fun mockRootInsets(insets: Insets): WindowInsetsCompat {
224+
val rootInsets = mock<WindowInsetsCompat>()
225+
val type = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
226+
whenever(rootInsets.getInsets(type)).thenReturn(insets)
227+
return rootInsets
228+
}
229+
230+
@Suppress("RestrictedApi")
231+
private fun withWindowMetricsCalculator(
232+
target: WindowMetricsCalculator,
233+
block: () -> Unit,
234+
) {
235+
WindowMetricsCalculator.overrideDecorator(
236+
object : WindowMetricsCalculatorDecorator {
237+
override fun decorate(calculator: WindowMetricsCalculator): WindowMetricsCalculator =
238+
target
239+
}
240+
)
241+
try {
242+
block()
243+
} finally {
244+
WindowMetricsCalculator.reset()
245+
}
246+
}
247+
157248
companion object {
158249
private fun verifyUpdateDimensionsEventsEmitted(
159250
context: ReactContext?,

0 commit comments

Comments
 (0)