Skip to content

Commit c432773

Browse files
Merge branch 'main' into perf/faster-bottomsheet-animation
2 parents 26a2d77 + 2ab978e commit c432773

File tree

105 files changed

+9084
-3104
lines changed

Some content is hidden

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

105 files changed

+9084
-3104
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ app/core/Engine/controllers/remote-feature-flag-controller/ @MetaMask/mobile-pla
4343
app/core/DeeplinkManager @MetaMask/mobile-platform
4444
scripts/build.sh @MetaMask/mobile-platform
4545
scripts/update-expo-channel.js @MetaMask/mobile-admins
46+
certs/certificate.pem @MetaMask/mobile-admins
4647

4748
# Platform & Snaps Code Fencing File
4849
metro.transform.js @MetaMask/mobile-platform @MetaMask/core-platform

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ android {
187187
applicationId "io.metamask"
188188
minSdkVersion rootProject.ext.minSdkVersion
189189
targetSdkVersion rootProject.ext.targetSdkVersion
190-
versionName "7.61.0"
191-
versionCode 2993
190+
versionName "7.61.99"
191+
versionCode 3092
192192
testBuildType System.getProperty('testBuildType', 'debug')
193193
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
194194
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"

app.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ module.exports = {
4646
owner: 'metamask-test',
4747
runtimeVersion: RUNTIME_VERSION,
4848
updates: {
49+
codeSigningCertificate: './certs/certificate.pem',
50+
codeSigningMetadata: {
51+
keyid: 'main',
52+
alg: 'rsa-v1_5-sha256',
53+
},
4954
url: UPDATE_URL,
5055
// Channel is set by requestHeaders, will be overridden with build script
5156
requestHeaders: {

app/__mocks__/rive-react-native.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { forwardRef, useImperativeHandle } from 'react';
1+
import React, { forwardRef, useImperativeHandle, useEffect } from 'react';
22
import { View, ViewProps } from 'react-native';
33

44
export interface RiveRef {
@@ -27,6 +27,7 @@ type MockRiveProps = ViewProps & {
2727
alignment?: string;
2828
autoplay?: boolean;
2929
stateMachineName?: string;
30+
onPlay?: () => void;
3031
};
3132

3233
const DEFAULT_TEST_ID = 'mock-rive-animation';
@@ -48,12 +49,19 @@ const updateLastMockedMethods = (methods: RiveRef) => {
4849
};
4950

5051
const RiveMock = forwardRef<RiveRef, MockRiveProps>(
51-
({ testID = DEFAULT_TEST_ID, mockedMethods, ...viewProps }, ref) => {
52+
({ testID = DEFAULT_TEST_ID, mockedMethods, onPlay, ...viewProps }, ref) => {
5253
const methods = createMockedMethods(mockedMethods);
5354
updateLastMockedMethods(methods);
5455

5556
useImperativeHandle(ref, () => methods, [methods]);
5657

58+
useEffect(() => {
59+
if (onPlay) {
60+
onPlay();
61+
}
62+
// eslint-disable-next-line react-hooks/exhaustive-deps
63+
}, []);
64+
5765
return <View testID={testID} {...viewProps} />;
5866
},
5967
);

app/components/UI/AssetOverview/AssetOverview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ import {
7676
useSwapBridgeNavigation,
7777
SwapBridgeNavigationLocation,
7878
} from '../Bridge/hooks/useSwapBridgeNavigation';
79-
import { swapsUtils } from '@metamask/swaps-controller';
79+
import { NATIVE_SWAPS_TOKEN_ADDRESS } from '../../../constants/bridge';
8080
import { TraceName, endTrace } from '../../../util/trace';
8181
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
8282
import { selectMultichainAssetsRates } from '../../../selectors/multichain';
@@ -185,7 +185,7 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
185185
sourcePage: 'MainView',
186186
sourceToken: {
187187
...asset,
188-
address: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS,
188+
address: asset.address ?? NATIVE_SWAPS_TOKEN_ADDRESS,
189189
chainId: asset.chainId as Hex,
190190
decimals: asset.decimals,
191191
symbol: asset.symbol,

app/components/UI/FoxAnimation/FoxAnimation.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect, useRef } from 'react';
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
22
import { StyleSheet, Platform, View } from 'react-native';
33
import Rive, { Alignment, Fit, RiveRef } from 'rive-react-native';
44
import { useSafeAreaInsets, EdgeInsets } from 'react-native-safe-area-context';
@@ -77,6 +77,8 @@ const FoxAnimation = ({
7777
const insets = useSafeAreaInsets();
7878
const styles = createStyles(hasFooter, insets);
7979

80+
const [isPlaying, setIsPlaying] = useState(false);
81+
8082
const showFoxAnimation = useCallback(async () => {
8183
if (foxRef.current && trigger) {
8284
try {
@@ -88,8 +90,10 @@ const FoxAnimation = ({
8890
}, [foxRef, trigger]);
8991

9092
useEffect(() => {
91-
showFoxAnimation();
92-
}, [showFoxAnimation]);
93+
if (isPlaying) {
94+
showFoxAnimation();
95+
}
96+
}, [showFoxAnimation, isPlaying]);
9397

9498
return (
9599
<View style={[styles.foxAnimationWrapper]}>
@@ -99,9 +103,11 @@ const FoxAnimation = ({
99103
source={FoxAnimationRive}
100104
fit={Fit.Contain}
101105
alignment={Alignment.Center}
102-
autoplay={false}
103106
stateMachineName="FoxRaiseUp"
104107
testID="fox-animation"
108+
onPlay={() => {
109+
setIsPlaying(true);
110+
}}
105111
/>
106112
</View>
107113
);

app/components/UI/NetworkManager/index.test.tsx

Lines changed: 216 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import configureStore from 'redux-mock-store';
55

66
import NetworkManager from './index';
77
import { useNetworksByNamespace } from '../../hooks/useNetworksByNamespace/useNetworksByNamespace';
8+
import { useNetworkEnablement } from '../../hooks/useNetworkEnablement/useNetworkEnablement';
89
import Engine from '../../../core/Engine';
910

1011
// Create mock functions that we can spy on
@@ -147,16 +148,40 @@ jest.mock('../../hooks/useNetworksByNamespace/useNetworksByNamespace', () => ({
147148
},
148149
}));
149150

151+
const mockEnableNetwork = jest.fn();
152+
150153
jest.mock('../../hooks/useNetworkEnablement/useNetworkEnablement', () => ({
151-
useNetworkEnablement: () => ({
154+
useNetworkEnablement: jest.fn(() => ({
152155
disableNetwork: mockDisableNetwork,
156+
enableNetwork: mockEnableNetwork,
153157
enabledNetworksByNamespace: {
154158
eip155: {
155159
'0x1': true,
156160
'0x89': true,
157161
},
158162
},
159-
}),
163+
})),
164+
}));
165+
166+
jest.mock('../../hooks/useNetworksToUse/useNetworksToUse', () => ({
167+
useNetworksToUse: jest.fn(() => ({
168+
networksToUse: [
169+
{
170+
id: 'eip155:1',
171+
name: 'Ethereum Mainnet',
172+
caipChainId: 'eip155:1',
173+
isSelected: true,
174+
imageSource: { uri: 'ethereum.png' },
175+
},
176+
{
177+
id: 'eip155:137',
178+
name: 'Polygon Mainnet',
179+
caipChainId: 'eip155:137',
180+
isSelected: false,
181+
imageSource: { uri: 'polygon.png' },
182+
},
183+
],
184+
})),
160185
}));
161186

162187
jest.mock('../../../util/device', () => ({
@@ -1011,4 +1036,193 @@ describe('NetworkManager Component', () => {
10111036
});
10121037
});
10131038
});
1039+
1040+
describe('Enabled Networks Processing', () => {
1041+
const mockUseNetworkEnablement =
1042+
useNetworkEnablement as jest.MockedFunction<typeof useNetworkEnablement>;
1043+
1044+
beforeEach(() => {
1045+
jest.clearAllMocks();
1046+
});
1047+
1048+
it('should correctly process enabled networks from flat structure', () => {
1049+
mockUseNetworkEnablement.mockReturnValue({
1050+
disableNetwork: mockDisableNetwork,
1051+
enableNetwork: mockEnableNetwork,
1052+
enabledNetworksByNamespace: {
1053+
eip155: {
1054+
'0x1': true,
1055+
'0x89': true,
1056+
'0xa': false,
1057+
},
1058+
},
1059+
namespace: 'eip155',
1060+
enabledNetworksForCurrentNamespace: {},
1061+
networkEnablementController: {} as never,
1062+
isNetworkEnabled: jest.fn(),
1063+
hasOneEnabledNetwork: false,
1064+
enableAllPopularNetworks: jest.fn(),
1065+
tryEnableEvmNetwork: jest.fn(),
1066+
enabledNetworksForAllNamespaces: {
1067+
'0x1': true,
1068+
'0x89': true,
1069+
'0xa': false,
1070+
},
1071+
});
1072+
1073+
// The component internally processes enabledNetworksByNamespace
1074+
// We verify it renders without errors and has correct tab state
1075+
const { getByTestId } = renderComponent();
1076+
1077+
// Verify component renders successfully with processed networks
1078+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1079+
});
1080+
1081+
it('should correctly process enabled networks from nested structure', () => {
1082+
mockUseNetworkEnablement.mockReturnValue({
1083+
disableNetwork: mockDisableNetwork,
1084+
enableNetwork: mockEnableNetwork,
1085+
enabledNetworksByNamespace: {
1086+
eip155: {
1087+
'0x1': true,
1088+
'0x89': false,
1089+
},
1090+
bip122: {
1091+
'0x1': true,
1092+
},
1093+
} as never,
1094+
namespace: 'eip155',
1095+
enabledNetworksForCurrentNamespace: {},
1096+
networkEnablementController: {} as never,
1097+
isNetworkEnabled: jest.fn(),
1098+
hasOneEnabledNetwork: false,
1099+
enableAllPopularNetworks: jest.fn(),
1100+
tryEnableEvmNetwork: jest.fn(),
1101+
enabledNetworksForAllNamespaces: {
1102+
'0x1': true,
1103+
'0x89': false,
1104+
'0xa': false,
1105+
},
1106+
});
1107+
1108+
// The component should handle nested namespace structures
1109+
const { getByTestId } = renderComponent();
1110+
1111+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1112+
});
1113+
1114+
it('should handle empty enabled networks gracefully', () => {
1115+
mockUseNetworkEnablement.mockReturnValue({
1116+
disableNetwork: mockDisableNetwork,
1117+
enableNetwork: mockEnableNetwork,
1118+
enabledNetworksByNamespace: {},
1119+
namespace: 'eip155',
1120+
enabledNetworksForCurrentNamespace: {},
1121+
networkEnablementController: {} as never,
1122+
isNetworkEnabled: jest.fn(),
1123+
hasOneEnabledNetwork: false,
1124+
enableAllPopularNetworks: jest.fn(),
1125+
tryEnableEvmNetwork: jest.fn(),
1126+
enabledNetworksForAllNamespaces: {},
1127+
});
1128+
1129+
const { getByTestId } = renderComponent();
1130+
1131+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1132+
});
1133+
1134+
it('should filter out disabled networks correctly', () => {
1135+
mockUseNetworkEnablement.mockReturnValue({
1136+
disableNetwork: mockDisableNetwork,
1137+
enableNetwork: mockEnableNetwork,
1138+
enabledNetworksByNamespace: {
1139+
eip155: {
1140+
'0x1': true,
1141+
'0x89': false,
1142+
'0xa': false,
1143+
'0xa4b1': true,
1144+
},
1145+
},
1146+
namespace: 'eip155',
1147+
enabledNetworksForCurrentNamespace: {},
1148+
networkEnablementController: {} as never,
1149+
isNetworkEnabled: jest.fn(),
1150+
hasOneEnabledNetwork: false,
1151+
enableAllPopularNetworks: jest.fn(),
1152+
tryEnableEvmNetwork: jest.fn(),
1153+
enabledNetworksForAllNamespaces: {
1154+
'0x1': true,
1155+
'0x89': false,
1156+
'0xa': false,
1157+
'0xa4b1': true,
1158+
},
1159+
});
1160+
1161+
// Component should only include enabled (true) networks
1162+
const { getByTestId } = renderComponent();
1163+
1164+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1165+
});
1166+
1167+
it('should enable and disable networks correctly during deletion', async () => {
1168+
const otherNetwork = {
1169+
id: 'eip155:137',
1170+
name: 'Polygon Mainnet',
1171+
caipChainId: 'eip155:137',
1172+
isSelected: false,
1173+
imageSource: { uri: 'polygon.png' },
1174+
};
1175+
1176+
(useNetworksByNamespace as jest.Mock).mockImplementation((args) => {
1177+
if (args.networkType === 'custom') {
1178+
return {
1179+
networks: [otherNetwork],
1180+
areAllNetworksSelected: false,
1181+
};
1182+
}
1183+
return { selectedCount: 2 };
1184+
});
1185+
1186+
mockUseNetworkEnablement.mockReturnValue({
1187+
disableNetwork: mockDisableNetwork,
1188+
enableNetwork: mockEnableNetwork,
1189+
enabledNetworksByNamespace: {
1190+
eip155: {
1191+
'0x1': true,
1192+
'0x89': true,
1193+
},
1194+
},
1195+
namespace: 'eip155',
1196+
enabledNetworksForCurrentNamespace: {},
1197+
networkEnablementController: {} as never,
1198+
isNetworkEnabled: jest.fn(),
1199+
hasOneEnabledNetwork: false,
1200+
enableAllPopularNetworks: jest.fn(),
1201+
tryEnableEvmNetwork: jest.fn(),
1202+
enabledNetworksForAllNamespaces: {
1203+
'0x1': true,
1204+
'0x89': true,
1205+
},
1206+
});
1207+
1208+
const { getByTestId } = renderComponent();
1209+
1210+
// Open modal and trigger delete confirmation
1211+
const openModalButton = getByTestId('open-modal-button');
1212+
fireEvent.press(openModalButton);
1213+
1214+
await waitFor(() => {
1215+
const deleteButton = getByTestId('account-action-app_settings.delete');
1216+
fireEvent.press(deleteButton);
1217+
});
1218+
1219+
// Confirm deletion
1220+
const confirmButton = getByTestId('footer-button-app_settings.delete');
1221+
fireEvent.press(confirmButton);
1222+
1223+
// Should enable the other network and disable the deleted one
1224+
expect(mockEnableNetwork).toHaveBeenCalledWith('eip155:137');
1225+
expect(mockDisableNetwork).toHaveBeenCalledWith('eip155:1');
1226+
});
1227+
});
10141228
});

0 commit comments

Comments
 (0)