From 505094650f4291f3fcdc4b7478fc0486bf459ec3 Mon Sep 17 00:00:00 2001 From: Yash Singh <123385188+yashs33244@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:59:47 +0530 Subject: [PATCH] fix(twenty-shared): derive short-number suffix from the rounded value (#21591) `formatToShortNumber` (`packages/twenty-shared/src/utils/format/formatToShortNumber.ts`) picked the unit suffix from the **raw** value but printed the **rounded** figure, so `999999` rendered as `"1000k"` instead of `"1m"`, and `999999999` as `"1000m"` instead of `"1b"`. This affects number/currency cells, column-footer aggregates, and dashboard charts. The fix replaces the hard-coded band branches with a promotion loop that derives the suffix from the rounded display value, so the suffix and figure always agree at boundaries. Adds boundary, just-below-boundary, and negative-boundary tests. Red-green proven: the two new boundary tests fail on the original source (`expected "1m" but got "1000k"`); the 11 pre-existing tests still pass; all 13 pass with the fix. Verified with a standalone strict `tsc` (0 errors) and oxlint on both changed files. Review in cubic --- .../__tests__/formatToShortNumber.test.ts | 15 ++++++++++ .../src/utils/format/formatToShortNumber.ts | 28 +++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/twenty-shared/src/utils/__tests__/formatToShortNumber.test.ts b/packages/twenty-shared/src/utils/__tests__/formatToShortNumber.test.ts index e2504cea10..05fe0e29cd 100644 --- a/packages/twenty-shared/src/utils/__tests__/formatToShortNumber.test.ts +++ b/packages/twenty-shared/src/utils/__tests__/formatToShortNumber.test.ts @@ -30,6 +30,17 @@ describe('formatToShortNumber', () => { expect(formatToShortNumber(0)).toBe('0'); }); + it('promotes to the next unit when rounding reaches the boundary', () => { + expect(formatToShortNumber(999999)).toBe('1m'); + expect(formatToShortNumber(999950)).toBe('1m'); + expect(formatToShortNumber(999999999)).toBe('1b'); + }); + + it('does not promote just below a rounding boundary', () => { + expect(formatToShortNumber(999949)).toBe('999.9k'); + expect(formatToShortNumber(999)).toBe('999'); + }); + describe('negative numbers', () => { it('formats negative numbers less than 1000 correctly', () => { expect(formatToShortNumber(-500)).toBe('-500'); @@ -50,5 +61,9 @@ describe('formatToShortNumber', () => { expect(formatToShortNumber(-1200000000)).toBe('-1.2b'); expect(formatToShortNumber(-987654321987)).toBe('-987.7b'); }); + + it('promotes negative numbers when rounding reaches the boundary', () => { + expect(formatToShortNumber(-999999)).toBe('-1m'); + }); }); }); diff --git a/packages/twenty-shared/src/utils/format/formatToShortNumber.ts b/packages/twenty-shared/src/utils/format/formatToShortNumber.ts index 43359643f4..fb83792ab5 100644 --- a/packages/twenty-shared/src/utils/format/formatToShortNumber.ts +++ b/packages/twenty-shared/src/utils/format/formatToShortNumber.ts @@ -2,25 +2,17 @@ export const formatToShortNumber = (amount: number) => { const sign = amount < 0 ? '-' : ''; const absoluteAmount = Math.abs(amount); - if (absoluteAmount < 1000) { - return sign + absoluteAmount.toFixed(1).replace(/\.?0+$/, ''); - } + const format = (scaled: number, suffix: string) => + sign + scaled.toFixed(1).replace(/\.?0+$/, '') + suffix; - if (absoluteAmount < 1_000_000) { - return ( - sign + (absoluteAmount / 1000).toFixed(1).replace(/\.?0+$/, '') + 'k' - ); + if (Number(absoluteAmount.toFixed(1)) < 1000) { + return format(absoluteAmount, ''); } - - if (absoluteAmount < 1_000_000_000) { - return ( - sign + (absoluteAmount / 1_000_000).toFixed(1).replace(/\.?0+$/, '') + 'm' - ); + if (Number((absoluteAmount / 1_000).toFixed(1)) < 1000) { + return format(absoluteAmount / 1_000, 'k'); } - - return ( - sign + - (absoluteAmount / 1_000_000_000).toFixed(1).replace(/\.?0+$/, '') + - 'b' - ); + if (Number((absoluteAmount / 1_000_000).toFixed(1)) < 1000) { + return format(absoluteAmount / 1_000_000, 'm'); + } + return format(absoluteAmount / 1_000_000_000, 'b'); };