Compare commits

...
34 changed files with 385 additions and 282 deletions
@@ -1,5 +1,5 @@
import { useAssetDataProvider } from '@vegaprotocol/assets';
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { AssetLink } from '../links';
export type AssetBalanceProps = {
@@ -23,7 +23,7 @@ const AssetBalance = ({
const label =
!loading && asset && asset.decimals
? addDecimalsFixedFormatNumber(price, asset.decimals)
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
@@ -120,6 +120,6 @@ describe('Order TX Summary component', () => {
// After fetch renders formatted price and asset quotename
expect(await res.findByText('3.33')).toBeInTheDocument();
expect(await res.findByText('TEST')).toBeInTheDocument();
expect(await res.getByText('0.10')).toBeInTheDocument();
expect(await res.findByText('0.1')).toBeInTheDocument();
});
});
@@ -64,7 +64,7 @@ describe('Price in Market component', () => {
it('Renders the formatted price when market data is fetched, using market decimals by default', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
expect(await res.findByText('1')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
@@ -69,6 +69,6 @@ describe('Size in Market component', () => {
it('Renders the formatted size when market data is fetched', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
expect(await res.findByText('1')).toBeInTheDocument();
});
});
@@ -3,14 +3,14 @@ import { formatNumber } from './format-number';
describe('formatNumber and formatNumberPercentage', () => {
it.each([
{ v: new BigNumber(123), d: 3, o: '123.00' },
{ v: new BigNumber(123), d: 3, o: '123.000' },
{ v: new BigNumber(123.123), d: 3, o: '123.123' },
{ v: new BigNumber(123.123), d: 6, o: '123.123' },
{ v: new BigNumber(123.123), d: 6, o: '123.123000' },
{ v: new BigNumber(123.123), d: 0, o: '123' },
{ v: new BigNumber(123), d: undefined, o: '123.00' }, // it default to 2 decimal places
{ v: new BigNumber(30000), d: undefined, o: '30,000.00' },
{ v: new BigNumber(3.000001), d: undefined, o: '3.000001' },
])(`formats given number with decimals correctly`, ({ v, d, o }) => {
])(`formatNumber($v, $d) -> $o`, ({ v, d, o }) => {
expect(formatNumber(v, d)).toStrictEqual(o);
});
});
@@ -1,8 +1,8 @@
import { getNumberFormat } from '@vegaprotocol/utils';
import { addDays } from 'date-fns';
import sortBy from 'lodash/sortBy';
import omit from 'lodash/omit';
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
const STAKING_TIERS_MAPPING: Record<number, string> = {
1: 'Tradestarter',
@@ -85,9 +85,7 @@ export const useReferralProgram = () => {
discountFactor: Number(t.referralDiscountFactor),
discount: Number(t.referralDiscountFactor) * 100 + '%',
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
volume: getNumberFormat(0).format(
Number(t.minimumRunningNotionalTakerVolume)
),
volume: addDecimalsFormatNumber(t.minimumRunningNotionalTakerVolume, 0),
epochs: Number(t.minimumEpochs),
};
});
@@ -16,7 +16,6 @@ import {
addDecimalsFormatNumber,
getDateFormat,
getDateTimeFormat,
getNumberFormat,
getUserLocale,
removePaginationWrapper,
} from '@vegaprotocol/utils';
@@ -256,7 +255,7 @@ export const Statistics = ({
})}
description={<QUSDTooltip />}
>
{getNumberFormat(0).format(Number(totalCommissionValue))}
{addDecimalsFormatNumber(totalCommissionValue.toString(), 0)}
</StatTile>
);
@@ -418,8 +417,8 @@ export const Statistics = ({
)
.map((r) => ({
...r,
volume: getNumberFormat(0).format(r.volume),
commission: getNumberFormat(0).format(r.commission),
volume: addDecimalsFormatNumber(r.volume, 0),
commission: addDecimalsFormatNumber(r.commission, 0),
}))
.reverse()}
/>
@@ -139,7 +139,7 @@ describe('RewardPot', () => {
renderComponent(props);
expect(screen.getByTestId('total-rewards')).toHaveTextContent(
`7.00 ${rewardAsset.symbol}`
`7.0000 ${rewardAsset.symbol}`
);
expect(screen.getByText(/Locked/).nextElementSibling).toHaveTextContent(
+45 -10
View File
@@ -1,4 +1,4 @@
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';
@@ -123,11 +123,13 @@ describe('AccountsTable', () => {
/>
);
const cells = await screen.findAllByRole('gridcell');
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
await assertCells({
usedAmount: '1,256',
usedPct: '0.00%',
available: '1,256',
total: '2,512',
});
const rows = container.querySelector('.ag-center-cols-container');
expect(rows?.childElementCount).toBe(1);
});
@@ -160,12 +162,13 @@ describe('AccountsTable', () => {
/>
);
const cells = await screen.findAllByRole('gridcell');
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
expect(cells.length).toBe(expectedValues.length);
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
await assertCells({
usedAmount: '1,256',
usedPct: '0.00%',
available: '1,256',
total: '2,512',
});
const rows = container.querySelector('.ag-center-cols-container');
expect(rows?.childElementCount).toBe(1);
});
@@ -248,4 +251,36 @@ describe('AccountsTable', () => {
];
expect(result).toEqual(expected);
});
const assertCells = async ({
usedAmount,
usedPct,
available,
total,
}: {
usedAmount: string;
usedPct: string;
available: string;
total: string;
}) => {
const cells = await screen.findAllByRole('gridcell');
const usedCell = within(
cells.find(
(cell) => cell.getAttribute('col-id') === 'used'
) as HTMLElement
);
expect(usedCell.getByTestId('used-amount')).toHaveTextContent(usedAmount);
expect(usedCell.getByTestId('used-pct')).toHaveTextContent(usedPct);
const availableCell = cells.find(
(cell) => cell.getAttribute('col-id') === 'available'
);
expect(availableCell).toHaveTextContent(available);
const totalCell = cells.find(
(cell) => cell.getAttribute('col-id') === 'total'
);
expect(totalCell).toHaveTextContent(total);
};
});
+11 -3
View File
@@ -178,20 +178,28 @@ export const AccountTable = ({
return data.breakdown ? (
<>
<span className="underline">{valueFormatted}</span>
<span className="underline" data-testid="used-amount">
{valueFormatted}
</span>
<span
className={classNames(
colorClass(percentageUsed),
'ml-1 inline-block w-14'
)}
data-testid="used-pct"
>
{percentageUsed.toFixed(2)}%
</span>
</>
) : (
<>
<span className="underline">{valueFormatted}</span>
<span className="inline-block ml-2 w-14 text-muted">
<span className="underline" data-testid="used-amount">
{valueFormatted}
</span>
<span
className="inline-block ml-2 w-14 text-muted"
data-testid="used-pct"
>
{(0).toFixed(2)}%
</span>
</>
@@ -63,9 +63,9 @@ describe('BreakdownTable', () => {
const expectedValues = [
'BTCUSD.MF21',
'Margin',
'1,256.00 (50%)',
'1,256.00',
'1,256.00',
'1,256 (50%)',
'1,256',
'1,256',
];
cells.slice(0, -1).forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
@@ -74,7 +74,7 @@ describe('MarginHealthChart', () => {
it('should render correct values', async () => {
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
const chart = screen.getByTestId('margin-health-chart');
expect(chart).toHaveTextContent('3.00 above maintenance level');
expect(chart).toHaveTextContent('3 above maintenance level');
const red = screen.getByTestId('margin-health-chart-red');
const orange = screen.getByTestId('margin-health-chart-orange');
const yellow = screen.getByTestId('margin-health-chart-yellow');
@@ -121,7 +121,7 @@ describe('MarginHealthChartTooltip', () => {
expect(value).toHaveTextContent(expectedLabels[i]);
});
const values = await screen.findAllByTestId('margin-health-tooltip-value');
const expectedValues = ['4.00', '5.00', '6.00', '8.00', '10.00'];
const expectedValues = ['4', '5', '6', '8', '10'];
values.forEach((value, i) => {
expect(value).toHaveTextContent(expectedValues[i]);
});
@@ -137,7 +137,7 @@ describe('MarginHealthChartTooltip', () => {
);
let values = await screen.findAllByTestId('margin-health-tooltip-value');
expect(values[2]).toHaveTextContent('7.00');
expect(values[2]).toHaveTextContent('7');
rerender(
<MarginHealthChartTooltip
@@ -149,6 +149,6 @@ describe('MarginHealthChartTooltip', () => {
values = await screen.findAllByTestId('margin-health-tooltip-value');
expect(values.length).toBe(5);
expect(values[3]).toHaveTextContent('9.00');
expect(values[3]).toHaveTextContent('9');
});
});
@@ -1,5 +1,5 @@
import { memo } from 'react';
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { NumericCell } from './numeric-cell';
import { theme } from '@vegaprotocol/tailwindcss-config';
@@ -57,7 +57,7 @@ export const CumulativeVol = memo(
(
<NumericCell
value={Number(indicativeVolume)}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
indicativeVolume,
positionDecimalPlaces ?? 0
)}
@@ -69,7 +69,7 @@ export const CumulativeVol = memo(
{ask ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
ask,
positionDecimalPlaces ?? 0
)}
@@ -79,7 +79,7 @@ export const CumulativeVol = memo(
{bid ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
bid,
positionDecimalPlaces ?? 0
)}
+2 -2
View File
@@ -1,6 +1,6 @@
import { forwardRef } from 'react';
import classNames from 'classnames';
import { getDecimalSeparator, isNumeric } from '@vegaprotocol/utils';
import { getNumberParts, isNumeric } from '@vegaprotocol/utils';
interface NumericCellProps {
value: number | bigint | null | undefined;
@@ -23,7 +23,7 @@ export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
);
}
const decimalSeparator = getDecimalSeparator();
const decimalSeparator = getNumberParts().decimalSeparator;
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
@@ -10,12 +10,12 @@ describe('PriceChangeCell', () => {
/>
);
expect(screen.getByText('-48.51%')).toBeInTheDocument();
expect(screen.getByText('-22.10')).toBeInTheDocument();
expect(screen.getByText('-22.100')).toBeInTheDocument();
});
it('renders correctly and calculates the price change without decimals', () => {
render(<PriceChangeCell candles={['45556', '678678', '23456']} />);
expect(screen.getByText('-48.51%')).toBeInTheDocument();
expect(screen.getByText('-22,100.00')).toBeInTheDocument();
expect(screen.getByText('-22,100.000')).toBeInTheDocument();
});
});
@@ -136,7 +136,7 @@ describe('StopOrder', () => {
await userEvent.type(screen.getByTestId(sizeInput), '10');
await userEvent.type(screen.getByTestId(priceInput), '10');
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
'Notional100.00 BTC'
'Notional100 BTC'
);
});
@@ -147,13 +147,13 @@ describe('StopOrder', () => {
await userEvent.type(screen.getByTestId(sizeInput), '10');
// price trigger is selected but it's empty, calculate base on size and marketPrice prop
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
'Notional20.00 BTC'
'Notional20 BTC'
);
await userEvent.type(screen.getByTestId(triggerPriceInput), '3');
// calculate base on size and price trigger
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
'Notional30.00 BTC'
'Notional30 BTC'
);
});
+7 -7
View File
@@ -84,11 +84,11 @@ describe('FillsTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.code || '',
'+3.00',
'1.00 BTC',
'+3',
'1 BTC',
'3.00 BTC',
'Maker',
'2.00 BTC',
'2 BTC',
'0.27 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
'', // action column
@@ -121,8 +121,8 @@ describe('FillsTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.code || '',
'-3.00',
'1.00 BTC',
'-3',
'1 BTC',
'3.00 BTC',
'Taker',
'0.03 BTC',
@@ -158,8 +158,8 @@ describe('FillsTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.code || '',
'-3.00',
'1.00 BTC',
'-3',
'1 BTC',
'3.00 BTC',
'-',
'0.03 BTC',
@@ -55,7 +55,7 @@ describe('FundingPaymentsTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
fundingPayment.market?.tradableInstrument.instrument.code || '',
'1.00 BTC',
'1 BTC',
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
];
cells.forEach((cell, i) => {
@@ -77,7 +77,7 @@ describe('FundingPaymentsTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
fundingPayment.market?.tradableInstrument.instrument.code || '',
'-1.00 BTC',
'-1 BTC',
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
];
cells.forEach((cell, i) => {
+3 -3
View File
@@ -1,7 +1,7 @@
import { DepthChart } from 'pennant';
import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { addDecimal, getNumberFormat } from '@vegaprotocol/utils';
import { addDecimal, addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketDepthProvider } from './market-depth-provider';
@@ -216,13 +216,13 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const volumeFormat = useCallback(
(volume: number) =>
getNumberFormat(market?.positionDecimalPlaces || 0).format(volume),
addDecimalsFormatNumber(volume, market?.positionDecimalPlaces || 0),
[market?.positionDecimalPlaces]
);
const priceFormat = useCallback(
(price: number) =>
getNumberFormat(market?.decimalPlaces || 0).format(price),
addDecimalsFormatNumber(price, market?.decimalPlaces || 0),
[market?.decimalPlaces]
);
@@ -102,12 +102,7 @@ export const OrderbookControls = ({
};
export const formatResolution = (r: number, decimalPlaces: number) => {
let num = addDecimalsFormatNumber(r, decimalPlaces);
// Remove trailing zeroes
num = num.replace(/\.?0+$/, '');
return num;
return addDecimalsFormatNumber(r, decimalPlaces);
};
/**
+4 -4
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import { memo } from 'react';
import { addDecimal, addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { addDecimal, addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { NumericCell } from '@vegaprotocol/datagrid';
import { VolumeType } from './orderbook-data';
import classNames from 'classnames';
@@ -55,7 +55,7 @@ export const OrderbookRow = memo(
<NumericCell
testId={`price-${price}`}
value={BigInt(price)}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
price,
decimalPlaces,
priceFormatDecimalPlaces
@@ -76,7 +76,7 @@ export const OrderbookRow = memo(
<NumericCell
testId={`${txtId}-vol-${price}`}
value={volume}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
volume,
positionDecimalPlaces ?? 0
)}
@@ -94,7 +94,7 @@ export const OrderbookRow = memo(
<NumericCell
testId={`cumulative-vol-${price}`}
value={cumulativeVolume}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
cumulativeVolume,
positionDecimalPlaces
)}
+1 -1
View File
@@ -43,7 +43,7 @@ describe('Orderbook', () => {
);
expect(
screen.getByTestId(`last-traded-${params.lastTradedPrice}`)
).toHaveTextContent('122.90');
).toHaveTextContent('122.9');
});
it('should format correctly the numbers on resolution change', async () => {
+3 -2
View File
@@ -172,8 +172,9 @@ export const Orderbook = ({
// we'll want to only display a relevant number of dps based on the
// current resolution selection
const priceFormatDecimalPlaces = Math.ceil(
decimalPlaces - Math.log10(resolution)
const priceFormatDecimalPlaces = Math.max(
0,
Math.ceil(decimalPlaces - Math.log10(resolution))
);
return (
@@ -80,7 +80,7 @@ describe('OrderListTable', () => {
const expectedValues: string[] = [
marketOrder.market?.tradableInstrument.instrument.code || '',
'0.05',
'0.10',
'0.1',
Schema.OrderTypeMapping[marketOrder.type as Schema.OrderType] || '',
Schema.OrderStatusMapping[marketOrder.status],
'-',
@@ -102,7 +102,7 @@ describe('OrderListTable', () => {
const expectedValues: string[] = [
limitOrder.market?.tradableInstrument.instrument.code || '',
'0.05',
'0.10',
'0.1',
Schema.OrderTypeMapping[limitOrder.type || Schema.OrderType.TYPE_LIMIT],
Schema.OrderStatusMapping[limitOrder.status],
'-',
@@ -135,8 +135,8 @@ describe('OrderListTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues: string[] = [
icebergOrder.market?.tradableInstrument.instrument.code || '',
'0.00',
'+1.00',
'0',
'+1',
Schema.OrderTypeMapping[
icebergOrder.type || Schema.OrderType.TYPE_LIMIT
] + ' (Iceberg)',
@@ -277,7 +277,7 @@ describe('OrderListTable', () => {
const amendCell = getAmendCell();
const typeCell = screen.getAllByRole('gridcell')[3];
expect(typeCell).toHaveTextContent('Mid - 10.0 Peg limit');
expect(typeCell).toHaveTextContent('Mid - 10 Peg limit');
expect(amendCell.queryByTestId('edit')).toBeInTheDocument();
expect(amendCell.queryByTestId('cancel')).toBeInTheDocument();
});
@@ -162,17 +162,15 @@ describe('OrderViewDialog', () => {
expect(screen.getByTestId('order-type-label')).toHaveTextContent('Type');
expect(screen.getByTestId('order-type-value')).toHaveTextContent('Limit');
expect(screen.getByTestId('order-price-label')).toHaveTextContent('Price');
expect(screen.getByTestId('order-price-value')).toHaveTextContent('150.00');
expect(screen.getByTestId('order-price-value')).toHaveTextContent('150');
expect(screen.getByTestId('order-size-label')).toHaveTextContent('Size');
expect(screen.getByTestId('order-size-value')).toHaveTextContent('+10.00');
expect(screen.getByTestId('order-size-value')).toHaveTextContent('+10');
expect(screen.getByTestId('order-remaining-label')).toHaveTextContent(
'Remaining'
);
expect(screen.getByTestId('order-remaining-value')).toHaveTextContent(
'+5.00'
);
expect(screen.getByTestId('order-remaining-value')).toHaveTextContent('+5');
expect(
screen.getByTestId('order-iceberg-order-reserved-remaining-value')
).toHaveTextContent('5.00');
).toHaveTextContent('5');
});
});
@@ -140,8 +140,8 @@ describe('StopOrdersTable', () => {
const cells = grid.querySelectorAll('.ag-body [col-id="trigger"]');
const expectedValues: string[] = [
'Mark < 8.0',
'Mark > 9.0',
'Mark < 8',
'Mark > 9',
'Mark +10.0%',
'Mark -20.0%',
];
@@ -173,7 +173,7 @@ describe('StopOrdersTable', () => {
const grid = screen.getByRole('treegrid');
const cells = grid.querySelectorAll('.ag-body [col-id="submission.size"]');
const expectedValues: string[] = ['+1.00', '-1.10'];
const expectedValues: string[] = ['+1', '-1.1'];
expectedValues.forEach((expectedValue, i) =>
expect(cells[i]).toHaveTextContent(expectedValue)
);
@@ -219,7 +219,7 @@ describe('StopOrdersTable', () => {
const grid = screen.getByRole('treegrid');
const cells = grid.querySelectorAll('.ag-body [col-id="submission.price"]');
const expectedValues: string[] = ['12.0', '-'];
const expectedValues: string[] = ['12', '-'];
expectedValues.forEach((expectedValue, i) =>
expect(cells[i]).toHaveTextContent(expectedValue)
);
@@ -74,7 +74,7 @@ describe('Positions', () => {
'+100'
);
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
'1,230.0'
'1,230'
);
});
@@ -90,7 +90,7 @@ describe('Positions', () => {
'-100'
);
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
'1,230.0'
'1,230'
);
});
@@ -334,7 +334,7 @@ describe('Positions', () => {
const tooltip = within(await screen.findByRole('tooltip'));
expect(tooltip.getByText('Realised PNL: 1.23')).toBeInTheDocument();
expect(
tooltip.getByText('Lifetime loss socialisation deductions: 5.00')
tooltip.getByText('Lifetime loss socialisation deductions: 5')
).toBeInTheDocument();
expect(
tooltip.getByText(
@@ -3,8 +3,7 @@ import type { BigNumber } from 'bignumber.js';
import { toNumberParts } from '@vegaprotocol/utils';
export const useNumberParts = (
value: BigNumber | null | undefined,
decimals: number
value: BigNumber | null | undefined
): [integers: string, decimalPlaces: string, separator: string | undefined] => {
return useMemo(() => toNumberParts(value, decimals), [decimals, value]);
return useMemo(() => toNumberParts(value), [value]);
};
+2 -2
View File
@@ -41,8 +41,8 @@ describe('TradesTable', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20.00',
'1,111,222',
'20',
getTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {
+128 -63
View File
@@ -3,7 +3,6 @@ import BigNumber from 'bignumber.js';
import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
formatNumber,
formatNumberPercentage,
getUnlimitedThreshold,
isNumeric,
@@ -12,94 +11,140 @@ import {
quantumDecimalPlaces,
toDecimal,
toNumberParts,
formatNumberRounded,
} from './number';
describe('number utils', () => {
it.each([
{ v: new BigNumber(123000), d: 5, o: '1.23' },
{ v: new BigNumber(123000), d: 3, o: '123.00' },
{ v: new BigNumber(123000), d: 1, o: '12,300.0' },
{ v: new BigNumber(123001), d: 2, o: '1,230.01' },
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00' },
])(
'formats with addDecimalsFormatNumber given number correctly',
({ v, d, o }) => {
expect(addDecimalsFormatNumber(v.toString(), d)).toStrictEqual(o);
}
);
{ v: new BigNumber(123000), d: 5, f: undefined, o: '1.23' },
{ v: new BigNumber(123000), d: 5, f: 3, o: '1.230' },
{ v: new BigNumber(123000), d: 3, f: undefined, o: '123' },
{ v: new BigNumber(123000), d: 1, f: undefined, o: '12,300' },
{ v: new BigNumber(123001), d: 2, f: undefined, o: '1,230.01' },
{ v: new BigNumber(123001000), d: 2, f: undefined, o: '1,230,010' },
it.each([
{ v: new BigNumber(123000), d: 5, o: '1.23', q: 0.1 },
{ v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 },
{ v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 },
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 },
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 100 },
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 },
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 1 },
// these would lose precision normally and get rounded to 0.9041951688292778
{
v: BigNumber('123456789123456789'),
d: 10,
o: '12,345,678.91234568',
q: '0.00003846',
v: new BigNumber('904195168829277777'),
d: 18,
f: undefined,
o: '0.904195168829277777',
},
{
v: BigNumber('123456789123456789'),
d: 10,
o: '12,345,678.91234568',
q: '1',
v: new BigNumber('1234567904195168829277777'),
d: 18,
f: undefined,
o: '1,234,567.904195168829277777',
},
// USDT / USDC
{ v: new BigNumber(12345678), d: 6, o: '12.35', q: 1000000 },
])(
'formats with addDecimalsFormatNumberQuantum given number correctly',
({ v, d, o, q }) => {
expect(addDecimalsFormatNumberQuantum(v.toString(), d, q)).toStrictEqual(
o
);
}
);
it.each([
{ v: new BigNumber(123), d: 3, o: '123.00' },
{ v: new BigNumber(123.123), d: 3, o: '123.123' },
{ v: new BigNumber(123.6666), d: 3, o: '123.667' },
{ v: new BigNumber(123.123), d: 6, o: '123.123' },
{ v: new BigNumber(123.123), d: 0, o: '123' },
{ v: new BigNumber(123), d: undefined, o: '123' },
{ v: new BigNumber(30000), d: undefined, o: '30,000' },
{ v: new BigNumber(3.000001), d: undefined, o: '3' },
])('formats with formatNumber given number correctly', ({ v, d, o }) => {
expect(formatNumber(v, d)).toStrictEqual(o);
{
v: new BigNumber('1234567904195168829277777'),
d: 18,
f: 2,
o: '1,234,567.90',
},
{
v: new BigNumber('1234567906195168829277777'),
d: 18,
f: 2,
o: '1,234,567.91', // should round here
},
])('addDecimalsFormatNumber formats $v as $o', ({ v, d, f, o }) => {
expect(addDecimalsFormatNumber(v.toString(), d, f)).toStrictEqual(o);
});
it.each([
{ v: new BigNumber(123), d: 3, o: '123.00%' },
// USDT / USDC
{
v: new BigNumber('12111111'),
d: 6,
q: '1000000',
o: '12.11',
},
{
v: new BigNumber('12456111111'),
d: 6,
q: '1000000',
o: '12,456.11',
},
{
v: new BigNumber('12345678'),
d: 6,
q: '1000000',
o: '12.35', // quantum should round
},
// WETH
{
v: new BigNumber('1'),
d: 18,
q: '500000000000000',
o: '0.000000', // actually 0.000000000000000001 but we are formatting with quantum so that last 1 weth is not relevant
},
{
v: new BigNumber('493000000000000'),
d: 18,
q: '500000000000000',
o: '0.000493', // 1 USD of WETH ~0.000493
},
{
v: new BigNumber('1000000493000000000000'),
d: 18,
q: '500000000000000',
o: '1,000.000493',
},
{ v: new BigNumber(123001), d: 2, q: 1, o: '1,230.0100' },
{ v: new BigNumber(123001), d: 2, q: 100, o: '1,230.01' },
{
v: BigNumber('123456789123456789'),
d: 10,
q: '1',
o: '12,345,678.912345678900',
},
// FRACTIONAL QUANTUM
{ v: new BigNumber(123000), d: 5, q: 0.1, o: '1.23000000' },
{ v: new BigNumber(123000), d: 3, q: 0.1, o: '123.000000' },
{ v: new BigNumber(123000), d: 1, q: 0.1, o: '12,300.0000' },
{ v: new BigNumber(123001000), d: 2, q: 0.1, o: '1,230,010.00000' },
{ v: new BigNumber(123001), d: 2, q: 0.1, o: '1,230.01000' },
{
v: BigNumber('123456789123456789'),
d: 10,
q: '0.00003846',
o: '12,345,678.91234567890000000',
},
])('addDecimalsFormatNumberQuantum($v, $d, $q) = $o', ({ v, d, q, o }) => {
expect(addDecimalsFormatNumberQuantum(v.toString(), d, q)).toStrictEqual(o);
});
it.each([
{ v: new BigNumber(123), d: 3, o: '123.000%' },
{ v: new BigNumber(123.123), d: 3, o: '123.123%' },
{ v: new BigNumber(123.123), d: 6, o: '123.123%' },
{ v: new BigNumber(123.123), d: 6, o: '123.123000%' },
{ v: new BigNumber(123.123), d: 0, o: '123%' },
{ v: new BigNumber(123), d: undefined, o: '123%' }, // it default to 2 decimal places
{ v: new BigNumber(30000), d: undefined, o: '30,000%' },
{ v: new BigNumber(3.000001), d: undefined, o: '3.000001%' },
])('formats given number correctly', ({ v, d, o }) => {
])('formatNumberRounded($v, $d) -> $o', ({ v, d, o }) => {
expect(formatNumberPercentage(v, d)).toStrictEqual(o);
});
describe('toNumberParts', () => {
it.each([
{ v: null, d: 3, o: ['0', '000', '.'] },
{ v: undefined, d: 3, o: ['0', '000', '.'] },
{ v: new BigNumber(123), d: 3, o: ['123', '00', '.'] },
{ v: new BigNumber(123.123), d: 3, o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), d: 6, o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), d: 0, o: ['123', '', '.'] },
{ v: new BigNumber(123), d: undefined, o: ['123', '00', '.'] },
{ v: null, o: ['0', '', '.'] },
{ v: undefined, o: ['0', '', '.'] },
{ v: new BigNumber(123), o: ['123', '', '.'] },
{ v: new BigNumber(123.123), o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), o: ['123', '123', '.'] },
{ v: new BigNumber(123), o: ['123', '', '.'] },
{
v: new BigNumber(30000),
d: undefined,
o: ['30,000', '00', '.'],
o: ['30,000', '', '.'],
},
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
expect(toNumberParts(v, d)).toStrictEqual(o);
])('$v -> $o', ({ v, o }) => {
expect(toNumberParts(v)).toStrictEqual(o);
});
});
@@ -246,3 +291,23 @@ describe('getUnlimitedThreshold', () => {
}
);
});
describe('formatNumberRounded', () => {
it.each([
{ n: new BigNumber(123), o: '123' },
{ n: new BigNumber(1234), o: '1,234' },
{ n: new BigNumber(404000), o: '404,000' },
{ n: new BigNumber(500000), o: '500,000' },
{ n: new BigNumber(1000000), o: '1m' },
{ n: new BigNumber(1500000), o: '1.5m' },
{ n: new BigNumber(1500001), o: '1.5m' },
{ n: new BigNumber(1510001), o: '1.5m' },
{ n: new BigNumber(1000000000), o: '1b' },
{ n: new BigNumber(1500000000), o: '1.5b' },
{ n: new BigNumber(1000000000000), o: '1t' },
{ n: new BigNumber(1500000000000), o: '1.5t' },
{ n: new BigNumber(99510000000000), o: '99.5t' },
])('$n -> $o', ({ n, o }) => {
expect(formatNumberRounded(n)).toEqual(o);
});
});
+97 -87
View File
@@ -1,9 +1,75 @@
import { BigNumber } from 'bignumber.js';
import isNil from 'lodash/isNil';
import memoize from 'lodash/memoize';
import { getUserLocale } from '../get-user-locale';
const DEFAULT_DECIMAL_SEPARATOR = '.';
const DEFAULT_GROUP_SEPARATOR = ',';
// get formatting characters for users locale
export const getNumberParts = memoize(() => {
// 1000.1 will get us a group character (, for thousand groups, . for decimals in en-GB)
const parts = new Intl.NumberFormat(getUserLocale()).formatToParts(1000.1);
const decimalSeparator = parts.find((part) => part.type === 'decimal');
const groupSeparator = parts.find((part) => part.type === 'group');
if (!decimalSeparator) {
console.warn('Could not get locales decimalSeparator');
}
if (!groupSeparator) {
console.warn('Could not get locales groupSeparator');
}
return {
decimalSeparator: decimalSeparator
? decimalSeparator.value
: DEFAULT_DECIMAL_SEPARATOR,
groupSeparator: groupSeparator
? groupSeparator.value
: DEFAULT_GROUP_SEPARATOR,
};
});
const parts = getNumberParts();
// Format for bignumber formatting
const FORMAT = {
prefix: '',
decimalSeparator: parts.decimalSeparator,
groupSeparator: parts.groupSeparator,
groupSize: 3,
secondaryGroupSize: 0,
fractionGroupSeparator: ' ',
fractionGroupSize: 0,
suffix: '',
};
BigNumber.config({ FORMAT });
export const isNumeric = (
value?: string | number | BigNumber | bigint | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
export const toNumberParts = (
value: BigNumber | null | undefined
): [integers: string, decimalPlaces: string, separator: string] => {
if (!value) {
return ['0', '', '.'];
}
const separator = getNumberParts().decimalSeparator;
const dps = value.dp() || 0;
const [integers, decimalsPlaces] = formatNumber(value, dps)
.toString()
.split(separator);
return [integers, decimalsPlaces || '', separator];
};
/**
* A raw unformatted value greater than this is considered and displayed
* as UNLIMITED.
@@ -16,9 +82,6 @@ export const UNLIMITED_THRESHOLD = new BigNumber(2).pow(256).times(0.8);
export const getUnlimitedThreshold = (decimalPlaces: number) =>
UNLIMITED_THRESHOLD.dividedBy(Math.pow(10, decimalPlaces));
const MIN_FRACTION_DIGITS = 2;
const MAX_FRACTION_DIGITS = 20;
export function toDecimal(numberOfDecimals: number) {
return new BigNumber(1)
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
@@ -53,35 +116,6 @@ export function removeDecimal(
return new BigNumber(value || 0).times(times).toFixed(0);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
export const getNumberFormat = memoize((digits: number) => {
if (isNil(digits) || digits < 0) {
return new Intl.NumberFormat(getUserLocale());
}
return new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.min(Math.max(0, digits), MIN_FRACTION_DIGITS),
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
});
});
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
export const getFixedNumberFormat = memoize((digits: number) => {
if (isNil(digits) || digits < 0) {
return new Intl.NumberFormat(getUserLocale());
}
return new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
});
});
export const getDecimalSeparator = memoize(
() =>
getNumberFormat(1)
.formatToParts(1.1)
.find((part) => part.type === 'decimal')?.value
);
/** formatNumber will format the number with fixed decimals
* @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN
* @param formatDecimals - number of decimals to use
@@ -90,18 +124,7 @@ export const formatNumber = (
rawValue: string | number | BigNumber,
formatDecimals = 0
) => {
return getNumberFormat(formatDecimals).format(Number(rawValue));
};
/** formatNumberFixed will format the number with fixed decimals
* @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN
* @param formatDecimals - number of decimals to use
*/
export const formatNumberFixed = (
rawValue: string | number | BigNumber,
formatDecimals = 0
) => {
return getFixedNumberFormat(formatDecimals).format(Number(rawValue));
return new BigNumber(rawValue).toFormat(formatDecimals);
};
export const quantumDecimalPlaces = (
@@ -117,7 +140,11 @@ export const quantumDecimalPlaces = (
? decimalPlaces
: Math.max(
0,
Math.log10(100 / Number(addDecimal(rawQuantum, decimalPlaces)))
Math.log10(
new BigNumber(100)
.dividedBy(toBigNum(rawQuantum, decimalPlaces))
.toNumber()
)
);
return Math.ceil(formatDecimals);
@@ -128,61 +155,44 @@ export const addDecimalsFormatNumberQuantum = (
decimalPlaces: number,
quantum: number | string
) => {
const val = toBigNum(rawValue, decimalPlaces);
let formatDps = val.dp() ?? decimalPlaces;
if (isNaN(Number(quantum))) {
return addDecimalsFormatNumber(rawValue, decimalPlaces);
return val.toFormat(formatDps);
}
const quantumValue = addDecimal(quantum, decimalPlaces);
const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue)));
return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP));
formatDps = quantumDecimalPlaces(quantum, decimalPlaces);
return val.toFormat(formatDps);
};
export const addDecimalsFormatNumber = (
rawValue: string | number,
decimalPlaces: number,
formatDecimals: number = decimalPlaces
formatDecimals?: number
) => {
const x = addDecimal(rawValue, decimalPlaces);
return formatNumber(x, formatDecimals);
const val = toBigNum(rawValue, decimalPlaces);
const naturalDp = val.dp() ?? 0;
const formatDps = Math.max(
0,
formatDecimals === undefined ? naturalDp : formatDecimals
);
return val.toFormat(formatDps || 0);
};
export const addDecimalsFixedFormatNumber = (
rawValue: string | number,
decimalPlaces: number,
formatDecimals: number = decimalPlaces
export const formatNumberPercentage = (
value: BigNumber,
formatDecimals?: number
) => {
const x = addDecimal(rawValue, decimalPlaces);
return formatNumberFixed(x, formatDecimals);
};
export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
const decimalPlaces =
typeof decimals === 'undefined' ? value.dp() || 0 : decimals;
return `${formatNumber(value, decimalPlaces)}%`;
typeof formatDecimals === 'undefined' ? value.dp() || 0 : formatDecimals;
return `${value.toFormat(decimalPlaces)}%`;
};
export const toNumberParts = (
value: BigNumber | null | undefined,
decimals = 18
): [integers: string, decimalPlaces: string, separator: string] => {
if (!value) {
return ['0', '0'.repeat(decimals), '.'];
}
const separator = getDecimalSeparator() || '.';
const [integers, decimalsPlaces] = formatNumber(value, decimals)
.toString()
.split(separator);
return [integers, decimalsPlaces || '', separator];
};
export const isNumeric = (
value?: string | number | BigNumber | bigint | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
/**
* Format a number greater than 1 million with m for million, b for billion
* and t for trillion
* Format numbers greater than 1 million with m for million, b for billion
* and t for trillion, rounding to the nearest half
*/
export const formatNumberRounded = (num: BigNumber) => {
let value = '';
@@ -204,7 +214,7 @@ export const formatNumberRounded = (num: BigNumber) => {
// Million
value = `${format('1e6')}m`;
} else {
value = formatNumber(num);
value = num.toFormat();
}
return value;
+24 -29
View File
@@ -3,61 +3,59 @@ import { formatRange, formatValue } from './range';
describe('formatValue', () => {
it.each([
{ v: 123000, d: 5, o: '1.23' },
{ v: 123000, d: 3, o: '123.00' },
{ v: 123000, d: 1, o: '12,300.0' },
{ v: 123001000, d: 2, o: '1,230,010.00' },
{ v: 123000, d: 3, o: '123' },
{ v: 123000, d: 1, o: '12,300' },
{ v: 123001000, d: 2, o: '1,230,010' },
{ v: 123001, d: 2, o: '1,230.01' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.91234568',
o: '12,345,678.9123456789',
},
])('formats values correctly', ({ v, d, o }) => {
])('formatValue($v, $d) -> $o', ({ v, d, o }) => {
expect(formatValue(v, d)).toStrictEqual(o);
});
it.each([
{ v: 123000, d: 5, o: '1.23', q: '0.1' },
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
{ v: 123000, d: 5, o: '1.2300000', q: '1' },
{ v: 123000, d: 3, o: '123.00000', q: '1' },
{ v: 123000, d: 1, o: '12,300.000', q: '1' },
{ v: 123001000, d: 2, o: '1,230,010.0000', q: '1' },
{ v: 123001, d: 2, o: '1,230.01', q: '100' },
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
{ v: 123001, d: 2, o: '1,230.0100', q: '1' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.91234568',
o: '12,345,678.91234567890000000',
q: '0.00003846',
},
])(
'formats with formatValue with quantum given number correctly',
({ v, d, o, q }) => {
expect(formatValue(v.toString(), d, q)).toStrictEqual(o);
}
);
])('with quantum formatValue($v, $d, $q) -> $o', ({ v, d, o, q }) => {
expect(formatValue(v.toString(), d, q)).toStrictEqual(o);
});
});
describe('formatRange', () => {
it.each([
{
min: 123000,
max: 12300011111,
d: 5,
o: '1.23 - 123,000.11111',
q: '0.1',
o: '1.2300000 - 123,000.1111100',
q: '1',
},
{
min: 123000,
max: 12300011111,
d: 3,
o: '123.00 - 12,300,011.111',
q: '0.1',
o: '123.00000 - 12,300,011.11100',
q: '1',
},
{
min: 123000,
max: 12300011111,
d: 1,
o: '12,300.00 - 1,230,001,111.10',
q: '0.1',
o: '12,300.000 - 1,230,001,111.100',
q: '1',
},
{
min: 123001000,
@@ -66,10 +64,7 @@ describe('formatRange', () => {
o: '1,230,010.00 - 123,000,111.11',
q: '100',
},
])(
'formats with formatValue with quantum given number correctly',
({ min, max, d, o, q }) => {
expect(formatRange(min, max, d, q)).toStrictEqual(o);
}
);
])('formatRange($min, $max, $d, $q) -> $o', ({ min, max, d, o, q }) => {
expect(formatRange(min, max, d, q)).toStrictEqual(o);
});
});
@@ -365,20 +365,20 @@ describe('VegaTransactionDetails', () => {
});
it.each([
{ tx: withdraw, details: 'Withdraw 12.34 $A' },
{ tx: submitOrder, details: 'Submit order - activeM1+0.10 @ 12.34 $A' },
{ tx: submitOrder, details: 'Submit order - activeM1+0.1 @ 12.34 $A' },
{
tx: submitStopOrder,
details: 'Submit stop orderM1+0.10 @ ~ $AMark > 12.34',
details: 'Submit stop orderM1+0.1 @ ~ $AMark > 12.34',
},
{
tx: editOrder,
details: 'Edit order - activeM1+0.10 @ 12.34 $A+0.11 @ 10.00 $A',
details: 'Edit order - activeM1+0.1 @ 12.34 $A+0.11 @ 10 $A',
},
{ tx: cancelOrder, details: 'Cancel orderM1+0.10 @ 12.34 $A' },
{ tx: cancelOrder, details: 'Cancel orderM1+0.1 @ 12.34 $A' },
{ tx: cancelAll, details: 'Cancel all orders' },
{
tx: cancelStopOrder,
details: 'Cancel stop orderM1-0.10 @ 12.34 $AMark > 12.34',
details: 'Cancel stop orderM1-0.1 @ 12.34 $AMark > 12.34',
},
{ tx: closePosition, details: 'Close position for M1' },
{ tx: batch, details: 'Batch market instruction' },
@@ -41,7 +41,7 @@ describe('Withdrawals', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'asset-symbol',
'1.00',
'1',
'123456…123456',
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
'-',
@@ -67,7 +67,7 @@ describe('Withdrawals', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'asset-symbol',
'1.00',
'1',
'123456…123456',
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
getTimeFormat().format(