Skip to content

Commit 2fc317b

Browse files
authored
chore: withdrawal crypto disclaimer and receipt unit tests (deriv-com#14123)
1 parent 91d8afb commit 2fc317b

File tree

8 files changed

+278
-12
lines changed

8 files changed

+278
-12
lines changed

packages/wallets/src/components/Base/WalletClipboard/WalletClipboard.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import './WalletClipboard.scss';
88

99
type TProps = {
1010
infoMessage?: string;
11-
popoverAlignment: 'bottom' | 'left' | 'right' | 'top';
12-
successMessage: string;
11+
popoverAlignment?: 'bottom' | 'left' | 'right' | 'top';
12+
successMessage?: string;
1313
textCopy: string;
1414
};
1515

packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoDisclaimer/WithdrawalCryptoDisclaimer.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const WithdrawalDisclaimer = () => (
1111
the initial coin offering (ICO) tokens will not be credited into your account.
1212
</li>
1313
<li>
14-
Please note that your maximum and minimum withdrawal limits arent fixed. They change due to the
15-
high volatility of cryptocurrency.
14+
Please note that your maximum and minimum withdrawal limits aren&apos;t fixed. They change due to
15+
the high volatility of cryptocurrency.
1616
</li>
1717
</ul>
1818
</InlineMessage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import WithdrawalCryptoDisclaimer from '../WithdrawalCryptoDisclaimer';
4+
5+
describe('WithdrawalCryptoDisclaimer', () => {
6+
it('should render content of withdrawal crypto disclaimer', () => {
7+
render(<WithdrawalCryptoDisclaimer />);
8+
9+
expect(screen.getByText(/Do not enter an address linked to an initial coin offering/)).toBeInTheDocument();
10+
expect(
11+
screen.getByText(/Please note that your maximum and minimum withdrawal limits aren't fixed./)
12+
).toBeInTheDocument();
13+
});
14+
});

packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoForm/components/WithdrawalCryptoAmountConverter/WithdrawalCryptoAmountConverter.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const WithdrawalCryptoAmountConverter: React.FC = () => {
6868
{({ field }: FieldProps<string>) => (
6969
<WalletTextField
7070
{...field}
71+
data-testid='dt_withdrawal_crypto_amount_input'
7172
errorMessage={errors.cryptoAmount}
7273
isInvalid={Boolean(errors.cryptoAmount)}
7374
label={`Amount (${activeWallet?.currency})`}
@@ -81,13 +82,15 @@ const WithdrawalCryptoAmountConverter: React.FC = () => {
8182
className={classNames('wallets-withdrawal-crypto-amount-converter__arrow', {
8283
'wallets-withdrawal-crypto-amount-converter__arrow--rtl': !isCryptoInputActive,
8384
})}
85+
data-testid='dt_withdrawal_crypto_amount_converter_arrow'
8486
>
8587
<ArrowBold />
8688
</div>
8789
<Field name='fiatAmount' validate={(value: string) => validateFiatInput(fractionalDigits, value)}>
8890
{({ field }: FieldProps<string>) => (
8991
<WalletTextField
9092
{...field}
93+
data-testid='dt_withdrawal_fiat_amount_input'
9194
errorMessage={errors.fiatAmount}
9295
isInvalid={Boolean(errors.fiatAmount)}
9396
label='Amount (USD)'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import React from 'react';
2+
import { Formik } from 'formik';
3+
import { act, fireEvent, render, screen } from '@testing-library/react';
4+
import { useWithdrawalCryptoContext } from '../../../../../provider';
5+
import { validateCryptoInput, validateFiatInput } from '../../../../../utils';
6+
import WithdrawalCryptoAmountConverter from '../WithdrawalCryptoAmountConverter';
7+
8+
jest.mock('../../../../../utils', () => ({
9+
...jest.requireActual('../../../../../utils'),
10+
validateCryptoInput: jest.fn(),
11+
validateFiatInput: jest.fn(),
12+
}));
13+
14+
jest.mock('../../../../../provider', () => ({
15+
...jest.requireActual('../../../../../provider'),
16+
useWithdrawalCryptoContext: jest.fn(),
17+
}));
18+
19+
const mockUseWithdrawalCryptoContext = useWithdrawalCryptoContext as jest.MockedFunction<
20+
typeof useWithdrawalCryptoContext
21+
>;
22+
const mockValidateCryptoInput = validateCryptoInput as jest.Mock;
23+
const mockValidateFiatInput = validateFiatInput as jest.Mock;
24+
25+
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => {
26+
return (
27+
<Formik
28+
initialErrors={{}}
29+
initialValues={{
30+
cryptoAddress: '',
31+
cryptoAmount: '',
32+
fiatAmount: '',
33+
}}
34+
onSubmit={jest.fn()}
35+
>
36+
{children}
37+
</Formik>
38+
);
39+
};
40+
41+
describe('WithdrawalCryptoAmountConverter', () => {
42+
beforeEach(() => {
43+
mockUseWithdrawalCryptoContext.mockReturnValue({
44+
accountLimits: {
45+
remainder: undefined,
46+
},
47+
// @ts-expect-error - since this is a mock, we only need partial properties of the hook
48+
activeAccount: {
49+
currency: 'BTC',
50+
},
51+
fractionalDigits: {
52+
crypto: 8,
53+
fiat: 2,
54+
},
55+
getConvertedCryptoAmount: (fiatInput: number | string) => fiatInput as string,
56+
getConvertedFiatAmount: (cryptoInput: number | string) => cryptoInput as string,
57+
isClientVerified: false,
58+
});
59+
});
60+
61+
afterEach(() => {
62+
jest.clearAllMocks();
63+
});
64+
65+
it('should display error below crypto input field if crypto input is invalid', async () => {
66+
mockValidateCryptoInput.mockReturnValue('Crypto Input Error');
67+
68+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
69+
70+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
71+
72+
await act(async () => {
73+
await fireEvent.change(cryptoInput, { target: { value: '10' } });
74+
});
75+
expect(screen.getByText('Crypto Input Error')).toBeInTheDocument();
76+
});
77+
78+
it('should change value of fiat input field when value of crypto input changes', async () => {
79+
mockValidateCryptoInput.mockReturnValue('');
80+
81+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
82+
83+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
84+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
85+
await act(async () => {
86+
await fireEvent.change(cryptoInput, { target: { value: '10' } });
87+
});
88+
expect(fiatInput).toHaveValue('10');
89+
});
90+
91+
it('should empty fiat input field if crypto input field has errors', async () => {
92+
mockValidateCryptoInput.mockReturnValue('Crypto Input Error');
93+
94+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
95+
96+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
97+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
98+
await act(async () => {
99+
await fireEvent.change(cryptoInput, { target: { value: '10' } });
100+
});
101+
expect(fiatInput).toHaveValue('');
102+
});
103+
104+
it('should display error below fiat input field if fiat input is invalid', async () => {
105+
mockValidateFiatInput.mockReturnValue('Fiat Input Error');
106+
107+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
108+
109+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
110+
111+
await act(async () => {
112+
await fireEvent.change(fiatInput, { target: { value: '10' } });
113+
});
114+
expect(screen.getByText('Fiat Input Error')).toBeInTheDocument();
115+
});
116+
117+
it('should change value of crypto input field when value of fiat input changes', async () => {
118+
mockValidateFiatInput.mockReturnValue('');
119+
120+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
121+
122+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
123+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
124+
await act(async () => {
125+
await fireEvent.change(fiatInput, { target: { value: '10' } });
126+
});
127+
expect(cryptoInput).toHaveValue('10');
128+
});
129+
130+
it('should empty crypto input field if fiat input field has errors', async () => {
131+
mockValidateFiatInput.mockReturnValue('Fiat Input Error');
132+
133+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
134+
135+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
136+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
137+
await act(async () => {
138+
await fireEvent.change(fiatInput, { target: { value: '10' } });
139+
});
140+
expect(cryptoInput).toHaveValue('');
141+
});
142+
143+
it('should handle onFocus for crypto input field', async () => {
144+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
145+
146+
const cryptoInput = screen.getByTestId('dt_withdrawal_crypto_amount_input');
147+
await act(async () => {
148+
await fireEvent.focus(cryptoInput);
149+
});
150+
151+
expect(screen.queryByTestId('dt_withdrawal_crypto_amount_converter_arrow')).not.toHaveClass(
152+
'wallets-withdrawal-crypto-amount-converter__arrow--rtl'
153+
);
154+
});
155+
156+
it('should handle onFocus for fiat input field', async () => {
157+
render(<WithdrawalCryptoAmountConverter />, { wrapper });
158+
159+
const fiatInput = screen.getByTestId('dt_withdrawal_fiat_amount_input');
160+
await act(async () => {
161+
await fireEvent.focus(fiatInput);
162+
});
163+
164+
expect(screen.getByTestId('dt_withdrawal_crypto_amount_converter_arrow')).toHaveClass(
165+
'wallets-withdrawal-crypto-amount-converter__arrow--rtl'
166+
);
167+
});
168+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { PropsWithChildren } from 'react';
2+
import { APIProvider, AuthProvider } from '@deriv/api-v2';
3+
import { fireEvent, render, screen } from '@testing-library/react';
4+
import WithdrawalCryptoReceipt from '../WithdrawalCryptoReceipt';
5+
6+
const mockPush = jest.fn();
7+
jest.mock('react-router-dom', () => ({
8+
...jest.requireActual('react-router-dom'),
9+
useHistory: jest.fn(() => ({
10+
push: mockPush,
11+
})),
12+
}));
13+
14+
const mockWithdrawalReceipt = {
15+
address: 'test_crypto_address',
16+
};
17+
18+
const wrapper = ({ children }: PropsWithChildren) => (
19+
<APIProvider>
20+
<AuthProvider>{children}</AuthProvider>
21+
</APIProvider>
22+
);
23+
24+
describe('WithdrawalCryptoReceipt', () => {
25+
it('should render the component with withdrawal information', () => {
26+
const mockBTCWithdrawalReceipt = {
27+
address: 'test_crypto_address',
28+
amount: '100',
29+
currency: 'BTC',
30+
};
31+
render(<WithdrawalCryptoReceipt onClose={() => jest.fn()} withdrawalReceipt={mockBTCWithdrawalReceipt} />, {
32+
wrapper,
33+
});
34+
35+
const amountElement = screen.getByText('100 BTC');
36+
expect(amountElement).toBeInTheDocument();
37+
38+
const addressElement = screen.getByText('test_crypto_address');
39+
expect(addressElement).toBeInTheDocument();
40+
41+
const reviewTextElement = screen.getByText(
42+
'Your withdrawal is currently in review. It will be processed within 24 hours. We’ll send you an email once your transaction has been processed.'
43+
);
44+
expect(reviewTextElement).toBeInTheDocument();
45+
});
46+
47+
it('should trigger the close function when the "Close" button is clicked', () => {
48+
const onCloseMock = jest.fn();
49+
50+
render(<WithdrawalCryptoReceipt onClose={onCloseMock} withdrawalReceipt={mockWithdrawalReceipt} />, {
51+
wrapper,
52+
});
53+
54+
fireEvent.click(screen.getByText('Close'));
55+
56+
expect(onCloseMock).toHaveBeenCalled();
57+
});
58+
59+
it('should navigate to the transactions page when the "View transactions" button is clicked', () => {
60+
render(<WithdrawalCryptoReceipt onClose={() => jest.fn()} withdrawalReceipt={mockWithdrawalReceipt} />, {
61+
wrapper,
62+
});
63+
64+
fireEvent.click(screen.getByText('View transactions'));
65+
66+
expect(mockPush).toHaveBeenCalledWith('/wallets/cashier/transactions');
67+
});
68+
});

packages/wallets/src/features/cashier/modules/WithdrawalCrypto/components/WithdrawalCryptoReceipt/components/WithdrawalCryptoDestinationAddress/WithdrawalCryptoDestinationAddress.tsx

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import React from 'react';
22
import { WalletClipboard, WalletText } from '../../../../../../../../components';
3-
import useDevice from '../../../../../../../../hooks/useDevice';
43
import './WithdrawalCryptoDestinationAddress.scss';
54

65
const WithdrawalCryptoDestinationAddress: React.FC<{ address?: string }> = ({ address }) => {
7-
const { isMobile } = useDevice();
86
return (
97
<div className='wallets-withdrawal-crypto-destination-address'>
108
<div className='wallets-withdrawal-crypto-destination-address__title'>
@@ -16,12 +14,7 @@ const WithdrawalCryptoDestinationAddress: React.FC<{ address?: string }> = ({ ad
1614
<WalletText size='sm' weight='bold'>
1715
{address}
1816
</WalletText>
19-
<WalletClipboard
20-
infoMessage={isMobile ? undefined : 'copy'}
21-
popoverAlignment={isMobile ? 'left' : 'bottom'}
22-
successMessage='copied'
23-
textCopy={address ?? ''}
24-
/>
17+
<WalletClipboard textCopy={address ?? ''} />
2518
</div>
2619
</div>
2720
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import WithdrawalCryptoDestinationAddress from '../WithdrawalCryptoDestinationAddress';
4+
5+
describe('WithdrawalCryptoDestinationAddress', () => {
6+
it('should render the component with the provided address', () => {
7+
const address = 'your_crypto_address';
8+
render(<WithdrawalCryptoDestinationAddress address={address} />);
9+
10+
const addressElement = screen.getByText(address);
11+
expect(addressElement).toBeInTheDocument();
12+
});
13+
14+
it('should render the component without an address', () => {
15+
render(<WithdrawalCryptoDestinationAddress />);
16+
17+
const noAddressElement = screen.getByText('Destination address');
18+
expect(noAddressElement).toBeInTheDocument();
19+
});
20+
});

0 commit comments

Comments
 (0)