Skip to content

Commit c904dce

Browse files
authored
Merge branch 'main' into e2e-perps-to-smoke
2 parents 082f377 + c64b1e4 commit c904dce

File tree

157 files changed

+17236
-2189
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

157 files changed

+17236
-2189
lines changed

.github/workflows/build-android-e2e.yml

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
runs-on: ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-lg
3636
env:
3737
GRADLE_USER_HOME: /home/admin/_work/.gradle
38+
CACHE_GENERATION: v1 # Increment this to bust the cache (v1, v2, v3, etc.)
3839
outputs:
3940
apk-uploaded: ${{ steps.upload-apk.outcome == 'success' }}
4041
aab-uploaded: ${{ steps.upload-aab.outcome == 'success' }}
@@ -63,16 +64,14 @@ jobs:
6364

6465
- name: Cache Gradle dependencies
6566
uses: cirruslabs/cache@v4
67+
id: gradle-cache-restore
68+
env:
69+
GRADLE_CACHE_VERSION: 1
6670
with:
6771
path: |
68-
/home/runner/_work/.gradle/caches
69-
/home/runner/_work/.gradle/wrapper
70-
/home/admin/_work/.gradle/caches
71-
/home/admin/_work/.gradle/wrapper
72-
android/.gradle
73-
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
74-
restore-keys: |
75-
gradle-${{ runner.os }}-
72+
~/_work/.gradle/caches
73+
~/_work/.gradle/wrapper
74+
key: gradle-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
7675

