From 6c7fee51892272c124b8adb3dda0be3fbda10ed3 Mon Sep 17 00:00:00 2001 From: Marve10s Date: Thu, 1 Oct 2026 15:25:25 +0300 Subject: [PATCH 1/3] Restore Android tap targets when disabled is cleared --- .../Components/Pressable/Pressable.js | 2 +- .../Pressable/__tests__/Pressable-test.js | 48 +++++++++++++++++ .../Components/Touchable/TouchableOpacity.js | 4 +- .../__tests__/TouchableOpacity-test.js | 54 +++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js create mode 100644 packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index ba26272d9304..6f57fa3f6c85 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -251,7 +251,7 @@ function Pressable({ let _accessibilityState = { busy: ariaBusy ?? accessibilityState?.busy, checked: ariaChecked ?? accessibilityState?.checked, - disabled: ariaDisabled ?? accessibilityState?.disabled, + disabled: ariaDisabled ?? accessibilityState?.disabled ?? false, expanded: ariaExpanded ?? accessibilityState?.expanded, selected: ariaSelected ?? accessibilityState?.selected, }; diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js new file mode 100644 index 000000000000..a16c65a81bfe --- /dev/null +++ b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js @@ -0,0 +1,48 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const Pressable = require('../Pressable').default; +const { + create, + unmount, + update, +} = require('@react-native/jest-preset/jest/renderer'); + +describe('Pressable native disabled state', () => { + it.each([undefined, null])( + 're-enables the native view when disabled changes from true to %s', + async disabled => { + const renderer = await create(); + + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update(renderer, ); + + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); + await unmount(renderer); + }, + ); + + it('preserves accessibility disabled state when the disabled prop is removed', async () => { + const renderer = await create(); + + await update(renderer, ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update(renderer, ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update(renderer, ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); + await unmount(renderer); + }); +}); diff --git a/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js b/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js index 6f86c40f369d..bc666479fd85 100644 --- a/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js +++ b/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js @@ -191,7 +191,9 @@ class TouchableOpacity extends React.Component< checked: this.props['aria-checked'] ?? this.props.accessibilityState?.checked, disabled: - this.props['aria-disabled'] ?? this.props.accessibilityState?.disabled, + this.props['aria-disabled'] ?? + this.props.accessibilityState?.disabled ?? + false, expanded: this.props['aria-expanded'] ?? this.props.accessibilityState?.expanded, selected: diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js new file mode 100644 index 000000000000..a2b37069909f --- /dev/null +++ b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js @@ -0,0 +1,54 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const TouchableOpacity = require('../TouchableOpacity').default; +const { + create, + unmount, + update, +} = require('@react-native/jest-preset/jest/renderer'); + +describe('TouchableOpacity native disabled state', () => { + it.each([undefined, null])( + 're-enables the native view when disabled changes from true to %s', + async disabled => { + const renderer = await create(); + + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update(renderer, ); + + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); + await unmount(renderer); + }, + ); + + it('preserves accessibility disabled state when the disabled prop is removed', async () => { + const renderer = await create(); + + await update( + renderer, + , + ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update(renderer, ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); + + await update( + renderer, + , + ); + expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); + await unmount(renderer); + }); +}); From e79c001299c0c1170cea2e55da0b9dd6e06ca701 Mon Sep 17 00:00:00 2001 From: Marve10s Date: Thu, 1 Oct 2026 15:36:46 +0300 Subject: [PATCH 2/3] Fix Pressable accessibility state Flow type --- .../react-native/Libraries/Components/Pressable/Pressable.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 6f57fa3f6c85..ba4e4a163e8f 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -15,6 +15,7 @@ import type { LayoutChangeEvent, MouseEvent, } from '../../Types/CoreEventTypes'; +import type {AccessibilityState} from '../View/ViewAccessibility'; import type {ViewProps} from '../View/ViewPropTypes'; import {PressabilityDebugView} from '../../Pressability/PressabilityDebug'; @@ -248,7 +249,7 @@ function Pressable({ const shouldUpdatePressed = typeof children === 'function' || typeof style === 'function'; - let _accessibilityState = { + let _accessibilityState: AccessibilityState = { busy: ariaBusy ?? accessibilityState?.busy, checked: ariaChecked ?? accessibilityState?.checked, disabled: ariaDisabled ?? accessibilityState?.disabled ?? false, From e4c82477101d676378cb226cc8aea6ba980622cc Mon Sep 17 00:00:00 2001 From: Marve10s Date: Thu, 1 Oct 2026 22:25:53 +0300 Subject: [PATCH 3/3] Fix cleared disabled state in Android view handling --- .../Components/Pressable/Pressable.js | 5 +- .../Pressable/__tests__/Pressable-test.js | 48 --------------- .../Components/Touchable/TouchableOpacity.js | 4 +- .../__tests__/TouchableOpacity-test.js | 54 ---------------- .../ReactAndroid/api/ReactAndroid.api | 2 + .../react/views/view/ReactViewGroup.kt | 1 + .../react/views/view/ReactViewManager.kt | 21 +++++++ .../react/uimanager/BaseViewManagerTest.kt | 53 +++++++++++++++- .../textinput/ReactTextInputPropertyTest.kt | 12 ++++ .../react/views/view/ReactViewGroupTest.kt | 10 +++ .../components/view/HostPlatformViewProps.cpp | 5 ++ .../view/tests/AccessibilityPropsTest.cpp | 61 +++++++++++++++++++ 12 files changed, 167 insertions(+), 109 deletions(-) delete mode 100644 packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js delete mode 100644 packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index ba4e4a163e8f..ba26272d9304 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -15,7 +15,6 @@ import type { LayoutChangeEvent, MouseEvent, } from '../../Types/CoreEventTypes'; -import type {AccessibilityState} from '../View/ViewAccessibility'; import type {ViewProps} from '../View/ViewPropTypes'; import {PressabilityDebugView} from '../../Pressability/PressabilityDebug'; @@ -249,10 +248,10 @@ function Pressable({ const shouldUpdatePressed = typeof children === 'function' || typeof style === 'function'; - let _accessibilityState: AccessibilityState = { + let _accessibilityState = { busy: ariaBusy ?? accessibilityState?.busy, checked: ariaChecked ?? accessibilityState?.checked, - disabled: ariaDisabled ?? accessibilityState?.disabled ?? false, + disabled: ariaDisabled ?? accessibilityState?.disabled, expanded: ariaExpanded ?? accessibilityState?.expanded, selected: ariaSelected ?? accessibilityState?.selected, }; diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js deleted file mode 100644 index a16c65a81bfe..000000000000 --- a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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. - * - * @flow strict-local - * @format - */ - -import * as React from 'react'; - -const Pressable = require('../Pressable').default; -const { - create, - unmount, - update, -} = require('@react-native/jest-preset/jest/renderer'); - -describe('Pressable native disabled state', () => { - it.each([undefined, null])( - 're-enables the native view when disabled changes from true to %s', - async disabled => { - const renderer = await create(); - - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update(renderer, ); - - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); - await unmount(renderer); - }, - ); - - it('preserves accessibility disabled state when the disabled prop is removed', async () => { - const renderer = await create(); - - await update(renderer, ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update(renderer, ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update(renderer, ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); - await unmount(renderer); - }); -}); diff --git a/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js b/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js index bc666479fd85..6f86c40f369d 100644 --- a/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js +++ b/packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js @@ -191,9 +191,7 @@ class TouchableOpacity extends React.Component< checked: this.props['aria-checked'] ?? this.props.accessibilityState?.checked, disabled: - this.props['aria-disabled'] ?? - this.props.accessibilityState?.disabled ?? - false, + this.props['aria-disabled'] ?? this.props.accessibilityState?.disabled, expanded: this.props['aria-expanded'] ?? this.props.accessibilityState?.expanded, selected: diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js deleted file mode 100644 index a2b37069909f..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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. - * - * @flow strict-local - * @format - */ - -import * as React from 'react'; - -const TouchableOpacity = require('../TouchableOpacity').default; -const { - create, - unmount, - update, -} = require('@react-native/jest-preset/jest/renderer'); - -describe('TouchableOpacity native disabled state', () => { - it.each([undefined, null])( - 're-enables the native view when disabled changes from true to %s', - async disabled => { - const renderer = await create(); - - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update(renderer, ); - - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); - await unmount(renderer); - }, - ); - - it('preserves accessibility disabled state when the disabled prop is removed', async () => { - const renderer = await create(); - - await update( - renderer, - , - ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update(renderer, ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(true); - - await update( - renderer, - , - ); - expect(renderer.toJSON()?.props.accessibilityState.disabled).toBe(false); - await unmount(renderer); - }); -}); diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 29a04dfbfc07..0e5aa416a2e6 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6540,6 +6540,8 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react public fun setTVPreferredFocus (Lcom/facebook/react/views/view/ReactViewGroup;Z)V public synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V protected fun setTransformProperty (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V + public synthetic fun setViewState (Landroid/view/View;Lcom/facebook/react/bridge/ReadableMap;)V + public fun setViewState (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableMap;)V } public final class com/facebook/react/views/view/ReactViewManager$Companion { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt index 363f1336d000..843dbe5c4b86 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt @@ -182,6 +182,7 @@ public open class ReactViewGroup public constructor(context: Context?) : */ private fun initView() { clipChildren = false + isEnabled = true _removeClippedSubviews = false inSubviewClippingLoop = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt index 21ef11182da3..46b9cc0f032e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt @@ -10,6 +10,7 @@ package com.facebook.react.views.view import android.graphics.Rect import android.view.View import com.facebook.common.logging.FLog +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Dynamic import com.facebook.react.bridge.DynamicFromObject import com.facebook.react.bridge.JSApplicationIllegalArgumentException @@ -97,6 +98,26 @@ public open class ReactViewManager : ReactClippingViewManager() view.isFocusable = accessible } + @ReactProp(name = ViewProps.ACCESSIBILITY_STATE) + override fun setViewState(view: ReactViewGroup, accessibilityState: ReadableMap?) { + if ( + javaClass != ReactViewManager::class.java || + accessibilityState != null && + accessibilityState.hasKey("disabled") && + !accessibilityState.isNull("disabled") + ) { + super.setViewState(view, accessibilityState) + return + } + + val normalizedState = Arguments.createMap() + if (accessibilityState != null) { + normalizedState.merge(accessibilityState) + } + normalizedState.putBoolean("disabled", false) + super.setViewState(view, normalizedState) + } + @ReactProp(name = ViewProps.ACCESSIBILITY_ORDER) public open fun setAccessibilityOrder(view: ReactViewGroup, nativeIds: ReadableArray?) { if (!ReactNativeFeatureFlags.enableAccessibilityOrder()) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt index 8915d91903a7..6171990cd814 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt @@ -10,11 +10,13 @@ package com.facebook.react.uimanager import android.view.View.OnFocusChangeListener +import android.widget.FrameLayout import com.facebook.react.R import com.facebook.react.bridge.BridgeReactContext import com.facebook.react.bridge.DynamicFromObject import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.ReactViewManager @@ -67,10 +69,59 @@ class BaseViewManagerTest { val accessibilityState = JavaOnlyMap() accessibilityState.putBoolean("selected", true) viewManager.setViewState(view, accessibilityState) - Assertions.assertThat(view.getTag(R.id.accessibility_state)).isEqualTo(accessibilityState) + Assertions.assertThat(view.getTag(R.id.accessibility_state)) + .isEqualTo(JavaOnlyMap.of("selected", true, "disabled", false)) Assertions.assertThat(view.isSelected).isEqualTo(true) } + @Test + fun testClearingAccessibilityDisabledRestoresTapTarget() { + val root = FrameLayout(themedReactContext) + root.id = 1 + root.layout(0, 0, 300, 300) + view.id = 3 + root.addView(view) + view.layout(0, 0, 200, 100) + + for (state in listOf(JavaOnlyMap(), JavaOnlyMap.of("disabled", null), null)) { + viewManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(1) + + viewManager.setViewState(view, state) + + Assertions.assertThat(view.isEnabled).isTrue() + Assertions.assertThat(TouchTargetHelper.findTargetTagForTouch(20f, 50f, root)).isEqualTo(3) + Assertions.assertThat( + (view.getTag(R.id.accessibility_state) as ReadableMap).getBoolean("disabled") + ) + .isFalse() + } + } + + @Test + fun testClearingAccessibilityDisabledPreservesOtherState() { + val state = JavaOnlyMap.of("busy", true, "checked", "mixed") + viewManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + + viewManager.setViewState(view, state) + + val nativeState = view.getTag(R.id.accessibility_state) as ReadableMap + Assertions.assertThat(view.isEnabled).isTrue() + Assertions.assertThat(nativeState.getBoolean("busy")).isTrue() + Assertions.assertThat(nativeState.getString("checked")).isEqualTo("mixed") + Assertions.assertThat(state.hasKey("disabled")).isFalse() + } + + @Test + fun testViewManagerSubclassPreservesDisabledDuringPartialUpdates() { + val subclassManager = object : ReactViewManager() {} + subclassManager.setViewState(view, JavaOnlyMap.of("disabled", true)) + + subclassManager.setViewState(view, JavaOnlyMap.of("busy", true)) + + Assertions.assertThat(view.isEnabled).isFalse() + } + @Test fun testRoleList() { viewManager.setRole(view, "list") diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index 216dd5522be9..e7194a7f9bcf 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -242,6 +242,18 @@ class ReactTextInputPropertyTest { assertThat(view.isEnabled).isTrue } + @Test + fun testClearingAccessibilityDisabledPreservesNonEditableInput() { + manager.updateProperties( + view, + buildStyles("editable", false, "accessibilityState", JavaOnlyMap.of("disabled", true)), + ) + + manager.updateProperties(view, buildStyles("accessibilityState", JavaOnlyMap.of("busy", true))) + + assertThat(view.isEnabled).isFalse() + } + @Test fun testAutoCompleteExtendedHints() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt index 56383ff7efeb..0a3851f35fdb 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt @@ -34,6 +34,16 @@ class ReactViewGroupTest { context = Robolectric.buildActivity(Activity::class.java).create().get() } + @Test + fun `recycling restores enabled state`() { + val view = ReactViewGroup(context) + view.isEnabled = false + + view.recycleView() + + assertThat(view.isEnabled).isTrue() + } + @Test fun `View clipping - ensure allChildren properly resizes when adding views in sequence`() { val rvg = ReactViewGroup(context) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 8dc2c403abc8..cbc996e5120f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -8,6 +8,7 @@ #include "HostPlatformViewProps.h" #include +#include #include #include @@ -842,6 +843,10 @@ folly::dynamic HostPlatformViewProps::getDiffProps( if (accessibilityState != oldProps->accessibilityState) { updateAccessibilityStateProp( result, accessibilityState, oldProps->accessibilityState); + if (std::strcmp(getDiffPropsImplementationTarget(), "View") == 0) { + result["accessibilityState"]["disabled"] = + accessibilityState.has_value() && accessibilityState->disabled; + } } if (accessibilityLabel != oldProps->accessibilityLabel) { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/AccessibilityPropsTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/AccessibilityPropsTest.cpp index 38c1d82b9694..35fea9f432a4 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/AccessibilityPropsTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/AccessibilityPropsTest.cpp @@ -185,4 +185,65 @@ TEST(AccessibilityPropsTest, unset_selected_omits_selected_trait) { hasTrait(props.accessibilityTraits, AccessibilityTraits::Selected)); } +#if defined(ANDROID) && defined(RN_SERIALIZABLE_STATE) + +TEST(AccessibilityPropsTest, keeps_view_disabled_during_busy_updates) { + ViewProps oldProps; + oldProps.accessibilityState = AccessibilityState{.disabled = true}; + ViewProps newProps = oldProps; + newProps.accessibilityState->busy = true; + + auto diff = newProps.getDiffProps(&oldProps); + + EXPECT_EQ(diff["accessibilityState"]["disabled"], true); + EXPECT_EQ(diff["accessibilityState"]["busy"], true); +} + +TEST(AccessibilityPropsTest, keeps_view_enabled_during_busy_updates) { + ViewProps oldProps; + oldProps.accessibilityState = AccessibilityState{}; + ViewProps newProps = oldProps; + newProps.accessibilityState->busy = true; + + auto diff = newProps.getDiffProps(&oldProps); + + EXPECT_EQ(diff["accessibilityState"]["disabled"], false); +} + +TEST( + AccessibilityPropsTest, + removing_view_accessibility_state_clears_disabled) { + ViewProps oldProps; + oldProps.accessibilityState = AccessibilityState{.disabled = true}; + ViewProps newProps = oldProps; + newProps.accessibilityState.reset(); + + auto diff = newProps.getDiffProps(&oldProps); + + EXPECT_EQ(diff["accessibilityState"]["disabled"], false); +} + +TEST( + AccessibilityPropsTest, + leaves_other_components_disabled_updates_unchanged) { + class CustomViewProps : public ViewProps { + public: + ComponentName getDiffPropsImplementationTarget() const override { + return "CustomView"; + } + }; + + CustomViewProps oldProps; + oldProps.accessibilityState = AccessibilityState{.disabled = true}; + CustomViewProps newProps = oldProps; + newProps.accessibilityState->busy = true; + + auto diff = newProps.getDiffProps(&oldProps); + + EXPECT_EQ(diff["accessibilityState"].count("disabled"), 0); + EXPECT_EQ(diff["accessibilityState"]["busy"], true); +} + +#endif + } // namespace facebook::react