Feat/647 disconnect eth (#813)
* feat: add disconnect feature form ethereum wallet * fix: unit tests * fix: format * fix: format * fix: e2e withdrawal check * fix: format again * fix: eth address in e2e * fix: env var for eth address
This commit is contained in:
parent
960ff40cc9
commit
4670d5e6cf
@ -11,6 +11,7 @@ describe('withdraw', () => {
|
||||
const amountField = 'input[name="amount"]';
|
||||
const useMaximumAmount = 'use-maximum';
|
||||
const submitWithdrawBtn = 'submit-withdrawal';
|
||||
const ethAddressValue = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
@ -41,12 +42,8 @@ describe('withdraw', () => {
|
||||
// only 2 despite 3 fields because the ethereum address will be auto populated
|
||||
cy.getByTestId(formFieldError).should('have.length', 2);
|
||||
|
||||
// Test for invalid Ethereum address
|
||||
cy.get(toAddressField)
|
||||
.clear()
|
||||
.type('invalid-ethereum-address')
|
||||
.next('[data-testid="input-error-text"]')
|
||||
.should('contain.text', 'Invalid Ethereum address');
|
||||
// Test for Ethereum address
|
||||
cy.get(toAddressField).should('have.value', ethAddressValue);
|
||||
|
||||
// Test min amount
|
||||
cy.get(assetSelectField).select('Asset 1'); // Select asset so we have a min viable amount calculated
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { waitFor, fireEvent, render, screen } from '@testing-library/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { DepositFormProps } from './deposit-form';
|
||||
import { DepositForm } from './deposit-form';
|
||||
@ -25,6 +25,7 @@ function generateAsset(): Asset {
|
||||
|
||||
let asset: Asset;
|
||||
let props: DepositFormProps;
|
||||
const MOCK_ETH_ADDRESS = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
|
||||
beforeEach(() => {
|
||||
asset = generateAsset();
|
||||
@ -43,171 +44,214 @@ beforeEach(() => {
|
||||
allowance: new BigNumber(30),
|
||||
isFaucetable: true,
|
||||
};
|
||||
|
||||
(useVegaWallet as jest.Mock).mockReturnValue({ keypair: null });
|
||||
(useWeb3React as jest.Mock).mockReturnValue({ account: MOCK_ETH_ADDRESS });
|
||||
});
|
||||
|
||||
it('Form validation', async () => {
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ keypair: null });
|
||||
describe('Deposit form', () => {
|
||||
it('renders with default values', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account: undefined });
|
||||
// Assert default values (including) from/to provided by useVegaWallet and useWeb3React
|
||||
expect(screen.getByLabelText('From (Ethereum address)')).toHaveValue(
|
||||
MOCK_ETH_ADDRESS
|
||||
);
|
||||
expect(screen.getByLabelText('Asset')).toHaveValue('');
|
||||
expect(screen.getByLabelText('To (Vega key)')).toHaveValue('');
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(null);
|
||||
});
|
||||
|
||||
const { rerender } = render(<DepositForm {...props} />);
|
||||
describe('fields validation', () => {
|
||||
it('fails when submitted with empty required fields', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
// Assert default values (including) from/to provided by useVegaWallet and useWeb3React
|
||||
expect(screen.getByLabelText('From (Ethereum address)')).toHaveValue('');
|
||||
expect(screen.getByLabelText('Asset')).toHaveValue('');
|
||||
expect(screen.getByLabelText('To (Vega key)')).toHaveValue('');
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(null);
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(props.submitDeposit).not.toHaveBeenCalled();
|
||||
const validationMessages = await screen.findAllByRole('alert');
|
||||
expect(validationMessages).toHaveLength(3);
|
||||
validationMessages.forEach((el) => {
|
||||
expect(el).toHaveTextContent('Required');
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when submitted with invalid ethereum address', async () => {
|
||||
(useWeb3React as jest.Mock).mockReturnValue({ account: '123' });
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Invalid Ethereum address')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted with invalid vega wallet key', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
const invalidVegaKey = 'abc';
|
||||
fireEvent.change(screen.getByLabelText('To (Vega key)'), {
|
||||
target: { value: invalidVegaKey },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(await screen.findByText('Invalid Vega key')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is more than the amount available in the ethereum wallet', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
// Max amount validation
|
||||
const amountMoreThanAvailable = '7';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanAvailable },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Insufficient amount in Ethereum wallet')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is more than the maximum limit', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
const amountMoreThanLimit = '21';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanLimit },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Amount is above permitted maximum')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is more than the approved amount', async () => {
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
|
||||
/>
|
||||
);
|
||||
|
||||
const amountMoreThanAllowance = '31';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanAllowance },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Amount is above approved amount')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is less than the minimum limit', async () => {
|
||||
// Min amount validation
|
||||
render(<DepositForm {...props} selectedAsset={asset} />); // Render with selected asset so we have asset.decimals
|
||||
|
||||
const amountLessThanMinViable = '0.00001';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountLessThanMinViable },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is less than zero', async () => {
|
||||
render(<DepositForm {...props} />);
|
||||
|
||||
const amountLessThanZero = '-0.00001';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountLessThanZero },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles deposit approvals', () => {
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ keypair: null });
|
||||
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account: undefined });
|
||||
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(0)}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByText(`Approve ${asset.symbol}`, {
|
||||
selector: '[type="button"]',
|
||||
})
|
||||
);
|
||||
|
||||
expect(props.submitApprove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles submitting a deposit', async () => {
|
||||
const vegaKey =
|
||||
'f8885edfa7ffdb6ed996ca912e9258998e47bf3515c885cf3c63fb56b15de36f';
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ keypair: { pub: vegaKey } });
|
||||
|
||||
const account = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account });
|
||||
|
||||
const limits = {
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
};
|
||||
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(100)}
|
||||
available={new BigNumber(50)}
|
||||
limits={limits}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check deposit limit is displayed
|
||||
expect(
|
||||
screen.getByText('Max deposit total', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(limits.max.toString());
|
||||
expect(
|
||||
screen.getByText('Remaining available', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '8' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
screen.getByText('Deposit', { selector: '[type="submit"]' })
|
||||
);
|
||||
});
|
||||
|
||||
expect(props.submitDeposit).not.toHaveBeenCalled();
|
||||
const validationMessages = screen.getAllByRole('alert');
|
||||
expect(validationMessages).toHaveLength(4);
|
||||
validationMessages.forEach((el) => {
|
||||
expect(el).toHaveTextContent('Required');
|
||||
});
|
||||
|
||||
// Address validation
|
||||
const invalidEthereumAddress = '123';
|
||||
fireEvent.change(screen.getByLabelText('From (Ethereum address)'), {
|
||||
target: { value: invalidEthereumAddress },
|
||||
});
|
||||
expect(
|
||||
await screen.findByText('Invalid Ethereum address')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const invalidVegaKey = 'abc';
|
||||
fireEvent.change(screen.getByLabelText('To (Vega key)'), {
|
||||
target: { value: invalidVegaKey },
|
||||
});
|
||||
expect(await screen.findByText('Invalid Vega key')).toBeInTheDocument();
|
||||
|
||||
// Max amount validation
|
||||
const amountMoreThanAvailable = '7'; // but also less than lifetime limit available
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanAvailable },
|
||||
});
|
||||
expect(
|
||||
await screen.findByText('Insufficient amount in Ethereum wallet')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const amountMoreThanLimit = '11';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanLimit },
|
||||
});
|
||||
expect(
|
||||
await screen.findByText('Amount is above permitted maximum')
|
||||
).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<DepositForm
|
||||
{...props}
|
||||
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
|
||||
/>
|
||||
);
|
||||
|
||||
const amountMoreThanAllowance = '31';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountMoreThanAllowance },
|
||||
});
|
||||
expect(
|
||||
await screen.findByText('Amount is above approved amount')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Min amount validation
|
||||
rerender(<DepositForm {...props} selectedAsset={asset} />); // Rerender with selected asset so we have asset.decimals
|
||||
|
||||
const amountLessThanMinViable = '0.00001';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountLessThanMinViable },
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Value is below minimum')).toBeInTheDocument();
|
||||
|
||||
const amountLessThanZero = '-0.00001';
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amountLessThanZero },
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Value is below minimum')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Approval', () => {
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ keypair: null });
|
||||
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account: undefined });
|
||||
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(0)}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByText(`Approve ${asset.symbol}`, { selector: '[type="button"]' })
|
||||
);
|
||||
|
||||
expect(props.submitApprove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Deposit', async () => {
|
||||
const vegaKey =
|
||||
'f8885edfa7ffdb6ed996ca912e9258998e47bf3515c885cf3c63fb56b15de36f';
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ keypair: { pub: vegaKey } });
|
||||
|
||||
const account = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account });
|
||||
|
||||
const limits = {
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
};
|
||||
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(100)}
|
||||
available={new BigNumber(50)}
|
||||
limits={limits}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check deposit limit is displayed
|
||||
expect(
|
||||
screen.getByText('Max deposit total', { selector: 'th' }).nextElementSibling
|
||||
).toHaveTextContent(limits.max.toString());
|
||||
expect(
|
||||
screen.getByText('Remaining available', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '8' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
screen.getByText('Deposit', { selector: '[type="submit"]' })
|
||||
);
|
||||
});
|
||||
|
||||
expect(props.submitDeposit).toHaveBeenCalledWith({
|
||||
// @ts-ignore contract address definitely defined
|
||||
assetSource: asset.source.contractAddress,
|
||||
amount: '800',
|
||||
vegaPublicKey: vegaKey,
|
||||
await waitFor(() => {
|
||||
expect(props.submitDeposit).toHaveBeenCalledWith({
|
||||
// @ts-ignore contract address definitely defined
|
||||
assetSource: asset.source.contractAddress,
|
||||
amount: '800',
|
||||
vegaPublicKey: vegaKey,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
removeDecimal,
|
||||
t,
|
||||
ethereumAddress,
|
||||
t,
|
||||
required,
|
||||
vegaPublicKey,
|
||||
minSafe,
|
||||
@ -18,10 +18,10 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { Web3WalletInput } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { DepositLimits } from './deposit-limits';
|
||||
import type { Asset } from './deposit-manager';
|
||||
@ -141,9 +141,11 @@ export const DepositForm = ({
|
||||
label={t('From (Ethereum address)')}
|
||||
labelFor="ethereum-address"
|
||||
>
|
||||
<Input
|
||||
{...register('from', { validate: { required, ethereumAddress } })}
|
||||
id="ethereum-address"
|
||||
<Web3WalletInput
|
||||
inputProps={{
|
||||
id: 'ethereum-address',
|
||||
...register('from', { validate: { required, ethereumAddress } }),
|
||||
}}
|
||||
/>
|
||||
{errors.from?.message && (
|
||||
<InputError intent="danger" className="mt-4">
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { SelectHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '..';
|
||||
import { defaultFormElement } from '../../utils/shared';
|
||||
|
||||
export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
@ -12,10 +13,17 @@ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
|
||||
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
||||
({ className, hasError, ...props }, ref) => (
|
||||
<select
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={classNames(defaultFormElement(hasError), className, 'h-28')}
|
||||
/>
|
||||
<div className="flex items-center relative">
|
||||
<select
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={classNames(
|
||||
defaultFormElement(hasError),
|
||||
className,
|
||||
'appearance-none h-28 pr-28'
|
||||
)}
|
||||
/>
|
||||
<Icon name="chevron-down" className="absolute right-8 z-10" />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
@ -9,3 +9,4 @@ export * from './lib/use-ethereum-transaction';
|
||||
export * from './lib/transaction-dialog';
|
||||
export * from './lib/web3-provider';
|
||||
export * from './lib/web3-connect-dialog';
|
||||
export * from './lib/web3-wallet-input';
|
||||
|
43
libs/web3/src/lib/web3-wallet-input.tsx
Normal file
43
libs/web3/src/lib/web3-wallet-input.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Button, Input, Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
type Web3WalletInputProps = {
|
||||
inputProps: Partial<
|
||||
Omit<
|
||||
ComponentProps<typeof Input>,
|
||||
'appendIconName' | 'prependIconName' | 'appendElement' | 'prependElement'
|
||||
>
|
||||
>;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
export const Web3WalletInput = ({ inputProps }: Web3WalletInputProps) => {
|
||||
const [isDialogOpen, setDialogOpen] = useState(false);
|
||||
const { account, connector } = useWeb3React();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
{...inputProps}
|
||||
appendIconName="chevron-down"
|
||||
className="cursor-pointer select-none"
|
||||
onChange={noop}
|
||||
onClick={() => setDialogOpen(true)}
|
||||
/>
|
||||
<Dialog open={isDialogOpen} onChange={setDialogOpen}>
|
||||
<p className="mb-16">
|
||||
{t('Connected with ')}
|
||||
<span className="font-mono">{account}</span>
|
||||
</p>
|
||||
<Button onClick={() => connector.deactivate()}>
|
||||
{t('Disconnect Ethereum Wallet')}
|
||||
</Button>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,14 +1,20 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { WithdrawForm } from './withdraw-form';
|
||||
import type { WithdrawFormProps } from './withdraw-form';
|
||||
import { generateAsset } from './test-helpers';
|
||||
import type { Asset } from './types';
|
||||
import type { WithdrawFormProps } from './withdraw-form';
|
||||
|
||||
const ethereumAddress = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
jest.mock('@web3-react/core');
|
||||
|
||||
const MOCK_ETH_ADDRESS = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
|
||||
let assets: Asset[];
|
||||
let props: WithdrawFormProps;
|
||||
|
||||
beforeEach(() => {
|
||||
const assets = [
|
||||
assets = [
|
||||
generateAsset(),
|
||||
generateAsset({
|
||||
id: 'asset-id-2',
|
||||
@ -16,6 +22,7 @@ beforeEach(() => {
|
||||
name: 'asset-name-2',
|
||||
}),
|
||||
];
|
||||
|
||||
props = {
|
||||
assets,
|
||||
min: new BigNumber(0.00001),
|
||||
@ -23,89 +30,95 @@ beforeEach(() => {
|
||||
limits: {
|
||||
max: new BigNumber(200),
|
||||
},
|
||||
ethereumAccount: undefined,
|
||||
selectedAsset: undefined,
|
||||
onSelectAsset: jest.fn(),
|
||||
submitWithdraw: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
};
|
||||
(useWeb3React as jest.Mock).mockReturnValue({ account: MOCK_ETH_ADDRESS });
|
||||
});
|
||||
|
||||
const generateJsx = (props: WithdrawFormProps) => <WithdrawForm {...props} />;
|
||||
describe('Withdrawal form', () => {
|
||||
it('renders with default values', async () => {
|
||||
render(<WithdrawForm {...props} />);
|
||||
|
||||
it('Validation', async () => {
|
||||
const { rerender } = render(generateJsx(props));
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
|
||||
expect(await screen.findAllByRole('alert')).toHaveLength(3);
|
||||
expect(screen.getAllByText('Required')).toHaveLength(3);
|
||||
|
||||
// Selected asset state lives in state so rerender with it now selected
|
||||
rerender(generateJsx({ ...props, selectedAsset: props.assets[0] }));
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Asset'), {
|
||||
target: { value: props.assets[0].id },
|
||||
expect(screen.getByLabelText('Asset')).toHaveValue('');
|
||||
expect(screen.getByLabelText('To (Ethereum address)')).toHaveValue(
|
||||
MOCK_ETH_ADDRESS
|
||||
);
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(null);
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('To (Ethereum address)'), {
|
||||
target: { value: 'invalid-address' },
|
||||
describe('field validation', () => {
|
||||
it('fails when submitted with empty required fields', async () => {
|
||||
render(<WithdrawForm {...props} />);
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
|
||||
expect(await screen.findAllByRole('alert')).toHaveLength(2);
|
||||
expect(screen.getAllByText('Required')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('fails when submitted with invalid ethereum address', async () => {
|
||||
(useWeb3React as jest.Mock).mockReturnValue({ account: '123' });
|
||||
render(<WithdrawForm {...props} selectedAsset={props.assets[0]} />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Asset'), {
|
||||
target: { value: props.assets[0].id },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '101' },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Invalid Ethereum address')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Value is above maximum')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is less than the minimum limit', async () => {
|
||||
render(<WithdrawForm {...props} selectedAsset={props.assets[0]} />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.000000000001' },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('passes validation with correct field values', async () => {
|
||||
render(<WithdrawForm {...props} selectedAsset={props.assets[0]} />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '40' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
});
|
||||
|
||||
expect(props.submitWithdraw).toHaveBeenCalledWith({
|
||||
asset: props.assets[0].id,
|
||||
amount: '4000000',
|
||||
receiverAddress: MOCK_ETH_ADDRESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '101' },
|
||||
});
|
||||
it('populates amount field with maximum value when clicking the "use maximum" button', () => {
|
||||
const asset = props.assets[0];
|
||||
render(<WithdrawForm {...props} selectedAsset={asset} />);
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
fireEvent.click(screen.getByText('Use maximum'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Invalid Ethereum address')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Value is above maximum')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('To (Ethereum address)'), {
|
||||
target: { value: ethereumAddress },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.000000000001' },
|
||||
});
|
||||
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
|
||||
expect(await screen.findByText('Value is below minimum')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '40' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
});
|
||||
|
||||
expect(props.submitWithdraw).toHaveBeenCalledWith({
|
||||
asset: props.assets[0].id,
|
||||
amount: '4000000',
|
||||
receiverAddress: ethereumAddress,
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(
|
||||
Number(props.max.toFixed(asset.decimals))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Use max button', () => {
|
||||
const asset = props.assets[0];
|
||||
render(generateJsx({ ...props, selectedAsset: asset }));
|
||||
|
||||
fireEvent.click(screen.getByText('Use maximum'));
|
||||
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(
|
||||
Number(props.max.toFixed(asset.decimals))
|
||||
);
|
||||
});
|
||||
|
||||
it('Use connected Ethereum account', () => {
|
||||
render(generateJsx({ ...props, ethereumAccount: ethereumAddress }));
|
||||
|
||||
fireEvent.click(screen.getByText('Use connected'));
|
||||
|
||||
expect(screen.getByLabelText('To (Ethereum address)')).toHaveValue(
|
||||
ethereumAddress
|
||||
);
|
||||
});
|
||||
|
@ -13,6 +13,8 @@ import {
|
||||
InputError,
|
||||
Select,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Web3WalletInput } from '@vegaprotocol/web3';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import type BigNumber from 'bignumber.js';
|
||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
@ -31,7 +33,6 @@ export interface WithdrawFormProps {
|
||||
max: BigNumber;
|
||||
min: BigNumber;
|
||||
selectedAsset?: Asset;
|
||||
ethereumAccount?: string;
|
||||
limits: {
|
||||
max: BigNumber;
|
||||
} | null;
|
||||
@ -44,11 +45,11 @@ export const WithdrawForm = ({
|
||||
max,
|
||||
min,
|
||||
selectedAsset,
|
||||
ethereumAccount,
|
||||
limits,
|
||||
onSelectAsset,
|
||||
submitWithdraw,
|
||||
}: WithdrawFormProps) => {
|
||||
const { account } = useWeb3React();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -59,7 +60,7 @@ export const WithdrawForm = ({
|
||||
} = useForm<FormFields>({
|
||||
defaultValues: {
|
||||
asset: selectedAsset?.id,
|
||||
to: ethereumAccount,
|
||||
to: account,
|
||||
},
|
||||
});
|
||||
const onSubmit = async (fields: FormFields) => {
|
||||
@ -117,27 +118,17 @@ export const WithdrawForm = ({
|
||||
labelFor="ethereum-address"
|
||||
className="relative"
|
||||
>
|
||||
<Input
|
||||
{...register('to', { validate: { required, ethereumAddress } })}
|
||||
id="ethereum-address"
|
||||
autoComplete="off"
|
||||
<Web3WalletInput
|
||||
inputProps={{
|
||||
id: 'ethereum-address',
|
||||
...register('to', { validate: { required, ethereumAddress } }),
|
||||
}}
|
||||
/>
|
||||
{errors.to?.message && (
|
||||
<InputError intent="danger" className="mt-4">
|
||||
{errors.to.message}
|
||||
</InputError>
|
||||
)}
|
||||
{ethereumAccount && (
|
||||
<UseButton
|
||||
data-testid="use-connected"
|
||||
onClick={() => {
|
||||
setValue('to', ethereumAccount);
|
||||
clearErrors('to');
|
||||
}}
|
||||
>
|
||||
{t('Use connected')}
|
||||
</UseButton>
|
||||
)}
|
||||
</FormGroup>
|
||||
{selectedAsset && limits && (
|
||||
<div className="mb-20">
|
||||
|
@ -9,7 +9,6 @@ import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Account, Asset } from './types';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useGetWithdrawLimits } from './use-get-withdraw-limits';
|
||||
|
||||
export interface WithdrawManagerProps {
|
||||
@ -29,7 +28,6 @@ export const WithdrawManager = ({
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [assetId, setAssetId] = useState<string | undefined>(initialAssetId);
|
||||
|
||||
const { account: ethereumAccount } = useWeb3React();
|
||||
const { ethTx, vegaTx, approval, submit, reset } = useWithdraw(
|
||||
dialogDismissed.current,
|
||||
isNewContract
|
||||
@ -86,7 +84,6 @@ export const WithdrawManager = ({
|
||||
return (
|
||||
<>
|
||||
<WithdrawForm
|
||||
ethereumAccount={ethereumAccount}
|
||||
selectedAsset={asset}
|
||||
onSelectAsset={(id) => setAssetId(id)}
|
||||
assets={sortBy(assets, 'name')}
|
||||
|
Loading…
Reference in New Issue
Block a user