From bab7242c131c3ede8b9a69aeeed27ea6d994cf20 Mon Sep 17 00:00:00 2001 From: vinnyhoward Date: Wed, 15 Jul 2026 12:28:22 -0600 Subject: [PATCH] fix: render non-EVM swap/bridge rows with the redesigned Activity row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-EVM (e.g. Solana) swaps and bridges submitted from the device carry a local bridge-history entry, which routed them to the legacy MultichainBridgeTransactionListItem — larger typography, per-row status and timestamp — while identical remote-history rows used the redesigned compact ActivityListItemRow. Remove the legacy branch so all rows render through ActivityListItemRow, and move the tap routing into the shared press handler: cross-chain bridges keep their dedicated bridge-status screen (mirroring local EVM bridges), same-chain swaps fall through to the shared detail flows. --- .../Views/ActivityList/ActivityList.test.tsx | 69 +++++++++++++++---- .../Views/ActivityList/ActivityList.tsx | 60 +++++++--------- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/app/components/Views/ActivityList/ActivityList.test.tsx b/app/components/Views/ActivityList/ActivityList.test.tsx index d05d9d19ce3..f162b6db203 100644 --- a/app/components/Views/ActivityList/ActivityList.test.tsx +++ b/app/components/Views/ActivityList/ActivityList.test.tsx @@ -326,18 +326,6 @@ jest.mock('../../UI/ActivityListItemRow/ActivityListItemRow', () => ({ resolveActivityListItemTitle: jest.fn(() => 'Activity title'), })); -jest.mock('../../UI/MultichainBridgeTransactionListItem', () => { - const { Text, View } = jest.requireActual('react-native'); - return { - __esModule: true, - default: ({ transaction }: { transaction: { id: string } }) => ( - - Bridge tx - - ), - }; -}); - jest.mock('../../UI/Transactions/TransactionsFooter', () => { const { Text, TouchableOpacity } = jest.requireActual('react-native'); return { @@ -382,7 +370,13 @@ jest.mock('../../UI/Bridge/hooks/useBridgeHistoryItemBySrcTxHash', () => ({ useBridgeHistoryItemBySrcTxHash: jest.fn(() => ({ bridgeHistoryItemsBySrcTxHash: { '0xconfirmed': { title: 'bridge-history' }, + // Same-chain swap (no quote → not a cross-chain bridge). solanaBridge: { title: 'solana-bridge' }, + // Cross-chain bridge (src and dest chains differ). + solanaCross: { + title: 'solana-cross-bridge', + quote: { srcChainId: 'solana:mainnet', destChainId: 1 }, + }, }, })), })); @@ -390,6 +384,11 @@ jest.mock('../../UI/Bridge/hooks/useBridgeHistoryItemBySrcTxHash', () => ({ jest.mock('../../UI/Bridge/utils/transaction-history', () => ({ getSwapBridgeTxActivityTitle: jest.fn(() => 'Bridge title'), handleUnifiedSwapsTxHistoryItemClick: jest.fn(), + isBridgeTxHistoryItemBridge: jest.fn( + (item: { quote?: { srcChainId?: unknown; destChainId?: unknown } }) => + item?.quote !== undefined && + item.quote.srcChainId !== item.quote.destChainId, + ), })); jest.mock('../../../util/multichain/multichainTransactionTokenScan', () => ({ @@ -1711,7 +1710,7 @@ describe('ActivityList', () => { }); }); - it('renders non-EVM bridge rows and footer when only non-EVM chains are enabled', () => { + it('renders non-EVM swap/bridge rows through ActivityListItemRow with the bridge-history title', () => { selectorValues.enabledEvm = []; selectorValues.enabledNonEvm = ['solana:mainnet']; selectorValues.nonEvmState = { @@ -1732,11 +1731,53 @@ describe('ActivityList', () => { render(); - expect(screen.getByTestId('bridge-solanaBridge')).toBeOnTheScreen(); + expect(screen.getByTestId('row-solanaBridge')).toBeOnTheScreen(); + expect(screen.getByText('Bridge title')).toBeOnTheScreen(); fireEvent.press(screen.getByTestId('non-evm-footer')); expect(mockNavigate).toHaveBeenCalledWith('Webview', { params: { url: 'https://solana.explorer/address/sol' }, screen: 'SimpleWebview', }); }); + + it('routes non-EVM cross-chain bridge taps to the unified swaps detail screen', () => { + selectorValues.enabledNonEvm = ['solana:mainnet']; + selectorValues.nonEvmState = { + transactions: [{ chain: 'solana:mainnet', id: 'solanaCross' }], + }; + + render(} />); + + fireEvent.press(screen.getByTestId('row-solanaCross')); + + expect(handleUnifiedSwapsTxHistoryItemClick).toHaveBeenCalledWith({ + navigation: expect.any(Object), + multiChainTx: { chain: 'solana:mainnet', id: 'solanaCross' }, + bridgeTxHistoryItem: expect.objectContaining({ + title: 'solana-cross-bridge', + }), + }); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it('opens the multichain details sheet for non-EVM same-chain swaps with bridge history', () => { + selectorValues.enabledNonEvm = ['solana:mainnet']; + selectorValues.nonEvmState = { + transactions: [ + { chain: 'solana:mainnet', id: 'solanaBridge', from: [], to: [] }, + ], + }; + + render(} />); + + fireEvent.press(screen.getByTestId('row-solanaBridge')); + + expect(handleUnifiedSwapsTxHistoryItemClick).not.toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith( + Routes.MODAL.ROOT_MODAL_FLOW, + expect.objectContaining({ + screen: Routes.SHEET.MULTICHAIN_TRANSACTION_DETAILS, + }), + ); + }); }); diff --git a/app/components/Views/ActivityList/ActivityList.tsx b/app/components/Views/ActivityList/ActivityList.tsx index 57adf4be0c6..d18540de37e 100644 --- a/app/components/Views/ActivityList/ActivityList.tsx +++ b/app/components/Views/ActivityList/ActivityList.tsx @@ -72,8 +72,8 @@ import { useBridgeHistoryItemBySrcTxHash } from '../../UI/Bridge/hooks/useBridge import { getSwapBridgeTxActivityTitle, handleUnifiedSwapsTxHistoryItemClick, + isBridgeTxHistoryItemBridge, } from '../../UI/Bridge/utils/transaction-history'; -import MultichainBridgeTransactionListItem from '../../UI/MultichainBridgeTransactionListItem'; import TransactionsFooter from '../../UI/Transactions/TransactionsFooter'; // eslint-disable-next-line import-x/no-restricted-paths -- TODO(ADR-0020): route-isolation backlog import MultichainTransactionsFooter from '../MultichainTransactionsView/MultichainTransactionsFooter'; @@ -82,7 +82,6 @@ import { getAddressUrl } from '../../../core/Multichain/utils'; import { CancelSpeedupModal } from '../confirmations/components/modals/cancel-speedup-modal'; import styleSheet from './ActivityList.styles'; import { useUnifiedTxActions } from './useUnifiedTxActions'; -import { TransactionDetailLocation } from '../../../core/Analytics/events/transactions'; import { useTransactionAutoScroll } from './useTransactionAutoScroll'; import useBlockExplorer from '../../hooks/useBlockExplorer'; import { selectBridgeHistoryForAccount } from '../../../selectors/bridgeStatusController'; @@ -172,7 +171,6 @@ interface ActivityListProps { header?: React.ReactElement; tabLabel?: string; chainId?: string; // used by non-EVM list items for explorer links - location?: TransactionDetailLocation; /** * Shared value updated on the UI thread with the list's vertical scroll * offset, for driving scroll-linked animations in the parent. @@ -190,15 +188,7 @@ export interface ActivityListHandle { const ActivityList = forwardRef( ( - { - header, - chainId, - location, - scrollY, - typeFilter, - networkFilter, - subFilterKinds, - }, + { header, chainId, scrollY, typeFilter, networkFilter, subFilterKinds }, ref, ) => { const navigation = useNavigation(); @@ -818,6 +808,27 @@ const ActivityList = forwardRef( const { raw } = item; if (!raw) return; + // Non-EVM swaps/bridges submitted from this device carry a + // bridge-history entry. Cross-chain bridges keep their dedicated + // bridge-status screen, mirroring hasDedicatedDetailScreen for local + // EVM bridges; same-chain swaps fall through to the shared detail flows. + if (raw.type === 'keyringTransaction') { + const keyringBridgeHistoryItem = getBridgeHistoryItemByHash( + item.hash, + ); + if ( + keyringBridgeHistoryItem && + isBridgeTxHistoryItemBridge(keyringBridgeHistoryItem) + ) { + handleUnifiedSwapsTxHistoryItemClick({ + navigation, + multiChainTx: raw.data, + bridgeTxHistoryItem: keyringBridgeHistoryItem, + }); + return; + } + } + if (isTransactionsRedesignEnabled) { const detailsRoute = getActivityDetailsRoute(item); if (detailsRoute) { @@ -1219,30 +1230,9 @@ const ActivityList = forwardRef( } const { item } = groupedItem; - const raw = item.raw; - - // Non-EVM bridge transactions: route to MultichainBridgeTransactionListItem. - if (raw?.type === 'keyringTransaction') { - const srcTxHash = raw.data.id; - const bridgeHistoryItem = getBridgeHistoryItemByHash(srcTxHash); - if (bridgeHistoryItem) { - return ( - - ); - } - } - // All other items (API EVM confirmed, completed local EVM, non-EVM non-bridge): - // render from the shared ActivityListItem shape. + // All items (API EVM confirmed, completed local EVM, non-EVM) render from + // the shared ActivityListItem shape. // // Preserve the legacy Activity title for swap/bridge rows (e.g. // "Swap ETH to USDC", "Bridge to Optimism") by deriving it from bridge