Skip to content

Commit b913f78

Browse files
committed
fixup! feat(suite-native): wire approval and revoke screen to trade data
1 parent 880cff6 commit b913f78

File tree

7 files changed

+309
-47
lines changed

7 files changed

+309
-47
lines changed

suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/ExchangeApprovalLimitSheet.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
selectTradingCoinSymbolByCryptoId,
1010
selectTradingProviderByNameAndTradeType,
1111
} from '@suite-common/trading';
12+
import { isNetworkSymbol } from '@suite-common/wallet-config';
13+
import { TokenSymbol } from '@suite-common/wallet-types';
1214
import { BottomSheetModal, VStack, useBottomSheetModal } from '@suite-native/atoms';
15+
import { CryptoAmountFormatter, TokenAmountFormatter } from '@suite-native/formatters';
1316
import { Translation } from '@suite-native/intl';
1417

1518
import { ExchangeApprovalLimitCard } from './ExchangeApprovalLimitCard';
@@ -53,7 +56,22 @@ export const ExchangeApprovalLimitSheet = memo(
5356
return null;
5457
}
5558

56-
const limitAmount = `${quote.sendStringAmount} ${coinSymbol}`;
59+
const limitAmount =
60+
!!coinSymbol &&
61+
(isNetworkSymbol(coinSymbol) ? (
62+
<CryptoAmountFormatter
63+
value={quote.sendStringAmount ?? '0'}
64+
symbol={coinSymbol}
65+
isBalance={false}
66+
variant="callout"
67+
/>
68+
) : (
69+
<TokenAmountFormatter
70+
value={quote.sendStringAmount ?? '0'}
71+
tokenSymbol={coinSymbol as TokenSymbol}
72+
variant="callout"
73+
/>
74+
));
5775

5876
return (
5977
<BottomSheetModal

suite-native/module-trading/src/components/exchange/ExchangeApprovalLimitSheet/__tests__/ExchangeApprovalLimitSheet.comp.test.tsx

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getInitializedTradingState } from '../../../../__fixtures__/tradingStat
55
import { ExchangeApprovalLimitSheet } from '../ExchangeApprovalLimitSheet';
66

77
const mockOnDismiss = jest.fn();
8+
const mockOnApprovalTypeSelect = jest.fn();
89

910
const testQuote = exchangeQuotes[0];
1011

@@ -20,40 +21,32 @@ const getPreloadedState = (): PreloadedState => ({
2021
},
2122
});
2223

23-
const getPreloadedStateWithoutQuote = (): PreloadedState => ({
24-
wallet: {
25-
trading: {
26-
...getInitializedTradingState('exchange'),
27-
exchange: {
28-
...getInitializedTradingState('exchange').exchange,
29-
selectedQuote: undefined,
30-
},
31-
},
32-
},
33-
});
34-
35-
const renderSheet = (isVisible = true, preloadedState = getPreloadedState()) =>
24+
const renderSheet = (
25+
isVisible = true,
26+
quote = testQuote,
27+
selectedApprovalType: 'INFINITE' | 'MINIMAL' = 'INFINITE',
28+
) =>
3629
renderWithStoreProviderAsync(
37-
<ExchangeApprovalLimitSheet isVisible={isVisible} onDismiss={mockOnDismiss} />,
38-
{ preloadedState },
30+
<ExchangeApprovalLimitSheet
31+
isVisible={isVisible}
32+
onDismiss={mockOnDismiss}
33+
quote={quote}
34+
onApprovalTypeSelect={mockOnApprovalTypeSelect}
35+
selectedApprovalType={selectedApprovalType}
36+
/>,
37+
{ preloadedState: getPreloadedState() },
3938
);
4039

