diff --git a/src/shared/utils/index.test.ts b/src/shared/utils/index.test.ts new file mode 100644 index 000000000..0fde2d88a --- /dev/null +++ b/src/shared/utils/index.test.ts @@ -0,0 +1,62 @@ +import { formatUsdValue, formatTokenValue } from './index' + +describe('formatUsdValue', () => { + test('formats basic USD values correctly', () => { + expect(formatUsdValue('5678.90')).toBe('5,678.90') + expect(formatUsdValue(1234567.89123)).toBe('1,234,567.89') + expect(formatUsdValue(1234567.89)).toBe('1,234,567.89') + expect(formatUsdValue(1234567)).toBe('1,234,567.00') + expect(formatUsdValue(1234.5)).toBe('1,234.50') + expect(formatUsdValue(1234)).toBe('1,234.00') + }) + + test('handles zero as a special case', () => { + expect(formatUsdValue(0)).toBe('0.00') + expect(formatUsdValue('0')).toBe('0.00') + }) + + test('formats negative USD values correctly', () => { + expect(formatUsdValue(-1234.56)).toBe('-1,234.56') + }) + + test('rounds to two decimal places', () => { + expect(formatUsdValue(1234.567)).toBe('1,234.57') + }) + + test('small amounts', () => { + expect(formatUsdValue(0.0000000099)).toBe('<0.01') + expect(formatUsdValue(0.009)).toBe('<0.01') + expect(formatUsdValue(0.0100000001)).toBe('0.01') + expect(formatUsdValue(0.01)).toBe('0.01') + expect(formatUsdValue(0.1)).toBe('0.10') + }) +}) + +describe('formatTokenValue', () => { + test('formats basic token values correctly', () => { + expect(formatTokenValue(1234)).toBe('1,234') + expect(formatTokenValue('5678.901234')).toBe('5,678.901234') + }) + + test('removes unnecessary trailing zeros', () => { + expect(formatTokenValue(1234.5)).toBe('1,234.5') + }) + + test('handles zero as a special case', () => { + expect(formatTokenValue(0)).toBe('0.00') + expect(formatTokenValue('0')).toBe('0.00') + }) + + test('formats very small token values correctly', () => { + expect(formatTokenValue(0.0000000099)).toBe('<0.00000001') + expect(formatTokenValue(0.000123)).toBe('0.000123') + }) + + test('formats negative token values correctly', () => { + expect(formatTokenValue(-1234.56789)).toBe('-1,234.56789') + }) + + test('rounds to six decimal places where applicable', () => { + expect(formatTokenValue(1234.567890123)).toBe('1,234.56789012') + }) +}) diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index 32347ad98..ac3c05d5a 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -90,6 +90,97 @@ export const formatTokenValues = (number: string | number) => { return longNumberArr.join('') } +interface FormatNumberOptions { + decimalPlaces?: number + useThousandSeparator?: boolean + isCurrency?: boolean +} + +/** + * Formats a number or a numeric string with the given options. + * @param num The number or string to format. + * @param options The formatting options to use. + * @returns The formatted number as a string. + */ +const formatNumber = ( + num: number | string, + options: FormatNumberOptions = {}, +): string => { + // Attempt to parse if num is a string + if (typeof num === 'string') { + const parsedNum = parseFloat(num) + if (isNaN(parsedNum)) { + return num + } + num = parsedNum + } + + // Immediately return "0.00" for zero values + if (num === 0) { + return '0.00' + } + + // Destructure with default values + const { + decimalPlaces = 8, + useThousandSeparator = true, + isCurrency = false, + } = options + + // Calculate the minimum value that can be displayed given the number of decimal places + const minValue = 1 / Math.pow(10, decimalPlaces) + + // Check for small positive amounts less than the minimum value + if (num > 0 && num < minValue) { + const smallAmountDisplay = `<0.${'0'.repeat(decimalPlaces - 1)}1` + return smallAmountDisplay + } + + // Format the number with fixed decimal places + let result = num.toFixed(isCurrency ? 2 : decimalPlaces) + + // For non-currency numbers, remove unnecessary trailing zeros + if (!isCurrency) { + result = result.replace(/(\.\d*?[1-9])0+$|\.0*$/, '$1') + } + + // Add thousand separators if enabled + if (useThousandSeparator) { + const parts = result.split('.') + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') + result = parts.join('.') + } + + return result +} + +/** + * Formats a number or a numeric string as a USD value. + * @param value The number or string to format. + * @returns The formatted USD value as a string. + */ +export const formatUsdValue = (value: number | string): string => + formatNumber(value, { + decimalPlaces: 2, + useThousandSeparator: true, + isCurrency: true, + }) + +/** + * Formats a number or a numeric string as a token value. + * @param value The number or string to format. + * @returns The formatted token value as a string. + */ +export const formatTokenValue = ( + value: number | string, + precision = 8, +): string => + formatNumber(value, { + decimalPlaces: precision, + useThousandSeparator: true, + isCurrency: false, + }) + export const errorHandler = (error: unknown) => { if (typeof error === 'object' && Object.hasOwn(error as object, 'message')) { const err = error as ErrorWithMessage