Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export const ICON_BY_TAB_BAR_ICON_KEY: IconByTabBarIconKey = {
[TabBarIconKey.Activity]: IconName.Activity,
[TabBarIconKey.Setting]: IconName.Setting,
[TabBarIconKey.Rewards]: IconName.MetamaskFoxOutline,
[TabBarIconKey.Trending]: IconName.TrendUp,
};

export const LABEL_BY_TAB_BAR_ICON_KEY = {
[TabBarIconKey.Wallet]: 'bottom_nav.home',
[TabBarIconKey.Browser]: 'bottom_nav.browser',
[TabBarIconKey.Trending]: 'bottom_nav.trending',
[TabBarIconKey.Actions]: 'bottom_nav.trade',
[TabBarIconKey.Trade]: 'bottom_nav.trade',
[TabBarIconKey.Activity]: 'bottom_nav.activity',
Expand Down
131 changes: 107 additions & 24 deletions app/component-library/components/Navigation/TabBar/TabBar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
// Third party dependencies.
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { ParamListBase, TabNavigationState } from '@react-navigation/native';
import {
ParamListBase,
TabNavigationState,
NavigationHelpers,
} from '@react-navigation/native';

// External dependencies
import renderWithProvider from '../../../../util/test/renderWithProvider';
import { backgroundState } from '../../../../util/test/initial-root-state';

// Internal dependencies
import TabBar from './TabBar';
import { TabBarIconKey } from './TabBar.types';
import { TabBarIconKey, ExtendedBottomTabDescriptor } from './TabBar.types';
import Routes from '../../../../constants/navigation/Routes';
import { selectAssetsTrendingTokensEnabled } from '../../../../selectors/featureFlagController/assetsTrendingTokens';

// Minimal descriptor interface for tests - only includes what TabBar component uses
interface TestTabDescriptor {
options: {
tabBarIconKey: TabBarIconKey;
rootScreenName: string;
callback?: () => void;
};
}

interface TestDescriptors {
[key: string]: TestTabDescriptor;
}

// Force rewards feature flag to be enabled for this test file
jest.mock('../../../../selectors/featureFlagController/rewards', () => ({
selectRewardsEnabledFlag: () => true,
}));

// Mock the navigation object.
const navigation = {
// Mock trending tokens feature flag selector
jest.mock('../../../../selectors/featureFlagController/assetsTrendingTokens');

// Mock the navigation object with proper typing
const navigation: NavigationHelpers<ParamListBase> = {
navigate: jest.fn(),
goBack: jest.fn(),
reset: jest.fn(),
setParams: jest.fn(),
dispatch: jest.fn(),
isFocused: jest.fn(),
canGoBack: jest.fn(),
dangerouslyGetParent: jest.fn(),
dangerouslyGetState: jest.fn(),
emit: jest.fn(),
};

const mockInitialState = {
Expand Down Expand Up @@ -54,7 +84,7 @@ describe('TabBar', () => {
{ key: '5', name: 'Tab 5' },
],
};
const descriptors = {
const descriptors: TestDescriptors = {
'1': {
options: {
tabBarIconKey: TabBarIconKey.Wallet,
Expand Down Expand Up @@ -91,12 +121,8 @@ describe('TabBar', () => {
const { toJSON } = renderWithProvider(
<TabBar
state={state as TabNavigationState<ParamListBase>}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
descriptors={descriptors as any}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation={navigation as any}
descriptors={descriptors as Record<string, ExtendedBottomTabDescriptor>}
navigation={navigation}
/>,
{ state: mockInitialState },
);
Expand All @@ -107,12 +133,8 @@ describe('TabBar', () => {
const { getByTestId } = renderWithProvider(
<TabBar
state={state as TabNavigationState<ParamListBase>}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
descriptors={descriptors as any}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation={navigation as any}
descriptors={descriptors as Record<string, ExtendedBottomTabDescriptor>}
navigation={navigation}
/>,
{ state: mockInitialState },
);
Expand Down Expand Up @@ -152,29 +174,90 @@ describe('TabBar', () => {
index: 0,
routes: [{ key: '1', name: 'Tab 1' }],
};
const rewardsDescriptors = {
const rewardsDescriptors: TestDescriptors = {
'1': {
options: {
tabBarIconKey: TabBarIconKey.Rewards,
rootScreenName: Routes.REWARDS_VIEW,
callback: () => ({}),
},
},
};

const { getByTestId } = renderWithProvider(
<TabBar
state={rewardsState as TabNavigationState<ParamListBase>}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
descriptors={rewardsDescriptors as any}
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation={navigation as any}
descriptors={
rewardsDescriptors as Record<string, ExtendedBottomTabDescriptor>
}
navigation={navigation}
/>,
{ state: mockInitialState },
);

fireEvent.press(getByTestId(`tab-bar-item-${TabBarIconKey.Rewards}`));
expect(navigation.navigate).toHaveBeenCalledWith(Routes.REWARDS_VIEW);
});

it('navigates to trending when trending tab is pressed', () => {
jest.mocked(selectAssetsTrendingTokensEnabled).mockReturnValue(true);

const trendingState = {
index: 0,
routes: [{ key: '1', name: 'Tab 1' }],
};
const trendingDescriptors: TestDescriptors = {
'1': {
options: {
tabBarIconKey: TabBarIconKey.Trending,
rootScreenName: Routes.TRENDING_VIEW,
},
},
};

const { getByTestId } = renderWithProvider(
<TabBar
state={trendingState as TabNavigationState<ParamListBase>}
descriptors={
trendingDescriptors as Record<string, ExtendedBottomTabDescriptor>
}
navigation={navigation}
/>,
{ state: mockInitialState },
);

fireEvent.press(getByTestId(`tab-bar-item-${TabBarIconKey.Trending}`));
expect(navigation.navigate).toHaveBeenCalledWith(Routes.TRENDING_VIEW);
});

it('does not navigate to trending when trending feature flag is disabled', () => {
jest.mocked(selectAssetsTrendingTokensEnabled).mockReturnValue(false);

const trendingState = {
index: 0,
routes: [{ key: '1', name: 'Tab 1' }],
};
const trendingDescriptors: TestDescriptors = {
'1': {
options: {
tabBarIconKey: TabBarIconKey.Trending,
rootScreenName: Routes.TRENDING_VIEW,
},
},
};

const { getByTestId } = renderWithProvider(
<TabBar
state={trendingState as TabNavigationState<ParamListBase>}
descriptors={
trendingDescriptors as Record<string, ExtendedBottomTabDescriptor>
}
navigation={navigation}
/>,
{ state: mockInitialState },
);

fireEvent.press(getByTestId(`tab-bar-item-${TabBarIconKey.Trending}`));
expect(navigation.navigate).not.toHaveBeenCalledWith(Routes.TRENDING_VIEW);
});
});
11 changes: 11 additions & 0 deletions app/component-library/components/Navigation/TabBar/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ import {
} from './TabBar.constants';
import { selectChainId } from '../../../../selectors/networkController';
import { selectRewardsEnabledFlag } from '../../../../selectors/featureFlagController/rewards';
import { selectAssetsTrendingTokensEnabled } from '../../../../selectors/featureFlagController/assetsTrendingTokens';

const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
const { trackEvent, createEventBuilder } = useMetrics();
const { bottom: bottomInset } = useSafeAreaInsets();
const chainId = useSelector(selectChainId);
const isRewardsEnabled = useSelector(selectRewardsEnabledFlag);
const isAssetsTrendingTokensEnabled = useSelector(
selectAssetsTrendingTokensEnabled,
);
const tabBarRef = useRef(null);
const tw = useTailwind();

Expand Down Expand Up @@ -90,6 +94,12 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
navigation.navigate(Routes.SETTINGS_VIEW, {
screen: 'Settings',
});
break;
case Routes.TRENDING_VIEW:
if (isAssetsTrendingTokensEnabled) {
navigation.navigate(Routes.TRENDING_VIEW);
}
break;
}
};

Expand Down Expand Up @@ -118,6 +128,7 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
createEventBuilder,
tw,
isRewardsEnabled,
isAssetsTrendingTokensEnabled,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum TabBarIconKey {
Activity = 'Activity',
Setting = 'Setting',
Rewards = 'Rewards',
Trending = 'Trending',
}

/**
Expand All @@ -31,7 +32,7 @@ export type IconByTabBarIconKey = {
[key in TabBarIconKey]: IconName;
};

interface ExtendedBottomTabDescriptor extends BottomTabDescriptor {
export interface ExtendedBottomTabDescriptor extends BottomTabDescriptor {
options: BottomTabNavigationOptions & {
tabBarIconKey: TabBarIconKey;
callback: () => void;
Expand Down
40 changes: 39 additions & 1 deletion app/components/Nav/Main/MainNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { Confirm as RedesignedConfirm } from '../../Views/confirmations/componen
import ContactForm from '../../Views/Settings/Contacts/ContactForm';
import ActivityView from '../../Views/ActivityView';
import RewardsNavigator from '../../UI/Rewards/RewardsNavigator';
import TrendingView from '../../Views/TrendingView';
import SwapsAmountView from '../../UI/Swaps';
import SwapsQuotesView from '../../UI/Swaps/QuotesView';
import CollectiblesDetails from '../../UI/CollectibleModal';
Expand Down Expand Up @@ -106,6 +107,7 @@ import {
selectPredictEnabledFlag,
} from '../../UI/Predict';
import { selectRewardsEnabledFlag } from '../../../selectors/featureFlagController/rewards';
import { selectAssetsTrendingTokensEnabled } from '../../../selectors/featureFlagController/assetsTrendingTokens';
import PerpsPositionTransactionView from '../../UI/Perps/Views/PerpsTransactionsView/PerpsPositionTransactionView';
import PerpsOrderTransactionView from '../../UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView';
import PerpsFundingTransactionView from '../../UI/Perps/Views/PerpsTransactionsView/PerpsFundingTransactionView';
Expand Down Expand Up @@ -269,6 +271,16 @@ const RewardsHome = () => (
</Stack.Navigator>
);

const TrendingHome = () => (
<Stack.Navigator mode="modal" screenOptions={clearStackNavigatorOptions}>
<Stack.Screen
name={Routes.TRENDING_VIEW}
component={TrendingView}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);

/* eslint-disable react/prop-types */
const BrowserFlow = (props) => (
<Stack.Navigator
Expand Down Expand Up @@ -499,6 +511,9 @@ const HomeTabs = () => {
const accountsLength = useSelector(selectAccountsLength);
const isRewardsEnabled = useSelector(selectRewardsEnabledFlag);
const rewardsSubscription = useSelector(selectRewardsSubscriptionId);
const isAssetsTrendingTokensEnabled = useSelector(
selectAssetsTrendingTokensEnabled,
);

const chainId = useSelector((state) => {
const providerConfig = selectProviderConfig(state);
Expand Down Expand Up @@ -569,6 +584,18 @@ const HomeTabs = () => {
rootScreenName: Routes.REWARDS_VIEW,
unmountOnBlur: true,
},
trending: {
tabBarIconKey: TabBarIconKey.Trending,
callback: () => {
trackEvent(
createEventBuilder(
MetaMetricsEvents.NAVIGATION_TAPS_TRENDING,
).build(),
);
},
rootScreenName: Routes.TRENDING_VIEW,
unmountOnBlur: true,
},
settings: {
tabBarIconKey: TabBarIconKey.Setting,
callback: () => {
Expand Down Expand Up @@ -640,9 +667,20 @@ const HomeTabs = () => {
options={options.home}
component={WalletTabModalFlow}
/>
{isAssetsTrendingTokensEnabled && (
<Tab.Screen
name={Routes.TRENDING_VIEW}
options={options.trending}
component={TrendingHome}
layout={({ children }) => UnmountOnBlurComponent(children)}
/>
)}
<Tab.Screen
name={Routes.BROWSER.HOME}
options={options.browser}
options={{
...options.browser,
tabBarButton: isAssetsTrendingTokensEnabled ? () => null : undefined,
}}
component={BrowserFlow}
layout={({ children }) => <UnmountOnBlur>{children}</UnmountOnBlur>}
/>
Expand Down
Loading
Loading