Skip to content
Open
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
69 changes: 55 additions & 14 deletions app/components/Views/ActivityList/ActivityList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }) => (
<View testID={`bridge-${transaction.id}`}>
<Text>Bridge tx</Text>
</View>
),
};
});

jest.mock('../../UI/Transactions/TransactionsFooter', () => {
const { Text, TouchableOpacity } = jest.requireActual('react-native');
return {
Expand Down Expand Up @@ -382,14 +370,25 @@ 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 },
},
},
})),
}));

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', () => ({
Expand Down Expand Up @@ -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 = {
Expand All @@ -1732,11 +1731,53 @@ describe('ActivityList', () => {

render(<ActivityList chainId="solana:mainnet" />);

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(<ActivityList header={<></>} />);

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(<ActivityList header={<></>} />);

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,
}),
);
});
});
60 changes: 25 additions & 35 deletions app/components/Views/ActivityList/ActivityList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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.
Expand All @@ -190,15 +188,7 @@ export interface ActivityListHandle {

const ActivityList = forwardRef<ActivityListHandle, ActivityListProps>(
(
{
header,
chainId,
location,
scrollY,
typeFilter,
networkFilter,
subFilterKinds,
},
{ header, chainId, scrollY, typeFilter, networkFilter, subFilterKinds },
ref,
) => {
const navigation = useNavigation();
Expand Down Expand Up @@ -818,6 +808,27 @@ const ActivityList = forwardRef<ActivityListHandle, ActivityListProps>(
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) {
Expand Down Expand Up @@ -1219,30 +1230,9 @@ const ActivityList = forwardRef<ActivityListHandle, ActivityListProps>(
}

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 (
<MultichainBridgeTransactionListItem
transaction={raw.data}
bridgeHistoryItem={bridgeHistoryItem}
navigation={navigation}
index={index}
location={location}
showDestinationPerspective={
!configuredNonEVMChainIds.includes(raw.data.chain)
}
/>
);
}
}

// 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
Expand Down
Loading