Skip to content

feat: add support for testID #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/green-ravens-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-native-bottom-tabs": patch
"@bottom-tabs/react-navigation": patch
---

feat: add support for testID
1 change: 1 addition & 0 deletions apps/example/src/Examples/NativeBottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function NativeBottomTabs() {
},
}}
options={{
tabBarButtonTestID: 'articleTestID',
tabBarBadge: '10',
tabBarIcon: ({ focused }) =>
focused
Expand Down
3 changes: 3 additions & 0 deletions apps/example/src/Examples/ThreeTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ export default function ThreeTabs() {
focusedIcon: require('../../assets/icons/article_dark.png'),
unfocusedIcon: require('../../assets/icons/chat_dark.png'),
badge: '!',
testID: 'articleTestID',
},
{
key: 'albums',
title: 'Albums',
focusedIcon: require('../../assets/icons/grid_dark.png'),
badge: '5',
testID: 'albumsTestID',
},
{
key: 'contacts',
focusedIcon: require('../../assets/icons/person_dark.png'),
title: 'Contacts',
testID: 'contactsTestID',
},
]);

Expand Down
5 changes: 5 additions & 0 deletions docs/docs/docs/guides/standalone-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,8 @@ Function to get the icon for a tab.
Function to determine if a tab should be hidden.

- Default: Uses `route.hidden`

#### `getTestID`

Function to get the test ID for a tab item.
- Default: Uses `route.testID`
6 changes: 5 additions & 1 deletion docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ Whether to enable haptic feedback on tab press. Defaults to false.
Object containing styles for the tab label.

Supported properties:

- `fontFamily`
- `fontSize`
- `fontWeight`


### Options

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

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.

#### `tabBarButtonTestID`

Test ID for the tab item. This can be used to find the tab item in the native view hierarchy.

### Events

The navigator can emit events on certain actions. Supported events are:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,22 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
removeBadge(index)
}
post {
findViewById<View>(menuItem.itemId).setOnLongClickListener {
onTabLongPressed(menuItem)
true
}
findViewById<View>(menuItem.itemId).setOnClickListener {
onTabSelected(menuItem)
updateTintColors(menuItem)
val itemView = findViewById<View>(menuItem.itemId)
itemView?.let { view ->
view.setOnLongClickListener {
onTabLongPressed(menuItem)
true
}
view.setOnClickListener {
onTabSelected(menuItem)
updateTintColors(menuItem)
}

item.testID?.let { testId ->
view.findViewById<View>(com.google.android.material.R.id.navigation_bar_item_content_container)?.apply {
tag = testId
}
}
}
updateTextAppearance()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ data class TabInfo(
val badge: String,
val activeTintColor: Int?,
val hidden: Boolean,
val testID: String?,
)

class RCTTabViewImpl {
Expand All @@ -31,7 +32,8 @@ class RCTTabViewImpl {
title = item.getString("title") ?: "",
badge = item.getString("badge") ?: "",
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false,
testID = item.getString("testID")
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ bool areTabItemsEqual(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStr
lhs.sfSymbol == rhs.sfSymbol &&
lhs.badge == rhs.badge &&
lhs.activeTintColor == rhs.activeTintColor &&
lhs.hidden == rhs.hidden;
lhs.hidden == rhs.hidden &&
lhs.testID == rhs.testID;
}

bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
Expand Down Expand Up @@ -201,7 +202,8 @@ bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
badge:RCTNSStringFromStringNilIfEmpty(item.badge)
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
hidden:item.hidden];
hidden:item.hidden
testID:RCTNSStringFromStringNilIfEmpty(item.testID)];

[result addObject:tabInfo];
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-bottom-tabs/ios/TabViewImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ struct TabViewImpl: View {
sfSymbol: tabData?.sfSymbol,
labeled: props.labeled
)
.accessibilityIdentifier(tabData?.testID ?? "")
}
.tag(tabData?.key)
.tabBadge(tabData?.badge)
Expand Down
8 changes: 6 additions & 2 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ public final class TabInfo: NSObject {
public let sfSymbol: String
public let activeTintColor: PlatformColor?
public let hidden: Bool
public let testID: String?

public init(
key: String,
title: String,
badge: String,
sfSymbol: String,
activeTintColor: PlatformColor?,
hidden: Bool
hidden: Bool,
testID: String?
) {
self.key = key
self.title = title
self.badge = badge
self.sfSymbol = sfSymbol
self.activeTintColor = activeTintColor
self.hidden = hidden
self.testID = testID
super.init()
}
}
Expand Down Expand Up @@ -264,7 +267,8 @@ public final class TabInfo: NSObject {
badge: itemDict["badge"] as? String ?? "",
sfSymbol: itemDict["sfSymbol"] as? String ?? "",
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber),
hidden: itemDict["hidden"] as? Bool ?? false
hidden: itemDict["hidden"] as? Bool ?? false,
testID: itemDict["testID"] as? String ?? ""
)
)
}
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
*/
getHidden?: (props: { route: Route }) => boolean | undefined;

