Skip to content

Commit 39c8ecf

Browse files
committed
feat: add support for testID
1 parent eda6b67 commit 39c8ecf

File tree

13 files changed

+71
-138
lines changed

13 files changed

+71
-138
lines changed

apps/example/ios/Podfile.lock

Lines changed: 13 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,31 +1210,6 @@ PODS:
12101210
- ReactCommon/turbomodule/core
12111211
- Yoga
12121212
- react-native-bottom-tabs (0.7.1):
1213-
- DoubleConversion
1214-
- glog
1215-
- RCT-Folly (= 2024.01.01.00)
1216-
- RCTRequired
1217-
- RCTTypeSafety
1218-
- React-Core
1219-
- React-debug
1220-
- React-Fabric
1221-
- React-featureflags
1222-
- React-graphics
1223-
- React-ImageManager
1224-
- React-jsi
1225-
- react-native-bottom-tabs/common (= 0.7.1)
1226-
- React-NativeModulesApple
1227-
- React-RCTFabric
1228-
- React-rendererdebug
1229-
- React-utils
1230-
- ReactCodegen
1231-
- ReactCommon/turbomodule/bridging
1232-
- ReactCommon/turbomodule/core
1233-
- SDWebImage (>= 5.19.1)
1234-
- SDWebImageSVGCoder (>= 1.7.0)
1235-
- SwiftUIIntrospect (~> 1.0)
1236-
- Yoga
1237-
- react-native-bottom-tabs/common (0.7.1):
12381213
- DoubleConversion
12391214
- glog
12401215
- RCT-Folly (= 2024.01.01.00)
@@ -1259,71 +1234,7 @@ PODS:
12591234
- SwiftUIIntrospect (~> 1.0)
12601235
- Yoga
12611236
- react-native-safe-area-context (4.14.0):
1262-
- DoubleConversion
1263-
- glog
1264-
- RCT-Folly (= 2024.01.01.00)
1265-
- RCTRequired
1266-
- RCTTypeSafety
1267-
- React-Core
1268-
- React-debug
1269-
- React-Fabric
1270-
- React-featureflags
1271-
- React-graphics
1272-
- React-ImageManager
1273-
- React-jsi
1274-
- react-native-safe-area-context/common (= 4.14.0)
1275-
- react-native-safe-area-context/fabric (= 4.14.0)
1276-
- React-NativeModulesApple
1277-
- React-RCTFabric
1278-
- React-rendererdebug
1279-
- React-utils
1280-
- ReactCodegen
1281-
- ReactCommon/turbomodule/bridging
1282-
- ReactCommon/turbomodule/core
1283-
- Yoga
1284-
- react-native-safe-area-context/common (4.14.0):
1285-
- DoubleConversion
1286-
- glog
1287-
- RCT-Folly (= 2024.01.01.00)
1288-
- RCTRequired
1289-
- RCTTypeSafety
12901237
- React-Core
1291-
- React-debug
1292-
- React-Fabric
1293-
- React-featureflags
1294-
- React-graphics
1295-
- React-ImageManager
1296-
- React-jsi
1297-
- React-NativeModulesApple
1298-
- React-RCTFabric
1299-
- React-rendererdebug
1300-
- React-utils
1301-
- ReactCodegen
1302-
- ReactCommon/turbomodule/bridging
1303-
- ReactCommon/turbomodule/core
1304-
- Yoga
1305-
- react-native-safe-area-context/fabric (4.14.0):
1306-
- DoubleConversion
1307-
- glog
1308-
- RCT-Folly (= 2024.01.01.00)
1309-
- RCTRequired
1310-
- RCTTypeSafety
1311-
- React-Core
1312-
- React-debug
1313-
- React-Fabric
1314-
- React-featureflags
1315-
- React-graphics
1316-
- React-ImageManager
1317-
- React-jsi
1318-
- react-native-safe-area-context/common
1319-
- React-NativeModulesApple
1320-
- React-RCTFabric
1321-
- React-rendererdebug
1322-
- React-utils
1323-
- ReactCodegen
1324-
- ReactCommon/turbomodule/bridging
1325-
- ReactCommon/turbomodule/core
1326-
- Yoga
13271238
- React-nativeconfig (0.75.4)
13281239
- React-NativeModulesApple (0.75.4):
13291240
- glog
@@ -1582,7 +1493,6 @@ PODS:
15821493
- React-ImageManager
15831494
- React-jsi
15841495
- React-NativeModulesApple
1585-
- React-RCTAppDelegate
15861496
- React-RCTFabric
15871497
- React-rendererdebug
15881498
- React-utils
@@ -1616,29 +1526,6 @@ PODS:
16161526
- ReactCommon/turbomodule/core
16171527
- Yoga
16181528
- RNScreens (4.3.0):
1619-
- DoubleConversion
1620-
- glog
1621-
- RCT-Folly (= 2024.01.01.00)
1622-
- RCTRequired
1623-
- RCTTypeSafety
1624-
- React-Core
1625-
- React-debug
1626-
- React-Fabric
1627-
- React-featureflags
1628-
- React-graphics
1629-
- React-ImageManager
1630-
- React-jsi
1631-
- React-NativeModulesApple
1632-
- React-RCTFabric
1633-
- React-RCTImage
1634-
- React-rendererdebug
1635-
- React-utils
1636-
- ReactCodegen
1637-
- ReactCommon/turbomodule/bridging
1638-
- ReactCommon/turbomodule/core
1639-
- RNScreens/common (= 4.3.0)
1640-
- Yoga
1641-
- RNScreens/common (4.3.0):
16421529
- DoubleConversion
16431530
- glog
16441531
- RCT-Folly (= 2024.01.01.00)
@@ -1719,7 +1606,6 @@ DEPENDENCIES:
17191606
- React-idlecallbacksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)
17201607
- React-ImageManager (from `../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
17211608
- React-jsc (from `../../../node_modules/react-native/ReactCommon/jsc`)
1722-
- React-jsc/Fabric (from `../../../node_modules/react-native/ReactCommon/jsc`)
17231609
- React-jserrorhandler (from `../../../node_modules/react-native/ReactCommon/jserrorhandler`)
17241610
- React-jsi (from `../../../node_modules/react-native/ReactCommon/jsi`)
17251611
- React-jsiexecutor (from `../../../node_modules/react-native/ReactCommon/jsiexecutor`)
@@ -1926,15 +1812,15 @@ SPEC CHECKSUMS:
19261812
React-CoreModules: 9fac2d31803c0ed03e4ddaa17f1481714f8633a5
19271813
React-cxxreact: c72a7a8066fc4323ea85a3137de50c8a10a69794
19281814
React-debug: 3d21f69d8def0656f8b8ec25c0f05954f4d862c5
1929-
React-defaultsnativemodule: 95882787871a9e80337f464e4643f1c5e0a39198
1930-
React-domnativemodule: dc8aac826a90479ca4e05f096f1f8cd35c216f31
1815+
React-defaultsnativemodule: ca56510e8d1c07a48817b1423528c7464407ca45
1816+
React-domnativemodule: 5ef5656fda7dc3d35fdac312e06dec031481c183
19311817
React-Fabric: 9347fa5c8fbfac6d5276dd9e52c91058467d0960
19321818
React-FabricComponents: 68b9f8c4a7189c055a7eb67b182e8d98c4f75f47
19331819
React-FabricImage: 062e20f8b360ca008f44d00a639951c8c37ba2aa
19341820
React-featureflags: ee1abd6f71555604a36cda6476e3c502ca9a48e5
1935-
React-featureflagsnativemodule: 209f660bc398849cfb81712c93010276f25f337b
1821+
React-featureflagsnativemodule: 183b42cafab1e70a56b2608c26d04aff2bdf43d1
19361822
React-graphics: d7dd9c8d75cad5af19e19911fa370f78f2febd96
1937-
React-idlecallbacksnativemodule: 17a0e379a7e3c9bc9653b6902f22f4208658d687
1823+
React-idlecallbacksnativemodule: 208b0d15e33c443607eccdc1fe3e081fe263a17f
19381824
React-ImageManager: ab7a7d17dd0ff1ef1d4e1e88197d1119da9957ce
19391825
React-jsc: 4d3352be620f3fe2272238298aaccc9323b01824
19401826
React-jserrorhandler: d9e867bb83b868472f3f7601883f0403b3e3942d
@@ -1944,18 +1830,18 @@ SPEC CHECKSUMS:
19441830
React-jsitracing: 0e8c0aadb1fcec6b1e4f2a66ee3b0da80f0f8615
19451831
React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b
19461832
React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3
1947-
React-microtasksnativemodule: 8fa285fed833a04a754bf575f8ded65fc240b88d
1948-
react-native-bottom-tabs: a08eaf6baf1b78375e17019536783dbbca3190ba
1949-
react-native-safe-area-context: 73505107f7c673cd550a561aeb6271f152c483b6
1833+
React-microtasksnativemodule: 475ea38712131abd7f7935c82a41364cea2fa01f
1834+
react-native-bottom-tabs: 98686073234188180cb1ede85a0f977ddde9c8d0
1835+
react-native-safe-area-context: 4532f1a0c5d34a46b9324ccaaedcb5582a302b7d
19501836
React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794
19511837
React-NativeModulesApple: b8465afc883f5bf3fe8bac3767e394d581a5f123
19521838
React-perflogger: 59e1a3182dca2cee7b9f1f7aab204018d46d1914
19531839
React-performancetimeline: a9d05533ff834c6aa1f532e05e571f3fd2e3c1ed
19541840
React-RCTActionSheet: d80e68d3baa163e4012a47c1f42ddd8bcd9672cc
19551841
React-RCTAnimation: bde981f6bd7f8493696564da9b3bd05721d3b3cc
1956-
React-RCTAppDelegate: e5865dbf46ddec6d9ed9310a05094d13f9bb043f
1842+
React-RCTAppDelegate: bc9c02d6dd4d162e3e1850283aba81bd246fc688
19571843
React-RCTBlob: e492d54533e61a81f2601494a6f393b3e15e33b9
1958-
React-RCTFabric: e6ef266a60e885a0548b0f7a5e9737f42ccd596b
1844+
React-RCTFabric: f2f86a175bb2fd3ce6760fd37338c6332efac2a6
19591845
React-RCTImage: 90448d2882464af6015ed57c98f463f8748be465
19601846
React-RCTLinking: 1bd95d0a704c271d21d758e0f0388cced768d77d
19611847
React-RCTNetwork: 218af6e63eb9b47935cc5a775b7a1396cf10ff91
@@ -1972,12 +1858,12 @@ SPEC CHECKSUMS:
19721858
React-utils: 546831c4f1be57fac614f68de34ac8763e67db55
19731859
ReactCodegen: 54f07f54275a16f8e3a32cbdd62bf008b0869c9c
19741860
ReactCommon: 8377a2a5504f72e284ce1b1cd207d8455bdbfdf3
1975-
ReactNativeHost: a3cd2bc15b6deac7439318607ce5637d8a93a117
1861+
ReactNativeHost: 99c0ffb175cd69de2ac9a70892cd22dac65ea79d
19761862
ReactTestApp-DevSupport: ce66fc1bbcf598d7e90616db390a0274c13e14e7
19771863
ReactTestApp-Resources: 9c387cfe7185736e6a9045e5aa3e085367be6aa3
1978-
RNGestureHandler: 492b1d415a25506d1dc612e6a14932b1e697d835
1979-
RNScreens: 16a61c0a9fe4cd69af6489b8d10ba580b5e22ed0
1980-
RNVectorIcons: a1344e212e80e6e0f4537a9960148201175f4225
1864+
RNGestureHandler: 6980c0e9faf478d385c9fdfcee5175c4b1215a7c
1865+
RNScreens: 7e89f78c273715ff4aa625130ae7b3aabb940c28
1866+
RNVectorIcons: 0cbcb1c0cb6da1ca1d43d3d990255e584bb99028
19811867
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
19821868
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
19831869
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d

apps/example/src/Examples/ThreeTabs.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ export default function ThreeTabs() {
1313
focusedIcon: require('../../assets/icons/article_dark.png'),
1414
unfocusedIcon: require('../../assets/icons/chat_dark.png'),
1515
badge: '!',
16+
testID: 'articleTestID',
1617
},
1718
{
1819
key: 'albums',
1920
title: 'Albums',
2021
focusedIcon: require('../../assets/icons/grid_dark.png'),
2122
badge: '5',
23+
testID: 'albumsTestID',
2224
},
2325
{
2426
key: 'contacts',
2527
focusedIcon: require('../../assets/icons/person_dark.png'),
2628
title: 'Contacts',
29+
testID: 'contactsTestID',
2730
},
2831
]);
2932

docs/docs/docs/guides/standalone-usage.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,8 @@ Function to get the icon for a tab.
218218
Function to determine if a tab should be hidden.
219219

220220
- Default: Uses `route.hidden`
221+
222+
#### `getTestID`
223+
224+
Function to get the test ID for a tab item.
225+
- Default: Uses `route.testID`

docs/docs/docs/guides/usage-with-react-navigation.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ Whether to enable haptic feedback on tab press. Defaults to false.
159159
Object containing styles for the tab label.
160160

161161
Supported properties:
162+
162163
- `fontFamily`
163164
- `fontSize`
164165
- `fontWeight`
165166

166-
167167
### Options
168168

169169
The following options can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.navigator` or `options` prop of `Tab.Screen`.
@@ -226,6 +226,10 @@ Due to native limitations on iOS, this option doesn't hide the tab item **when h
226226

227227
Whether this screens should render the first time it's accessed. Defaults to true. Set it to false if you want to render the screen on initial render.
228228

229+
#### `testID`
230+
231+
Test ID for the tab item. This can be used to find the tab item in the native view hierarchy.
232+
229233
### Events
230234

231235
The navigator can emit events on certain actions. Supported events are:

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,24 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
120120
removeBadge(index)
121121
}
122122
post {
123-
findViewById<View>(menuItem.itemId).setOnLongClickListener {
124-
onTabLongPressed(menuItem)
125-
true
126-
}
127-
findViewById<View>(menuItem.itemId).setOnClickListener {
128-
onTabSelected(menuItem)
129-
updateTintColors(menuItem)
123+
val itemView = findViewById<View>(menuItem.itemId)
124+
itemView?.let { view ->
125+
view.setOnLongClickListener {
126+
onTabLongPressed(menuItem)
127+
true
128+
}
129+
view.setOnClickListener {
130+
onTabSelected(menuItem)
131+
updateTintColors(menuItem)
132+
}
133+
134+
item.testID?.let { testId ->
135+
Log.d("RCTTabView", "Setting test ID for item: $testId")
136+
137+
view.findViewById<View>(com.google.android.material.R.id.navigation_bar_item_content_container)?.apply {
138+
tag = testId
139+
}
140+
}
130141
}
131142
updateTextAppearance()
132143
}

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ data class TabInfo(
1414
val badge: String,
1515
val activeTintColor: Int?,
1616
val hidden: Boolean,
17+
val testID: String?,
1718
)
1819

1920
class RCTTabViewImpl {
@@ -31,7 +32,8 @@ class RCTTabViewImpl {
3132
title = item.getString("title") ?: "",
3233
badge = item.getString("badge") ?: "",
3334
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
34-
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false
35+
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false,
36+
testID = item.getString("testID")
3537
)
3638
)
3739
}

