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
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const QuoteDetailsCard: React.FC = () => {
nativeToken: nativeTokenName,
}),
size: TooltipSizes.Sm,
iconName: IconName.Info,
},
}}
value={{
Expand Down
33 changes: 0 additions & 33 deletions app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2220,39 +2220,6 @@ export function getDeFiProtocolPositionDetailsNavbarOptions(navigation) {
};
}

/**
* Function that returns the navigation options for the Address List screen
*
* @param {Object} navigation - Navigation object required to push new views
* @param {string} title - Title in string format
* @param {string} testID - Test ID for the back button
* @returns {Object} - Corresponding navbar options
*/
export function getAddressListNavbarOptions(navigation, title, testID) {
const innerStyles = StyleSheet.create({
headerLeft: {
marginHorizontal: 8,
},
});
return {
headerTitleAlign: 'center',
headerTitle: () => (
<MorphText variant={TextVariant.BodyMDBold}>{title}</MorphText>
),
headerLeft: () => (
<View style={innerStyles.headerLeft}>
<ButtonIcon
testID={testID}
iconName={IconName.ArrowLeft}
size={ButtonIconSize.Md}
iconProps={{ color: MMDSIconColor.IconDefault }}
onPress={() => navigation.goBack()}
/>
</View>
),
};
}

/**
* Generic navbar with only a close button on the right
* @param {Object} navigation - Navigation object
Expand Down
87 changes: 0 additions & 87 deletions app/components/UI/Navbar/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { fireEvent } from '@testing-library/react-native';
import renderWithProvider from '../../../util/test/renderWithProvider';
import { backgroundState } from '../../../util/test/initial-root-state';
import {
getAddressListNavbarOptions,
getDepositNavbarOptions,
getNetworkNavbarOptions,
getOnboardingNavbarOptions,
Expand Down Expand Up @@ -151,92 +150,6 @@ describe('getNetworkNavbarOptions', () => {
});
});

describe('getAddressListNavbarOptions', () => {
const mockNavigation = {
goBack: jest.fn(),
};

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

it('returns navbar options with correct structure', () => {
const options = getAddressListNavbarOptions(
mockNavigation,
'Receiving address',
'test-back-button',
);

expect(options).toBeDefined();
expect(options.headerTitle).toBeInstanceOf(Function);
expect(options.headerLeft).toBeInstanceOf(Function);
});

it('renders title correctly', () => {
const title = 'Test Title';
const options = getAddressListNavbarOptions(
mockNavigation,
title,
'test-back-button',
);

const { getByText } = renderWithProvider(<options.headerTitle />, {
state: { engine: { backgroundState } },
});

expect(getByText(title)).toBeTruthy();
});

it('calls navigation.goBack when back button is pressed', () => {
const options = getAddressListNavbarOptions(
mockNavigation,
'Test Title',
'test-back-button',
);

const { getByTestId } = renderWithProvider(<options.headerLeft />, {
state: { engine: { backgroundState } },
});

const backButton = getByTestId('test-back-button');
fireEvent.press(backButton);

expect(mockNavigation.goBack).toHaveBeenCalledTimes(1);
});

it('handles different titles', () => {
const titles = ['Receiving address', 'Account details', ''];

titles.forEach((title) => {
expect(() => {
const options = getAddressListNavbarOptions(
mockNavigation,
title,
'test-back-button',
);
expect(options).toBeDefined();
expect(options.headerTitle).toBeInstanceOf(Function);
}).not.toThrow();
});
});

it('handles different test IDs', () => {
const testIds = ['back-button', 'go-back', 'navigation-back'];

testIds.forEach((testId) => {
expect(() => {
const options = getAddressListNavbarOptions(
mockNavigation,
'Test Title',
testId,
);
expect(options).toBeDefined();
expect(options.headerLeft).toBeInstanceOf(Function);
}).not.toThrow();
});
});
});

describe('getDepositNavbarOptions', () => {
const mockNavigation = {
pop: jest.fn(),
Expand Down
10 changes: 9 additions & 1 deletion app/components/UI/Ramp/Deposit/Views/OtpCode/OtpCode.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,15 @@ describe('OtpCode Screen', () => {
mockSendUserOtp.mockRejectedValue(new Error('Failed to resend'));
render(OtpCode);
const resendButton = screen.getByText('Resend it');
fireEvent.press(resendButton);

await act(async () => {
fireEvent.press(resendButton);
});

await waitFor(() => {
expect(screen.getByText('Error resending code.')).toBeOnTheScreen();
});

expect(screen.toJSON()).toMatchSnapshot();
});

Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/Ramp/Deposit/Views/OtpCode/OtpCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ const OtpCode = () => {
{resendButtonState === 'resendError' ? (
<ResendButton
onPress={handleContactSupport}
text="deposit.otp_code.resend_error"
text="deposit.otp_code.resend_code_error"
button="deposit.otp_code.contact_support"
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4329,8 +4329,28 @@ exports[`OtpCode Screen renders resend error snapshot when resend fails 1`] = `
}
}
>
Resend code in 30 seconds
Error resending code.
</Text>
<TouchableOpacity
onPress={[Function]}
>
<Text
accessibilityRole="text"
style={
{
"color": "#686e7d",
"fontFamily": "Geist-Regular",
"fontSize": 16,
"letterSpacing": 0,
"lineHeight": 24,
"marginLeft": 4,
"textDecorationLine": "underline",
}
}
>
Contact support
</Text>
</TouchableOpacity>
</View>
</View>
</View>
Expand Down
12 changes: 12 additions & 0 deletions app/components/UI/Transactions/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,17 @@ describe('filterDuplicateOutgoingTransactions', () => {
expect(result).toHaveLength(1);
expect(result[0].id).toBe('3');
});

it("preserves transactions when hash is placeholder '0x0'", () => {
const PLACEHOLDER_HASH = '0x0';
const transactions = [
createTransaction('1', 'musdConversion', PLACEHOLDER_HASH, '100'),
createTransaction('2', 'musdConversion', PLACEHOLDER_HASH, '100'),
];

const result = filterDuplicateOutgoingTransactions(transactions);

expect(result).toEqual(transactions);
});
});
});
4 changes: 3 additions & 1 deletion app/components/UI/Transactions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export const filterDuplicateOutgoingTransactions = (
}

return transactions.filter((currentTx, currentIndex) => {
if (!currentTx.hash) {
// Treat placeholder hash ('0x0') as "no hash" to avoid deduping unrelated tx metas
// (e.g., MetaMask Pay intent/wrapper transactions that never receive a real tx hash).
if (!currentTx.hash || currentTx.hash === '0x0') {
return true; // Keep transactions without a hash
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,10 @@ describe('AddressList', () => {
const { getAllByText, getByText } = renderWithAddressList();

// The title is set in navigation options, not rendered in the component
expect(mockSetOptions).toHaveBeenCalledWith(
expect.objectContaining({
headerShown: true,
headerTitleAlign: 'center',
headerTitle: expect.any(Function),
headerLeft: expect.any(Function),
}),
);
expect(mockSetOptions).toHaveBeenCalledWith({
header: expect.any(Function),
headerShown: true,
});

expect(getAllByText(shortenedEthAddress).length).toBe(3);
expect(getByText('Ethereum')).toBeDefined();
Expand All @@ -173,10 +169,8 @@ describe('AddressList', () => {
renderWithAddressList();

expect(mockSetOptions).toHaveBeenCalledWith({
header: expect.any(Function),
headerShown: true,
headerTitleAlign: 'center',
headerTitle: expect.any(Function),
headerLeft: expect.any(Function),
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import styleSheet from './styles';
import type { AddressListProps, AddressItem } from './types';
import ClipboardManager from '../../../../core/ClipboardManager';
import { strings } from '../../../../../locales/i18n';
import { getAddressListNavbarOptions } from '../../../UI/Navbar';
import getHeaderCenterNavbarOptions from '../../../../component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions';

export const createAddressListNavigationDetails =
createNavigationDetails<AddressListProps>(
Expand Down Expand Up @@ -89,11 +89,12 @@ export const AddressList = () => {
useLayoutEffect(() => {
if (title) {
navigation.setOptions({
...getAddressListNavbarOptions(
navigation,
...getHeaderCenterNavbarOptions({
title,
AddressListIds.GO_BACK,
),
onBack: () => navigation.goBack(),
backButtonProps: { testID: AddressListIds.GO_BACK },
includesTopInset: true,
}),
headerShown: true,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { TouchableOpacity } from 'react-native';
import ShareAddressQR from '.';
import {
createMockSnapInternalAccount,
Expand Down Expand Up @@ -302,19 +301,11 @@ describe('ShareAddressQR', () => {

it('navigates back when back button is pressed', () => {
// Arrange
const rendered = render();
const { root } = rendered;
const touchableOpacities = root.findAllByType(TouchableOpacity);
const backButton = touchableOpacities.find(
(touchable) =>
touchable.props.accessible === true && touchable.props.onPress,
);
const { getByTestId } = render();
const backButton = getByTestId('share-address-qr-go-back');

// Act
expect(backButton).toBeTruthy();
if (backButton) {
fireEvent.press(backButton);
}
fireEvent.press(backButton);

// Assert
expect(mockGoBack).toHaveBeenCalledTimes(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AccountGroupId } from '@metamask/account-api';
import BottomSheet, {
BottomSheetRef,
} from '../../../../../component-library/components/BottomSheets/BottomSheet';
import BottomSheetHeader from '../../../../../component-library/components/BottomSheets/BottomSheetHeader';
import HeaderCenter from '../../../../../component-library/components-temp/HeaderCenter';
import { strings } from '../../../../../../locales/i18n';
import {
ParamListBase,
Expand Down Expand Up @@ -71,9 +71,11 @@ export const ShareAddressQR = () => {

return (
<BottomSheet ref={sheetRef}>
<BottomSheetHeader onBack={handleOnBack}>
{`${accountGroupName} / ${networkName}`}
</BottomSheetHeader>
<HeaderCenter
title={`${accountGroupName} / ${networkName}`}
onBack={handleOnBack}
backButtonProps={{ testID: ShareAddressQRIds.GO_BACK }}
/>
<Box
flexDirection={BoxFlexDirection.Column}
alignItems={BoxAlignItems.Center}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from '@metamask/transaction-controller';
import React, { useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
import { useSelector } from 'react-redux';
import { ConfirmationRowComponentIDs } from '../../../../../../../../e2e/selectors/Confirmation/ConfirmationView.selectors';
import { strings } from '../../../../../../../../locales/i18n';
import Icon, {
Expand Down Expand Up @@ -37,6 +38,9 @@ import InfoSection from '../../../UI/info-row/info-section';
import { Skeleton } from '../../../../../../../component-library/components/Skeleton';
import styleSheet from './gas-fee-details-row.styles';
import { IconColor } from '../../../../../../../component-library/components/Icons/Icon/Icon.types';
import { selectNetworkConfigurationByChainId } from '../../../../../../../selectors/networkController';
import type { RootState } from '../../../../../../../reducers';
import useNetworkInfo from '../../../../hooks/useNetworkInfo';

const PaidByMetaMask = () => (
<Text variant={TextVariant.BodyMD} testID="paid-by-metamask">
Expand Down Expand Up @@ -255,14 +259,30 @@ const GasFeesDetailsRow = ({
});
};

const networkConfiguration = useSelector((state: RootState) =>
selectNetworkConfigurationByChainId(state, transactionMetadata?.chainId),
);
const { nativeCurrency } = networkConfiguration ?? {};

const { networkNativeCurrency } = useNetworkInfo(
transactionMetadata?.chainId,
);

// Fallback chain: networkConfiguration.nativeCurrency -> networkNativeCurrency -> empty string
const nativeTokenSymbol = nativeCurrency ?? networkNativeCurrency ?? '';

const showGasFeeTokenInfo =
gasFeeToken?.metaMaskFee && gasFeeToken?.metaMaskFee !== '0x0';

const confirmGasFeeTokenTooltip = showGasFeeTokenInfo
? strings('transactions.confirm_gas_fee_token_tooltip', {
metamaskFeeFiat,
const confirmGasFeeTokenTooltip = isGasFeeSponsored
? strings('bridge.network_fee_info_content_sponsored', {
nativeToken: nativeTokenSymbol,
})
: strings('transactions.network_fee_tooltip');
: showGasFeeTokenInfo
? strings('transactions.confirm_gas_fee_token_tooltip', {
metamaskFeeFiat,
})
: strings('transactions.network_fee_tooltip');

const Container = noSection ? View : InfoSection;

Expand Down
Loading
Loading