Repository navigation
Commit 7bba622
Report a disabled Switch as disabled to iOS accessibility (#59333)
Summary:
On iOS, `<Switch disabled />` is drawn greyed out and ignores taps, but VoiceOver and XCUITest still report it as enabled: the `UIAccessibilityTraitNotEnabled` trait is missing.
Since #50796, the inner `UISwitch` is the Switch's accessibility element, so `RCTViewComponentView` writes the traits it derives from the role onto the `UISwitch`, replacing the ones UIKit sets, including `NotEnabled`. Since #57516, `accessibilityState.disabled` adds `NotEnabled` to those traits, but the iOS branch of `Switch.js` never sets it from `disabled`. The Android branch already does.
This moves the existing Android logic above the platform check so iOS gets the same `accessibilityState`. As on Android, `disabled` wins over `accessibilityState.disabled` when both are set. It is done in `Switch.js` rather than `RCTSwitchComponentView` because the Android branch already does it here, and a Switch-specific native writer for the traits would bring back the second trait writer that #57516 removed.
`disabled` now behaves exactly like `accessibilityState={{disabled: true}}` already does. That includes a Switch whose role maps to no trait, such as `accessibilityRole="none"`: it gets `NotEnabled` alone, without the switch trait.
## Changelog:
[IOS] [FIXED] - Report a disabled `Switch` as disabled to VoiceOver and XCUITest
Pull Request resolved: #59333
Test Plan:
Local regression validation:
```
js1 test "(CosmoCollapsibleSelector|CosmoConsentSection|CosmoReportBugForm|TWUISwitch|TWUIListCell|OCDevModeToggleRow|CosmoGeneralAppPreferencesSection|CosmoImportWizard|CosmoSettingsRow|TWAUGroupedMenuListCell|CosmoStudioSettingsBox|TWUIPaymentListCell|TWUIPeopleListCell)-test\.js$" --maxWorkers=4
# 13 suites passed; 66 tests passed; 30 snapshots passed; exit 0
arc lint -a
# No lint issues
```
The same targeted run before the snapshot update failed all 13 suites (16 snapshot failures). Updated only the expected disabled accessibility metadata in 16 snapshots. Removed one obsolete `TWUIPeopleListCell renders testSupportingText 2` entry: the helper invokes one snapshot assertion per exported test, so that second entry has no live producer; both retained snapshots are unchanged. Independent review found no blockers.
Added two cases to `Switch-itest.js`: `disabled` overriding `accessibilityState.disabled`, and no `accessibilityState` when `disabled` is not set. Fantom renders the Android branch, so these cover the logic both platforms now share; the iOS side is covered by the manual check below. Each new case fails when its part of the logic is changed.
```
yarn fantom packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js
# 6 passed
yarn flow-check
# Found 0 errors
yarn eslint --max-warnings 0 packages/react-native/Libraries/Components/Switch/Switch.js packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js
# no warnings
yarn prettier --check packages/react-native/Libraries/Components/Switch/Switch.js packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js
# All matched files use Prettier code style!
```
Fantom was built locally on macOS with Xcode 27, whose clang needed `-Wno-deprecated-literal-operator` for the bundled `nlohmann/json.hpp`.
Manual check: a new 0.88.0 app (prebuilt core; `Switch.js` is the same on 0.88.0 and `main`) on an iPhone 17 Pro simulator, iOS 27. For "After", this branch's `Switch.js` was copied into the app's `node_modules/react-native/Libraries/Components/Switch/`. The accessibility tree was read with [AXe](https://github.com/cameroncooke/AXe) `describe-ui`, which reports each element's `enabled` attribute.
```tsx
<Switch testID="disabled-mount" value disabled />
<Switch testID="disabled-live" value disabled={liveDisabled} />
<Switch testID="disabled-state" value disabled accessibilityState={{disabled: true}} />
```
| Switch | Before | After |
| --- | --- | --- |
| `disabled-mount` | enabled | not enabled |
| `disabled-live`, after `disabled` is set to `true` | enabled | not enabled |
| `disabled-live`, after `disabled` is set back to `false` | enabled | enabled |
| `disabled-state` | not enabled | not enabled |
The role (`switch`) and value (`1`) are unchanged.
Not run: VoiceOver on a device.
Reviewed By: rubennorte
Differential Revision: D127141579
Pulled By: javache
fbshipit-source-id: c68bc8353788bd850cc2e00a7e69a8a0cb10bc921 parent bd08daf commit 7bba622
2 files changed
Lines changed: 33 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
| 180 | + | |
180 | 181 | | |
181 | 182 | | |
182 | 183 | | |
| |||
227 | 228 | | |
228 | 229 | | |
229 | 230 | | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
230 | 238 | | |
231 | 239 | | |
232 | | - | |
233 | | - | |
234 | | - | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
| |||
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
| 265 | + | |
265 | 266 | | |
266 | 267 | | |
267 | 268 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
49 | 73 | | |
50 | 74 | | |
51 | 75 | | |
| |||
0 commit comments