packages/react-native-bottom-tabs/ios/TabViewImpl.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ struct TabViewImpl: View {
130130
sfSymbol: tabData?.sfSymbol,
131131
labeled: props.labeled
132132
)
133+
.accessibilityIdentifier(tabData?.testID ?? "")
133134
}
134135
.tag(tabData?.key)
135136
.tabBadge(tabData?.badge)

packages/react-native-bottom-tabs/ios/TabViewProvider.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@ public final class TabInfo: NSObject {
1212
public let sfSymbol: String
1313
public let activeTintColor: UIColor?
1414
public let hidden: Bool
15+
public let testID: String?
1516

1617
public init(
1718
key: String,
1819
title: String,
1920
badge: String,
2021
sfSymbol: String,
2122
activeTintColor: UIColor?,
22-
hidden: Bool
23+
hidden: Bool,
24+
testID: String
2325
) {
2426
self.key = key
2527
self.title = title
2628
self.badge = badge
2729
self.sfSymbol = sfSymbol
2830
self.activeTintColor = activeTintColor
2931
self.hidden = hidden
32+
self.testID = testID
3033
super.init()
3134
}
3235
}
@@ -255,7 +258,8 @@ public final class TabInfo: NSObject {
255258
badge: itemDict["badge"] as? String ?? "",
256259
sfSymbol: itemDict["sfSymbol"] as? String ?? "",
257260
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber),
258-
hidden: itemDict["hidden"] as? Bool ?? false
261+
hidden: itemDict["hidden"] as? Bool ?? false,
262+
testID: itemDict["testID"] as? String ?? ""
259263
)
260264
)
261265
}

