-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: implement BalanceEmptyState component #21391
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
Changes from all commits
99befba
3527a41
9eea033
538d947
6784d3e
1516927
9e2b1b9
a617f83
b6006ae
5c1ceb1
225087b
637f4a8
449a35f
14c56ab
2078f33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,38 @@ jest.mock('../../../../../selectors/assets/balances', () => ({ | |
| selectBalanceBySelectedAccountGroup: jest.fn(() => null), | ||
| // This one is a factory: selectBalanceChangeBySelectedAccountGroup(period) -> (state) => value | ||
| selectBalanceChangeBySelectedAccountGroup: jest.fn(() => () => null), | ||
| // This selector is used to display the BalanceEmptyState | ||
| selectAccountGroupBalanceForEmptyState: jest.fn(() => null), | ||
| })); | ||
|
|
||
| // Mock homepage redesign feature flag for BalanceEmptyState | ||
| jest.mock('../../../../../selectors/featureFlagController/homepage', () => ({ | ||
| selectHomepageRedesignV1Enabled: jest.fn(() => true), | ||
| })); | ||
|
|
||
| // This selector is used to determine if the current network is a testnet for BalanceEmptyState display logic | ||
| jest.mock('../../../../../selectors/networkController', () => ({ | ||
| ...jest.requireActual('../../../../../selectors/networkController'), | ||
| selectEvmChainId: jest.fn(() => '0x1'), // Ethereum mainnet (not a testnet) | ||
| selectChainId: jest.fn(() => '0x1'), // BalanceEmptyState also needs this | ||
| })); | ||
|
|
||
| // Mock navigation hooks used by BalanceEmptyState | ||
| jest.mock('@react-navigation/native', () => ({ | ||
| ...jest.requireActual('@react-navigation/native'), | ||
| useNavigation: () => ({ | ||
| navigate: jest.fn(), | ||
| goBack: jest.fn(), | ||
| reset: jest.fn(), | ||
| }), | ||
| })); | ||
|
|
||
| // Mock metrics hook used by BalanceEmptyState | ||
| jest.mock('../../../../../components/hooks/useMetrics', () => ({ | ||
| useMetrics: () => ({ | ||
| trackEvent: jest.fn(), | ||
| createEventBuilder: jest.fn(() => ({ record: jest.fn() })), | ||
| }), | ||
| })); | ||
|
|
||
| const testState = { | ||
|
|
@@ -52,4 +84,36 @@ describe('AccountGroupBalance', () => { | |
| const el = getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT); | ||
| expect(el).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('renders empty state when account group balance is zero', () => { | ||
| const { | ||
| selectAccountGroupBalanceForEmptyState, | ||
| selectBalanceBySelectedAccountGroup, | ||
| } = jest.requireMock('../../../../../selectors/assets/balances'); | ||
|
|
||
| // Mock the regular balance selector to return data (prevents skeleton loader) | ||
| (selectBalanceBySelectedAccountGroup as jest.Mock).mockImplementation( | ||
| () => ({ | ||
| totalBalanceInUserCurrency: 100, // Some non-zero amount for current network | ||
| userCurrency: 'usd', | ||
| }), | ||
| ); | ||
|
|
||
| // Mock the empty state selector to return zero balance across all mainnet networks | ||
| (selectAccountGroupBalanceForEmptyState as jest.Mock).mockImplementation( | ||
| () => ({ | ||
| totalBalanceInUserCurrency: 0, // Zero across all mainnet networks | ||
| userCurrency: 'usd', | ||
| }), | ||
| ); | ||
|
|
||
| const { getByTestId } = renderWithProvider(<AccountGroupBalance />, { | ||
| state: testState, | ||
| }); | ||
|
|
||
| const el = getByTestId( | ||
| WalletViewSelectorsIDs.BALANCE_EMPTY_STATE_CONTAINER, | ||
| ); | ||
| expect(el).toBeOnTheScreen(); | ||
| }); | ||
|
Comment on lines
+88
to
+118
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding test to check for balance emtpy state |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,11 @@ import { selectPrivacyMode } from '../../../../../selectors/preferencesControlle | |
| import { | ||
| selectBalanceBySelectedAccountGroup, | ||
| selectBalanceChangeBySelectedAccountGroup, | ||
| selectAccountGroupBalanceForEmptyState, | ||
| } from '../../../../../selectors/assets/balances'; | ||
| import { selectHomepageRedesignV1Enabled } from '../../../../../selectors/featureFlagController/homepage'; | ||
| import { selectEvmChainId } from '../../../../../selectors/networkController'; | ||
| import { TEST_NETWORK_IDS } from '../../../../../constants/network'; | ||
|
Comment on lines
+10
to
+14
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Importing new selector to calculate account group balance across all main nets, the feature flag for the new homepage redesign which includes full page scroll, the selector for current evm network and test networks ids for empty state display logic |
||
| import SensitiveText, { | ||
| SensitiveTextLength, | ||
| } from '../../../../../component-library/components/Texts/SensitiveText'; | ||
|
|
@@ -16,16 +20,24 @@ 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'; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Importing |
||
|
|
||
| const AccountGroupBalance = () => { | ||
| const { PreferencesController } = Engine.context; | ||
| const styles = createStyles(); | ||
| const { formatCurrency } = useFormatters(); | ||
| const privacyMode = useSelector(selectPrivacyMode); | ||
| const groupBalance = useSelector(selectBalanceBySelectedAccountGroup); | ||
| const accountGroupBalance = useSelector( | ||
| selectAccountGroupBalanceForEmptyState, | ||
| ); | ||
|
Comment on lines
+31
to
+33
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Getting account group balance across all chains using new selector |
||
| const balanceChange1d = useSelector( | ||
| selectBalanceChangeBySelectedAccountGroup('1d'), | ||
| ); | ||
| const isHomepageRedesignV1Enabled = useSelector( | ||
| selectHomepageRedesignV1Enabled, | ||
| ); | ||
|
Comment on lines
+37
to
+39
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using new feature flag to conditionally render the balance empty state |
||
| const selectedChainId = useSelector(selectEvmChainId); | ||
|
|
||
| const togglePrivacy = useCallback( | ||
| (value: boolean) => { | ||
|
|
@@ -38,10 +50,30 @@ const AccountGroupBalance = () => { | |
| const userCurrency = groupBalance?.userCurrency ?? ''; | ||
| const displayBalance = formatCurrency(totalBalance, userCurrency); | ||
|
|
||
| // Check if account group balance (across all mainnet networks) is zero for empty state | ||
| const hasZeroAccountGroupBalance = | ||
| accountGroupBalance && accountGroupBalance.totalBalanceInUserCurrency === 0; | ||
|
|
||
| // Check if current network is a testnet | ||
| const isCurrentNetworkTestnet = TEST_NETWORK_IDS.includes(selectedChainId); | ||
georgewrmarshall marked this conversation as resolved.
Show resolved
Hide resolved
georgewrmarshall marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We currently only show EVM test networks but this is a good point. If we were to add solana test net we may see the empty state. Do we have a non EVM test network constant that we can use here. Do you know @salimtb? |
||
|
|
||
| // Show empty state on accounts with an aggregated mainnet balance of zero | ||
| const shouldShowEmptyState = | ||
| hasZeroAccountGroupBalance && | ||
| isHomepageRedesignV1Enabled && | ||
| !isCurrentNetworkTestnet; | ||
|
Comment on lines
+53
to
+64
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic for displaying Balance empty state.
|
||
|
|
||
| return ( | ||
| <View style={styles.accountGroupBalance}> | ||
| <View> | ||
| {groupBalance ? ( | ||
| {!groupBalance ? ( | ||
| <View style={styles.skeletonContainer}> | ||
georgewrmarshall marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <Skeleton width={100} height={40} /> | ||
| <Skeleton width={100} height={20} /> | ||
| </View> | ||
| ) : shouldShowEmptyState ? ( | ||
| <BalanceEmptyState testID="account-group-balance-empty-state" /> | ||
| ) : ( | ||
| <TouchableOpacity | ||
| onPress={() => togglePrivacy(!privacyMode)} | ||
| testID="balance-container" | ||
|
|
@@ -66,11 +98,6 @@ const AccountGroupBalance = () => { | |
| /> | ||
| )} | ||
| </TouchableOpacity> | ||
| ) : ( | ||
| <View style={styles.skeletonContainer}> | ||
| <Skeleton width={100} height={40} /> | ||
| <Skeleton width={100} height={20} /> | ||
| </View> | ||
| )} | ||
| </View> | ||
| </View> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,6 +107,7 @@ import { | |
| selectBalancePercentChangeByAccountGroup, | ||
| selectBalanceBySelectedAccountGroup, | ||
| selectBalanceChangeBySelectedAccountGroup, | ||
| selectAccountGroupBalanceForEmptyState, | ||
| } from './balances'; | ||
|
|
||
| // Enhanced state factory with realistic data | ||
|
|
@@ -484,4 +485,116 @@ describe('assets balance and balance change selectors (mobile)', () => { | |
| expect(selector(state)).toBeNull(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('selectAccountGroupBalanceForEmptyState', () => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding tests for new selector |
||
| it('excludes testnet chains and includes only mainnet chains in balance calculation', () => { | ||
| const mockCalculateBalanceForAllWallets = jest.requireMock( | ||
| '@metamask/assets-controllers', | ||
| ).calculateBalanceForAllWallets; | ||
|
|
||
| const state = makeState({ | ||
| engine: { | ||
| backgroundState: { | ||
| ...makeState().engine.backgroundState, | ||
| NetworkController: { | ||
| networkConfigurationsByChainId: { | ||
| // Mainnet networks - should be included | ||
| '0x1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| '0x89': { chainId: '0x89', name: 'Polygon Mainnet' }, | ||
| // Testnet networks - should be excluded | ||
| '0xaa36a7': { chainId: '0xaa36a7', name: 'Sepolia' }, | ||
| '0x5': { chainId: '0x5', name: 'Goerli' }, | ||
| }, | ||
| }, | ||
| MultichainNetworkController: { | ||
| multichainNetworkConfigurationsByChainId: { | ||
| // Mainnet networks - should be included | ||
| 'eip155:1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| 'eip155:137': { chainId: '0x89', name: 'Polygon Mainnet' }, | ||
| // Testnet networks - should be excluded | ||
| 'eip155:11155111': { chainId: '0xaa36a7', name: 'Sepolia' }, | ||
| 'eip155:5': { chainId: '0x5', name: 'Goerli' }, | ||
| // Non-EVM testnet - should be excluded | ||
| 'solana:103': { chainId: 'solana:103', name: 'Solana Testnet' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) as unknown as RootState; | ||
|
|
||
| // Clear previous calls | ||
| mockCalculateBalanceForAllWallets.mockClear(); | ||
|
|
||
| selectAccountGroupBalanceForEmptyState(state); | ||
|
|
||
| // Verify calculateBalanceForAllWallets was called with proper enabledNetworkMap | ||
| expect(mockCalculateBalanceForAllWallets).toHaveBeenCalledTimes(1); | ||
| const enabledNetworkMap = | ||
| mockCalculateBalanceForAllWallets.mock.calls[0][8]; | ||
|
|
||
| // Should include mainnet networks only | ||
| expect(enabledNetworkMap).toEqual({ | ||
| eip155: { | ||
| '0x1': true, // Ethereum mainnet | ||
| '0x89': true, // Polygon mainnet | ||
| }, | ||
| // No testnet networks should be present | ||
| }); | ||
| }); | ||
|
|
||
| it('returns null when no account group is selected', () => { | ||
| const state = makeState({ | ||
| engine: { | ||
| backgroundState: { | ||
| ...makeState().engine.backgroundState, | ||
| NetworkController: { | ||
| networkConfigurationsByChainId: { | ||
| '0x1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| }, | ||
| }, | ||
| MultichainNetworkController: { | ||
| multichainNetworkConfigurationsByChainId: { | ||
| 'eip155:1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) as unknown as RootState; | ||
| state.engine.backgroundState.AccountTreeController.accountTree.selectedAccountGroup = | ||
| ''; | ||
|
|
||
| const result = selectAccountGroupBalanceForEmptyState(state); | ||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('returns zeroed fallback when selected group does not exist', () => { | ||
| const state = makeState({ | ||
| engine: { | ||
| backgroundState: { | ||
| ...makeState().engine.backgroundState, | ||
| NetworkController: { | ||
| networkConfigurationsByChainId: { | ||
| '0x1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| }, | ||
| }, | ||
| MultichainNetworkController: { | ||
| multichainNetworkConfigurationsByChainId: { | ||
| 'eip155:1': { chainId: '0x1', name: 'Ethereum Mainnet' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) as unknown as RootState; | ||
| state.engine.backgroundState.AccountTreeController.accountTree.selectedAccountGroup = | ||
| 'keyring:wallet-1/group-999'; | ||
|
|
||
| const result = selectAccountGroupBalanceForEmptyState(state); | ||
| expect(result).toEqual({ | ||
| walletId: 'keyring:wallet-1', | ||
| groupId: 'keyring:wallet-1/group-999', | ||
| totalBalanceInUserCurrency: 0, | ||
| userCurrency: 'usd', | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding necessary mocks for BalanceEmptyState component