Compare commits

..
7 changed files with 282 additions and 165 deletions
+1
View File
@@ -52,3 +52,4 @@ cypress.env.json
/apps/**/cypress/reports/
/apps/**/cypress/downloads/
/apps/**/fixtures/wallet/node**
.nx/
@@ -0,0 +1,12 @@
import { Settings } from './settings';
import { render, screen } from '@testing-library/react';
describe('Settings', () => {
it('should the settings component with all the options', () => {
render(<Settings />);
expect(screen.getByText('Dark mode')).toBeInTheDocument();
expect(screen.getByText('Share usage data')).toBeInTheDocument();
expect(screen.getByText('Toast location')).toBeInTheDocument();
expect(screen.getByText('Reset to default')).toBeInTheDocument();
});
});
+55 -2
View File
@@ -1,12 +1,19 @@
import { t } from '@vegaprotocol/i18n';
import { Switch, ToastPositionSetter } from '@vegaprotocol/ui-toolkit';
import {
Dialog,
Intent,
Switch,
ToastPositionSetter,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import type { ReactNode } from 'react';
import { useState, type ReactNode } from 'react';
export const Settings = () => {
const { theme, setTheme } = useThemeSwitcher();
const [isApproved, setIsApproved] = useTelemetryApproval();
const [open, setOpen] = useState(false);
return (
<div>
<SettingsGroup label={t('Dark mode')}>
@@ -31,6 +38,52 @@ export const Settings = () => {
<SettingsGroup label={t('Toast location')}>
<ToastPositionSetter />
</SettingsGroup>
<SettingsGroup label={t('Reset to default')}>
<TradingButton
name="reset-to-defaults"
size="small"
intent={Intent.None}
onClick={() => {
setOpen(true);
}}
>
{t('Reset')}
</TradingButton>
<Dialog open={open} title={t('Reset')}>
<div className="mb-4">
<p>
{t(
'You will lose all persisted settings and you will be logged out.'
)}
</p>
<p>
{t('Are you sure you want to reset all settings to default?')}
</p>
</div>
<div className="flex flex-col gap-4">
<TradingButton
name="reset-to-defaults-cancel"
intent={Intent.Primary}
onClick={() => {
localStorage.clear();
window.location.reload();
}}
>
{t('Yes, clear cache and refresh')}
</TradingButton>
<TradingButton
name="reset-to-defaults-cancel"
intent={Intent.None}
onClick={() => {
setOpen(false);
}}
>
{t('No, keep settings')}
</TradingButton>
</div>
</Dialog>
</SettingsGroup>
</div>
);
};
+28 -40
View File
@@ -1,10 +1,23 @@
import { act, render, screen } from '@testing-library/react';
import { act, render, screen, within } from '@testing-library/react';
import * as Types from '@vegaprotocol/types';
import type { AccountFields } from './accounts-data-provider';
import { getAccountData } from './accounts-data-provider';
import { AccountTable } from './accounts-table';
import userEvent from '@testing-library/user-event';
const asset1 = {
__typename: 'Asset',
id: 'asset-1',
symbol: 'tBTC',
decimals: 5,
name: 'T BTC',
};
const asset2 = {
__typename: 'Asset',
id: 'asset-2',
symbol: 'aBTC',
decimals: 5,
name: 'A BTC',
};
const singleRow = {
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
@@ -13,12 +26,7 @@ const singleRow = {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
decimals: 5,
},
asset: asset1,
available: '125600000',
used: '125600000',
total: '251200000',
@@ -33,12 +41,7 @@ const secondRow = {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'aBTC',
decimals: 5,
},
asset: asset2,
available: '125600001',
used: '125600001',
total: '251200002',
@@ -134,7 +137,7 @@ describe('AccountsTable', () => {
it('should sort assets', async () => {
// 7001-COLL-010
const { container } = render(
render(
<AccountTable
rowData={multiRowData}
onClickAsset={() => null}
@@ -142,13 +145,13 @@ describe('AccountsTable', () => {
/>
);
const headerCell = screen.getByText('Asset');
await userEvent.click(headerCell);
const rows = container.querySelectorAll(
'.ag-center-cols-container .ag-row'
);
expect(rows[0].textContent).toContain('aBTC');
expect(rows[1].textContent).toContain('tBTC');
const headerCell = screen
.getAllByRole('columnheader')
.find((h) => h?.getAttribute('col-id') === 'asset.symbol') as HTMLElement;
await userEvent.click(within(headerCell).getByText(/asset/i));
expect(headerCell).toHaveAttribute('aria-sort', 'ascending');
});
it('should apply correct formatting in view as user mode', async () => {
@@ -176,12 +179,7 @@ describe('AccountsTable', () => {
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
pinnedAsset={{
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
name: 'tBTC',
}}
pinnedAsset={asset1}
/>
);
await screen.findAllByRole('rowgroup');
@@ -213,23 +211,13 @@ describe('AccountsTable', () => {
const result = getAccountData([singleRow]);
const expected = [
{
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
asset: asset1,
available: '0',
balance: '0',
breakdown: [
{
__typename: 'AccountBalance',
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
asset: asset1,
available: '0',
balance: '125600000',
total: '125600000',
+94 -10
View File
@@ -1,4 +1,10 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import {
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BigNumber from 'bignumber.js';
import {
@@ -7,7 +13,7 @@ import {
TransferForm,
type TransferFormProps,
} from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
import { removeDecimal } from '@vegaprotocol/utils';
describe('TransferForm', () => {
@@ -39,8 +45,7 @@ describe('TransferForm', () => {
};
const amount = '100';
const pubKey =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
const pubKey = '1'.repeat(64);
const asset = {
id: 'eur',
symbol: '€',
@@ -50,10 +55,7 @@ describe('TransferForm', () => {
};
const props = {
pubKey,
pubKeys: [
pubKey,
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
],
pubKeys: [pubKey, '2'.repeat(64)],
feeFactor: '0.001',
submitTransfer: jest.fn(),
accounts: [
@@ -182,7 +184,7 @@ describe('TransferForm', () => {
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('1000');
expect(amountInput).toHaveValue('1000.00');
// Test amount validation
await userEvent.clear(amountInput);
@@ -267,7 +269,7 @@ describe('TransferForm', () => {
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('100');
expect(amountInput).toHaveValue('100.00');
// If transfering from a vested account 'include fees' checkbox should
// be disabled and fees should be 0
@@ -296,6 +298,88 @@ describe('TransferForm', () => {
});
});
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 and submits when checkbox is checked', async () => {
const mockSubmit = jest.fn();
+4 -10
View File
@@ -5,7 +5,6 @@ import {
vegaPublicKey,
addDecimal,
formatNumber,
addDecimalsFormatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -214,12 +213,7 @@ export const TransferForm = ({
<AssetOption
key={a.key}
asset={a}
balance={
<Balance
balance={formatNumber(a.balance, a.decimals)}
symbol={a.symbol}
/>
}
balance={<Balance balance={a.balance} symbol={a.symbol} />}
/>
))}
</TradingRichSelect>
@@ -286,8 +280,8 @@ export const TransferForm = ({
return (
<option value={id} key={id}>
{AccountTypeMapping[a.type]} (
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
{a.asset.symbol})
{addDecimal(a.balance, a.asset.decimals)} {a.asset.symbol}
)
</option>
);
})}
@@ -413,7 +407,7 @@ export const TransferForm = ({
type="button"
className="absolute top-0 right-0 ml-auto text-xs underline"
onClick={() =>
setValue('amount', parseFloat(accountBalance).toString(), {
setValue('amount', accountBalance, {
shouldValidate: true,
})
}
@@ -1,6 +1,6 @@
import { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { getAsset, getProductType, getQuoteName } from '@vegaprotocol/markets';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
@@ -151,6 +151,7 @@ export const DealTicketMarginDetails = ({
const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined;
if (marginEstimate) {
if (currentMargins) {
marginRequiredBestCase = (
@@ -286,115 +287,99 @@ export const DealTicketMarginDetails = ({
);
const quoteName = getQuoteName(market);
const productType = getProductType(market);
return (
<div className="flex flex-col w-full gap-2 pt-2">
{/*
TODO: remove this conditional check once the following PRs are deployed
and the estimatePosition query is working for perps
- https://github.com/vegaprotocol/vega/pull/10119
- https://github.com/vegaprotocol/vega/pull/10122
*/}
{productType === 'Future' && (
<>
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
>
<span className="text-muted">
{t('Margin required')}
</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</AccordionPrimitive.Trigger>
}
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals, quantum),
formatValue(marginAccountBalance, assetDecimals, quantum),
formatValue(
currentMargins?.maintenanceLevel,
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
),
assetSymbol
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
</>
)}
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals, quantum),
formatValue(marginAccountBalance, assetDecimals, quantum),
formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}