7776
- name: Setup project dependencies with retry
7877
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
@@ -115,24 +114,24 @@ jobs:
115114
fi
116115
117116
- name: Check and restore cached APKs if Fingerprint is found
118-
id: cache-restore
117+
id: apk-cache-restore
119118
uses: cirruslabs/cache@v4
120119
with:
121120
path: |
122121
${{ steps.determine-target-paths.outputs.apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.apk
123122
${{ steps.determine-target-paths.outputs.test-apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}-androidTest.apk
124123
${{ steps.determine-target-paths.outputs.aab-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.aab
125-
key: android-apk-${{ inputs.build_type }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
124+
key: android-apk-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
126125
restore-keys: |
127-
android-apk-${{ inputs.build_type }}-
126+
android-apk-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-
128127
android-apk-
129128
130129
- name: Build Android E2E APKs
131-
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' }}
130+
if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' || steps.gradle-cache-restore.outputs.cache-hit != 'true' }}
132131
run: |
133132
echo "🏗 Building Android E2E APKs..."
134133
export NODE_OPTIONS="--max-old-space-size=8192"
135-
# cp android/gradle.properties.github android/gradle.properties - Build for all apps to stabilize e2e
134+
cp android/gradle.properties.github android/gradle.properties
136135
yarn build:android:${{ inputs.build_type }}:e2e
137136
shell: bash
138137
env:
@@ -179,7 +178,7 @@ jobs:
179178
MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }}
180179

181180
- name: Repack APK with JS updates using @expo/repack-app
182-
if: ${{ steps.cache-restore.outputs.cache-hit == 'true' }}
181+
if: ${{ steps.apk-cache-restore.outputs.cache-hit == 'true' && steps.gradle-cache-restore.outputs.cache-hit == 'true' }}
183182
run: |
184183
echo "📦 Repacking APK with updated JavaScript bundle using @expo/repack-app..."
185184
# Use the optimized repack script which uses @expo/repack-app
@@ -230,14 +229,14 @@ jobs:
230229

231230
# Cache build artifacts with the pre-build fingerprint
232231
- name: Cache build artifacts
233-
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' }}
232+
if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' || steps.gradle-cache-restore.outputs.cache-hit != 'true' }}
234233
uses: cirruslabs/cache@v4
235234
with:
236235
path: |
237236
${{ steps.determine-target-paths.outputs.apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.apk
238237
${{ steps.determine-target-paths.outputs.test-apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}-androidTest.apk
239238
${{ steps.determine-target-paths.outputs.aab-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.aab
240-
key: android-apk-${{ inputs.build_type }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
239+
key: android-apk-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
241240

242241
- name: Upload Android APK
243242
id: upload-apk

app/components/UI/Assets/components/Balance/AccountGroupBalance.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,38 @@ jest.mock('../../../../../selectors/assets/balances', () => ({
99
selectBalanceBySelectedAccountGroup: jest.fn(() => null),
1010
// This one is a factory: selectBalanceChangeBySelectedAccountGroup(period) -> (state) => value
1111
selectBalanceChangeBySelectedAccountGroup: jest.fn(() => () => null),
12+
// This selector is used to display the BalanceEmptyState
13+
selectAccountGroupBalanceForEmptyState: jest.fn(() => null),
14+
}));
15+
16+
// Mock homepage redesign feature flag for BalanceEmptyState
17+
jest.mock('../../../../../selectors/featureFlagController/homepage', () => ({
18+
selectHomepageRedesignV1Enabled: jest.fn(() => true),
19+
}));
20+
21+
// This selector is used to determine if the current network is a testnet for BalanceEmptyState display logic
22+
jest.mock('../../../../../selectors/networkController', () => ({
23+
...jest.requireActual('../../../../../selectors/networkController'),
24+
selectEvmChainId: jest.fn(() => '0x1'), // Ethereum mainnet (not a testnet)
25+
selectChainId: jest.fn(() => '0x1'), // BalanceEmptyState also needs this
26+
}));
27+
28+
// Mock navigation hooks used by BalanceEmptyState
29+
jest.mock('@react-navigation/native', () => ({
30+
...jest.requireActual('@react-navigation/native'),
31+
useNavigation: () => ({
32+
navigate: jest.fn(),
33+
goBack: jest.fn(),
34+
reset: jest.fn(),
35+
}),
36+
}));
37+
38+
// Mock metrics hook used by BalanceEmptyState
39+
jest.mock('../../../../../components/hooks/useMetrics', () => ({
40+
useMetrics: () => ({
41+
trackEvent: jest.fn(),
42+
createEventBuilder: jest.fn(() => ({ record: jest.fn() })),
43+
}),
1244
}));
1345

1446
const testState = {
@@ -52,4 +84,36 @@ describe('AccountGroupBalance', () => {
5284
const el = getByTestId(WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT);
5385
expect(el).toBeTruthy();
5486
});
87+
88+
it('renders empty state when account group balance is zero', () => {
89+
const {
90+
selectAccountGroupBalanceForEmptyState,
91+
selectBalanceBySelectedAccountGroup,
92+
} = jest.requireMock('../../../../../selectors/assets/balances');
93+
94+
// Mock the regular balance selector to return data (prevents skeleton loader)
95+
(selectBalanceBySelectedAccountGroup as jest.Mock).mockImplementation(
96+
() => ({
97+
totalBalanceInUserCurrency: 100, // Some non-zero amount for current network
98+
userCurrency: 'usd',
99+
}),
100+
);
101+
102+
// Mock the empty state selector to return zero balance across all mainnet networks
103+
(selectAccountGroupBalanceForEmptyState as jest.Mock).mockImplementation(
104+
() => ({
105+
totalBalanceInUserCurrency: 0, // Zero across all mainnet networks
106+
userCurrency: 'usd',
107+
}),
108+
);
109+
110+
const { getByTestId } = renderWithProvider(<AccountGroupBalance />, {
111+
state: testState,
112+
});
113+
114+
const el = getByTestId(
115+
WalletViewSelectorsIDs.BALANCE_EMPTY_STATE_CONTAINER,
116+
);
117+
expect(el).toBeOnTheScreen();
118+
});
55119
});

app/components/UI/Assets/components/Balance/AccountGroupBalance.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { selectPrivacyMode } from '../../../../../selectors/preferencesControlle
77
import {
88
selectBalanceBySelectedAccountGroup,
99
selectBalanceChangeBySelectedAccountGroup,
10+
selectAccountGroupBalanceForEmptyState,
1011
} from '../../../../../selectors/assets/balances';
12+
import { selectHomepageRedesignV1Enabled } from '../../../../../selectors/featureFlagController/homepage';
13+
import { selectEvmChainId } from '../../../../../selectors/networkController';
14+
import { TEST_NETWORK_IDS } from '../../../../../constants/network';
1115
import SensitiveText, {
1216
SensitiveTextLength,
1317
} from '../../../../../component-library/components/Texts/SensitiveText';
@@ -16,16 +20,24 @@ import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/W
1620
import { Skeleton } from '../../../../../component-library/components/Skeleton';
1721
import { useFormatters } from '../../../../hooks/useFormatters';
1822
import AccountGroupBalanceChange from '../../components/BalanceChange/AccountGroupBalanceChange';
23+
import BalanceEmptyState from '../../../BalanceEmptyState';
1924

2025
const AccountGroupBalance = () => {
2126
const { PreferencesController } = Engine.context;
2227
const styles = createStyles();
2328
const { formatCurrency } = useFormatters();
2429
const privacyMode = useSelector(selectPrivacyMode);
2530
const groupBalance = useSelector(selectBalanceBySelectedAccountGroup);
31+
const accountGroupBalance = useSelector(
32+
selectAccountGroupBalanceForEmptyState,
33+
);
2634
const balanceChange1d = useSelector(
2735
selectBalanceChangeBySelectedAccountGroup('1d'),
2836
);
37+
const isHomepageRedesignV1Enabled = useSelector(
38+
selectHomepageRedesignV1Enabled,
39+
);
40+
const selectedChainId = useSelector(selectEvmChainId);
2941

3042
const togglePrivacy = useCallback(
3143
(value: boolean) => {
@@ -38,10 +50,30 @@ const AccountGroupBalance = () => {
3850
const userCurrency = groupBalance?.userCurrency ?? '';
3951
const displayBalance = formatCurrency(totalBalance, userCurrency);
4052

53+
// Check if account group balance (across all mainnet networks) is zero for empty state
54+
const hasZeroAccountGroupBalance =
55+
accountGroupBalance && accountGroupBalance.totalBalanceInUserCurrency === 0;
56+
57+
// Check if current network is a testnet
58+
const isCurrentNetworkTestnet = TEST_NETWORK_IDS.includes(selectedChainId);
59+
60+
// Show empty state on accounts with an aggregated mainnet balance of zero
61+
const shouldShowEmptyState =
62+
hasZeroAccountGroupBalance &&
63+
isHomepageRedesignV1Enabled &&
64+
!isCurrentNetworkTestnet;
65+
4166
return (
4267
<View style={styles.accountGroupBalance}>
4368
<View>
44-
{groupBalance ? (
69+
{!groupBalance ? (
70+
<View style={styles.skeletonContainer}>
71+
<Skeleton width={100} height={40} />
72+
<Skeleton width={100} height={20} />
73+
</View>
74+
) : shouldShowEmptyState ? (
75+
<BalanceEmptyState testID="account-group-balance-empty-state" />
76+
) : (
4577
<TouchableOpacity
4678
onPress={() => togglePrivacy(!privacyMode)}
4779
testID="balance-container"
@@ -66,11 +98,6 @@ const AccountGroupBalance = () => {
6698
/>
6799
)}
68100
</TouchableOpacity>
69-
) : (
70-
<View style={styles.skeletonContainer}>
71-
<Skeleton width={100} height={40} />
72-
<Skeleton width={100} height={20} />
73-
</View>
74101
)}
75102
</View>
76103
</View>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { StyleSheet } from 'react-native';
2+
import type { Theme } from '../../../../../util/theme/models';
3+
4+
export const createStyles = (_theme: Theme) =>
5+
StyleSheet.create({
6+
contentContainer: {
7+
paddingHorizontal: 16,
8+
paddingVertical: 16,
9+
},
10+
loadingContainer: {
11+
paddingVertical: 32,
12+
alignItems: 'center',
13+
justifyContent: 'center',
14+
},
15+
loadingText: {
16+
marginTop: 12,
17+
},
18+
emptyContainer: {
19+
paddingVertical: 32,
20+
paddingHorizontal: 16,
21+
alignItems: 'center',
22+
justifyContent: 'center',
23+
},
24+
footerContainer: {
25+
paddingHorizontal: 16,
26+
},
27+
});

0 commit comments

Comments
 (0)