Skip to content

Commit 1bf8da2

Browse files
committed
update
1 parent 71858eb commit 1bf8da2

7 files changed

Lines changed: 292 additions & 10 deletions

File tree

app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export interface CustomAmountInfoProps {
8383
* When true, the confirm/continue button is disabled regardless of alert state.
8484
*/
8585
disableConfirm?: boolean;
86+
/**
87+
* When true, the account selector is shown.
88+
*/
89+
supportAccountSelection?: boolean;
8690
}
8791

8892
export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
@@ -96,6 +100,7 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
96100
hidePayTokenAmount,
97101
preferredToken,
98102
footerText,
103+
supportAccountSelection,
99104
}) => {
100105
useClearConfirmationOnBackSwipe();
101106

@@ -195,7 +200,7 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
195200
</Box>
196201
<Box gap={16}>
197202
<AlertMessage alertMessage={alertMessage} />
198-
{!hidePayTokenAmount && (
203+
{supportAccountSelection && (
199204
<PayAccountSelector
200205
selectedAccount={selectedAccount}
201206
onSelectedAccountChange={setSelectedAccount}
@@ -248,7 +253,9 @@ export const CustomAmountInfo: React.FC<CustomAmountInfoProps> = memo(
248253
{!isKeyboardVisible && (
249254
<ConfirmButton
250255
alertTitle={alertTitle}
251-
disableConfirm={disableConfirm}
256+
disableConfirm={
257+
disableConfirm || (supportAccountSelection && !selectedAccount)
258+
}
252259
/>
253260
)}
254261
</Box>

app/components/Views/confirmations/components/info/money-account-deposit-info/money-account-deposit-info.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ export const MONEY_ACCOUNT_CURRENCY = 'usd';
88
export function MoneyAccountDepositInfo() {
99
useNavbar(strings('confirm.title.money_account_deposit'));
1010

11-
return <CustomAmountInfo currency={MONEY_ACCOUNT_CURRENCY} />;
11+
return (
12+
<CustomAmountInfo
13+
currency={MONEY_ACCOUNT_CURRENCY}
14+
supportAccountSelection
15+
/>
16+
);
1217
}

app/components/Views/confirmations/components/info/money-account-withdraw-info/money-account-withdraw-info.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function MoneyAccountWithdrawInfo() {
2828
address: MUSD_TOKEN_ADDRESS,
2929
chainId: CHAIN_IDS.MAINNET,
3030
}}
31+
supportAccountSelection
3132
>
3233
<MoneyAccountWithdrawBalance />
3334
</CustomAmountInfo>
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { renderHook, act } from '@testing-library/react-native';
2+
import { TransactionType } from '@metamask/transaction-controller';
3+
4+
import { useMoneyAccountPayToken } from './useMoneyAccountPayToken';
5+
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
6+
import { useTransactionPayToken } from './useTransactionPayToken';
7+
import { useAccountTokens } from '../send/useAccountTokens';
8+
import { hasTransactionType } from '../../utils/transaction';
9+
10+
jest.mock('../transactions/useTransactionMetadataRequest');
11+
jest.mock('./useTransactionPayToken');
12+
jest.mock('../send/useAccountTokens');
13+
jest.mock('../../utils/transaction');
14+
15+
const useTransactionMetadataRequestMock = jest.mocked(
16+
useTransactionMetadataRequest,
17+
);
18+
const useTransactionPayTokenMock = jest.mocked(useTransactionPayToken);
19+
const useAccountTokensMock = jest.mocked(useAccountTokens);
20+
const hasTransactionTypeMock = jest.mocked(hasTransactionType);
21+
22+
const MUSD_TOKEN_ADDRESS = '0xaca92e438df0b2401ff60da7e4337b687a2435da';
23+
24+
const setPayTokenMock = jest.fn();
25+
26+
describe('useMoneyAccountPayToken', () => {
27+
beforeEach(() => {
28+
jest.clearAllMocks();
29+
30+
useTransactionMetadataRequestMock.mockReturnValue({
31+
id: 'tx-1',
32+
type: TransactionType.simpleSend,
33+
txParams: { from: '0xabc' },
34+
} as never);
35+
36+
useTransactionPayTokenMock.mockReturnValue({
37+
payToken: undefined,
38+
setPayToken: setPayTokenMock,
39+
});
40+
41+
useAccountTokensMock.mockReturnValue([]);
42+
43+
hasTransactionTypeMock.mockReturnValue(false);
44+
});
45+
46+
it('returns all flags as false for non-money-account transactions', () => {
47+
const { result } = renderHook(() => useMoneyAccountPayToken());
48+
49+
expect(result.current.isMoneyAccountWithdraw).toBe(false);
50+
expect(result.current.isMoneyAccountDeposit).toBe(false);
51+
expect(result.current.isAwaitingAccountSelection).toBe(false);
52+
expect(result.current.displayToken).toBeUndefined();
53+
});
54+
55+
it('returns isMoneyAccountWithdraw true for moneyAccountWithdraw type', () => {
56+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
57+
(types as TransactionType[]).includes(
58+
TransactionType.moneyAccountWithdraw,
59+
),
60+
);
61+
62+
const { result } = renderHook(() => useMoneyAccountPayToken());
63+
64+
expect(result.current.isMoneyAccountWithdraw).toBe(true);
65+
});
66+
67+
it('returns isMoneyAccountDeposit true for moneyAccountDeposit type', () => {
68+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
69+
(types as TransactionType[]).includes(
70+
TransactionType.moneyAccountDeposit,
71+
),
72+
);
73+
74+
const { result } = renderHook(() => useMoneyAccountPayToken());
75+
76+
expect(result.current.isMoneyAccountDeposit).toBe(true);
77+
});
78+
79+
it('returns isAwaitingAccountSelection true when money account type and no selectedAccount', () => {
80+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
81+
(types as TransactionType[]).includes(
82+
TransactionType.moneyAccountDeposit,
83+
),
84+
);
85+
86+
const { result } = renderHook(() => useMoneyAccountPayToken());
87+
88+
expect(result.current.isAwaitingAccountSelection).toBe(true);
89+
});
90+
91+
it('returns isAwaitingAccountSelection false when selectedAccount is provided', () => {
92+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
93+
(types as TransactionType[]).includes(
94+
TransactionType.moneyAccountDeposit,
95+
),
96+
);
97+
98+
const { result } = renderHook(() =>
99+
useMoneyAccountPayToken('0xSelectedAccount'),
100+
);
101+
102+
expect(result.current.isAwaitingAccountSelection).toBe(false);
103+
});
104+
105+
it('sets MUSD pay token on withdraw when selectedAccount is provided', () => {
106+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
107+
(types as TransactionType[]).includes(
108+
TransactionType.moneyAccountWithdraw,
109+
),
110+
);
111+
112+
renderHook(() => useMoneyAccountPayToken('0xSelectedAccount'));
113+
114+
expect(setPayTokenMock).toHaveBeenCalledWith({
115+
address: MUSD_TOKEN_ADDRESS,
116+
chainId: '0x1',
117+
});
118+
});
119+
120+
it('sets first EVM token on deposit when selectedAccount is provided', () => {
121+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
122+
(types as TransactionType[]).includes(
123+
TransactionType.moneyAccountDeposit,
124+
),
125+
);
126+
127+
useAccountTokensMock.mockReturnValue([
128+
{
129+
address: '0xTokenA',
130+
chainId: '0x1',
131+
symbol: 'USDC',
132+
decimals: 6,
133+
},
134+
] as never);
135+
136+
renderHook(() => useMoneyAccountPayToken('0xSelectedAccount'));
137+
138+
expect(setPayTokenMock).toHaveBeenCalledWith({
139+
address: '0xTokenA',
140+
chainId: '0x1',
141+
});
142+
});
143+
144+
it('skips testnet tokens for deposit token selection', () => {
145+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
146+
(types as TransactionType[]).includes(
147+
TransactionType.moneyAccountDeposit,
148+
),
149+
);
150+
151+
useAccountTokensMock.mockReturnValue([
152+
{
153+
address: '0xTestToken',
154+
chainId: '0xaa36a7',
155+
symbol: 'TEST',
156+
decimals: 18,
157+
},
158+
{
159+
address: '0xMainnetToken',
160+
chainId: '0x1',
161+
symbol: 'USDC',
162+
decimals: 6,
163+
},
164+
] as never);
165+
166+
renderHook(() => useMoneyAccountPayToken('0xSelectedAccount'));
167+
168+
expect(setPayTokenMock).toHaveBeenCalledWith({
169+
address: '0xMainnetToken',
170+
chainId: '0x1',
171+
});
172+
});
173+
174+
it('does not call setPayToken when selectedAccount is undefined', () => {
175+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
176+
(types as TransactionType[]).includes(
177+
TransactionType.moneyAccountWithdraw,
178+
),
179+
);
180+
181+
renderHook(() => useMoneyAccountPayToken());
182+
183+
expect(setPayTokenMock).not.toHaveBeenCalled();
184+
});
185+
186+
it('returns MUSD fallback as displayToken for withdraw when payToken is undefined', () => {
187+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
188+
(types as TransactionType[]).includes(
189+
TransactionType.moneyAccountWithdraw,
190+
),
191+
);
192+
193+
const { result } = renderHook(() => useMoneyAccountPayToken());
194+
195+
expect(result.current.displayToken).toEqual({
196+
address: MUSD_TOKEN_ADDRESS,
197+
chainId: '0x1',
198+
symbol: 'mUSD',
199+
decimals: 6,
200+
});
201+
});
202+
203+
it('returns payToken as displayToken for withdraw when payToken is set', () => {
204+
const mockPayToken = {
205+
address: '0xPayToken' as const,
206+
chainId: '0x1' as const,
207+
symbol: 'PAY',
208+
decimals: 18,
209+
};
210+
211+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
212+
(types as TransactionType[]).includes(
213+
TransactionType.moneyAccountWithdraw,
214+
),
215+
);
216+
217+
useTransactionPayTokenMock.mockReturnValue({
218+
payToken: mockPayToken as never,
219+
setPayToken: setPayTokenMock,
220+
});
221+
222+
const { result } = renderHook(() => useMoneyAccountPayToken());
223+
224+
expect(result.current.displayToken).toBe(mockPayToken);
225+
});
226+
227+
it('returns undefined displayToken for non-withdraw transactions', () => {
228+
hasTransactionTypeMock.mockImplementation((_meta, types) =>
229+
(types as TransactionType[]).includes(
230+
TransactionType.moneyAccountDeposit,
231+
),
232+
);
233+
234+
const { result } = renderHook(() =>
235+
useMoneyAccountPayToken('0xSelectedAccount'),
236+
);
237+
238+
expect(result.current.displayToken).toBeUndefined();
239+
});
240+
});

