Skip to content

Commit 2fa9c5c

Browse files
committed
feat: add support for testID
1 parent eda6b67 commit 2fa9c5c

File tree

13 files changed

+59
-11
lines changed

13 files changed

+59
-11
lines changed

apps/example/src/Examples/NativeBottomTabs.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function NativeBottomTabs() {
4141
},
4242
}}
4343
options={{
44+
tabBarButtonTestID: 'articleTestID',
4445
tabBarBadge: '10',
4546
tabBarIcon: ({ focused }) =>
4647
focused

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+
#### `tabBarButtonTestID`
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: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,22 @@ 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+
view.findViewById<View>(com.google.android.material.R.id.navigation_bar_item_content_container)?.apply {
136+
tag = testId
137+
}
138+
}
130139
}
131140
updateTextAppearance()
132141
}

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

packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type TabViewItems = ReadonlyArray<{
2929
badge?: string;
3030
activeTintColor?: ProcessedColorValue | null;
3131
hidden?: boolean;
32+
testID?: string;
3233
}>;
3334

3435
export interface TabViewProps extends ViewProps {

packages/react-native-bottom-tabs/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type BaseRoute = {
1414
unfocusedIcon?: ImageSourcePropType | AppleIcon;
1515
activeTintColor?: string;
1616
hidden?: boolean;
17+
testID?: string;
1718
};
1819

1920
export type NavigationState<Route extends BaseRoute> = {

packages/react-navigation/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ export type NativeBottomTabNavigationOptions = {
8686
* Active tab color.
8787
*/
8888
tabBarActiveTintColor?: string;
89+
90+
/**
91+
* TestID for the tab.
92+
*/
93+
tabBarButtonTestID?: string;
8994
};
9095

9196
export type NativeBottomTabDescriptor = Descriptor<
@@ -111,5 +116,6 @@ export type NativeBottomTabNavigationConfig = Partial<
111116
| 'getBadge'
112117
| 'onTabLongPress'
113118
| 'getActiveTintColor'
119+
| 'getTestID'
114120
>
115121
>;

packages/react-navigation/src/views/NativeBottomTabView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export default function NativeBottomTabView({
4545
const options = descriptors[route.key]?.options;
4646
return options?.tabBarItemHidden === true;
4747
}}
48+
getTestID={({ route }) =>
49+
descriptors[route.key]?.options.tabBarButtonTestID
50+
}
4851
getIcon={({ route, focused }) => {
4952
const options = descriptors[route.key]?.options;
5053

0 commit comments

Comments
 (0)