Skip to content
Open
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 @@ -19,6 +19,7 @@ import android.widget.EditText
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
Expand Down Expand Up @@ -103,6 +104,18 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
}
val accessibilityActions = host.getTag(R.id.accessibility_actions) as ReadableArray?

val accessibilityCollection = host.getTag(R.id.accessibility_collection) as ReadableMap?
if (accessibilityCollection != null) {
val rowCount = accessibilityCollection.getInt("rowCount")
val columnCount = accessibilityCollection.getInt("columnCount")
val hierarchical =
accessibilityCollection.hasKey("hierarchical") &&
accessibilityCollection.getBoolean("hierarchical")

val collectionInfoCompat = CollectionInfoCompat.obtain(rowCount, columnCount, hierarchical)
info.setCollectionInfo(collectionInfoCompat)
}

val accessibilityCollectionItem =
host.getTag(R.id.accessibility_collection_item) as ReadableMap?
if (accessibilityCollectionItem != null) {
Expand Down Expand Up @@ -597,6 +610,7 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
view.getTag(R.id.accessibility_state) != null ||
view.getTag(R.id.accessibility_actions) != null ||
view.getTag(R.id.react_test_id) != null ||
view.getTag(R.id.accessibility_collection) != null ||
view.getTag(R.id.accessibility_collection_item) != null ||
view.getTag(R.id.accessibility_links) != null ||
view.getTag(R.id.role) != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ internal class ReactScrollViewAccessibilityDelegate : AccessibilityDelegateCompa
val accessibilityCollection =
view.getTag(R.id.accessibility_collection) as? ReadableMap ?: return

event.itemCount = accessibilityCollection.getInt("itemCount")
if (accessibilityCollection.hasKey("itemCount")) {
event.itemCount = accessibilityCollection.getInt("itemCount")
}

val contentView = (view as? ViewGroup)?.getChildAt(0) as? ViewGroup ?: return

Expand All @@ -71,10 +73,10 @@ internal class ReactScrollViewAccessibilityDelegate : AccessibilityDelegateCompa
return
}
var accessibilityCollectionItem: ReadableMap? =
nextChild.getTag(R.id.accessibility_collection_item) as ReadableMap
nextChild.getTag(R.id.accessibility_collection_item) as? ReadableMap

if (nextChild !is ViewGroup) {
return
continue
}

// If this child's accessibilityCollectionItem is null, we'll check one more
Expand All @@ -93,10 +95,12 @@ internal class ReactScrollViewAccessibilityDelegate : AccessibilityDelegateCompa
}

if (isVisible && accessibilityCollectionItem != null) {
if (firstVisibleIndex == null) {
if (accessibilityCollectionItem.hasKey("itemIndex") && firstVisibleIndex == null) {
firstVisibleIndex = accessibilityCollectionItem.getInt("itemIndex")
}
lastVisibleIndex = accessibilityCollectionItem.getInt("itemIndex")
if (accessibilityCollectionItem.hasKey("itemIndex")) {
lastVisibleIndex = accessibilityCollectionItem.getInt("itemIndex")
}
}

if (firstVisibleIndex != null && lastVisibleIndex != null) {
Expand All @@ -121,7 +125,9 @@ internal class ReactScrollViewAccessibilityDelegate : AccessibilityDelegateCompa
if (accessibilityCollection != null) {
val rowCount = accessibilityCollection.getInt("rowCount")
val columnCount = accessibilityCollection.getInt("columnCount")
val hierarchical = accessibilityCollection.getBoolean("hierarchical")
val hierarchical =
accessibilityCollection.hasKey("hierarchical") &&
accessibilityCollection.getBoolean("hierarchical")

val collectionInfoCompat =
AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ internal class ReactTextViewAccessibilityDelegate(
view.getTag(R.id.accessibility_state) != null ||
view.getTag(R.id.accessibility_actions) != null ||
view.getTag(R.id.react_test_id) != null ||
view.getTag(R.id.accessibility_collection) != null ||
view.getTag(R.id.accessibility_collection_item) != null ||
view.getTag(R.id.accessibility_links) != null ||
view.getTag(R.id.role) != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package com.facebook.react.uimanager

import android.os.Bundle
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.react.R
import com.facebook.react.bridge.BridgeReactContext
Expand Down Expand Up @@ -123,6 +124,41 @@ class ReactAccessibilityDelegateTest {
assertThat(result).isTrue()
}

@Test
fun testAccessibilityCollection_setsCollectionInfo() {
view.setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("rowCount", 4)
putInt("columnCount", 2)
putBoolean("hierarchical", true)
},
)

val nodeInfo = AccessibilityNodeInfoCompat.obtain()
accessibilityDelegate.onInitializeAccessibilityNodeInfo(view, nodeInfo)

assertThat(nodeInfo.collectionInfo).isNotNull()
assertThat(nodeInfo.collectionInfo.rowCount).isEqualTo(4)
assertThat(nodeInfo.collectionInfo.columnCount).isEqualTo(2)
assertThat(nodeInfo.collectionInfo.isHierarchical).isTrue()
}

@Test
fun testSetDelegate_accessibilityCollection_installsAccessibilityDelegate() {
view.setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("rowCount", 4)
putInt("columnCount", 2)
},
)

ReactAccessibilityDelegate.setDelegate(view, false, 0)

assertThat(ViewCompat.hasAccessibilityDelegate(view)).isTrue()
}

