481 lines
14 KiB
TypeScript
481 lines
14 KiB
TypeScript
import {
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import BigNumber from 'bignumber.js';
|
|
import {
|
|
AddressField,
|
|
TransferFee,
|
|
TransferForm,
|
|
type TransferFormProps,
|
|
} from './transfer-form';
|
|
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
|
import { removeDecimal } from '@vegaprotocol/utils';
|
|
|
|
describe('TransferForm', () => {
|
|
const renderComponent = (props: TransferFormProps) => {
|
|
return render(<TransferForm {...props} />);
|
|
};
|
|
|
|
const submit = async () => {
|
|
await userEvent.click(
|
|
screen.getByRole('button', { name: 'Confirm transfer' })
|
|
);
|
|
};
|
|
|
|
const selectAsset = async (asset: {
|
|
id: string;
|
|
name: string;
|
|
decimals: number;
|
|
}) => {
|
|
// Bypass RichSelect and target hidden native select
|
|
// eslint-disable-next-line
|
|
fireEvent.change(document.querySelector('select[name="asset"]')!, {
|
|
target: { value: asset.id },
|
|
});
|
|
|
|
// assert rich select as updated
|
|
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
|
asset.name
|
|
);
|
|
};
|
|
|
|
const amount = '100';
|
|
const pubKey = '1'.repeat(64);
|
|
const asset = {
|
|
id: 'eur',
|
|
symbol: '€',
|
|
name: 'EUR',
|
|
decimals: 2,
|
|
quantum: '1',
|
|
};
|
|
const props = {
|
|
pubKey,
|
|
pubKeys: [pubKey, '2'.repeat(64)],
|
|
feeFactor: '0.001',
|
|
submitTransfer: jest.fn(),
|
|
accounts: [
|
|
{
|
|
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
|
asset,
|
|
balance: '100000',
|
|
},
|
|
{
|
|
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
|
asset,
|
|
balance: '10000',
|
|
},
|
|
],
|
|
minQuantumMultiple: '1',
|
|
};
|
|
|
|
const propsNoAssets = {
|
|
pubKey,
|
|
pubKeys: [
|
|
pubKey,
|
|
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
|
|
],
|
|
feeFactor: '0.001',
|
|
submitTransfer: jest.fn(),
|
|
accounts: [],
|
|
minQuantumMultiple: '1',
|
|
};
|
|
|
|
it('renders no assets', async () => {
|
|
renderComponent(propsNoAssets);
|
|
expect(screen.getByTestId('no-assets-available')).toBeVisible();
|
|
});
|
|
|
|
it('renders no accounts', async () => {
|
|
renderComponent(propsNoAssets);
|
|
expect(screen.getByTestId('no-accounts-available')).toBeVisible();
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
targetText: 'Transfer fee',
|
|
tooltipText: /transfer\.fee\.factor/,
|
|
},
|
|
{
|
|
targetText: 'Amount to be transferred',
|
|
tooltipText: /without the fee/,
|
|
},
|
|
{
|
|
targetText: 'Total amount (with fee)',
|
|
tooltipText: /total amount taken from your account/,
|
|
},
|
|
])('Tooltip for "$targetText" shows', async (o) => {
|
|
// 1003-TRAN-015
|
|
// 1003-TRAN-016
|
|
// 1003-TRAN-017
|
|
// 1003-TRAN-018
|
|
// 1003-TRAN-019
|
|
renderComponent(props);
|
|
// Select a pubkey
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('To Vega key'),
|
|
props.pubKeys[1]
|
|
);
|
|
|
|
// Select asset
|
|
await selectAsset(asset);
|
|
|
|
// set valid amount
|
|
const amountInput = screen.getByLabelText('Amount');
|
|
await userEvent.type(amountInput, amount);
|
|
expect(amountInput).toHaveValue(amount);
|
|
|
|
const label = screen.getByText(o.targetText);
|
|
await userEvent.hover(label);
|
|
expect(await screen.findByRole('tooltip')).toHaveTextContent(o.tooltipText);
|
|
});
|
|
|
|
it('validates a manually entered address', async () => {
|
|
// 1003-TRAN-012
|
|
// 1003-TRAN-013
|
|
// 1003-TRAN-004
|
|
renderComponent(props);
|
|
await submit();
|
|
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
|
const toggle = screen.getByText('Enter manually');
|
|
await userEvent.click(toggle);
|
|
// has switched to input
|
|
expect(toggle).toHaveTextContent('Select from wallet');
|
|
expect(screen.getByLabelText('To Vega key')).toHaveAttribute(
|
|
'type',
|
|
'text'
|
|
);
|
|
await userEvent.type(
|
|
screen.getByLabelText('To Vega key'),
|
|
'invalid-address'
|
|
);
|
|
expect(screen.getAllByTestId('input-error-text')[1]).toHaveTextContent(
|
|
'Invalid Vega key'
|
|
);
|
|
});
|
|
|
|
it('sends transfer from general accounts', async () => {
|
|
// 1003-TRAN-002
|
|
// 1003-TRAN-003
|
|
// 1002-WITH-010
|
|
// 1003-TRAN-011
|
|
// 1003-TRAN-014
|
|
renderComponent(props);
|
|
|
|
// check current pubkey not shown
|
|
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
|
|
expect(keySelect.children).toHaveLength(3);
|
|
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
|
'',
|
|
pubKey,
|
|
props.pubKeys[1],
|
|
]);
|
|
|
|
await submit();
|
|
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
|
|
|
// Select a pubkey
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('To Vega key'),
|
|
props.pubKeys[1]
|
|
);
|
|
|
|
// Select asset
|
|
await selectAsset(asset);
|
|
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('From account'),
|
|
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
|
);
|
|
|
|
const amountInput = screen.getByLabelText('Amount');
|
|
|
|
// Test use max button
|
|
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
|
expect(amountInput).toHaveValue('1000.00');
|
|
|
|
// Test amount validation
|
|
await userEvent.clear(amountInput);
|
|
await userEvent.type(amountInput, '0.001'); // Below quantum multiple amount
|
|
expect(
|
|
await screen.findByText(/Amount below minimum requirement/)
|
|
).toBeInTheDocument();
|
|
|
|
await userEvent.clear(amountInput);
|
|
await userEvent.type(amountInput, '9999999');
|
|
expect(
|
|
await screen.findByText(/cannot transfer more/i)
|
|
).toBeInTheDocument();
|
|
|
|
// set valid amount
|
|
await userEvent.clear(amountInput);
|
|
await userEvent.type(amountInput, amount);
|
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
|
|
new BigNumber(props.feeFactor).times(amount).toFixed()
|
|
);
|
|
|
|
await submit();
|
|
|
|
await waitFor(() => {
|
|
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
|
expect(props.submitTransfer).toHaveBeenCalledWith({
|
|
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
|
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
|
to: props.pubKeys[1],
|
|
asset: asset.id,
|
|
amount: removeDecimal(amount, asset.decimals),
|
|
oneOff: {},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('sends transfer from vested accounts', async () => {
|
|
const mockSubmit = jest.fn();
|
|
renderComponent({
|
|
...props,
|
|
submitTransfer: mockSubmit,
|
|
minQuantumMultiple: '100000',
|
|
});
|
|
|
|
// check current pubkey not shown
|
|
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
|
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
|
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
|
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
|
pubKeyOptions
|
|
);
|
|
|
|
await submit();
|
|
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
|
|
|
// Select a pubkey
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('To Vega key'),
|
|
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
|
|
);
|
|
|
|
// Select asset
|
|
await selectAsset(asset);
|
|
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('From account'),
|
|
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${asset.id}`
|
|
);
|
|
|
|
// Check switch back to connected key
|
|
expect(screen.getByLabelText('To Vega key')).toHaveValue(props.pubKey);
|
|
|
|
const amountInput = screen.getByLabelText('Amount');
|
|
|
|
await userEvent.clear(amountInput);
|
|
await userEvent.type(amountInput, '50');
|
|
|
|
expect(await screen.findByText(/Use max to bypass/)).toBeInTheDocument();
|
|
|
|
// Test use max button
|
|
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
|
expect(amountInput).toHaveValue('100.00');
|
|
|
|
// If transfering from a vested account fees should be 0
|
|
const expectedFee = '0';
|
|
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
|
|
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
|
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
|
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
|
|
|
await submit();
|
|
|
|
await waitFor(() => {
|
|
// 1003-TRAN-023
|
|
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
|
expect(mockSubmit).toHaveBeenCalledWith({
|
|
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
|
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
|
to: props.pubKey,
|
|
asset: asset.id,
|
|
amount: removeDecimal(amount, asset.decimals),
|
|
oneOff: {},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('handles lots of decimal places', async () => {
|
|
const balance = '904195168829277777';
|
|
const expectedBalance = '0.904195168829277777';
|
|
|
|
const longDecimalAsset = {
|
|
id: 'assetId',
|
|
symbol: 'VEGA',
|
|
name: 'VEGA',
|
|
decimals: 18,
|
|
quantum: '1',
|
|
};
|
|
|
|
const account = {
|
|
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
|
asset: longDecimalAsset,
|
|
balance,
|
|
};
|
|
|
|
const mockSubmit = jest.fn();
|
|
|
|
renderComponent({
|
|
...props,
|
|
accounts: [account],
|
|
submitTransfer: mockSubmit,
|
|
minQuantumMultiple: '100000',
|
|
});
|
|
|
|
// Select a pubkey
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('To Vega key'),
|
|
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
|
|
);
|
|
|
|
// Select asset
|
|
await selectAsset(longDecimalAsset);
|
|
|
|
const accountSelect = screen.getByLabelText('From account');
|
|
const option = within(accountSelect)
|
|
.getAllByRole('option')
|
|
.find(
|
|
(o) => o.getAttribute('value') === `${account.type}-${account.asset.id}`
|
|
);
|
|
// plus one for disabled 'please select' option
|
|
|
|
expect(option).toHaveTextContent(
|
|
`${AccountTypeMapping[account.type]} (${expectedBalance} ${
|
|
account.asset.symbol
|
|
})`
|
|
);
|
|
|
|
await userEvent.selectOptions(
|
|
accountSelect,
|
|
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
|
|
);
|
|
|
|
expect(accountSelect).toHaveValue(
|
|
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
|
|
);
|
|
|
|
// Check switch back to connected key
|
|
const amountInput = screen.getByLabelText('Amount');
|
|
|
|
// Test use max button
|
|
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
|
expect(amountInput).toHaveValue(expectedBalance);
|
|
|
|
await submit();
|
|
|
|
await waitFor(() => {
|
|
// 1003-TRAN-023
|
|
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
|
expect(mockSubmit).toHaveBeenCalledWith({
|
|
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
|
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
|
to: props.pubKey,
|
|
asset: longDecimalAsset.id,
|
|
amount: balance,
|
|
oneOff: {},
|
|
});
|
|
});
|
|
});
|
|
describe('IncludeFeesCheckbox', () => {
|
|
it('validates fields when checkbox is not checked', async () => {
|
|
renderComponent(props);
|
|
|
|
// check current pubkey not shown
|
|
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
|
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
|
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
|
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
|
pubKeyOptions
|
|
);
|
|
|
|
await submit();
|
|
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
|
|
|
// Select a pubkey
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('To Vega key'),
|
|
props.pubKeys[1]
|
|
);
|
|
|
|
// Select asset
|
|
await selectAsset(asset);
|
|
|
|
await userEvent.selectOptions(
|
|
screen.getByLabelText('From account'),
|
|
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
|
);
|
|
|
|
const amountInput = screen.getByLabelText('Amount');
|
|
|
|
await userEvent.type(amountInput, amount);
|
|
const expectedFee = new BigNumber(amount)
|
|
.times(props.feeFactor)
|
|
.toFixed();
|
|
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
|
// 1003-TRAN-021
|
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
|
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
|
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
|
});
|
|
});
|
|
|
|
describe('AddressField', () => {
|
|
const props = {
|
|
mode: 'select' as const,
|
|
select: <div>select</div>,
|
|
input: <div>input</div>,
|
|
onChange: jest.fn(),
|
|
};
|
|
|
|
it('renders correct content by mode prop and calls onChange', async () => {
|
|
const mockOnChange = jest.fn();
|
|
const { rerender } = render(
|
|
<AddressField {...props} onChange={mockOnChange} />
|
|
);
|
|
|
|
// select should be shown by default
|
|
expect(screen.getByText('select')).toBeInTheDocument();
|
|
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
|
await userEvent.click(screen.getByText('Enter manually'));
|
|
expect(mockOnChange).toHaveBeenCalled();
|
|
|
|
rerender(<AddressField {...props} mode="input" />);
|
|
|
|
expect(screen.queryByText('select')).not.toBeInTheDocument();
|
|
expect(screen.getByText('input')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('TransferFee', () => {
|
|
const props = {
|
|
amount: '200',
|
|
feeFactor: '0.001',
|
|
fee: '0.2',
|
|
transferAmount: '200',
|
|
decimals: 8,
|
|
};
|
|
it('calculates and renders the transfer fee', () => {
|
|
render(<TransferFee {...props} />);
|
|
|
|
const expected = new BigNumber(props.amount)
|
|
.times(props.feeFactor)
|
|
.toFixed();
|
|
const total = new BigNumber(props.amount).plus(expected).toFixed();
|
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
|
|
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
|
props.amount
|
|
);
|
|
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
|
});
|
|
});
|
|
});
|