chore(trading): handle negative decimals (#4659)
This commit is contained in:
parent
e41aff88b1
commit
9e5bc9c8d1
@ -21,6 +21,8 @@ import {
|
||||
import * as positionsTools from '@vegaprotocol/positions';
|
||||
import { OrdersDocument } from '@vegaprotocol/orders';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@ -36,12 +38,19 @@ const market = generateMarket();
|
||||
const marketData = generateMarketData();
|
||||
const submit = jest.fn();
|
||||
|
||||
function generateJsx(mocks: MockedResponse[] = []) {
|
||||
function generateJsx(
|
||||
mocks: MockedResponse[] = [],
|
||||
marketOverrides: PartialDeep<Market> = {}
|
||||
) {
|
||||
const joinedMarket: Market = {
|
||||
...market,
|
||||
...marketOverrides,
|
||||
} as Market;
|
||||
return (
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<VegaWalletContext.Provider value={{ pubKey, isReadOnly: false } as any}>
|
||||
<DealTicket
|
||||
market={market}
|
||||
market={joinedMarket}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice}
|
||||
submit={submit}
|
||||
@ -367,7 +376,6 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('iceberg')).toBeDisabled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it('handles TIF select box dependent on order type', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
@ -533,6 +541,25 @@ describe('DealTicket', () => {
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates size when positionDecimalPlaces is negative', async () => {
|
||||
render(generateJsx([], { positionDecimalPlaces: -4 }));
|
||||
const sizeErrorMessage = 'deal-ticket-error-message-size';
|
||||
const sizeInput = 'order-size';
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size cannot be lower than 10000'
|
||||
);
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10001');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size must be a multiple of 10000 for this market'
|
||||
);
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10000');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates iceberg field', async () => {
|
||||
const peakSizeErrorMessage = 'deal-ticket-peak-error-message';
|
||||
const minimumSizeErrorMessage = 'deal-ticket-minimum-error-message';
|
||||
|
@ -4,7 +4,6 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
|
||||
import { FillsTable, getFeesBreakdown } from './fills-table';
|
||||
import { generateFill } from './test-helpers';
|
||||
|
||||
@ -215,6 +214,27 @@ describe('FillsTable', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||
const partyId = 'party-id';
|
||||
const negativeDecimalPositionFill = generateFill({
|
||||
...defaultFill,
|
||||
market: {
|
||||
...defaultFill.market,
|
||||
positionDecimalPlaces: -4,
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
render(
|
||||
<FillsTable partyId={partyId} rowData={[negativeDecimalPositionFill]} />
|
||||
);
|
||||
});
|
||||
|
||||
const sizeCell = screen
|
||||
.getAllByRole('gridcell')
|
||||
.find((c) => c.getAttribute('col-id') === 'size');
|
||||
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
||||
});
|
||||
|
||||
describe('getFeesBreakdown', () => {
|
||||
it('should return correct fees breakdown for a taker', () => {
|
||||
const fees = {
|
||||
|
@ -6,7 +6,7 @@ import type { PartialDeep } from 'type-fest';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { OrderFieldsFragment, OrderListTableProps } from '../';
|
||||
import type { Order, OrderFieldsFragment, OrderListTableProps } from '../';
|
||||
import { OrderListTable } from '../';
|
||||
import {
|
||||
generateOrder,
|
||||
@ -164,6 +164,24 @@ describe('OrderListTable', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||
const localMarketOrder = {
|
||||
...marketOrder,
|
||||
size: '3000',
|
||||
market: {
|
||||
...marketOrder.market,
|
||||
positionDecimalPlaces: -4,
|
||||
},
|
||||
} as Order;
|
||||
|
||||
await act(async () => {
|
||||
render(generateJsx({ rowData: [localMarketOrder] }));
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[2]).toHaveTextContent('+30,000,000');
|
||||
});
|
||||
|
||||
describe('amend cell', () => {
|
||||
it('allows cancelling and editing for permitted orders', async () => {
|
||||
const mockEdit = jest.fn();
|
||||
|
@ -201,6 +201,19 @@ describe('Positions', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handle negative positionDecimalPlaces', async () => {
|
||||
await renderComponent({
|
||||
...singleRow,
|
||||
openVolume: '-2000',
|
||||
positionDecimalPlaces: -4,
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[1];
|
||||
expect(within(cell).getByTestId('stack-cell-primary')).toHaveTextContent(
|
||||
'-20,000,000'
|
||||
);
|
||||
});
|
||||
|
||||
describe('PNLCell', () => {
|
||||
const props = {
|
||||
data: undefined,
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
formatNumberPercentage,
|
||||
getUnlimitedThreshold,
|
||||
isNumeric,
|
||||
removeDecimal,
|
||||
toBigNum,
|
||||
quantumDecimalPlaces,
|
||||
toDecimal,
|
||||
toNumberParts,
|
||||
@ -153,10 +155,36 @@ describe('number utils', () => {
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
{ v: -1, o: '10' },
|
||||
{ v: -2, o: '100' },
|
||||
{ v: -3, o: '1000' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe('positive and negative decimals should be handled correctly', () => {
|
||||
const baseNum = '2000';
|
||||
const methods = [removeDecimal, toBigNum];
|
||||
it.each([
|
||||
{ decimals: 0, result: ['2000', '2000'] },
|
||||
{ decimals: 1, result: ['20000', '200'] },
|
||||
{ decimals: -1, result: ['200', '20000'] },
|
||||
{ decimals: 2, result: ['200000', '20'] },
|
||||
{ decimals: -2, result: ['20', '200000'] },
|
||||
{ decimals: 3, result: ['2000000', '2'] },
|
||||
{ decimals: -3, result: ['2', '2000000'] },
|
||||
{ decimals: 4, result: ['20000000', '0.2'] },
|
||||
{ decimals: -4, result: ['0', '20000000'] }, // removeDecimal has toFixed(0) at the end
|
||||
])(
|
||||
'number methods should handle negative decimals',
|
||||
({ decimals, result }) => {
|
||||
methods.forEach((method, i) => {
|
||||
expect(method(baseNum, decimals).toString()).toEqual(result[i]);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('quantumDecimalPlaces', () => {
|
||||
|
@ -21,7 +21,7 @@ const MAX_FRACTION_DIGITS = 20;
|
||||
|
||||
export function toDecimal(numberOfDecimals: number) {
|
||||
return new BigNumber(1)
|
||||
.dividedBy(Math.pow(10, numberOfDecimals))
|
||||
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
|
||||
.toString(10);
|
||||
}
|
||||
|
||||
@ -29,7 +29,8 @@ export function toBigNum(
|
||||
rawValue: string | number,
|
||||
decimals: number
|
||||
): BigNumber {
|
||||
return new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimals));
|
||||
const divides = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(rawValue || 0).dividedBy(divides);
|
||||
}
|
||||
|
||||
export function addDecimal(
|
||||
@ -48,7 +49,8 @@ export function removeDecimal(
|
||||
value: string | BigNumber,
|
||||
decimals: number
|
||||
): string {
|
||||
return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0);
|
||||
const times = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(value || 0).times(times).toFixed(0);
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
|
||||
|
@ -4,6 +4,12 @@ export const validateAmount = (step: number | string, field: string) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
|
||||
return (value?: string) => {
|
||||
if (Number(step) > 1) {
|
||||
if (Number(value) % Number(step) > 0) {
|
||||
return t(`${field} must be a multiple of ${step} for this market`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [, valueDecimals = ''] = (value || '').split('.');
|
||||
if (stepDecimals.length < valueDecimals.length) {
|
||||
if (stepDecimals === '') {
|
||||
|
Loading…
Reference in New Issue
Block a user