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
6 changes: 2 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,6 @@ jobs:

- name: Generate iOS bundle
run: yarn gen-bundle:ios
env:
NODE_OPTIONS: --max_old_space_size=12288

- name: Check bundle size
run: ./scripts/js-bundle-stats.sh ios/main.jsbundle 52
Expand Down Expand Up @@ -557,13 +555,13 @@ jobs:
echo "Android E2E tests failed (result: $ANDROID_RESULT)"
exit 1
fi

IOS_RESULT="${{ needs.e2e-smoke-tests-ios.result }}"
if [[ "$IOS_RESULT" == "failure" ]] || [[ "$IOS_RESULT" == "cancelled" ]]; then
echo "iOS E2E tests failed (result: $IOS_RESULT)"
exit 1
fi

FLASK_RESULT="${{ needs.e2e-smoke-tests-android-flask.result }}"
if [[ "$FLASK_RESULT" == "failure" ]] || [[ "$FLASK_RESULT" == "cancelled" ]]; then
echo "Android Flask E2E tests failed (result: $FLASK_RESULT)"
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/release-pr-approval.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Release PR Approval

on:
pull_request:
pull_request_review:
types: [submitted]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,29 @@ index d1459423fe85502017d2b424f61718ce1ae082b1..7fda7cf056af3e7e8904a0e2aa3391dc
}