/**
* Get testID for the tab, uses `route.testID` by default.
*/
getTestID?: (props: { route: Route }) => string | undefined;

/**
* Background color of the tab bar.
*/
Expand Down Expand Up @@ -164,6 +169,7 @@
barTintColor,
getHidden = ({ route }: { route: Route }) => route.hidden,
getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor,
getTestID = ({ route }: { route: Route }) => route.testID,
hapticFeedbackEnabled = false,
tabLabelStyle,
...props
Expand Down Expand Up @@ -195,7 +201,7 @@

if (!loaded.includes(focusedKey)) {
// Set the current tab to be loaded if it was not loaded before
setLoaded((loaded) => [...loaded, focusedKey]);

Check warning on line 204 in packages/react-native-bottom-tabs/src/TabView.tsx

View workflow job for this annotation

GitHub Actions / lint

'loaded' is already declared in the upper scope on line 200 column 10
}

const icons = React.useMemo(
Expand Down Expand Up @@ -228,6 +234,7 @@
badge: getBadge?.({ route }),
activeTintColor: processColor(getActiveTintColor({ route })),
hidden: getHidden?.({ route }),
testID: getTestID?.({ route }),
};
}),
[
Expand All @@ -237,6 +244,7 @@
getBadge,
getActiveTintColor,
getHidden,
getTestID,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type TabViewItems = ReadonlyArray<{
badge?: string;
activeTintColor?: ProcessedColorValue | null;
hidden?: boolean;
testID?: string;
}>;

export interface TabViewProps extends ViewProps {
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-bottom-tabs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type BaseRoute = {
unfocusedIcon?: ImageSourcePropType | AppleIcon;
activeTintColor?: string;
hidden?: boolean;
testID?: string;
};

export type NavigationState<Route extends BaseRoute> = {
Expand Down
6 changes: 6 additions & 0 deletions packages/react-navigation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export type NativeBottomTabNavigationOptions = {
* Active tab color.
*/
tabBarActiveTintColor?: string;

/**
* TestID for the tab.
*/
tabBarButtonTestID?: string;
};

export type NativeBottomTabDescriptor = Descriptor<
Expand All @@ -111,5 +116,6 @@ export type NativeBottomTabNavigationConfig = Partial<
| 'getBadge'
| 'onTabLongPress'
| 'getActiveTintColor'
| 'getTestID'
>
>;
3 changes: 3 additions & 0 deletions packages/react-navigation/src/views/NativeBottomTabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export default function NativeBottomTabView({
const options = descriptors[route.key]?.options;
return options?.tabBarItemHidden === true;
}}
getTestID={({ route }) =>
descriptors[route.key]?.options.tabBarButtonTestID
}
getIcon={({ route, focused }) => {
const options = descriptors[route.key]?.options;

Expand Down
Loading