|
8 | 8 | package com.facebook.react.uimanager |
9 | 9 |
|
10 | 10 | import android.annotation.SuppressLint |
| 11 | +import android.graphics.Matrix |
11 | 12 | import android.graphics.Rect |
12 | 13 | import android.util.DisplayMetrics |
13 | 14 | import android.view.View |
@@ -39,9 +40,11 @@ import org.mockito.kotlin.whenever |
39 | 40 | import org.robolectric.RobolectricTestRunner |
40 | 41 | import org.robolectric.RuntimeEnvironment |
41 | 42 | import org.robolectric.annotation.Config |
| 43 | +import org.robolectric.annotation.GraphicsMode |
42 | 44 |
|
43 | 45 | @RunWith(RobolectricTestRunner::class) |
44 | 46 | @Config(shadows = [ShadowArguments::class]) |
| 47 | +@GraphicsMode(GraphicsMode.Mode.NATIVE) |
45 | 48 | class SafeAreaInsetsObserverTest { |
46 | 49 | private lateinit var root: FrameLayout |
47 | 50 | private val events = mutableListOf<SafeAreaInsetsChangeEvent>() |
@@ -204,7 +207,7 @@ class SafeAreaInsetsObserverTest { |
204 | 207 | } |
205 | 208 |
|
206 | 209 | @Test |
207 | | - fun rootOffset_isNotMixedWithRootLocalInsets() { |
| 210 | + fun rootLayoutOrigin_isNotAddedToRootLocalBounds() { |
208 | 211 | root.layout(70, 120, 570, 1120) |
209 | 212 | val view = addView(root, 0, 0, 500, 1000) |
210 | 213 |
|
@@ -266,6 +269,97 @@ class SafeAreaInsetsObserverTest { |
266 | 269 | assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)).isNull() |
267 | 270 | } |
268 | 271 |
|
| 272 | + @Test |
| 273 | + fun counterRotatedAncestorAndView_reportAxisAlignedInsets() { |
| 274 | + val parent = addParent(0, 0, 500, 1000) |
| 275 | + parent.clipChildren = false |
| 276 | + parent.pivotX = 0f |
| 277 | + parent.pivotY = 0f |
| 278 | + parent.rotation = 30f |
| 279 | + val view = addView(parent, 0, 0, 500, 1000) |
| 280 | + view.pivotX = 0f |
| 281 | + view.pivotY = 0f |
| 282 | + view.rotation = -30f |
| 283 | + |
| 284 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)) |
| 285 | + .isEqualTo(Insets.of(40, 100, 40, 100)) |
| 286 | + } |
| 287 | + |
| 288 | + @Test |
| 289 | + fun threeCancellingRotations_reportAxisAlignedInsets() { |
| 290 | + val outer = addParent(0, 0, 500, 1000) |
| 291 | + outer.clipChildren = false |
| 292 | + outer.pivotX = 0f |
| 293 | + outer.pivotY = 0f |
| 294 | + outer.rotation = 10f |
| 295 | + val inner = FrameLayout(root.context) |
| 296 | + outer.addView(inner) |
| 297 | + inner.layout(0, 0, 500, 1000) |
| 298 | + inner.clipChildren = false |
| 299 | + inner.pivotX = 0f |
| 300 | + inner.pivotY = 0f |
| 301 | + inner.rotation = 20f |
| 302 | + val view = addView(inner, 0, 0, 500, 1000) |
| 303 | + view.pivotX = 0f |
| 304 | + view.pivotY = 0f |
| 305 | + view.rotation = -30f |
| 306 | + |
| 307 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)) |
| 308 | + .isEqualTo(Insets.of(40, 100, 40, 100)) |
| 309 | + } |
| 310 | + |
| 311 | + @Test |
| 312 | + fun rotatedAncestor_returnsNull() { |
| 313 | + val parent = addParent(0, 0, 500, 1000) |
| 314 | + val view = addView(parent, 0, 0, 500, 1000) |
| 315 | + parent.rotation = 45f |
| 316 | + |
| 317 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)).isNull() |
| 318 | + } |
| 319 | + |
| 320 | + @Test |
| 321 | + fun skewedAncestor_returnsNull() { |
| 322 | + val matrix = Matrix().apply { setSkew(0.25f, 0f) } |
| 323 | + val parent = |
| 324 | + object : FrameLayout(root.context) { |
| 325 | + override fun getMatrix(): Matrix = matrix |
| 326 | + } |
| 327 | + root.addView(parent) |
| 328 | + parent.layout(0, 0, 500, 1000) |
| 329 | + val view = addView(parent, 0, 0, 500, 1000) |
| 330 | + |
| 331 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)).isNull() |
| 332 | + } |
| 333 | + |
| 334 | + @Test |
| 335 | + fun skewBeyondAxisAlignmentErrorBudget_returnsNull() { |
| 336 | + for (matrix in |
| 337 | + arrayOf( |
| 338 | + Matrix().apply { setSkew(-0.000002f, 0f) }, |
| 339 | + Matrix().apply { setSkew(0.000002f, 0f) }, |
| 340 | + Matrix().apply { setSkew(0f, -0.000002f) }, |
| 341 | + Matrix().apply { setSkew(0f, 0.000002f) }, |
| 342 | + )) { |
| 343 | + val view = |
| 344 | + object : View(root.context) { |
| 345 | + override fun getMatrix(): Matrix = matrix |
| 346 | + } |
| 347 | + root.addView(view) |
| 348 | + view.layout(0, 0, 10000, 10000) |
| 349 | + |
| 350 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)).isNull() |
| 351 | + root.removeView(view) |
| 352 | + } |
| 353 | + } |
| 354 | + |
| 355 | + @Test |
| 356 | + fun perspectiveRotation_returnsNull() { |
| 357 | + val view = addView(root, 0, 0, 500, 1000) |
| 358 | + view.rotationX = 45f |
| 359 | + |
| 360 | + assertThat(SafeAreaInsetsObserver.getSafeAreaInsets(view)).isNull() |
| 361 | + } |
| 362 | + |
269 | 363 | @Test |
270 | 364 | fun reflectedView_preservesWindowEdges() { |
271 | 365 | windowInsets = Insets.of(40, 100, 20, 60) |
@@ -401,6 +495,42 @@ class SafeAreaInsetsObserverTest { |
401 | 495 | assertThat(events).hasSize(1) |
402 | 496 | } |
403 | 497 |
|
| 498 | + @Test |
| 499 | + @Suppress("DEPRECATION") |
| 500 | + @SuppressLint("DeprecatedMethod") |
| 501 | + fun subpixelChanges_emitOnlyBeyondOnePhysicalPixelWithoutRoundingPayload() { |
| 502 | + DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics().apply { density = 2f }) |
| 503 | + val view = addView(root, 0, 0, 500, 1000) |
| 504 | + val observer = observe(view) |
| 505 | + observer.onPreDraw() |
| 506 | + view.translationY = 0.5f |
| 507 | + observer.onPreDraw() |
| 508 | + view.translationY = 1f |
| 509 | + observer.onPreDraw() |
| 510 | + assertThat(events).hasSize(1) |
| 511 | + |
| 512 | + view.translationY = 1.25f |
| 513 | + observer.onPreDraw() |
| 514 | + observer.onPreDraw() |
| 515 | + assertThat(events).hasSize(2) |
| 516 | + val emitter = mock<RCTModernEventEmitter>() |
| 517 | + val payload = argumentCaptor<WritableMap>() |
| 518 | + events.last().dispatchModern(emitter) |
| 519 | + verify(emitter) |
| 520 | + .receiveEvent( |
| 521 | + eq(1), |
| 522 | + eq(2), |
| 523 | + eq("topSafeAreaInsetsChange"), |
| 524 | + any(), |
| 525 | + any(), |
| 526 | + payload.capture(), |
| 527 | + any(), |
| 528 | + ) |
| 529 | + val insets = checkNotNull(payload.firstValue.getMap("insets")) |
| 530 | + assertThat(insets.getDouble("top")).isEqualTo(49.375) |
| 531 | + assertThat(insets.getDouble("bottom")).isEqualTo(50.625) |
| 532 | + } |
| 533 | + |
404 | 534 | @Test |
405 | 535 | fun supportedUnsupportedSupported_reEmitsAfterRecovery() { |
406 | 536 | val view = addView(root, 0, 0, 500, 1000) |
@@ -436,6 +566,21 @@ class SafeAreaInsetsObserverTest { |
436 | 566 | assertThat(events).hasSize(2) |
437 | 567 | } |
438 | 568 |
|
| 569 | + @Test |
| 570 | + fun detachAndReattach_reEmitsUnchangedInsetsOnNextPreDraw() { |
| 571 | + val view = addView(root, 0, 0, 500, 1000) |
| 572 | + val observer = observe(view) |
| 573 | + observer.onPreDraw() |
| 574 | + observer.onViewDetachedFromWindow(view) |
| 575 | + observer.onViewAttachedToWindow(view) |
| 576 | + |
| 577 | + assertThat(view.getTag(R.id.safe_area_insets_observer)).isSameAs(observer) |
| 578 | + assertThat(events).hasSize(1) |
| 579 | + observer.onPreDraw() |
| 580 | + observer.onPreDraw() |
| 581 | + assertThat(events).hasSize(2) |
| 582 | + } |
| 583 | + |
439 | 584 | @Test |
440 | 585 | fun disabledObserver_isRemovedAndReenableStartsFresh() { |
441 | 586 | val view = addView(root, 0, 0, 500, 1000) |
|
0 commit comments