if (!pendingPermissions.isEmpty()) {
diff --git a/apple/RNCWebViewImpl.m b/apple/RNCWebViewImpl.m
index c3bb7cc41097a39964b56386dbde321f7d52b35a..ebbb771d6bba6182e03f3c96ae8c9ab6514bd4f9 100644
--- a/apple/RNCWebViewImpl.m
+++ b/apple/RNCWebViewImpl.m
@@ -973,11 +973,20 @@ -(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAct
}

- (void)downloadBase64File:(NSString *)base64String {
+ // Infer file extension from the data: URL before doing any heavy work.
+ // For generic application/octet-stream payloads we suppress the download
+ // entirely (no "Do you want to download File.bin?" alert), since these
+ // are typically iframe-driven noise rather than user‑initiated downloads.
+ NSString *fileExtension = [self fileExtensionFromBase64String:base64String];
+ if ([[fileExtension lowercaseString] isEqualToString:@"bin"]) {
+ NSLog(@"RNCWebViewImpl: Ignoring generic binary download (File.bin) from data: URL");
+ return;
+ }
+
NSArray *components = [base64String componentsSeparatedByString:@","];
NSString *base64ContentPart = components.lastObject;

NSData *fileData = [[NSData alloc] initWithBase64EncodedString:base64ContentPart options:NSDataBase64DecodingIgnoreUnknownCharacters];
- NSString *fileExtension = [self fileExtensionFromBase64String:base64String];
[self showDownloadAlert:fileExtension invokeDownload:^{
NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"File.%@", fileExtension]];
[fileData writeToFile:tempFilePath atomically:YES];
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { Pressable, View } from 'react-native';

import { strings } from '../../../../locales/i18n';
import InfoModal from '../../../components/UI/Swaps/components/InfoModal';
import InfoModal from '../../../components/Base/InfoModal';
import { TOKEN_APPROVAL_SPENDING_CAP } from '../../../constants/urls';
import formatNumber from '../../../util/formatNumber';
import { isNumber } from '../../../util/number';
Expand Down
210 changes: 210 additions & 0 deletions app/components/Base/InfoModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import InfoModal from './InfoModal';

// Mock IonicIcon
jest.mock('react-native-vector-icons/Ionicons', () => 'Icon');

// Mock react-native-modal
jest.mock('react-native-modal', () => {
const MockedModal = ({
isVisible,
children,
}: {
isVisible?: boolean;
children?: React.ReactNode;
[key: string]: unknown;
}) => {
if (!isVisible) return null;
return <>{children}</>;
};
return MockedModal;
});

// Mock Base/Text component
jest.mock('./Text', () => {
/* eslint-disable @typescript-eslint/no-shadow */
const React = jest.requireActual('react');
const { Text: RNText } = jest.requireActual('react-native');
/* eslint-enable @typescript-eslint/no-shadow */
return {
__esModule: true,
default: (props: { children?: React.ReactNode; [key: string]: unknown }) =>
React.createElement(RNText, props, props.children),
};
});

// Mock Base/Title component
jest.mock('./Title', () => {
/* eslint-disable @typescript-eslint/no-shadow */
const React = jest.requireActual('react');
const { Text: RNText } = jest.requireActual('react-native');
/* eslint-enable @typescript-eslint/no-shadow */
return {
__esModule: true,
default: (props: { children?: React.ReactNode; [key: string]: unknown }) =>
React.createElement(RNText, props, props.children),
};
});

// Mock useTheme hook
jest.mock('../../util/theme', () => ({
useTheme: jest.fn(() => ({
colors: {
background: { default: '#FFFFFF' },
text: { default: '#000000' },
overlay: { default: '#00000099' },
},
shadows: {
size: {
sm: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
},
},
})),
}));

describe('InfoModal', () => {
const mockToggleModal = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

describe('Rendering', () => {
it('renders content when visible', () => {
const { queryByText } = render(
<InfoModal
isVisible
title="Test Title"
toggleModal={mockToggleModal}
/>,
);

expect(queryByText('Test Title')).toBeOnTheScreen();
});

it('hides content when not visible', () => {
const { queryByText } = render(
<InfoModal
isVisible={false}
title="Test Title"
toggleModal={mockToggleModal}
/>,
);

expect(queryByText('Test Title')).toBeNull();
});

it('renders title as string', () => {
const { getByText } = render(
<InfoModal
isVisible
title="String Title"
toggleModal={mockToggleModal}
/>,
);

expect(getByText('String Title')).toBeOnTheScreen();
});

it('renders title as ReactNode', () => {
const { getByText } = render(
<InfoModal
isVisible
title={<>ReactNode Title</>}
toggleModal={mockToggleModal}
/>,
);

expect(getByText('ReactNode Title')).toBeOnTheScreen();
});

it('renders body as ReactNode', () => {
const { Text: RNText } = jest.requireActual('react-native');

const { getByText } = render(
<InfoModal
isVisible
body={<RNText>Body Content</RNText>}
toggleModal={mockToggleModal}
/>,
);

expect(getByText('Body Content')).toBeOnTheScreen();
});

it('renders message text', () => {
const { getByText } = render(
<InfoModal
isVisible
message="Test message"
toggleModal={mockToggleModal}
/>,
);

expect(getByText('Test message')).toBeOnTheScreen();
});

it('renders urlText link when provided with message', () => {
const { getByText } = render(
<InfoModal
isVisible
message="Test message"
urlText="Learn more"
toggleModal={mockToggleModal}
/>,
);

expect(getByText('Learn more')).toBeOnTheScreen();
});

it('renders complete modal with all props', () => {
const mockUrlCallback = jest.fn();
const { Text: RNText } = jest.requireActual('react-native');

const { getByText } = render(
<InfoModal
isVisible
title="Full Title"
body={<RNText>Full Body</RNText>}
message="Full Message"
urlText="Full Link"
url={mockUrlCallback}
toggleModal={mockToggleModal}
propagateSwipe
testID="full-modal"
/>,
);

expect(getByText('Full Title')).toBeOnTheScreen();
expect(getByText('Full Body')).toBeOnTheScreen();
expect(getByText('Full Message')).toBeOnTheScreen();
expect(getByText('Full Link')).toBeOnTheScreen();
});
});

describe('Interactions', () => {
it('calls url callback when link is pressed', () => {
const mockUrlCallback = jest.fn();

const { getByText } = render(
<InfoModal
isVisible
message="Test message"
urlText="Click here"
url={mockUrlCallback}
toggleModal={mockToggleModal}
/>,
);

fireEvent.press(getByText('Click here'));

expect(mockUrlCallback).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import { StyleSheet, View, TouchableOpacity, SafeAreaView } from 'react-native';
import Modal from 'react-native-modal';
import IonicIcon from 'react-native-vector-icons/Ionicons';
import Text from '../../../Base/Text';
import Title from '../../../Base/Title';
import { useTheme } from '../../../../util/theme';
import Text from './Text';
import Title from './Title';
import { useTheme } from '../../util/theme';
import { Theme } from '@metamask/design-tokens';

const createStyles = (colors: Theme['colors'], shadows: Theme['shadows']) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { decodeTransferData } from '../../../util/transactions';
import { doENSReverseLookup } from '../../../util/ENSUtils';
import { areAddressesEqual, toFormattedAddress } from '../../../util/address';
import { useTheme } from '../../../util/theme';
import InfoModal from '../Swaps/components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import useExistingAddress from '../../hooks/useExistingAddress';
import { AddressTo } from '../AddressInputs';
import createStyles from './AccountFromToInfoCard.styles';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/EditGasFee1559/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Text from '../../Base/Text';
import StyledButton from '../StyledButton';
import RangeInput from '../../Base/RangeInput';
import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import InfoModal from '../Swaps/components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import Icon from 'react-native-vector-icons/Ionicons';
import { strings } from '../../../../locales/i18n';
import Alert, { AlertType } from '../../Base/Alert';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/EditGasFeeLegacy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Text from '../../Base/Text';
import StyledButton from '../StyledButton';
import RangeInput from '../../Base/RangeInput';
import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import InfoModal from '../Swaps/components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import Icon from 'react-native-vector-icons/Ionicons';
import { strings } from '../../../../locales/i18n';
import Alert, { AlertType } from '../../Base/Alert';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/Swaps/QuotesView.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import Ratio from './components/Ratio';
import ActionAlert from './components/ActionAlert';
import ApprovalTransactionEditionModal from './components/ApprovalTransactionEditionModal';
import GasEditModal from './components/GasEditModal';
import InfoModal from './components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import useModalHandler from '../../Base/hooks/useModalHandler';
import useBalance from './utils/useBalance';
import { decodeApproveData, getTicker } from '../../../util/transactions';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/Swaps/components/AssetSwapButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native';

import Text from '../../../Base/Text';
import AssetActionButton from '../../AssetOverview/AssetActionButton';
import InfoModal from './InfoModal';
import InfoModal from '../../../Base/InfoModal';

import useModalHandler from '../../../Base/hooks/useModalHandler';
import { strings } from '../../../../../locales/i18n';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/Swaps/components/GasEditModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc
import { connect } from 'react-redux';

import Text from '../../../Base/Text';
import InfoModal from './InfoModal';
import InfoModal from '../../../Base/InfoModal';
import EditGasFeeLegacy from '../../EditGasFeeLegacy';
import EditGasFee1559 from '../../EditGasFee1559';
import {
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/Swaps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import TokenSelectModal from './components/TokenSelectModal';
import SlippageModal from './components/SlippageModal';
import useBalance from './utils/useBalance';
import useBlockExplorer from './utils/useBlockExplorer';
import InfoModal from './components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import { AlertType } from '../../Base/Alert';
import { isZero, gte } from '../../../util/lodash';
import { useTheme } from '../../../util/theme';
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/TimeEstimateInfoModal/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { View } from 'react-native';
import Text from '../../Base/Text';
import InfoModal from '../Swaps/components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import PropTypes from 'prop-types';
import { strings } from '../../../../locales/i18n';
import AppConstants from '../../../core/AppConstants';
Expand Down
2 changes: 1 addition & 1 deletion app/components/Views/NetworkSelector/NetworkSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {
// Internal dependencies
import createStyles from './NetworkSelector.styles';
import { InfuraNetworkType } from '@metamask/controller-utils';
import InfoModal from '../../../../app/components/UI/Swaps/components/InfoModal';
import InfoModal from '../../Base/InfoModal';
import hideKeyFromUrl from '../../../util/hideKeyFromUrl';
import CustomNetwork from '../Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork';
import { NetworksSelectorSelectorsIDs } from '../../../../e2e/selectors/Settings/NetworksView.selectors';
Expand Down
Loading
Loading