Skip to content

Commit 4771cfd

Browse files
feat: add support for testID (#179)
* feat: add support for testID * Create green-ravens-fail.md --------- Co-authored-by: Oskar Kwaśniewski <oskarkwasniewski@icloud.com>
1 parent 980e9ec commit 4771cfd

File tree

15 files changed

+69
-13
lines changed

15 files changed

+69
-13
lines changed

.changeset/green-ravens-fail.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"react-native-bottom-tabs": patch
3+
"@bottom-tabs/react-navigation": patch
4+
---
5+
6+
feat: add support for testID

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
@@ -219,3 +219,8 @@ Function to get the icon for a tab.
219219
Function to determine if a tab should be hidden.
220220

221221
- Default: Uses `route.hidden`
222+
223+
#### `getTestID`
224+
225+
Function to get the test ID for a tab item.
226+
- 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
@@ -162,11 +162,11 @@ Whether to enable haptic feedback on tab press. Defaults to false.
162162
Object containing styles for the tab label.
163163

164164
Supported properties:
165+
165166
- `fontFamily`
166167
- `fontSize`
167168
- `fontWeight`
168169

169-
170170
### Options
171171

172172
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`.
@@ -229,6 +229,10 @@ Due to native limitations on iOS, this option doesn't hide the tab item **when h
229229

230230
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.
231231

232+
#### `tabBarButtonTestID`
233+
234+
Test ID for the tab item. This can be used to find the tab item in the native view hierarchy.
235+
232236
### Events
233237

234238
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/Fabric/RCTTabViewComponentView.mm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ bool areTabItemsEqual(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStr
173173
lhs.sfSymbol == rhs.sfSymbol &&
174174
lhs.badge == rhs.badge &&
175175
lhs.activeTintColor == rhs.activeTintColor &&
176-
lhs.hidden == rhs.hidden;
176+
lhs.hidden == rhs.hidden &&
177+
lhs.testID == rhs.testID;
177178
}
178179

179180
bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
@@ -201,7 +202,8 @@ bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
201202
badge:RCTNSStringFromStringNilIfEmpty(item.badge)
202203
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
203204
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
204-
hidden:item.hidden];
205+
hidden:item.hidden
206+
testID:RCTNSStringFromStringNilIfEmpty(item.testID)];
205207

206208
[result addObject:tabInfo];
207209
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ struct TabViewImpl: View {
9696
sfSymbol: tabData?.sfSymbol,
9797
labeled: props.labeled
9898
)
99+
.accessibilityIdentifier(tabData?.testID ?? "")
99100
}
100101
.tag(tabData?.key)
101102
.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: PlatformColor?
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: PlatformColor?,
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
}
@@ -264,7 +267,8 @@ public final class TabInfo: NSObject {
264267
badge: itemDict["badge"] as? String ?? "",
265268
sfSymbol: itemDict["sfSymbol"] as? String ?? "",
266269
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber),
267-
hidden: itemDict["hidden"] as? Bool ?? false
270+
hidden: itemDict["hidden"] as? Bool ?? false,
271+
testID: itemDict["testID"] as? String ?? ""
268272
)
269273
)
270274
}

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)