@Test
fun testPerformAccessibilityAction_activateAction_dispatchesEvent() {
val accessibilityActions = JavaOnlyArray()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.views.scroll

import android.content.Context
import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.facebook.react.R
import com.facebook.react.bridge.JavaOnlyMap
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class ReactScrollViewAccessibilityDelegateTest {
private lateinit var context: Context
private lateinit var scrollView: TestScrollView
private lateinit var contentView: FrameLayout
private lateinit var delegate: ReactScrollViewAccessibilityDelegate

@Before
fun setUp() {
context = RuntimeEnvironment.getApplication()
scrollView = TestScrollView(context)
contentView = FrameLayout(context)
scrollView.addView(contentView)
delegate = ReactScrollViewAccessibilityDelegate()
}

@Test
fun testOnInitializeAccessibilityEvent_allowsMissingOptionalCollectionFields() {
scrollView.setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("rowCount", 3)
putInt("columnCount", 1)
},
)
contentView.addView(View(context))

val event = AccessibilityEvent()
delegate.onInitializeAccessibilityEvent(scrollView, event)

assertThat(event.itemCount).isEqualTo(-1)
assertThat(event.fromIndex).isEqualTo(-1)
assertThat(event.toIndex).isEqualTo(-1)
}

@Test
fun testOnInitializeAccessibilityEvent_readsNestedCollectionItemIndex() {
scrollView.setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("itemCount", 5)
putInt("rowCount", 5)
putInt("columnCount", 1)
},
)
val wrapper = FrameLayout(context)
val item = View(context)
item.setTag(
R.id.accessibility_collection_item,
JavaOnlyMap().apply {
putInt("itemIndex", 2)
putInt("rowIndex", 2)
putInt("rowSpan", 1)
putInt("columnIndex", 0)
putInt("columnSpan", 1)
putBoolean("heading", false)
},
)
wrapper.addView(item)
contentView.addView(wrapper)

val event = AccessibilityEvent()
delegate.onInitializeAccessibilityEvent(scrollView, event)

assertThat(event.itemCount).isEqualTo(5)
assertThat(event.fromIndex).isEqualTo(2)
assertThat(event.toIndex).isEqualTo(2)
}

@Test
fun testOnInitializeAccessibilityNodeInfo_defaultsMissingHierarchicalToFalse() {
scrollView.setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("rowCount", 3)
putInt("columnCount", 1)
},
)

val nodeInfo = AccessibilityNodeInfoCompat.obtain()
delegate.onInitializeAccessibilityNodeInfo(scrollView, nodeInfo)

assertThat(nodeInfo.collectionInfo).isNotNull()
assertThat(nodeInfo.collectionInfo.rowCount).isEqualTo(3)
assertThat(nodeInfo.collectionInfo.columnCount).isEqualTo(1)
assertThat(nodeInfo.collectionInfo.isHierarchical).isFalse()
}

private class TestScrollView(context: Context) : FrameLayout(context), ReactAccessibleScrollView {
override val scrollEnabled: Boolean = true

override fun isPartiallyScrolledInView(view: View): Boolean = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.views.text

import android.view.View
import androidx.core.view.ViewCompat
import com.facebook.react.R
import com.facebook.react.bridge.JavaOnlyMap
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class ReactTextViewAccessibilityDelegateTest {

@Test
fun testSetDelegate_accessibilityCollection_installsAccessibilityDelegate() {
val view =
View(RuntimeEnvironment.getApplication()).apply {
setTag(
R.id.accessibility_collection,
JavaOnlyMap().apply {
putInt("rowCount", 4)
putInt("columnCount", 2)
},
)
}

ReactTextViewAccessibilityDelegate.setDelegate(view, false, 0)

assertThat(ViewCompat.hasAccessibilityDelegate(view)).isTrue()
}
}
Loading