4140
describe('ExchangeApprovalLimitSheet', () => {
4241
beforeEach(() => {
4342
jest.clearAllMocks();
4443
});
4544

46-
it('should render nothing when quote is not available', async () => {
47-
const { toJSON } = await renderSheet(true, getPreloadedStateWithoutQuote());
48-
49-
expect(toJSON()).toBeNull();
50-
});
51-
5245
it('should render the sheet when visible', async () => {
5346
const { getByText } = await renderSheet();
5447

5548
expect(getByText('Unlimited')).toBeTruthy();
56-
expect(getByText('200.32 USDC')).toBeTruthy();
49+
expect(getByText('100 USDC')).toBeTruthy();
5750
});
5851

5952
it('should render unlimited approval option with correct details', async () => {
@@ -70,7 +63,7 @@ describe('ExchangeApprovalLimitSheet', () => {
7063
it('should render limited approval option with correct amount', async () => {
7164
const { getByText } = await renderSheet();
7265

73-
expect(getByText('200.32 USDC')).toBeTruthy();
66+
expect(getByText('100 USDC')).toBeTruthy();
7467
expect(
7568
getByText(
7669
"Approve only the amount needed for this swap. You'll need to approve again (and pay a fee) for future swaps, but this reduces risk by keeping you in full control of your USDC.",
@@ -94,4 +87,26 @@ describe('ExchangeApprovalLimitSheet', () => {
9487
),
9588
).toBeTruthy();
9689
});
90+
91+
it('should display quote sendStringAmount in limited approval option', async () => {
92+
const customQuote = {
93+
...testQuote,
94+
sendStringAmount: '250',
95+
};
96+
const { getByText } = await renderSheet(true, customQuote);
97+
98+
expect(getByText('250 USDC')).toBeTruthy();
99+
});
100+
101+
it('should pass correct props when INFINITE is selected', async () => {
102+
await renderSheet(true, testQuote, 'INFINITE');
103+
104+
expect(mockOnApprovalTypeSelect).toBeDefined();
105+
});
106+
107+
it('should pass correct props when MINIMAL is selected', async () => {
108+
await renderSheet(true, testQuote, 'MINIMAL');
109+
110+
expect(mockOnApprovalTypeSelect).toBeDefined();
111+
});
97112
});

suite-native/module-trading/src/hooks/exchange/__tests__/useExchangeSelectQuote.test.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { exchangeQuotes } from '../../../__fixtures__/exchangeQuotes';
1212
import { btcAsset } from '../../../__fixtures__/tradeableAssets';
1313
import { getInitializedTradingStateWithQuotes } from '../../../__fixtures__/tradingState';
1414
import { ExchangeFormType } from '../../../types/exchange';
15+
import * as approvalStatusUtils from '../../../utils/general/approvalStatusUtils';
1516
import { useExchangeForm } from '../useExchangeForm';
1617
import { useExchangeSelectQuote } from '../useExchangeSelectQuote';
1718

@@ -221,7 +222,7 @@ describe('useExchangeSelectQuote', () => {
221222
nextStep();
222223
});
223224

224-
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview');
225+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {});
225226
});
226227

227228
it('should call cancelConsent when quote provider changes', async () => {
@@ -253,4 +254,147 @@ describe('useExchangeSelectQuote', () => {
253254
expect(result.current.isConsentRequested).toBe(false);
254255
});
255256
});
257+
258+
describe('navigation based on approval status', () => {
259+
beforeEach(async () => {
260+
store = await getInitializedStore({ isLoading: false });
261+
262+
const { result } = await renderExchangeForm();
263+
exchangeForm = result.current;
264+
});
265+
266+
it('should navigate to TradingExchangePreview when approval status is "approved"', async () => {
267+
jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('approved');
268+
269+
act(() => {
270+
exchangeForm.setValue('quote', exchangeQuotes[1]);
271+
});
272+
273+
const dispatchSpy = jest.spyOn(store, 'dispatch');
274+
const { result } = await renderUseExchangeSelectQuote();
275+
276+
act(() => {
277+
result.current.selectQuote();
278+
});
279+
280+
const dispatchCall = dispatchSpy.mock.calls[0][0];
281+
const { nextStep } = (dispatchCall as any).payload;
282+
283+
act(() => {
284+
nextStep();
285+
});
286+
287+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {});
288+
});
289+
290+
it('should navigate to TradingExchangePreview when approval status is "not_needed"', async () => {
291+
jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('not_needed');
292+
293+
act(() => {
294+
exchangeForm.setValue('quote', exchangeQuotes[1]);
295+
});
296+
297+
const dispatchSpy = jest.spyOn(store, 'dispatch');
298+
const { result } = await renderUseExchangeSelectQuote();
299+
300+
act(() => {
301+
result.current.selectQuote();
302+
});
303+
304+
const dispatchCall = dispatchSpy.mock.calls[0][0];
305+
const { nextStep } = (dispatchCall as any).payload;
306+
307+
act(() => {
308+
nextStep();
309+
});
310+
311+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangePreview', {});
312+
});
313+
314+
it('should navigate to TradingExchangeApproval when approval status is "needs_approval"', async () => {
315+
jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_approval');
316+
317+
act(() => {
318+
exchangeForm.setValue('quote', exchangeQuotes[1]);
319+
});
320+
321+
const dispatchSpy = jest.spyOn(store, 'dispatch');
322+
const { result } = await renderUseExchangeSelectQuote();
323+
324+
act(() => {
325+
result.current.selectQuote();
326+
});
327+
328+
const dispatchCall = dispatchSpy.mock.calls[0][0];
329+
const { nextStep } = (dispatchCall as any).payload;
330+
331+
act(() => {
332+
nextStep();
333+
});
334+
335+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeApproval', {
336+
quote: exchangeQuotes[1],
337+
});
338+
});
339+
340+
it('should navigate to TradingExchangeApproval with shouldIncreaseLimit when approval status is "needs_increase" and token supports increasing allowance', async () => {
341+
jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase');
342+
jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue(
343+
true,
344+
);
345+
346+
act(() => {
347+
exchangeForm.setValue('quote', exchangeQuotes[1]);
348+
});
349+
350+
const dispatchSpy = jest.spyOn(store, 'dispatch');
351+
const { result } = await renderUseExchangeSelectQuote();
352+
353+
act(() => {
354+
result.current.selectQuote();
355+
});
356+
357+
const dispatchCall = dispatchSpy.mock.calls[0][0];
358+
const { nextStep } = (dispatchCall as any).payload;
359+
360+
act(() => {
361+
nextStep();
362+
});
363+
364+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeApproval', {
365+
quote: exchangeQuotes[1],
366+
shouldIncreaseLimit: true,
367+
});
368+
});
369+
370+
it('should navigate to TradingExchangeRevoke when approval status is "needs_increase" and token does not support increasing allowance', async () => {
371+
jest.spyOn(approvalStatusUtils, 'getApprovalStatus').mockReturnValue('needs_increase');
372+
jest.spyOn(approvalStatusUtils, 'tokenSupportsIncreasingAllowance').mockReturnValue(
373+
false,
374+
);
375+
376+
act(() => {
377+
exchangeForm.setValue('quote', exchangeQuotes[1]);
378+
});
379+
380+
const dispatchSpy = jest.spyOn(store, 'dispatch');
381+
const { result } = await renderUseExchangeSelectQuote();
382+
383+
act(() => {
384+
result.current.selectQuote();
385+
});
386+
387+
const dispatchCall = dispatchSpy.mock.calls[0][0];
388+
const { nextStep } = (dispatchCall as any).payload;
389+
390+
act(() => {
391+
nextStep();
392+
});
393+
394+
expect(mockNavigation.navigate).toHaveBeenCalledWith('TradingExchangeRevoke', {
395+
quote: exchangeQuotes[1],
396+
shouldIncreaseLimit: true,
397+
});
398+
});
399+
});
256400
});

