Skip to content

Commit

Permalink
feat: add support for testID (#179)
Browse files Browse the repository at this point in the history
* feat: add support for testID

* Create green-ravens-fail.md

---------

Co-authored-by: Oskar Kwaśniewski <[email protected]>
  • Loading branch information
Nodonisko and okwasniewski authored Dec 7, 2024
1 parent 980e9ec commit 4771cfd
Show file tree
Hide file tree
Showing 15 changed files with 69 additions and 13 deletions.
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 @@ interface Props<Route extends BaseRoute> {
*/
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 @@ const TabView = <Route extends BaseRoute>({
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 @@ -228,6 +234,7 @@ const TabView = <Route extends BaseRoute>({
badge: getBadge?.({ route }),
activeTintColor: processColor(getActiveTintColor({ route })),
hidden: getHidden?.({ route }),
testID: getTestID?.({ route }),
};
}),
[
Expand All @@ -237,6 +244,7 @@ const TabView = <Route extends BaseRoute>({
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

0 comments on commit 4771cfd

Please sign in to comment.