diff --git a/app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx b/app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx
index 343eaef5f867..c69221620230 100644
--- a/app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx
+++ b/app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx
@@ -45,11 +45,40 @@ describe('AccountGroupBalance', () => {
}),
);
- const { getByTestId } = renderWithProvider(, {
- state: testState,
- });
+ const { getByTestId, queryByTestId } = renderWithProvider(
+ ,
+ {
+ state: testState,
+ },
+ );
+
+ // Should render balance text, not empty state
+ expect(getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT)).toBeTruthy();
+ expect(queryByTestId('account-group-balance-empty-state')).toBeNull();
+ });
- const el = getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT);
- expect(el).toBeTruthy();
+ it('renders balance empty state when balance is zero', () => {
+ const { selectBalanceBySelectedAccountGroup } = jest.requireMock(
+ '../../../../../selectors/assets/balances',
+ );
+ (selectBalanceBySelectedAccountGroup as jest.Mock).mockImplementation(
+ () => ({
+ walletId: 'wallet-1',
+ groupId: 'wallet-1/group-1',
+ totalBalanceInUserCurrency: 0, // Zero balance
+ userCurrency: 'usd',
+ }),
+ );
+
+ const { getByTestId, queryByTestId } = renderWithProvider(
+ ,
+ {
+ state: testState,
+ },
+ );
+
+ // Should render BalanceEmptyState instead of balance text
+ expect(getByTestId('account-group-balance-empty-state')).toBeDefined();
+ expect(queryByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT)).toBeNull();
});
});
diff --git a/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx b/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx
index eaec7978d5fa..3c2362d59d8f 100644
--- a/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx
+++ b/app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx
@@ -16,6 +16,7 @@ import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/W
import { Skeleton } from '../../../../../component-library/components/Skeleton';
import { useFormatters } from '../../../../hooks/useFormatters';
import AccountGroupBalanceChange from '../../components/BalanceChange/AccountGroupBalanceChange';
+import BalanceEmptyState from '../../../BalanceEmptyState';
const AccountGroupBalance = () => {
const { PreferencesController } = Engine.context;
@@ -38,10 +39,23 @@ const AccountGroupBalance = () => {
const userCurrency = groupBalance?.userCurrency ?? '';
const displayBalance = formatCurrency(totalBalance, userCurrency);
+ // Check if balance is zero (empty state) - only check when we have balance data
+ const hasZeroBalance =
+ groupBalance && groupBalance.totalBalanceInUserCurrency === 0;
+
return (
- {groupBalance ? (
+ {!groupBalance ? (
+
+
+
+
+ ) : hasZeroBalance ? (
+ <>
+
+ >
+ ) : (
togglePrivacy(!privacyMode)}
testID="balance-container"
@@ -66,11 +80,6 @@ const AccountGroupBalance = () => {
/>
)}
- ) : (
-
-
-
-
)}
diff --git a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.test.tsx b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.test.tsx
index 92ef10f79df5..7a7072facddf 100644
--- a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.test.tsx
+++ b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.test.tsx
@@ -5,21 +5,33 @@ import { backgroundState } from '../../../../../util/test/initial-root-state';
import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors';
import { PortfolioBalance } from '.';
import Engine from '../../../../../core/Engine';
+import { useSelectedAccountMultichainBalances } from '../../../../hooks/useMultichainBalances';
const { PreferencesController } = Engine.context;
// Mock the useMultichainBalances hook
const mockSelectedAccountMultichainBalance = {
displayBalance: '$123.45',
- totalFiatBalance: '123.45',
+ displayCurrency: 'USD',
+ totalFiatBalance: 123.45,
+ totalNativeTokenBalance: '0.1',
+ nativeTokenUnit: 'ETH',
shouldShowAggregatedPercentage: true,
+ isPortfolioVieEnabled: false,
+ aggregatedBalance: {
+ ethFiat: 123.45,
+ tokenFiat: 0,
+ tokenFiat1dAgo: 0,
+ ethFiat1dAgo: 100.0,
+ },
+ isLoadingAccount: false,
tokenFiatBalancesCrossChains: [],
};
jest.mock('../../../../hooks/useMultichainBalances', () => ({
- useSelectedAccountMultichainBalances: () => ({
+ useSelectedAccountMultichainBalances: jest.fn(() => ({
selectedAccountMultichainBalance: mockSelectedAccountMultichainBalance,
- }),
+ })),
}));
jest.mock('../../../../../core/Engine', () => ({
@@ -47,6 +59,30 @@ jest.mock('../../../../../core/Engine', () => ({
},
}));
+jest.mock('@react-navigation/native', () => {
+ const actualNav = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualNav,
+ useNavigation: () => ({
+ navigate: jest.fn(),
+ }),
+ };
+});
+
+jest.mock('../../../../../components/hooks/useMetrics', () => ({
+ useMetrics: () => ({
+ trackEvent: jest.fn(),
+ createEventBuilder: jest.fn(() => ({
+ addProperties: jest.fn().mockReturnThis(),
+ build: jest.fn(),
+ })),
+ }),
+ MetaMetricsEvents: {
+ CARD_ADD_FUNDS_DEPOSIT_CLICKED: 'CARD_ADD_FUNDS_DEPOSIT_CLICKED',
+ RAMPS_BUTTON_CLICKED: 'RAMPS_BUTTON_CLICKED',
+ },
+}));
+
const initialState = {
engine: {
backgroundState: {
@@ -146,6 +182,15 @@ const renderPortfolioBalance = (state: any = {}) =>
renderWithProvider(, { state });
describe('PortfolioBalance', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ // Reset to default mock before each test
+ const mockedHook = jest.mocked(useSelectedAccountMultichainBalances);
+ mockedHook.mockReturnValue({
+ selectedAccountMultichainBalance: mockSelectedAccountMultichainBalance,
+ });
+ });
+
it('fiat balance must be defined', () => {
const { getByTestId } = renderPortfolioBalance(initialState);
expect(
@@ -207,4 +252,51 @@ describe('PortfolioBalance', () => {
expect(PreferencesController.setPrivacyMode).toHaveBeenCalledWith(true);
});
+
+ it('displays BalanceEmptyState when balance is zero', () => {
+ // Mock zero balance
+ const mockSelectedAccountMultichainBalanceZero = {
+ displayBalance: '$0.00',
+ displayCurrency: 'USD',
+ totalFiatBalance: 0,
+ totalNativeTokenBalance: '0',
+ nativeTokenUnit: 'ETH',
+ shouldShowAggregatedPercentage: false,
+ isPortfolioVieEnabled: false,
+ aggregatedBalance: {
+ ethFiat: 123.45,
+ tokenFiat: 0,
+ tokenFiat1dAgo: 0,
+ ethFiat1dAgo: 100.0,
+ },
+ isLoadingAccount: false,
+ tokenFiatBalancesCrossChains: [],
+ };
+
+ const mockedHook = jest.mocked(useSelectedAccountMultichainBalances);
+ mockedHook.mockReturnValue({
+ selectedAccountMultichainBalance:
+ mockSelectedAccountMultichainBalanceZero,
+ });
+
+ const { getByTestId, queryByTestId } = renderPortfolioBalance(initialState);
+
+ // Should render BalanceEmptyState instead of balance text
+ expect(getByTestId('portfolio-balance-empty-state')).toBeDefined();
+ expect(queryByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT)).toBeNull();
+ });
+
+ it('displays loader when balance is not available', () => {
+ // Mock undefined balance
+ const mockedHook = jest.mocked(useSelectedAccountMultichainBalances);
+ mockedHook.mockReturnValue({
+ selectedAccountMultichainBalance: undefined,
+ });
+
+ const { queryByTestId } = renderPortfolioBalance(initialState);
+
+ // Should not render balance text or empty state
+ expect(queryByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT)).toBeNull();
+ expect(queryByTestId('portfolio-balance-empty-state')).toBeNull();
+ });
});
diff --git a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx
index 53366bdf0ca0..8343ef355a5a 100644
--- a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx
+++ b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx
@@ -15,6 +15,7 @@ import { useSelectedAccountMultichainBalances } from '../../../../hooks/useMulti
import Loader from '../../../../../component-library/components-temp/Loader/Loader';
import NonEvmAggregatedPercentage from '../../../../../component-library/components-temp/Price/AggregatedPercentage/NonEvmAggregatedPercentage';
import { selectIsEvmNetworkSelected } from '../../../../../selectors/multichainNetworkController';
+import BalanceEmptyState from '../../../BalanceEmptyState';
export const PortfolioBalance = React.memo(() => {
const { PreferencesController } = Engine.context;
@@ -57,10 +58,21 @@ export const PortfolioBalance = React.memo(() => {
[PreferencesController],
);
+ // Check if balance is zero (empty state) - only check when we have balance data
+ const hasZeroBalance =
+ selectedAccountMultichainBalance &&
+ selectedAccountMultichainBalance.totalFiatBalance === 0;
+
return (
- {selectedAccountMultichainBalance?.displayBalance ? (
+ {!selectedAccountMultichainBalance ? (
+
+
+
+ ) : hasZeroBalance ? (
+
+ ) : (
toggleIsBalanceAndAssetsHidden(!privacyMode)}
testID="balance-container"
@@ -78,10 +90,6 @@ export const PortfolioBalance = React.memo(() => {
{renderAggregatedPercentage()}
- ) : (
-
-
-
)}
diff --git a/app/components/UI/Tokens/TokenList/index.tsx b/app/components/UI/Tokens/TokenList/index.tsx
index b5c14752d91a..d9f3d0532bb9 100644
--- a/app/components/UI/Tokens/TokenList/index.tsx
+++ b/app/components/UI/Tokens/TokenList/index.tsx
@@ -13,7 +13,6 @@ import TextComponent, {
} from '../../../../component-library/components/Texts/Text';
import { TokenI } from '../types';
import { strings } from '../../../../../locales/i18n';
-import { TokenListFooter } from './TokenListFooter';
import { TokenListItem, TokenListItemBip44 } from './TokenListItem';
import { WalletViewSelectorsIDs } from '../../../../../e2e/selectors/wallet/WalletView.selectors';
import { useNavigation } from '@react-navigation/native';
@@ -107,7 +106,6 @@ const TokenListComponent = ({
return `${item.address}-${item.chainId}-${staked}-${idx}`;
}}
decelerationRate="fast"
- ListFooterComponent={}
refreshControl={