suite-native/module-trading/src/screens/__tests__/TradingExchangeApprovalScreen.comp.test.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ import { TradingExchangeApprovalScreen } from '../TradingExchangeApprovalScreen'
1212
const mockShowSheet = jest.fn();
1313
const mockHideSheet = jest.fn();
1414

15+
jest.mock('../../hooks/exchange/useExchangeFlow', () => ({
16+
useExchangeFlow: () => ({
17+
confirmTrade: jest.fn().mockResolvedValue(true),
18+
fetchFeesAndCompose: jest.fn(),
19+
}),
20+
}));
21+
1522
jest.mock('@react-navigation/native', () => ({
1623
...jest.requireActual('@react-navigation/native'),
1724
useRoute: () =>
@@ -46,9 +53,20 @@ const preloadedState = {
4653
};
4754

4855
const renderScreen = () =>
49-
renderWithStoreProviderAsync(<TradingExchangeApprovalScreen />, {
50-
preloadedState,
51-
});
56+
renderWithStoreProviderAsync(
57+
<TradingExchangeApprovalScreen
58+
route={
59+
{ params: { quote: testQuote } } as RouteProp<
60+
TradingStackParamList,
61+
TradingStackRoutes.TradingExchangeApproval
62+
>
63+
}
64+
navigation={{} as any}
65+
/>,
66+
{
67+
preloadedState,
68+
},
69+
);
5270

5371
describe('TradingExchangeApprovalScreen', () => {
5472
beforeEach(() => {

suite-native/module-trading/src/screens/__tests__/TradingExchangePreviewScreen.comp.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jest.mock('@react-navigation/native', () => ({
2020
...jest.requireActual('@react-navigation/native'),
2121
useRoute: () =>
2222
({
23-
params: undefined,
23+
params: {},
2424
}) as RouteProp<TradingStackParamList, TradingStackRoutes.TradingExchangePreview>,
2525
}));
2626

@@ -53,7 +53,7 @@ const mockNavigate = jest.fn();
5353
describe('TradingExchangePreviewScreen', () => {
5454
let store: TestStore;
5555

56-
const renderTradingExchangePreviewScreen = () =>
56+
const renderTradingExchangePreviewScreen = (isApproved: boolean = false) =>
5757
renderWithStoreProviderAsync(
5858
<TradingExchangePreviewScreen
5959
navigation={
@@ -62,7 +62,7 @@ describe('TradingExchangePreviewScreen', () => {
6262
popToTop: mockPopToTop,
6363
} as unknown as TradingExchangePreviewScreenProps['navigation']
6464
}
65-
route={{} as TradingExchangePreviewScreenProps['route']}
65+
route={{ params: { isApproved } } as TradingExchangePreviewScreenProps['route']}
6666
/>,
6767
{ store },
6868
);

0 commit comments

Comments
 (0)