Skip to content

Commit 749a9cf

Browse files
committed
Merge branch 'main' into test/temp-nightly
2 parents be8e384 + ed0638c commit 749a9cf

15 files changed

Lines changed: 869 additions & 38 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ jobs:
115115
build_name: ${{ inputs.build_name }}
116116
use-tarball: true
117117
upload-artifact: true
118-
artifact-name: node-modules-${{ matrix.platform }}
118+
artifact-name: node-modules-${{ inputs.build_name }}-${{ matrix.platform }}
119119
artifact-retention-days: 1
120120

121121
# Build
@@ -165,7 +165,7 @@ jobs:
165165
- name: Download node_modules tarball
166166
uses: actions/download-artifact@v4
167167
with:
168-
name: node-modules-${{ matrix.platform }}
168+
name: node-modules-${{ inputs.build_name }}-${{ matrix.platform }}
169169

170170
- name: Extract tarball (preserves symlinks)
171171
run: |

app/components/Views/confirmations/components/UI/nft/nft.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ export function Nft({ asset, onPress }: NftProps) {
2929
onPress(asset);
3030
}, [asset, onPress]);
3131

32+
const testID = `nft-${asset.name || asset.collectionName || 'NFT'}-${asset.tokenId}`;
33+
3234
return (
3335
<Pressable
36+
testID={testID}
3437
style={({ pressed }) =>
3538
tw.style(
3639
'w-full flex-row items-center justify-between py-2',

app/components/Views/confirmations/components/UI/recipient/recipient.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ export function Recipient({
8989
accessibilityRole="button"
9090
>
9191
<Box twClassName="flex-row items-center">
92-
<Box twClassName="h-12 justify-center">
92+
<Box
93+
twClassName="h-12 justify-center"
94+
testID={`recipient-avatar-${recipient.address}`}
95+
>
9396
<Avatar
9497
variant={AvatarVariant.Account}
9598
type={accountAvatarType}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from 'react';
2+
import { fireEvent } from '@testing-library/react-native';
3+
import { TransactionMeta } from '@metamask/transaction-controller';
4+
import { Hex } from '@metamask/utils';
5+
import renderWithProvider from '../../../../../../util/test/renderWithProvider';
6+
import { strings } from '../../../../../../../locales/i18n';
7+
import { useMultichainBlockExplorerTxUrl } from '../../../../../UI/Bridge/hooks/useMultichainBlockExplorerTxUrl';
8+
import Routes from '../../../../../../constants/navigation/Routes';
9+
import { useNetworkName } from '../../../hooks/useNetworkName';
10+
import { useTokenWithBalance } from '../../../hooks/tokens/useTokenWithBalance';
11+
import { selectBridgeHistoryForAccount } from '../../../../../../selectors/bridgeStatusController';
12+
import { useBridgeTxHistoryData } from '../../../../../../util/bridge/hooks/useBridgeTxHistoryData';
13+
import { useTokenAmount } from '../../../hooks/useTokenAmount';
14+
import { useTransactionDetails } from '../../../hooks/activity/useTransactionDetails';
15+
import { SourceHashSummaryLine } from './source-hash-summary-line';
16+
17+
const mockNavigate = jest.fn();
18+
19+
jest.mock('../../../../../UI/Bridge/hooks/useMultichainBlockExplorerTxUrl');
20+
jest.mock('../../../hooks/useNetworkName');
21+
jest.mock('../../../hooks/tokens/useTokenWithBalance');
22+
jest.mock('../../../../../../selectors/bridgeStatusController');
23+
jest.mock('../../../../../../util/bridge/hooks/useBridgeTxHistoryData');
24+
jest.mock('../../../hooks/useTokenAmount');
25+
jest.mock('../../../hooks/activity/useTransactionDetails');
26+
27+
jest.mock('@react-navigation/native', () => ({
28+
...jest.requireActual('@react-navigation/native'),
29+
useNavigation: () => ({
30+
navigate: mockNavigate,
31+
}),
32+
}));
33+
34+
function render() {
35+
return renderWithProvider(
36+
<SourceHashSummaryLine
37+
parentTransaction={
38+
{
39+
id: 'parent-id',
40+
chainId: '0x1',
41+
submittedTime: 1755719285723,
42+
metamaskPay: {
43+
tokenAddress: '0x123',
44+
chainId: '0x1',
45+
},
46+
} as unknown as TransactionMeta
47+
}
48+
sourceHash={'0xabc' as Hex}
49+
/>,
50+
{
51+
state: {
52+
engine: {
53+
backgroundState: {
54+
TransactionController: { transactions: [] },
55+
},
56+
},
57+
},
58+
},
59+
);
60+
}
61+
62+
describe('SourceHashSummaryLine', () => {
63+
const useMultichainBlockExplorerTxUrlMock = jest.mocked(
64+
useMultichainBlockExplorerTxUrl,
65+
);
66+
const useNetworkNameMock = jest.mocked(useNetworkName);
67+
const useTokenWithBalanceMock = jest.mocked(useTokenWithBalance);
68+
69+
beforeEach(() => {
70+
jest.resetAllMocks();
71+
72+
useMultichainBlockExplorerTxUrlMock.mockReturnValue({
73+
explorerTxUrl: 'https://explorer.example',
74+
explorerName: 'Explorer',
75+
} as ReturnType<typeof useMultichainBlockExplorerTxUrl>);
76+
77+
useNetworkNameMock.mockReturnValue('Ethereum');
78+
useTokenWithBalanceMock.mockReturnValue({ symbol: 'USDC' } as ReturnType<
79+
typeof useTokenWithBalance
80+
>);
81+
82+
jest.mocked(selectBridgeHistoryForAccount).mockReturnValue({});
83+
jest.mocked(useBridgeTxHistoryData).mockReturnValue({
84+
bridgeTxHistoryItem: undefined,
85+
isBridgeComplete: null,
86+
});
87+
jest
88+
.mocked(useTokenAmount)
89+
.mockReturnValue({} as ReturnType<typeof useTokenAmount>);
90+
jest.mocked(useTransactionDetails).mockReturnValue({
91+
transactionMeta: {} as TransactionMeta,
92+
});
93+
});
94+
95+
it('renders send title', () => {
96+
const { getByText } = render();
97+
98+
expect(
99+
getByText(
100+
strings('transaction_details.summary_title.bridge_send', {
101+
sourceSymbol: 'USDC',
102+
sourceChain: 'Ethereum',
103+
}),
104+
),
105+
).toBeDefined();
106+
});
107+
108+
it('navigates to block explorer when button is pressed', () => {
109+
const { getByTestId } = render();
110+
111+
fireEvent.press(getByTestId('block-explorer-button'));
112+
113+
expect(mockNavigate).toHaveBeenCalledWith(Routes.WEBVIEW.MAIN, {
114+
screen: Routes.WEBVIEW.SIMPLE,
115+
params: {
116+
url: 'https://explorer.example',
117+
title: 'Explorer',
118+
},
119+
});
120+
});
121+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { TransactionMeta } from '@metamask/transaction-controller';
3+
import { Hex } from '@metamask/utils';
4+
import { strings } from '../../../../../../../locales/i18n';
5+
import { useNetworkName } from '../../../hooks/useNetworkName';
6+
import { useTokenWithBalance } from '../../../hooks/tokens/useTokenWithBalance';
7+
import { TransactionSummaryLine } from './transaction-summary-line';
8+
9+
export function SourceHashSummaryLine({
10+
parentTransaction,
11+
sourceHash,
12+
}: {
13+
parentTransaction: TransactionMeta;
14+
sourceHash: Hex;
15+
}) {
16+
const tokenAddress = parentTransaction.metamaskPay?.tokenAddress;
17+
const tokenChainId = parentTransaction.metamaskPay?.chainId;
18+
19+
const sourceToken = useTokenWithBalance(
20+
tokenAddress ?? '0x0',
21+
tokenChainId ?? '0x0',
22+
);
23+
24+
const sourceNetworkName = useNetworkName(tokenChainId);
25+
const chainId = tokenChainId ?? parentTransaction.chainId;
26+
27+
const title =
28+
sourceToken?.symbol && sourceNetworkName
29+
? strings('transaction_details.summary_title.bridge_send', {
30+
sourceSymbol: sourceToken.symbol,
31+
sourceChain: sourceNetworkName,
32+
})
33+
: strings('transaction_details.summary_title.bridge_send_loading');
34+
35+
return (
36+
<TransactionSummaryLine
37+
title={title}
38+
transactionMeta={parentTransaction}
39+
chainId={chainId}
40+
txHash={sourceHash}
41+
/>
42+
);
43+
}

app/components/Views/confirmations/components/activity/transaction-details-summary/transaction-details-summary.test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ jest.mock('./default-summary-line', () => ({
4343
},
4444
}));
4545

46+
jest.mock('./source-hash-summary-line', () => ({
47+
SourceHashSummaryLine: () => {
48+
const ReactNative = require('react-native');
49+
50+
return <ReactNative.Text>SourceHashSummaryLine</ReactNative.Text>;
51+
},
52+
}));
53+
4654
function render({
4755
transactions,
4856
}: {
@@ -191,6 +199,68 @@ describe('TransactionDetailsSummary', () => {
191199
expect(getAllByText('DefaultSummaryLine')).toHaveLength(2);
192200
});
193201

202+
it('renders SourceHashSummaryLine when sourceHash exists and no deposit transactions', () => {
203+
useTransactionDetailsMock.mockReturnValue({
204+
transactionMeta: {
205+
id: transactionIdMock,
206+
chainId: '0x1',
207+
type: TransactionType.perpsDeposit,
208+
metamaskPay: {
209+
sourceHash: '0xabc',
210+
tokenAddress: '0x123',
211+
chainId: '0x1',
212+
},
213+
} as unknown as TransactionMeta,
214+
});
215+
216+
const { getByText } = render({
217+
transactions: [
218+
{
219+
id: transactionIdMock,
220+
chainId: '0x1',
221+
type: TransactionType.perpsDeposit,
222+
},
223+
],
224+
});
225+
226+
expect(getByText('SourceHashSummaryLine')).toBeDefined();
227+
});
228+
229+
it('does not render SourceHashSummaryLine when deposit transactions exist', () => {
230+
const depositId = 'deposit-id';
231+
232+
useTransactionDetailsMock.mockReturnValue({
233+
transactionMeta: {
234+
id: transactionIdMock,
235+
chainId: '0x1',
236+
type: TransactionType.perpsDeposit,
237+
requiredTransactionIds: [depositId],
238+
metamaskPay: {
239+
sourceHash: '0xabc',
240+
tokenAddress: '0x123',
241+
chainId: '0x1',
242+
},
243+
} as unknown as TransactionMeta,
244+
});
245+
246+
const { queryByText } = render({
247+
transactions: [
248+
{
249+
id: depositId,
250+
chainId: '0x1',
251+
type: TransactionType.relayDeposit,
252+
},
253+
{
254+
id: transactionIdMock,
255+
chainId: '0x1',
256+
type: TransactionType.perpsDeposit,
257+
},
258+
],
259+
});
260+
261+
expect(queryByText('SourceHashSummaryLine')).toBeNull();
262+
});
263+
194264
it('skips non-relay child transactions for mUSD conversion parent', () => {
195265
const sendId = 'send-id';
196266
const receiveId = 'receive-id';

app/components/Views/confirmations/components/activity/transaction-details-summary/transaction-details-summary.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { hasTransactionType } from '../../../utils/transaction';
1919
import { RELAY_DEPOSIT_TYPES } from '../../../constants/confirmations';
2020
import { ProgressList } from '../../progress-list';
21+
import { SourceHashSummaryLine } from './source-hash-summary-line';
2122
import { DepositSummaryLine } from './deposit-summary-line';
2223
import { ApprovalSummaryLine } from './approval-summary-line';
2324
import { ReceiveSummaryLine } from './receive-summary-line';
@@ -28,6 +29,7 @@ export function TransactionDetailsSummary() {
2829
const {
2930
batchId,
3031
id: transactionId,
32+
metamaskPay,
3133
requiredTransactionIds,
3234
} = transactionMeta;
3335

@@ -62,10 +64,21 @@ export function TransactionDetailsSummary() {
6264
transaction.id === transactionId,
6365
);
6466

67+
const hasDepositTransactions =
68+
(requiredTransactionIds?.length ?? 0) > 0 || batchTransactionIds.length > 0;
69+
70+
const { sourceHash } = metamaskPay ?? {};
71+
6572
return (
6673
<Box gap={12}>
6774
<Text color={TextColor.Alternative}>Summary</Text>
6875
<ProgressList>
76+
{!hasDepositTransactions && sourceHash ? (
77+
<SourceHashSummaryLine
78+
parentTransaction={transactionMeta}
79+
sourceHash={sourceHash}
80+
/>
81+
) : null}
6982
{transactions.map((transaction) => (
7083
<SummaryLine
7184
key={transaction.id}

0 commit comments

Comments
 (0)