From ce9f337d36bb4cc6071946b5aa72687e6598c6b8 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 21 Oct 2024 18:17:28 +0100 Subject: [PATCH 1/2] fix: Reduce usage of scientific notation --- .../info/hooks/use-token-values.test.ts | 62 ++++++++++++++++++- .../confirm/info/hooks/use-token-values.ts | 39 +++++++++++- .../info/shared/send-heading/send-heading.tsx | 40 ++++++++---- .../token-transfer.test.tsx.snap | 2 +- 4 files changed, 126 insertions(+), 17 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts index 1ed5e9c249ff..e66c8f3046e8 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts @@ -5,7 +5,11 @@ import mockState from '../../../../../../../test/data/mock-state.json'; import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; -import { useTokenValues } from './use-token-values'; +import { + roundDisplayValue, + toNonScientificString, + useTokenValues, +} from './use-token-values'; import { useDecodedTransactionData } from './useDecodedTransactionData'; jest.mock('../../../../hooks/useAssetDetails', () => ({ @@ -73,7 +77,8 @@ describe('useTokenValues', () => { await waitForNextUpdate(); expect(result.current).toEqual({ - decodedTransferValue: 7, + decodedTransferValue: '7', + displayTransferValue: '7', fiatDisplayValue: '$6.37', pending: false, }); @@ -118,9 +123,60 @@ describe('useTokenValues', () => { await waitForNextUpdate(); expect(result.current).toEqual({ - decodedTransferValue: 7, + decodedTransferValue: '7', + displayTransferValue: '7', fiatDisplayValue: null, pending: false, }); }); }); + +describe('roundDisplayValue', () => { + const TEST_CASES = [ + { value: 0, rounded: '0' }, + { value: 0.0000009, rounded: '<0.000001' }, + { value: 0.0000456, rounded: '0.000046' }, + { value: 0.0004567, rounded: '0.000457' }, + { value: 0.003456, rounded: '0.00346' }, + { value: 0.023456, rounded: '0.0235' }, + { value: 0.125456, rounded: '0.125' }, + { value: 1.0034, rounded: '1.003' }, + { value: 1.034, rounded: '1.034' }, + { value: 1.3034, rounded: '1.303' }, + { value: 7, rounded: '7' }, + { value: 7.1, rounded: '7.1' }, + { value: 12.0345, rounded: '12.03' }, + { value: 121.456, rounded: '121.5' }, + { value: 1034.123, rounded: '1034' }, + { value: 47361034.006, rounded: '47361034' }, + { value: 12130982923409.555, rounded: '12130982923410' }, + ]; + + // @ts-expect-error This is missing from the Mocha type definitions + it.each(TEST_CASES)( + 'Round $value to "$rounded"', + ({ value, rounded }: { value: number; rounded: string }) => { + const actual = roundDisplayValue(value); + + expect(actual).toEqual(rounded); + }, + ); +}); + +describe('toNonScientificString', () => { + const TEST_CASES = [ + { scientific: 1.23e-5, expanded: '0.0000123' }, + { scientific: 1e-10, expanded: '0.0000000001' }, + { scientific: 1.23e-21, expanded: '1.23e-21' }, + ]; + + // @ts-expect-error This is missing from the Mocha type definitions + it.each(TEST_CASES)( + 'Expand $scientific to "$expanded"', + ({ scientific, expanded }: { scientific: number; expanded: string }) => { + const actual = toNonScientificString(scientific); + + expect(actual).toEqual(expanded); + }, + ); +}); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts index 139a1e8116b9..6fb3c38629f4 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts @@ -56,9 +56,46 @@ export const useTokenValues = (transactionMeta: TransactionMeta) => { const fiatDisplayValue = fiatValue && fiatFormatter(fiatValue, { shorten: true }); + const displayTransferValue = roundDisplayValue(decodedTransferValue); + return { - decodedTransferValue, + decodedTransferValue: toNonScientificString(decodedTransferValue), + displayTransferValue, fiatDisplayValue, pending, }; }; + +export function roundDisplayValue(decodedTransferValue: number): string { + switch (true) { + case decodedTransferValue === 0: + return '0'; + case decodedTransferValue < 0.000001: + return '<0.000001'; + case decodedTransferValue < 0.001: + return parseFloat(decodedTransferValue.toFixed(6)).toString(); + case decodedTransferValue < 0.01: + return parseFloat(decodedTransferValue.toFixed(5)).toString(); + case decodedTransferValue < 0.1: + return parseFloat(decodedTransferValue.toFixed(4)).toString(); + case decodedTransferValue < 10: + return parseFloat(decodedTransferValue.toFixed(3)).toString(); + case decodedTransferValue < 100: + return parseFloat(decodedTransferValue.toFixed(2)).toString(); + case decodedTransferValue < 1000: + return parseFloat(decodedTransferValue.toFixed(1)).toString(); + case decodedTransferValue < 10000: + return parseFloat(decodedTransferValue.toFixed(0)).toString(); + default: + return parseFloat(decodedTransferValue.toFixed(0)).toString(); + } +} + +export function toNonScientificString(num: number): string { + if (num >= 10e-18) { + return num.toFixed(18).replace(/\.?0+$/u, ''); + } + + // keep in scientific notation + return num.toString(); +} diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx index 40c571d4bc75..5bb250ad74f0 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx @@ -7,6 +7,7 @@ import { Box, Text, } from '../../../../../../../components/component-library'; +import Tooltip from '../../../../../../../components/ui/tooltip'; import { AlignItems, BackgroundColor, @@ -16,14 +17,16 @@ import { TextColor, TextVariant, } from '../../../../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { getWatchedToken } from '../../../../../../../selectors'; import { MultichainState } from '../../../../../../../selectors/multichain'; import { useConfirmContext } from '../../../../../context/confirm'; -import { useTokenDetails } from '../../hooks/useTokenDetails'; import { useTokenValues } from '../../hooks/use-token-values'; +import { useTokenDetails } from '../../hooks/useTokenDetails'; import { ConfirmLoader } from '../confirm-loader/confirm-loader'; const SendHeading = () => { + const t = useI18nContext(); const { currentConfirmation: transactionMeta } = useConfirmContext(); const selectedToken = useSelector((state: MultichainState) => @@ -33,8 +36,12 @@ const SendHeading = () => { transactionMeta, selectedToken, ); - const { decodedTransferValue, fiatDisplayValue, pending } = - useTokenValues(transactionMeta); + const { + decodedTransferValue, + displayTransferValue, + fiatDisplayValue, + pending, + } = useTokenValues(transactionMeta); const TokenImage = ( { /> ); - const TokenValue = ( - <> + const TokenValue = + displayTransferValue === decodedTransferValue.toString() ? ( {`${decodedTransferValue || ''} ${tokenSymbol}`} - {fiatDisplayValue && ( - - {fiatDisplayValue} - - )} - + >{`${displayTransferValue} ${tokenSymbol || t('unknown')}`} + ) : ( + + {`${displayTransferValue} ${tokenSymbol || t('unknown')}`} + + ); + + const TokenFiatValue = fiatDisplayValue && ( + + {fiatDisplayValue} + ); if (pending) { @@ -81,6 +96,7 @@ const SendHeading = () => { > {TokenImage} {TokenValue} + {TokenFiatValue} ); }; diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap index e1d19241252b..f7a672912907 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap @@ -13,7 +13,7 @@ exports[`TokenTransferInfo renders correctly 1`] = `

- Unknown + 0 Unknown

Date: Thu, 24 Oct 2024 11:17:51 +0100 Subject: [PATCH 2/2] wip --- .../info/hooks/use-token-values.test.ts | 38 +------------------ .../confirm/info/hooks/use-token-values.ts | 34 ++++------------- .../info/shared/send-heading/send-heading.tsx | 23 ++++++----- 3 files changed, 22 insertions(+), 73 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts index e66c8f3046e8..1ca46bb20db6 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts @@ -5,11 +5,7 @@ import mockState from '../../../../../../../test/data/mock-state.json'; import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; -import { - roundDisplayValue, - toNonScientificString, - useTokenValues, -} from './use-token-values'; +import { toNonScientificString, useTokenValues } from './use-token-values'; import { useDecodedTransactionData } from './useDecodedTransactionData'; jest.mock('../../../../hooks/useAssetDetails', () => ({ @@ -131,38 +127,6 @@ describe('useTokenValues', () => { }); }); -describe('roundDisplayValue', () => { - const TEST_CASES = [ - { value: 0, rounded: '0' }, - { value: 0.0000009, rounded: '<0.000001' }, - { value: 0.0000456, rounded: '0.000046' }, - { value: 0.0004567, rounded: '0.000457' }, - { value: 0.003456, rounded: '0.00346' }, - { value: 0.023456, rounded: '0.0235' }, - { value: 0.125456, rounded: '0.125' }, - { value: 1.0034, rounded: '1.003' }, - { value: 1.034, rounded: '1.034' }, - { value: 1.3034, rounded: '1.303' }, - { value: 7, rounded: '7' }, - { value: 7.1, rounded: '7.1' }, - { value: 12.0345, rounded: '12.03' }, - { value: 121.456, rounded: '121.5' }, - { value: 1034.123, rounded: '1034' }, - { value: 47361034.006, rounded: '47361034' }, - { value: 12130982923409.555, rounded: '12130982923410' }, - ]; - - // @ts-expect-error This is missing from the Mocha type definitions - it.each(TEST_CASES)( - 'Round $value to "$rounded"', - ({ value, rounded }: { value: number; rounded: string }) => { - const actual = roundDisplayValue(value); - - expect(actual).toEqual(rounded); - }, - ); -}); - describe('toNonScientificString', () => { const TEST_CASES = [ { scientific: 1.23e-5, expanded: '0.0000123' }, diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts index 6fb3c38629f4..f416282ee4ef 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts @@ -3,10 +3,13 @@ import { isHexString } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; import { isBoolean } from 'lodash'; import { useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; import { Numeric } from '../../../../../../../shared/modules/Numeric'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; +import { getIntlLocale } from '../../../../../../ducks/locale/locale'; import { useFiatFormatter } from '../../../../../../hooks/useFiatFormatter'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; +import { formatAmount } from '../../../simulation-details/formatAmount'; import { useDecodedTransactionData } from './useDecodedTransactionData'; export const useTokenValues = (transactionMeta: TransactionMeta) => { @@ -56,7 +59,11 @@ export const useTokenValues = (transactionMeta: TransactionMeta) => { const fiatDisplayValue = fiatValue && fiatFormatter(fiatValue, { shorten: true }); - const displayTransferValue = roundDisplayValue(decodedTransferValue); + const locale = useSelector(getIntlLocale); + const displayTransferValue = formatAmount( + locale, + new BigNumber(decodedTransferValue), + ); return { decodedTransferValue: toNonScientificString(decodedTransferValue), @@ -66,31 +73,6 @@ export const useTokenValues = (transactionMeta: TransactionMeta) => { }; }; -export function roundDisplayValue(decodedTransferValue: number): string { - switch (true) { - case decodedTransferValue === 0: - return '0'; - case decodedTransferValue < 0.000001: - return '<0.000001'; - case decodedTransferValue < 0.001: - return parseFloat(decodedTransferValue.toFixed(6)).toString(); - case decodedTransferValue < 0.01: - return parseFloat(decodedTransferValue.toFixed(5)).toString(); - case decodedTransferValue < 0.1: - return parseFloat(decodedTransferValue.toFixed(4)).toString(); - case decodedTransferValue < 10: - return parseFloat(decodedTransferValue.toFixed(3)).toString(); - case decodedTransferValue < 100: - return parseFloat(decodedTransferValue.toFixed(2)).toString(); - case decodedTransferValue < 1000: - return parseFloat(decodedTransferValue.toFixed(1)).toString(); - case decodedTransferValue < 10000: - return parseFloat(decodedTransferValue.toFixed(0)).toString(); - default: - return parseFloat(decodedTransferValue.toFixed(0)).toString(); - } -} - export function toNonScientificString(num: number): string { if (num >= 10e-18) { return num.toFixed(18).replace(/\.?0+$/u, ''); diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx index 5bb250ad74f0..b6aff206a26a 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx @@ -8,6 +8,7 @@ import { Text, } from '../../../../../../../components/component-library'; import Tooltip from '../../../../../../../components/ui/tooltip'; +import { getIntlLocale } from '../../../../../../../ducks/locale/locale'; import { AlignItems, BackgroundColor, @@ -17,18 +18,19 @@ import { TextColor, TextVariant, } from '../../../../../../../helpers/constants/design-system'; -import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { MIN_AMOUNT } from '../../../../../../../hooks/useCurrencyDisplay'; import { getWatchedToken } from '../../../../../../../selectors'; import { MultichainState } from '../../../../../../../selectors/multichain'; import { useConfirmContext } from '../../../../../context/confirm'; +import { formatAmountMaxPrecision } from '../../../../simulation-details/formatAmount'; import { useTokenValues } from '../../hooks/use-token-values'; import { useTokenDetails } from '../../hooks/useTokenDetails'; import { ConfirmLoader } from '../confirm-loader/confirm-loader'; const SendHeading = () => { - const t = useI18nContext(); const { currentConfirmation: transactionMeta } = useConfirmContext(); + const locale = useSelector(getIntlLocale); const selectedToken = useSelector((state: MultichainState) => getWatchedToken(transactionMeta)(state), ); @@ -60,20 +62,21 @@ const SendHeading = () => { ); const TokenValue = - displayTransferValue === decodedTransferValue.toString() ? ( - {`${displayTransferValue} ${tokenSymbol || t('unknown')}`} - ) : ( + displayTransferValue === + `<${formatAmountMaxPrecision(locale, MIN_AMOUNT)}` ? ( {`${displayTransferValue} ${tokenSymbol || t('unknown')}`} + >{`${displayTransferValue} ${tokenSymbol}`} + ) : ( + {`${displayTransferValue} ${tokenSymbol}`} ); const TokenFiatValue = fiatDisplayValue && (