refactor: consolidate percent formatting util (#7467)

* refactor: consolidate percent formatting util

* refactor: use generic percent formatter

* fix: add descriptive comment for formatDelta
This commit is contained in:
cartcrom 2023-10-18 14:34:37 -04:00 committed by GitHub
parent c5f2df4bc0
commit 86fc15907a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 91 additions and 140 deletions

@ -161,7 +161,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable) const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
const shouldShowBuyFiatButton = useIsNotOriginCountry('GB') const shouldShowBuyFiatButton = useIsNotOriginCountry('GB')
const { formatNumber, formatPercent } = useFormatter() const { formatNumber, formatDelta } = useFormatter()
const shouldDisableNFTRoutes = useDisableNFTRoutes() const shouldDisableNFTRoutes = useDisableNFTRoutes()
@ -284,7 +284,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
{`${formatNumber({ {`${formatNumber({
input: Math.abs(absoluteChange as number), input: Math.abs(absoluteChange as number),
type: NumberType.PortfolioBalance, type: NumberType.PortfolioBalance,
})} (${formatPercent(percentChange)})`} })} (${formatDelta(percentChange)})`}
</ThemedText.BodySecondary> </ThemedText.BodySecondary>
</> </>
)} )}

@ -72,7 +72,7 @@ const TokenNameText = styled(ThemedText.SubHeader)`
type PortfolioToken = NonNullable<TokenBalance['token']> type PortfolioToken = NonNullable<TokenBalance['token']>
function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) { function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {
const { formatPercent } = useFormatter() const { formatDelta } = useFormatter()
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0 const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
const navigate = useNavigate() const navigate = useNavigate()
@ -124,7 +124,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
</ThemedText.SubHeader> </ThemedText.SubHeader>
<Row justify="flex-end"> <Row justify="flex-end">
<DeltaArrow delta={percentChange} /> <DeltaArrow delta={percentChange} />
<ThemedText.BodySecondary>{formatPercent(percentChange)}</ThemedText.BodySecondary> <ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
</Row> </Row>
</> </>
) )

@ -52,11 +52,11 @@ interface ChartDeltaProps {
function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) { function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) {
const delta = calculateDelta(startingPrice.value, endingPrice.value) const delta = calculateDelta(startingPrice.value, endingPrice.value)
const { formatPercent } = useFormatter() const { formatDelta } = useFormatter()
return ( return (
<DeltaContainer> <DeltaContainer>
{formatPercent(delta)} {formatDelta(delta)}
<DeltaArrow delta={delta} noColor={noColor} /> <DeltaArrow delta={delta} noColor={noColor} />
</DeltaContainer> </DeltaContainer>
) )

@ -22,7 +22,7 @@ export function FiatValue({
fiatValue: { data?: number; isLoading: boolean } fiatValue: { data?: number; isLoading: boolean }
priceImpact?: Percent priceImpact?: Percent
}) { }) {
const { formatNumber, formatPriceImpact } = useFormatter() const { formatNumber, formatPercent } = useFormatter()
const priceImpactColor = useMemo(() => { const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined if (!priceImpact) return undefined
@ -54,7 +54,7 @@ export function FiatValue({
<MouseoverTooltip <MouseoverTooltip
text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>} text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>}
> >
(<Trans>{formatPriceImpact(priceImpact)}</Trans>) (<Trans>{formatPercent(priceImpact.multiply(-1))}</Trans>)
</MouseoverTooltip> </MouseoverTooltip>
</ThemedText.BodySmall> </ThemedText.BodySmall>
)} )}

@ -129,7 +129,7 @@ interface TokenRowProps {
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => { export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
const addRecentlySearchedAsset = useAddRecentlySearchedAsset() const addRecentlySearchedAsset = useAddRecentlySearchedAsset()
const navigate = useNavigate() const navigate = useNavigate()
const { formatFiatPrice, formatPercent } = useFormatter() const { formatFiatPrice, formatDelta } = useFormatter()
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address
@ -194,7 +194,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
<DeltaArrow delta={token.market?.pricePercentChange?.value} /> <DeltaArrow delta={token.market?.pricePercentChange?.value} />
<ThemedText.BodySmall> <ThemedText.BodySmall>
<DeltaText delta={token.market?.pricePercentChange?.value}> <DeltaText delta={token.market?.pricePercentChange?.value}>
{formatPercent(Math.abs(token.market?.pricePercentChange?.value ?? 0))} {formatDelta(Math.abs(token.market?.pricePercentChange?.value ?? 0))}
</DeltaText> </DeltaText>
</ThemedText.BodySmall> </ThemedText.BodySmall>
</PriceChangeContainer> </PriceChangeContainer>

@ -38,23 +38,23 @@ const NUMBER_WITH_MAX_TWO_DECIMAL_PLACES = /^(?:\d*\.\d{0,2}|\d+)$/
const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000) const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000)
const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100) const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100)
function useFormatSlippageInput() { function useFormatPercentInput() {
const { formatSlippage } = useFormatter() const { formatPercent } = useFormatter()
return (slippage: Percent) => formatSlippage(slippage).slice(0, -1) // remove % sign return (slippage: Percent) => formatPercent(slippage).slice(0, -1) // remove % sign
} }
export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) { export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) {
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance() const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
const { formatSlippage } = useFormatter() const { formatPercent } = useFormatter()
const formatSlippageInput = useFormatSlippageInput() const formatPercentInput = useFormatPercentInput()
// In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`. // In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`.
// To do so, we use `autoSlippage` value. However, since users are likely to change that value, // To do so, we use `autoSlippage` value. However, since users are likely to change that value,
// we render it as a placeholder instead of a value. // we render it as a placeholder instead of a value.
const defaultSlippageInputValue = const defaultSlippageInputValue =
userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage) userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage)
? formatSlippageInput(userSlippageTolerance) ? formatPercentInput(userSlippageTolerance)
: '' : ''
// If user has previously entered a custom slippage, we want to show that value in the input field // If user has previously entered a custom slippage, we want to show that value in the input field
@ -124,7 +124,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
{userSlippageTolerance === SlippageTolerance.Auto ? ( {userSlippageTolerance === SlippageTolerance.Auto ? (
<Trans>Auto</Trans> <Trans>Auto</Trans>
) : ( ) : (
formatSlippage(userSlippageTolerance) formatPercent(userSlippageTolerance)
)} )}
</ThemedText.BodyPrimary> </ThemedText.BodyPrimary>
} }
@ -158,7 +158,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
<InputContainer gap="md" error={!!slippageError}> <InputContainer gap="md" error={!!slippageError}>
<Input <Input
data-testid="slippage-input" data-testid="slippage-input"
placeholder={formatSlippageInput(autoSlippage)} placeholder={formatPercentInput(autoSlippage)}
value={slippageInput} value={slippageInput}
onChange={(e) => parseSlippageInput(e.target.value)} onChange={(e) => parseSlippageInput(e.target.value)}
onBlur={() => { onBlur={() => {
@ -176,7 +176,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
<ThemedText.BodySmall color="deprecated_accentWarning"> <ThemedText.BodySmall color="deprecated_accentWarning">
{tooLow ? ( {tooLow ? (
<Trans> <Trans>
Slippage below {formatSlippage(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction Slippage below {formatPercent(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction
</Trans> </Trans>
) : ( ) : (
<Trans>Your transaction may be frontrun and result in an unfavorable trade.</Trans> <Trans>Your transaction may be frontrun and result in an unfavorable trade.</Trans>

@ -49,7 +49,7 @@ const IconContainerWithSlippage = styled(IconContainer)<{ displayWarning?: boole
const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => { const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => {
const [userSlippageTolerance] = useUserSlippageTolerance() const [userSlippageTolerance] = useUserSlippageTolerance()
const { formatSlippage } = useFormatter() const { formatPercent } = useFormatter()
if (userSlippageTolerance === SlippageTolerance.Auto || isUniswapXTrade(trade)) { if (userSlippageTolerance === SlippageTolerance.Auto || isUniswapXTrade(trade)) {
return ( return (
@ -64,7 +64,7 @@ const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => {
return ( return (
<IconContainerWithSlippage data-testid="settings-icon-with-slippage" gap="sm" displayWarning={isInvalidSlippage}> <IconContainerWithSlippage data-testid="settings-icon-with-slippage" gap="sm" displayWarning={isInvalidSlippage}>
<ThemedText.BodySmall> <ThemedText.BodySmall>
<Trans>{formatSlippage(userSlippageTolerance)} slippage</Trans> <Trans>{formatPercent(userSlippageTolerance)} slippage</Trans>
</ThemedText.BodySmall> </ThemedText.BodySmall>
<Icon /> <Icon />
</IconContainerWithSlippage> </IconContainerWithSlippage>

@ -442,7 +442,7 @@ interface LoadedRowProps {
/* Loaded State: row component with token information */ /* Loaded State: row component with token information */
export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HTMLDivElement>) => { export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HTMLDivElement>) => {
const { formatFiatPrice, formatNumber, formatPercent } = useFormatter() const { formatFiatPrice, formatNumber, formatDelta } = useFormatter()
const { tokenListIndex, tokenListLength, token, sortRank } = props const { tokenListIndex, tokenListLength, token, sortRank } = props
const filterString = useAtomValue(filterStringAtom) const filterString = useAtomValue(filterStringAtom)
@ -451,7 +451,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
const chainId = supportedChainIdFromGQLChain(filterNetwork) const chainId = supportedChainIdFromGQLChain(filterNetwork)
const timePeriod = useAtomValue(filterTimeAtom) const timePeriod = useAtomValue(filterTimeAtom)
const delta = token.market?.pricePercentChange?.value const delta = token.market?.pricePercentChange?.value
const formattedDelta = formatPercent(delta) const formattedDelta = formatDelta(delta)
const exploreTokenSelectedEventProperties = { const exploreTokenSelectedEventProperties = {
chain_id: chainId, chain_id: chainId,

@ -37,7 +37,7 @@ interface PriceImpactModalProps {
} }
export default function PriceImpactModal({ priceImpact, onDismiss, onContinue }: PriceImpactModalProps) { export default function PriceImpactModal({ priceImpact, onDismiss, onContinue }: PriceImpactModalProps) {
const { formatPriceImpact } = useFormatter() const { formatPercent } = useFormatter()
return ( return (
<Modal isOpen onDismiss={onDismiss}> <Modal isOpen onDismiss={onDismiss}>
@ -56,7 +56,7 @@ export default function PriceImpactModal({ priceImpact, onDismiss, onContinue }:
<Trans> <Trans>
This transaction will result in a{' '} This transaction will result in a{' '}
<ThemedText.BodyPrimary lineHeight="24px" color="critical" display="inline"> <ThemedText.BodyPrimary lineHeight="24px" color="critical" display="inline">
{formatPriceImpact(priceImpact)} ~{formatPercent(priceImpact)}
</ThemedText.BodyPrimary>{' '} </ThemedText.BodyPrimary>{' '}
price impact on the market price of this pool. Do you wish to continue? price impact on the market price of this pool. Do you wish to continue?
</Trans> </Trans>

@ -20,7 +20,7 @@ interface PriceImpactWarningProps {
} }
export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningProps) { export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningProps) {
const { formatPriceImpact } = useFormatter() const { formatPercent } = useFormatter()
const theme = useTheme() const theme = useTheme()
return ( return (
@ -41,7 +41,7 @@ export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningPr
</ThemedText.DeprecatedSubHeader> </ThemedText.DeprecatedSubHeader>
</RowFixed> </RowFixed>
<ThemedText.DeprecatedLabel textAlign="right" fontSize={14} color="critical"> <ThemedText.DeprecatedLabel textAlign="right" fontSize={14} color="critical">
{formatPriceImpact(priceImpact)} ~{formatPercent(priceImpact)}
</ThemedText.DeprecatedLabel> </ThemedText.DeprecatedLabel>
</RowBetween> </RowBetween>
</MouseoverTooltip> </MouseoverTooltip>

@ -103,9 +103,10 @@ function Loading({ width = 50 }: { width?: number }) {
return <LoadingRow data-testid="loading-row" height={15} width={width} /> return <LoadingRow data-testid="loading-row" height={15} width={width} />
} }
function ColoredPercentRow({ percent }: { percent: Percent }) { function ColoredPercentRow({ percent, estimate }: { percent: Percent; estimate?: boolean }) {
const { formatSlippage } = useFormatter() const { formatPercent } = useFormatter()
return <ColorWrapper textColor={getPriceImpactColor(percent)}>{formatSlippage(percent)}</ColorWrapper> const formattedPercent = (estimate ? '~' : '') + formatPercent(percent)
return <ColorWrapper textColor={getPriceImpactColor(percent)}>{formattedPercent}</ColorWrapper>
} }
function CurrencyAmountRow({ amount }: { amount: CurrencyAmount<Currency> }) { function CurrencyAmountRow({ amount }: { amount: CurrencyAmount<Currency> }) {
@ -136,7 +137,7 @@ type LineItemData = {
function useLineItem(props: SwapLineItemProps): LineItemData | undefined { function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
const { trade, syncing, allowedSlippage, type } = props const { trade, syncing, allowedSlippage, type } = props
const { formatNumber, formatSlippage } = useFormatter() const { formatNumber, formatPercent } = useFormatter()
const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto
const feesEnabled = useFeesEnabled() const feesEnabled = useFeesEnabled()
@ -179,7 +180,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
return { return {
Label: () => <Trans>Price impact</Trans>, Label: () => <Trans>Price impact</Trans>,
TooltipBody: () => <Trans>The impact your trade has on the market price of this pool.</Trans>, TooltipBody: () => <Trans>The impact your trade has on the market price of this pool.</Trans>,
Value: () => (isPreview ? <Loading /> : <ColoredPercentRow percent={trade.priceImpact} />), Value: () => (isPreview ? <Loading /> : <ColoredPercentRow percent={trade.priceImpact} estimate />),
} }
case SwapLineItemType.MAX_SLIPPAGE: case SwapLineItemType.MAX_SLIPPAGE:
return { return {
@ -187,7 +188,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
TooltipBody: () => <MaxSlippageTooltip {...props} />, TooltipBody: () => <MaxSlippageTooltip {...props} />,
Value: () => ( Value: () => (
<Row gap="8px"> <Row gap="8px">
{isAutoSlippage && <AutoBadge />} {formatSlippage(allowedSlippage)} {isAutoSlippage && <AutoBadge />} {formatPercent(allowedSlippage)}
</Row> </Row>
), ),
} }
@ -197,7 +198,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
return { return {
Label: () => ( Label: () => (
<> <>
<Trans>Fee</Trans> {trade.swapFee && `(${formatSlippage(trade.swapFee.percent)})`} <Trans>Fee</Trans> {trade.swapFee && `(${formatPercent(trade.swapFee.percent)})`}
</> </>
), ),
TooltipBody: () => <SwapFeeTooltipContent hasFee={Boolean(trade.swapFee)} />, TooltipBody: () => <SwapFeeTooltipContent hasFee={Boolean(trade.swapFee)} />,

@ -32,12 +32,12 @@ function RouteLabel({ trade }: { trade: SubmittableTrade }) {
} }
function PriceImpactRow({ trade }: { trade: ClassicTrade }) { function PriceImpactRow({ trade }: { trade: ClassicTrade }) {
const { formatPriceImpact } = useFormatter() const { formatPercent } = useFormatter()
return ( return (
<ThemedText.BodySmall color="neutral2"> <ThemedText.BodySmall color="neutral2">
<RowBetween> <RowBetween>
<Trans>Price Impact</Trans> <Trans>Price Impact</Trans>
<div>{formatPriceImpact(trade.priceImpact)}</div> <div>{formatPercent(trade.priceImpact)}</div>
</RowBetween> </RowBetween>
</ThemedText.BodySmall> </ThemedText.BodySmall>
) )

@ -347,7 +347,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>

@ -1661,7 +1661,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>
@ -3328,7 +3328,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>
@ -4995,7 +4995,7 @@ exports[`SwapLineItem.tsx exact output 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>
@ -6870,7 +6870,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>
@ -8745,7 +8745,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>

@ -287,7 +287,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
<span <span
class="" class=""
> >
-105566.373% ~-105566.373%
</span> </span>
</div> </div>
</div> </div>

@ -204,7 +204,7 @@ const StatItemText = styled(Text)`
` `
function StatItem({ title, value, delta }: { title: ReactNode; value: number; delta?: number }) { function StatItem({ title, value, delta }: { title: ReactNode; value: number; delta?: number }) {
const { formatNumber, formatPercent } = useFormatter() const { formatNumber, formatDelta } = useFormatter()
return ( return (
<StatItemColumn> <StatItemColumn>
@ -219,7 +219,7 @@ function StatItem({ title, value, delta }: { title: ReactNode; value: number; de
{!!delta && ( {!!delta && (
<Row width="max-content" padding="4px 0px"> <Row width="max-content" padding="4px 0px">
<DeltaArrow delta={delta} /> <DeltaArrow delta={delta} />
<ThemedText.BodySecondary>{formatPercent(delta)}</ThemedText.BodySecondary> <ThemedText.BodySecondary>{formatDelta(delta)}</ThemedText.BodySecondary>
</Row> </Row>
)} )}
</StatsTextContainer> </StatsTextContainer>

@ -356,71 +356,37 @@ describe('formatUSDPrice', () => {
}) })
}) })
describe('formatPriceImpact', () => { describe('formatPercent', () => {
beforeEach(() => { beforeEach(() => {
mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false })
mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true)
}) })
it('should correctly format undefined', () => { it('should correctly format undefined', () => {
const { formatPriceImpact } = renderHook(() => useFormatter()).result.current const { formatPercent } = renderHook(() => useFormatter()).result.current
expect(formatPriceImpact(undefined)).toBe('-') expect(formatPercent(undefined)).toBe('-')
})
it('correctly formats a percent with 3 significant digits', () => {
const { formatPriceImpact } = renderHook(() => useFormatter()).result.current
expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0.001%')
expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0.100%')
expect(formatPriceImpact(new Percent(-1, 100))).toBe('1.000%')
expect(formatPriceImpact(new Percent(-1, 10))).toBe('10.000%')
expect(formatPriceImpact(new Percent(-1, 1))).toBe('100.000%')
})
it('correctly formats a percent with 3 significant digits with french locale', () => {
mocked(useActiveLocale).mockReturnValue('fr-FR')
const { formatPriceImpact } = renderHook(() => useFormatter()).result.current
expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0,001%')
expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0,100%')
expect(formatPriceImpact(new Percent(-1, 100))).toBe('1,000%')
expect(formatPriceImpact(new Percent(-1, 10))).toBe('10,000%')
expect(formatPriceImpact(new Percent(-1, 1))).toBe('100,000%')
})
})
describe('formatSlippage', () => {
beforeEach(() => {
mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false })
mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true)
})
it('should correctly format undefined', () => {
const { formatSlippage } = renderHook(() => useFormatter()).result.current
expect(formatSlippage(undefined)).toBe('-')
}) })
it('correctly formats a percent with no trailing digits', () => { it('correctly formats a percent with no trailing digits', () => {
const { formatSlippage } = renderHook(() => useFormatter()).result.current const { formatPercent } = renderHook(() => useFormatter()).result.current
expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%') expect(formatPercent(new Percent(1, 100000))).toBe('0.001%')
expect(formatSlippage(new Percent(1, 1000))).toBe('0.1%') expect(formatPercent(new Percent(1, 1000))).toBe('0.1%')
expect(formatSlippage(new Percent(1, 100))).toBe('1%') expect(formatPercent(new Percent(1, 100))).toBe('1%')
expect(formatSlippage(new Percent(1, 10))).toBe('10%') expect(formatPercent(new Percent(1, 10))).toBe('10%')
expect(formatSlippage(new Percent(1, 1))).toBe('100%') expect(formatPercent(new Percent(1, 1))).toBe('100%')
}) })
it('correctly formats a percent with french locale', () => { it('correctly formats a percent with french locale', () => {
mocked(useActiveLocale).mockReturnValue('fr-FR') mocked(useActiveLocale).mockReturnValue('fr-FR')
const { formatSlippage } = renderHook(() => useFormatter()).result.current const { formatPercent } = renderHook(() => useFormatter()).result.current
expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%') expect(formatPercent(new Percent(1, 100000))).toBe('0,001%')
expect(formatSlippage(new Percent(1, 1000))).toBe('0,1%') expect(formatPercent(new Percent(1, 1000))).toBe('0,1%')
expect(formatSlippage(new Percent(1, 100))).toBe('1%') expect(formatPercent(new Percent(1, 100))).toBe('1%')
expect(formatSlippage(new Percent(1, 10))).toBe('10%') expect(formatPercent(new Percent(1, 10))).toBe('10%')
expect(formatSlippage(new Percent(1, 1))).toBe('100%') expect(formatPercent(new Percent(1, 1))).toBe('100%')
}) })
}) })
@ -461,36 +427,36 @@ describe('formatReviewSwapCurrencyAmount', () => {
}) })
}) })
describe('formatPercent', () => { describe('formatDelta', () => {
beforeEach(() => { beforeEach(() => {
mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false })
mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true)
}) })
it.each([[null], [undefined], [Infinity], [NaN]])('should correctly format %p', (value) => { it.each([[null], [undefined], [Infinity], [NaN]])('should correctly format %p', (value) => {
const { formatPercent } = renderHook(() => useFormatter()).result.current const { formatDelta } = renderHook(() => useFormatter()).result.current
expect(formatPercent(value)).toBe('-') expect(formatDelta(value)).toBe('-')
}) })
it('correctly formats a percent with 2 decimal places', () => { it('correctly formats a percent with 2 decimal places', () => {
const { formatPercent } = renderHook(() => useFormatter()).result.current const { formatDelta } = renderHook(() => useFormatter()).result.current
expect(formatPercent(0)).toBe('0.00%') expect(formatDelta(0)).toBe('0.00%')
expect(formatPercent(0.1)).toBe('0.10%') expect(formatDelta(0.1)).toBe('0.10%')
expect(formatPercent(1)).toBe('1.00%') expect(formatDelta(1)).toBe('1.00%')
expect(formatPercent(10)).toBe('10.00%') expect(formatDelta(10)).toBe('10.00%')
expect(formatPercent(100)).toBe('100.00%') expect(formatDelta(100)).toBe('100.00%')
}) })
it('correctly formats a percent with 2 decimal places in french locale', () => { it('correctly formats a percent with 2 decimal places in french locale', () => {
mocked(useActiveLocale).mockReturnValue('fr-FR') mocked(useActiveLocale).mockReturnValue('fr-FR')
const { formatPercent } = renderHook(() => useFormatter()).result.current const { formatDelta } = renderHook(() => useFormatter()).result.current
expect(formatPercent(0)).toBe('0,00%') expect(formatDelta(0)).toBe('0,00%')
expect(formatPercent(0.1)).toBe('0,10%') expect(formatDelta(0.1)).toBe('0,10%')
expect(formatPercent(1)).toBe('1,00%') expect(formatDelta(1)).toBe('1,00%')
expect(formatPercent(10)).toBe('10,00%') expect(formatDelta(10)).toBe('10,00%')
expect(formatPercent(100)).toBe('100,00%') expect(formatDelta(100)).toBe('100,00%')
}) })
}) })

@ -466,31 +466,22 @@ function formatCurrencyAmount({
}) })
} }
function formatPriceImpact(priceImpact: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE): string { function formatPercent(percent: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE) {
if (!priceImpact) return '-' if (!percent) return '-'
return `${Number(priceImpact.multiply(-1).toFixed(3)).toLocaleString(locale, { return `${Number(percent.toFixed(3)).toLocaleString(locale, {
minimumFractionDigits: 3,
maximumFractionDigits: 3, maximumFractionDigits: 3,
useGrouping: false, useGrouping: false,
})}%` })}%`
} }
function formatSlippage(slippage: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE) { // Used to format floats representing percent change with fixed decimal places
if (!slippage) return '-' function formatDelta(delta: Nullish<number>, locale: SupportedLocale = DEFAULT_LOCALE) {
if (delta === null || delta === undefined || delta === Infinity || isNaN(delta)) {
return `${Number(slippage.toFixed(3)).toLocaleString(locale, {
maximumFractionDigits: 3,
useGrouping: false,
})}%`
}
function formatPercent(percent: Nullish<number>, locale: SupportedLocale = DEFAULT_LOCALE) {
if (percent === null || percent === undefined || percent === Infinity || isNaN(percent)) {
return '-' return '-'
} }
return `${Number(Math.abs(percent).toFixed(2)).toLocaleString(locale, { return `${Number(Math.abs(delta).toFixed(2)).toLocaleString(locale, {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
useGrouping: false, useGrouping: false,
@ -694,21 +685,11 @@ export function useFormatter() {
[currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith]
) )
const formatPriceImpactWithLocales = useCallback(
(priceImpact: Percent | undefined) => formatPriceImpact(priceImpact, formatterLocale),
[formatterLocale]
)
const formatReviewSwapCurrencyAmountWithLocales = useCallback( const formatReviewSwapCurrencyAmountWithLocales = useCallback(
(amount: CurrencyAmount<Currency>) => formatReviewSwapCurrencyAmount(amount, formatterLocale), (amount: CurrencyAmount<Currency>) => formatReviewSwapCurrencyAmount(amount, formatterLocale),
[formatterLocale] [formatterLocale]
) )
const formatSlippageWithLocales = useCallback(
(slippage: Percent | undefined) => formatSlippage(slippage, formatterLocale),
[formatterLocale]
)
const formatTickPriceWithLocales = useCallback( const formatTickPriceWithLocales = useCallback(
(options: Omit<FormatTickPriceOptions, LocalesType>) => (options: Omit<FormatTickPriceOptions, LocalesType>) =>
formatTickPrice({ formatTickPrice({
@ -742,8 +723,13 @@ export function useFormatter() {
[currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith]
) )
const formatDeltaWithLocales = useCallback(
(percent: Nullish<number>) => formatDelta(percent, formatterLocale),
[formatterLocale]
)
const formatPercentWithLocales = useCallback( const formatPercentWithLocales = useCallback(
(percent: Nullish<number>) => formatPercent(percent, formatterLocale), (percent: Percent | undefined) => formatPercent(percent, formatterLocale),
[formatterLocale] [formatterLocale]
) )
@ -753,11 +739,10 @@ export function useFormatter() {
formatFiatPrice: formatFiatPriceWithLocales, formatFiatPrice: formatFiatPriceWithLocales,
formatNumber: formatNumberWithLocales, formatNumber: formatNumberWithLocales,
formatNumberOrString: formatNumberOrStringWithLocales, formatNumberOrString: formatNumberOrStringWithLocales,
formatDelta: formatDeltaWithLocales,
formatPercent: formatPercentWithLocales, formatPercent: formatPercentWithLocales,
formatPrice: formatPriceWithLocales, formatPrice: formatPriceWithLocales,
formatPriceImpact: formatPriceImpactWithLocales,
formatReviewSwapCurrencyAmount: formatReviewSwapCurrencyAmountWithLocales, formatReviewSwapCurrencyAmount: formatReviewSwapCurrencyAmountWithLocales,
formatSlippage: formatSlippageWithLocales,
formatTickPrice: formatTickPriceWithLocales, formatTickPrice: formatTickPriceWithLocales,
}), }),
[ [
@ -765,11 +750,10 @@ export function useFormatter() {
formatFiatPriceWithLocales, formatFiatPriceWithLocales,
formatNumberOrStringWithLocales, formatNumberOrStringWithLocales,
formatNumberWithLocales, formatNumberWithLocales,
formatDeltaWithLocales,
formatPercentWithLocales, formatPercentWithLocales,
formatPriceImpactWithLocales,
formatPriceWithLocales, formatPriceWithLocales,
formatReviewSwapCurrencyAmountWithLocales, formatReviewSwapCurrencyAmountWithLocales,
formatSlippageWithLocales,
formatTickPriceWithLocales, formatTickPriceWithLocales,
] ]
) )