packages/react-native-bottom-tabs/src/TabView.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ interface Props<Route extends BaseRoute> {
111111
*/
112112
getHidden?: (props: { route: Route }) => boolean | undefined;
113113

114+
/**
115+
* Get testID for the tab, uses `route.testID` by default.
116+
*/
117+
getTestID?: (props: { route: Route }) => string | undefined;
118+
114119
/**
115120
* Background color of the tab bar.
116121
*/
@@ -164,6 +169,7 @@ const TabView = <Route extends BaseRoute>({
164169
barTintColor,
165170
getHidden = ({ route }: { route: Route }) => route.hidden,
166171
getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor,
172+
getTestID = ({ route }: { route: Route }) => route.testID,
167173
hapticFeedbackEnabled = false,
168174
tabLabelStyle,
169175
...props
@@ -228,6 +234,7 @@ const TabView = <Route extends BaseRoute>({
228234
badge: getBadge?.({ route }),
229235
activeTintColor: processColor(getActiveTintColor({ route })),
230236
hidden: getHidden?.({ route }),
237+
testID: getTestID?.({ route }),
231238
};
232239
}),
233240
[
@@ -237,6 +244,7 @@ const TabView = <Route extends BaseRoute>({
237244
getBadge,
238245
getActiveTintColor,
239246
getHidden,
247+
getTestID,
240248
]
241249
);
242250

0 commit comments

Comments
 (0)