From df88e77cdfd9043eec4fcf0a7f5a59b6dd7476b7 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:23:07 +0300 Subject: [PATCH] fix(trading): liquidity provision table quantum formatting (#4114) --- .../src/integration/market-liquidity.cy.ts | 6 +- .../src/integration/trading-accounts.cy.ts | 27 +- .../client-pages/liquidity/liquidity.tsx | 3 + .../client-pages/market/trade-grid.tsx | 2 +- .../client-pages/market/trade-panels.tsx | 2 +- .../client-pages/portfolio/portfolio.tsx | 2 +- .../liquidity-supplied/liquidity-supplied.tsx | 21 +- .../lib/hooks/use-market-click-handler.ts | 20 +- libs/accounts/src/lib/accounts-manager.tsx | 1 - libs/accounts/src/lib/transfer-form.tsx | 8 + libs/cypress/src/lib/utils.ts | 36 ++- .../deal-ticket/deal-ticket-fee-details.tsx | 17 +- libs/liquidity/src/lib/liquidity-table.tsx | 266 ++++++++++-------- libs/utils/src/lib/format/number.spec.ts | 159 ++++++----- libs/utils/src/lib/format/number.ts | 3 +- libs/utils/src/lib/format/range.spec.ts | 14 +- 16 files changed, 337 insertions(+), 250 deletions(-) diff --git a/apps/trading-e2e/src/integration/market-liquidity.cy.ts b/apps/trading-e2e/src/integration/market-liquidity.cy.ts index b61997648..637bc0e13 100644 --- a/apps/trading-e2e/src/integration/market-liquidity.cy.ts +++ b/apps/trading-e2e/src/integration/market-liquidity.cy.ts @@ -81,6 +81,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => { cy.get(rowSelector).first().find(colFee).should('have.text', '0.09%'); + // 5002-LIQP-013 cy.get(rowSelector) .first() .find(colAverageEntryValuation) @@ -165,7 +166,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => { }); it('can see liquidity supplied', () => { - //// 5002-LIQP-008 + // 5002-LIQP-008 cy.getByTestId(marketSummaryBlock).within(() => { cy.getByTestId('liquidity-supplied').within(() => { cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied'); @@ -237,6 +238,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => { .find(colFee) .should('have.text', '0.09%'); + // 5002-LIQP-013 cy.get(rowSelectorLiquidityActive) .first() .find(colAverageEntryValuation) @@ -268,7 +270,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => { }); it('renders liquidity inactive table correctly', () => { - //// 5002-LIQP-012 + // 5002-LIQP-012 cy.getByTestId('Inactive').click(); cy.get(rowSelectorLiquidityInactive) .first() diff --git a/apps/trading-e2e/src/integration/trading-accounts.cy.ts b/apps/trading-e2e/src/integration/trading-accounts.cy.ts index c6578bf9a..14cee0cbd 100644 --- a/apps/trading-e2e/src/integration/trading-accounts.cy.ts +++ b/apps/trading-e2e/src/integration/trading-accounts.cy.ts @@ -128,6 +128,7 @@ describe('accounts', { tags: '@smoke' }, () => { cy.wrap(btn).click(); }); } + cy.contains('Loading...').should('not.exist'); }); // 7001-COLL-010 it('sorting by asset', () => { @@ -146,12 +147,17 @@ describe('accounts', { tags: '@smoke' }, () => { it('sorting by total', () => { cy.getByTestId('Collateral').click(); const marketsSortedDefault = [ - '1,000.00', + '1,000.00002', '1,000.01', '1,000.00', - '1,000.00', + '1,000.00001', + ]; + const marketsSortedAsc = [ + '1,000.00', + '1,000.00001', + '1,000.00002', + '1,000.01', ]; - const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01']; const marketsSortedDesc = Array.from(marketsSortedAsc).reverse(); checkSorting( @@ -188,19 +194,24 @@ describe('accounts', { tags: '@smoke' }, () => { ); }); - it('sorting by total', () => { + it('sorting by available', () => { cy.getByTestId('Collateral').click(); const marketsSortedDefault = [ - '1,000.00', - '1,000.01', + '1,000.00002', '1,000.00', '1,000.00', + '1,000.00001', + ]; + const marketsSortedAsc = [ + '1,000.00', + '1,000.00', + '1,000.00001', + '1,000.00002', ]; - const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01']; const marketsSortedDesc = Array.from(marketsSortedAsc).reverse(); checkSorting( - 'total', + 'available', marketsSortedDefault, marketsSortedAsc, marketsSortedDesc diff --git a/apps/trading/client-pages/liquidity/liquidity.tsx b/apps/trading/client-pages/liquidity/liquidity.tsx index b7d5fac8b..a15dded2f 100644 --- a/apps/trading/client-pages/liquidity/liquidity.tsx +++ b/apps/trading/client-pages/liquidity/liquidity.tsx @@ -83,6 +83,8 @@ export const LiquidityContainer = ({ const assetDecimalPlaces = market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0; + const quantum = + market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0; const symbol = market?.tradableInstrument.instrument.product.settlementAsset.symbol; @@ -98,6 +100,7 @@ export const LiquidityContainer = ({ rowData={data} symbol={symbol} assetDecimalPlaces={assetDecimalPlaces} + quantum={quantum} stakeToCcyVolume={stakeToCcyVolume} overlayNoRowsTemplate={error ? error.message : t('No data')} /> diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index d104fab95..3d0bb96cb 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -49,7 +49,7 @@ const MarketBottomPanel = memo( const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' }); const { screenSize } = useScreenDimensions(); const onMarketClick = useMarketClickHandler(true); - const onOrderTypeClick = useMarketLiquidityClickHandler(true); + const onOrderTypeClick = useMarketLiquidityClickHandler(); return 'xxxl' === screenSize ? ( { const [drawerOpen, setDrawerOpen] = useState(false); const onMarketClick = useMarketClickHandler(true); - const onOrderTypeClick = useMarketLiquidityClickHandler(true); + const onOrderTypeClick = useMarketLiquidityClickHandler(); const [view, setView] = useState('candles'); const renderView = () => { diff --git a/apps/trading/client-pages/portfolio/portfolio.tsx b/apps/trading/client-pages/portfolio/portfolio.tsx index 8e6a8fe91..7224ae6a1 100644 --- a/apps/trading/client-pages/portfolio/portfolio.tsx +++ b/apps/trading/client-pages/portfolio/portfolio.tsx @@ -34,7 +34,7 @@ export const Portfolio = () => { }, [updateTitle]); const onMarketClick = useMarketClickHandler(true); - const onOrderTypeClick = useMarketLiquidityClickHandler(true); + const onOrderTypeClick = useMarketLiquidityClickHandler(); const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' }); const wrapperClasses = 'h-full max-h-full flex flex-col'; return ( diff --git a/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx b/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx index e56951c40..d5aeb5bc6 100644 --- a/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx +++ b/apps/trading/components/liquidity-supplied/liquidity-supplied.tsx @@ -125,14 +125,19 @@ export const MarketLiquiditySupplied = ({
- - {t('View liquidity provision table')} - - {DocsLinks && ( - - {t('Learn about providing liquidity')} - - )} +
+ + {t('View liquidity provision table')} + + {DocsLinks && ( + + {t('Learn about providing liquidity')} + + )} +
{showMessage && (

{t( diff --git a/apps/trading/lib/hooks/use-market-click-handler.ts b/apps/trading/lib/hooks/use-market-click-handler.ts index 23a51a3db..546121246 100644 --- a/apps/trading/lib/hooks/use-market-click-handler.ts +++ b/apps/trading/lib/hooks/use-market-click-handler.ts @@ -20,20 +20,8 @@ export const useMarketClickHandler = (replace = false) => { ); }; -export const useMarketLiquidityClickHandler = (replace = false) => { - const navigate = useNavigate(); - const { marketId } = useParams(); - const { pathname } = useLocation(); - const isLiquidityPage = pathname.match(/^\/liquidity\/(.+)/); - return useCallback( - (selectedId: string, metaKey?: boolean) => { - const link = Links[Routes.LIQUIDITY](selectedId); - if (metaKey) { - window.open(`/#${link}`, '_blank'); - } else if (selectedId !== marketId || !isLiquidityPage) { - navigate(link, { replace }); - } - }, - [navigate, marketId, replace, isLiquidityPage] - ); +export const useMarketLiquidityClickHandler = () => { + return useCallback((selectedId: string, metaKey?: boolean) => { + window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self'); + }, []); }; diff --git a/libs/accounts/src/lib/accounts-manager.tsx b/libs/accounts/src/lib/accounts-manager.tsx index 437a08a3d..1caf44f32 100644 --- a/libs/accounts/src/lib/accounts-manager.tsx +++ b/libs/accounts/src/lib/accounts-manager.tsx @@ -72,7 +72,6 @@ export const AccountBreakdownDialog = memo( onClose: () => void; onMarketClick?: (marketId: string, metaKey?: boolean) => void; }) => { - console.log('render'); return (

{ if (!feeFactor || !amount || !transferAmount || !fee) return null; + if ( + isNaN(Number(feeFactor)) || + isNaN(Number(amount)) || + isNaN(Number(transferAmount)) || + isNaN(Number(fee)) + ) { + return null; + } const totalValue = new BigNumber(transferAmount).plus(fee).toString(); diff --git a/libs/cypress/src/lib/utils.ts b/libs/cypress/src/lib/utils.ts index bc3ff3977..6395bafe4 100644 --- a/libs/cypress/src/lib/utils.ts +++ b/libs/cypress/src/lib/utils.ts @@ -44,27 +44,35 @@ export const checkSorting = ( orderTabDesc: string[] ) => { checkSortChange(orderTabDefault, column); - cy.get('.ag-header-container').within(() => { - cy.get(`[col-id="${column}"]`).click(); - }); + cy.get('.ag-header-container') + .last() + .within(() => { + cy.get(`[col-id="${column}"]`).last().click(); + }); checkSortChange(orderTabAsc, column); - cy.get('.ag-header-container').within(() => { - cy.get(`[col-id="${column}"]`).click(); - }); + cy.get('.ag-header-container') + .last() + .within(() => { + cy.get(`[col-id="${column}"]`).click(); + }); checkSortChange(orderTabDesc, column); - cy.get('.ag-header-container').within(() => { - cy.get(`[col-id="${column}"]`).click(); - }); + cy.get('.ag-header-container') + .last() + .within(() => { + cy.get(`[col-id="${column}"]`).click(); + }); }; const checkSortChange = (tabsArr: string[], column: string) => { - cy.get('.ag-center-cols-container').within(() => { - tabsArr.forEach((entry, i) => { - cy.get(`[row-index="${i}"]`).within(() => { - cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]); + cy.get('.ag-center-cols-container') + .last() + .within(() => { + tabsArr.forEach((entry, i) => { + cy.get(`[row-index="${i}"]`).within(() => { + cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]); + }); }); }); - }); }; type Edges = { node: unknown }[]; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index 921513a90..aeb5ba213 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -113,6 +113,7 @@ export const DealTicketFeeDetails = ({ const { settlementAsset: asset } = market.tradableInstrument.instrument.product; const { decimals: assetDecimals, quantum } = asset; + const marketDecimals = market.decimalPlaces; let marginRequiredBestCase: string | undefined = undefined; let marginRequiredWorstCase: string | undefined = undefined; if (marginEstimate) { @@ -227,6 +228,9 @@ export const DealTicketFeeDetails = ({ liquidationEstimateWorstCaseIncludingSellOrders ? liquidationEstimateWorstCaseIncludingBuyOrders : liquidationEstimateWorstCaseIncludingSellOrders; + + // The estimate order query API gives us the liquidation price in formatted by asset decimals. + // We need to calculate it with asset decimals, but display it with market decimals precision until the API changes. liquidationPriceEstimate = formatRange( (liquidationEstimateBestCase < liquidationEstimateWorstCase ? liquidationEstimateBestCase @@ -247,14 +251,16 @@ export const DealTicketFeeDetails = ({ [] ); + const quoteName = market.tradableInstrument.instrument.product.quoteName; + return (
{partyId && ( diff --git a/libs/liquidity/src/lib/liquidity-table.tsx b/libs/liquidity/src/lib/liquidity-table.tsx index 5988dfc4c..9487497bc 100644 --- a/libs/liquidity/src/lib/liquidity-table.tsx +++ b/libs/liquidity/src/lib/liquidity-table.tsx @@ -1,20 +1,22 @@ -import { forwardRef } from 'react'; +import { forwardRef, useMemo } from 'react'; import { addDecimalsFormatNumber, + addDecimalsFormatNumberQuantum, formatNumberPercentage, getDateTimeFormat, } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; -import type { - VegaValueFormatterParams, - TypedDataAgGrid, -} from '@vegaprotocol/datagrid'; +import type { TypedDataAgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit'; import type { AgGridReact } from 'ag-grid-react'; -import { AgGridColumn } from 'ag-grid-react'; -import type { ValueFormatterParams } from 'ag-grid-community'; +import type { + ColDef, + ITooltipParams, + ValueFormatterParams, +} from 'ag-grid-community'; import BigNumber from 'bignumber.js'; +import type { LiquidityProvisionStatus } from '@vegaprotocol/types'; import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types'; import type { LiquidityProvisionData } from './liquidity-data-provider'; @@ -35,21 +37,152 @@ export interface LiquidityTableProps symbol?: string; assetDecimalPlaces?: number; stakeToCcyVolume: string | null; + quantum?: string | number; } export const LiquidityTable = forwardRef( - ({ symbol = '', assetDecimalPlaces, stakeToCcyVolume, ...props }, ref) => { - const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => { - if (!value) return '-'; - return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`; - }; - const stakeToCcyVolumeFormatter = ({ value }: ValueFormatterParams) => { - if (!value) return '-'; - const newValue = new BigNumber(value) - .times(Number(stakeToCcyVolume) || 1) - .toString(); - return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0, 5)}`; - }; + ( + { symbol = '', assetDecimalPlaces, stakeToCcyVolume, quantum, ...props }, + ref + ) => { + const colDefs = useMemo(() => { + const assetDecimalsFormatter = ({ value }: ITooltipParams) => { + if (!value) return '-'; + return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`; + }; + + const assetDecimalsQuantumFormatter = ({ + value, + }: ValueFormatterParams) => { + if (!value) return '-'; + return `${addDecimalsFormatNumberQuantum( + value, + assetDecimalPlaces ?? 0, + quantum ?? 0 + )}`; + }; + + const stakeToCcyVolumeFormatter = ({ value }: ITooltipParams) => { + if (!value) return '-'; + const newValue = new BigNumber(value) + .times(Number(stakeToCcyVolume) || 1) + .toString(); + return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`; + }; + + const stakeToCcyVolumeQuantumFormatter = ({ + value, + }: ValueFormatterParams) => { + if (!value) return '-'; + const newValue = new BigNumber(value) + .times(Number(stakeToCcyVolume) || 1) + .toString(); + return `${addDecimalsFormatNumberQuantum( + newValue, + assetDecimalPlaces ?? 0, + quantum ?? 0 + )}`; + }; + + const defs: ColDef[] = [ + { + headerName: t('Party'), + field: 'party.id', + headerTooltip: t( + 'The public key of the party making this commitment.' + ), + }, + { + headerName: t(`Commitment (${symbol})`), + field: 'commitmentAmount', + type: 'rightAligned', + headerTooltip: t( + 'The amount committed to the market by this liquidity provider.' + ), + valueFormatter: assetDecimalsQuantumFormatter, + tooltipValueGetter: assetDecimalsFormatter, + }, + { + headerName: t(`Share`), + field: 'equityLikeShare', + type: 'rightAligned', + headerTooltip: t( + 'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.' + ), + valueFormatter: percentageFormatter, + }, + { + headerName: t('Proposed fee'), + headerTooltip: t( + 'The fee percentage (per trade) proposed by each liquidity provider.' + ), + field: 'fee', + type: 'rightAligned', + valueFormatter: percentageFormatter, + }, + { + headerName: t('Market valuation at entry'), + field: 'averageEntryValuation', + type: 'rightAligned', + headerTooltip: t( + 'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.' + ), + minWidth: 160, + valueFormatter: assetDecimalsQuantumFormatter, + tooltipValueGetter: assetDecimalsFormatter, + }, + { + headerName: t('Obligation'), + field: 'commitmentAmount', + type: 'rightAligned', + headerTooltip: t( + `The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.` + ), + valueFormatter: stakeToCcyVolumeQuantumFormatter, + tooltipValueGetter: stakeToCcyVolumeFormatter, + }, + { + headerName: t('Supplied'), + field: 'balance', + type: 'rightAligned', + headerTooltip: t( + `The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.` + ), + valueFormatter: stakeToCcyVolumeQuantumFormatter, + tooltipValueGetter: stakeToCcyVolumeFormatter, + }, + { + headerName: t('Status'), + headerTooltip: t('The current status of this liquidity provision.'), + field: 'status', + valueFormatter: ({ value }) => { + if (!value) return value; + return LiquidityProvisionStatusMapping[ + value as LiquidityProvisionStatus + ]; + }, + }, + { + headerName: t('Created'), + headerTooltip: t( + 'The date and time this liquidity provision was created.' + ), + field: 'createdAt', + type: 'rightAligned', + valueFormatter: dateValueFormatter, + }, + { + headerName: t('Updated'), + headerTooltip: t( + 'The date and time this liquidity provision was last updated.' + ), + field: 'updatedAt', + type: 'rightAligned', + valueFormatter: dateValueFormatter, + }, + ]; + return defs; + }, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]); return ( ( }} storeKey="liquidityProvisionTable" {...props} - > - - - - - - - - ) => { - if (!value) return value; - return LiquidityProvisionStatusMapping[value]; - }} - /> - - - + columnDefs={colDefs} + > ); } ); diff --git a/libs/utils/src/lib/format/number.spec.ts b/libs/utils/src/lib/format/number.spec.ts index 621b1decd..0dadcaa76 100644 --- a/libs/utils/src/lib/format/number.spec.ts +++ b/libs/utils/src/lib/format/number.spec.ts @@ -29,14 +29,21 @@ describe('number utils', () => { { 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', q: 100 }, + { 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 }, { v: BigNumber('123456789123456789'), d: 10, - o: '12,345,678.9123457', + o: '12,345,678.91234568', q: '0.00003846', }, + { + v: BigNumber('123456789123456789'), + d: 10, + o: '12,345,678.91234568', + q: '1', + }, ])( 'formats with addDecimalsFormatNumberQuantum given number correctly', ({ v, d, o, q }) => { @@ -70,80 +77,80 @@ describe('number utils', () => { ])('formats given number correctly', ({ 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: new BigNumber(30000), - d: undefined, - o: ['30,000', '00'], - }, - ])('returns correct tuple given the different arguments', ({ v, d, o }) => { - expect(toNumberParts(v, d)).toStrictEqual(o); - }); -}); - -describe('isNumeric', () => { - it.each([ - { i: null, o: false }, - { i: undefined, o: false }, - { i: 1, o: true }, - { i: '1', o: true }, - { i: '-1', o: true }, - { i: 0.1, o: true }, - { i: '.1', o: true }, - { i: '-.1', o: true }, - { i: 123, o: true }, - { i: -123, o: true }, - { i: '123', o: true }, - { i: '123.01', o: true }, - { i: '-123.01', o: true }, - { i: '--123.01', o: false }, - { i: '123.', o: false }, - { i: '123.1.1', o: false }, - { i: BigInt(123), o: true }, - { i: BigInt(-1), o: true }, - { i: new BigNumber(123), o: true }, - { i: new BigNumber(123.123), o: true }, - { i: new BigNumber(123.123).toString(), o: true }, - { i: new BigNumber(123), o: true }, - { i: Infinity, o: false }, - { i: NaN, o: false }, - ])( - 'returns correct results', - ({ - i, - o, - }: { - i: number | string | undefined | null | BigNumber | bigint; - o: boolean; - }) => { - expect(isNumeric(i)).toStrictEqual(o); - } - ); -}); - -describe('toDecimal', () => { - it.each([ - { v: 0, o: '1' }, - { v: 1, o: '0.1' }, - { v: 2, o: '0.01' }, - { v: 3, o: '0.001' }, - { v: 4, o: '0.0001' }, - { v: 5, o: '0.00001' }, - { v: 6, o: '0.000001' }, - { v: 7, o: '0.0000001' }, - { v: 8, o: '0.00000001' }, - { v: 9, o: '0.000000001' }, - ])('formats with toNumber given number correctly', ({ v, o }) => { - expect(toDecimal(v)).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: new BigNumber(30000), + d: undefined, + o: ['30,000', '00'], + }, + ])('returns correct tuple given the different arguments', ({ v, d, o }) => { + expect(toNumberParts(v, d)).toStrictEqual(o); + }); + }); + + describe('isNumeric', () => { + it.each([ + { i: null, o: false }, + { i: undefined, o: false }, + { i: 1, o: true }, + { i: '1', o: true }, + { i: '-1', o: true }, + { i: 0.1, o: true }, + { i: '.1', o: true }, + { i: '-.1', o: true }, + { i: 123, o: true }, + { i: -123, o: true }, + { i: '123', o: true }, + { i: '123.01', o: true }, + { i: '-123.01', o: true }, + { i: '--123.01', o: false }, + { i: '123.', o: false }, + { i: '123.1.1', o: false }, + { i: BigInt(123), o: true }, + { i: BigInt(-1), o: true }, + { i: new BigNumber(123), o: true }, + { i: new BigNumber(123.123), o: true }, + { i: new BigNumber(123.123).toString(), o: true }, + { i: new BigNumber(123), o: true }, + { i: Infinity, o: false }, + { i: NaN, o: false }, + ])( + 'returns correct results', + ({ + i, + o, + }: { + i: number | string | undefined | null | BigNumber | bigint; + o: boolean; + }) => { + expect(isNumeric(i)).toStrictEqual(o); + } + ); + }); + + describe('toDecimal', () => { + it.each([ + { v: 0, o: '1' }, + { v: 1, o: '0.1' }, + { v: 2, o: '0.01' }, + { v: 3, o: '0.001' }, + { v: 4, o: '0.0001' }, + { v: 5, o: '0.00001' }, + { v: 6, o: '0.000001' }, + { v: 7, o: '0.0000001' }, + { v: 8, o: '0.00000001' }, + { v: 9, o: '0.000000001' }, + ])('formats with toNumber given number correctly', ({ v, o }) => { + expect(toDecimal(v)).toStrictEqual(o); + }); }); }); diff --git a/libs/utils/src/lib/format/number.ts b/libs/utils/src/lib/format/number.ts index 55e34cb1c..d3a8a2382 100644 --- a/libs/utils/src/lib/format/number.ts +++ b/libs/utils/src/lib/format/number.ts @@ -98,7 +98,8 @@ export const addDecimalsFormatNumberQuantum = ( if (isNaN(Number(quantum))) { return addDecimalsFormatNumber(rawValue, decimalPlaces); } - const numberDP = Math.max(0, Math.log10(100 / Number(quantum))); + const quantumValue = addDecimal(quantum, decimalPlaces); + const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue))); return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP)); }; diff --git a/libs/utils/src/lib/format/range.spec.ts b/libs/utils/src/lib/format/range.spec.ts index 6f59db248..5730c2fbc 100644 --- a/libs/utils/src/lib/format/range.spec.ts +++ b/libs/utils/src/lib/format/range.spec.ts @@ -21,12 +21,12 @@ describe('formatValue', () => { { 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: 123001, d: 2, o: '1,230', q: '100' }, + { v: 123001, d: 2, o: '1,230.01', q: '100' }, { v: 123001, d: 2, o: '1,230.01', q: '0.1' }, { v: '123456789123456789', d: 10, - o: '12,345,678.9123457', + o: '12,345,678.91234568', q: '0.00003846', }, ])( @@ -38,7 +38,13 @@ describe('formatValue', () => { }); describe('formatRange', () => { it.each([ - { min: 123000, max: 12300011111, d: 5, o: '1.23 - 123,000.111', q: '0.1' }, + { + min: 123000, + max: 12300011111, + d: 5, + o: '1.23 - 123,000.11111', + q: '0.1', + }, { min: 123000, max: 12300011111, @@ -57,7 +63,7 @@ describe('formatRange', () => { min: 123001000, max: 12300011111, d: 2, - o: '1,230,010 - 123,000,111', + o: '1,230,010.00 - 123,000,111.11', q: '100', }, ])(