Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ public object ReactScrollViewHelper {
return null
}

val contentView = host.getChildAt(0) ?: return null

val uimanager =
UIManagerHelper.getUIManager(host.context as ReactContext, UIManagerType.FABRIC)
?: return null
Expand All @@ -586,9 +588,8 @@ public object ReactScrollViewHelper {
?: return null

val ancestorIdList =
uimanager
.getRelativeAncestorList(host.getChildAt(0).id, nextFocusableViewId)
?.toMutableSet() ?: return null
uimanager.getRelativeAncestorList(contentView.id, nextFocusableViewId)?.toMutableSet()
?: return null

ancestorIdList.add(nextFocusableViewId)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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.
*/

// The deprecated APIs exercised here remain public and required by this Robolectric setup.
@file:Suppress("DEPRECATION")

package com.facebook.react.views.scroll

import android.view.View
import com.facebook.react.bridge.BridgeReactContext
import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.soloader.SoLoader
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.MockedStatic
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

/** Verifies the shadow-tree focus search on a scroll view whose content view is gone. */
@RunWith(RobolectricTestRunner::class)
class ReactScrollViewHelperFocusSearchTest {

private lateinit var themedContext: ThemedReactContext
private lateinit var uiManagerHelperMock: MockedStatic<UIManagerHelper>

@Before
fun setup() {
ReactNativeFeatureFlagsForTests.setUp()
SoLoader.setInTestMode()
val context = BridgeReactContext(RuntimeEnvironment.getApplication())
context.initializeWithInstance(createMockCatalystInstance())
themedContext = ThemedReactContext(context, context, null, -1)

val fabricUIManager: FabricUIManager = mock()
whenever(fabricUIManager.findNextFocusableElement(any(), any(), any())).thenReturn(NEXT_TAG)

uiManagerHelperMock = mockStatic(UIManagerHelper::class.java)
uiManagerHelperMock
.`when`<EventDispatcher?> { UIManagerHelper.getEventDispatcher(anyOrNull()) }
.thenReturn(mock())
uiManagerHelperMock
.`when`<FabricUIManager?> { UIManagerHelper.getUIManager(any(), eq(UIManagerType.FABRIC)) }
.thenReturn(fabricUIManager)
}

@After
fun teardown() {
uiManagerHelperMock.close()
}

@Test
fun testScrollViewWithoutContentViewHasNoNextFocusableView() {
val scrollView = ReactScrollView(themedContext)
val focused = View(themedContext)

assertThat(ReactScrollViewHelper.findNextFocusableView(scrollView, focused, View.FOCUS_DOWN))
.isNull()
}

@Test
fun testHorizontalScrollViewWithoutContentViewHasNoNextFocusableView() {
val scrollView = ReactHorizontalScrollView(themedContext)
val focused = View(themedContext)

assertThat(ReactScrollViewHelper.findNextFocusableView(scrollView, focused, View.FOCUS_RIGHT))
.isNull()
}

private companion object {
const val NEXT_TAG = 42
}
}
Loading