app/components/Views/confirmations/hooks/pay/useMoneyAccountPayToken.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef } from 'react';
1+
import { useEffect, useMemo } from 'react';
22
import { TransactionType } from '@metamask/transaction-controller';
33
import { Hex } from '@metamask/utils';
44

app/components/Views/confirmations/hooks/send/useAccountTokens.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -815,11 +815,7 @@ describe('useAccountTokens', () => {
815815
],
816816
};
817817

818-
it('uses from account assets when txParams.from resolves to an account group', () => {
819-
useTransactionMetadataRequestMock.mockReturnValue({
820-
txParams: { from: '0xFromAddress' },
821-
} as ReturnType<typeof useTransactionMetadataRequest>);
822-
818+
it('uses from account assets when accountAddress resolves to an account group', () => {
823819
mockUseSelector.mockImplementation((selector) => {
824820
if (selector === selectAssetsBySelectedAccountGroup) {
825821
return mockAssets;
@@ -840,7 +836,9 @@ describe('useAccountTokens', () => {
840836
return overrideAssets;
841837
});
842838

843-
const { result } = renderHook(() => useAccountTokens());
839+
const { result } = renderHook(() =>
840+
useAccountTokens({ accountAddress: '0xFromAddress' }),
841+
);
844842

845843
expect(result.current).toHaveLength(1);
846844
expect(result.current[0].symbol).toBe('OVERRIDE');
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
Messenger,
3+
type MessengerActions,
4+
type MessengerEvents,
5+
MOCK_ANY_NAMESPACE,
6+
type MockAnyNamespace,
7+
} from '@metamask/messenger';
8+
9+
import { RewardsControllerMessenger , getRewardsControllerMessenger } from './index';
10+
11+
type RootMessenger = Messenger<
12+
MockAnyNamespace,
13+
MessengerActions<RewardsControllerMessenger>,
14+
MessengerEvents<RewardsControllerMessenger>
15+
>;
16+
17+
function getRootMessenger(): RootMessenger {
18+
return new Messenger({
19+
namespace: MOCK_ANY_NAMESPACE,
20+
});
21+
}
22+
23+
describe('getRewardsControllerMessenger', () => {
24+
it('returns a messenger instance', () => {
25+
const rootMessenger = getRootMessenger();
26+
27+
const messenger = getRewardsControllerMessenger(rootMessenger);
28+
29+
expect(messenger).toBeInstanceOf(Messenger);
30+
});
31+
});

0 commit comments

Comments
 (0)