From 94564d003c361704a249c5dadd469dbbd350274e Mon Sep 17 00:00:00 2001 From: Marvell69 Date: Thu, 28 May 2026 14:07:36 +0100 Subject: [PATCH 1/2] Add unit tests for number formatting utilities --- .../__tests__/numberFormat.utils.test.ts | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/utils/__tests__/numberFormat.utils.test.ts diff --git a/src/utils/__tests__/numberFormat.utils.test.ts b/src/utils/__tests__/numberFormat.utils.test.ts new file mode 100644 index 0000000..57e5904 --- /dev/null +++ b/src/utils/__tests__/numberFormat.utils.test.ts @@ -0,0 +1,216 @@ +import { describe, it, expect } from 'vitest'; +import { + formatNumber, + formatCompactNumber, + formatFollowerCount, + formatPercent, + type FormatNumberOptions, +} from '../numberFormat.utils'; + +// --------------------------------------------------------------------------- +// Feature: Compact number formatting for supply values +// Validates: Acceptance Criteria 1, 2, 3 +// --------------------------------------------------------------------------- +describe('formatCompactNumber: Supply Value Formatting', () => { + describe('Large supply values are abbreviated', () => { + it('formats 1,200 as "1.2K"', () => { + expect(formatCompactNumber(1200)).toBe('1.2K'); + }); + + it('formats 4,500 as "4.5K"', () => { + expect(formatCompactNumber(4500)).toBe('4.5K'); + }); + + it('formats 1,000,000 as "1M"', () => { + expect(formatCompactNumber(1000000)).toBe('1M'); + }); + + it('formats 4,500,000 as "4.5M"', () => { + expect(formatCompactNumber(4500000)).toBe('4.5M'); + }); + + it('formats 1,200,000,000 as "1.2B"', () => { + expect(formatCompactNumber(1200000000)).toBe('1.2B'); + }); + }); + + describe('Small supply values display as-is', () => { + it('formats 0 as "0"', () => { + expect(formatCompactNumber(0)).toBe('0'); + }); + + it('formats 1 as "1"', () => { + expect(formatCompactNumber(1)).toBe('1'); + }); + + it('formats 999 as "999"', () => { + expect(formatCompactNumber(999)).toBe('999'); + }); + + it('formats 500 as "500"', () => { + expect(formatCompactNumber(500)).toBe('500'); + }); + }); + + describe('Edge cases and missing data', () => { + it('returns "—" for null', () => { + expect(formatCompactNumber(null)).toBe('—'); + }); + + it('returns "—" for undefined', () => { + expect(formatCompactNumber(undefined)).toBe('—'); + }); + + it('returns "—" for NaN', () => { + expect(formatCompactNumber(NaN)).toBe('—'); + }); + + it('returns "—" for Infinity', () => { + expect(formatCompactNumber(Infinity)).toBe('—'); + }); + + it('returns "—" for negative Infinity', () => { + expect(formatCompactNumber(-Infinity)).toBe('—'); + }); + }); +}); + +// --------------------------------------------------------------------------- +// Feature: Full number formatting with tooltip values +// Validates: Acceptance Criteria 2 (full value accessible via tooltip) +// --------------------------------------------------------------------------- +describe('formatNumber: Full Value Display for Tooltips', () => { + it('formats 1,200 as "1,200" in full style', () => { + expect(formatNumber(1200, { style: 'full' })).toBe('1,200'); + }); + + it('formats 4,500,000 as "4,500,000" in full style', () => { + expect(formatNumber(4500000, { style: 'full' })).toBe('4,500,000'); + }); + + it('default style is full, returns "1,200"', () => { + expect(formatNumber(1200)).toBe('1,200'); + }); + + it('returns "—" for null', () => { + expect(formatNumber(null)).toBe('—'); + }); + + it('returns "—" for undefined', () => { + expect(formatNumber(undefined)).toBe('—'); + }); + + it('handles negative numbers: -1200 → "-1,200"', () => { + expect(formatNumber(-1200)).toBe('-1,200'); + }); +}); + +// --------------------------------------------------------------------------- +// Feature: Configurable fraction digits +// Validates: Precision control for different use cases +// --------------------------------------------------------------------------- +describe('formatCompactNumber: Configurable precision', () => { + it('respects maximumFractionDigits option', () => { + expect(formatCompactNumber(1234, { maximumFractionDigits: 0 })).toBe('1K'); + expect(formatCompactNumber(1234, { maximumFractionDigits: 2 })).toBe('1.23K'); + }); + + it('respects minimumFractionDigits option', () => { + expect(formatCompactNumber(1000000, { minimumFractionDigits: 2 })).toBe('1.00M'); + }); +}); + +// --------------------------------------------------------------------------- +// Feature: Follower count formatting (legacy helper) +// Validates: Backward compatibility +// --------------------------------------------------------------------------- +describe('formatFollowerCount: Legacy follower abbreviation', () => { + it('formats 1,200 as "1.2K"', () => { + expect(formatFollowerCount(1200)).toBe('1.2K'); + }); + + it('formats 1,000,000 as "1M"', () => { + expect(formatFollowerCount(1000000)).toBe('1M'); + }); + + it('formats 999 as "999"', () => { + expect(formatFollowerCount(999)).toBe('999'); + }); + + it('formats 0 as "0"', () => { + expect(formatFollowerCount(0)).toBe('0'); + }); + + it('removes ".0" suffix: 1,000 → "1K" not "1.0K"', () => { + expect(formatFollowerCount(1000)).toBe('1K'); + }); +}); + +// --------------------------------------------------------------------------- +// Feature: Percentage formatting +// Validates: Acceptance Criteria for badge display +// --------------------------------------------------------------------------- +describe('formatPercent: Percentage badge formatting', () => { + it('formats 12.5 as "12.5%"', () => { + expect(formatPercent(12.5)).toBe('12.5%'); + }); + + it('formats positive value with sign option: +12.5%', () => { + expect(formatPercent(12.5, { signed: true })).toBe('+12.5%'); + }); + + it('formats negative value with sign option: -12.5%', () => { + expect(formatPercent(-12.5, { signed: true })).toBe('-12.5%'); + }); + + it('returns "—" for null', () => { + expect(formatPercent(null)).toBe('—'); + }); + + it('returns "—" for undefined', () => { + expect(formatPercent(undefined)).toBe('—'); + }); + + it('returns custom placeholder when provided', () => { + expect(formatPercent(null, { emptyPlaceholder: 'N/A' })).toBe('N/A'); + }); + + it('respects precision options', () => { + expect(formatPercent(12.567, { maximumFractionDigits: 1 })).toBe('12.6%'); + }); +}); + +// --------------------------------------------------------------------------- +// Integration: Verify compact display + full tooltip pattern +// Validates: Complete acceptance criteria +// --------------------------------------------------------------------------- +describe('Integration: Compact display with full tooltip pattern', () => { + it('compact format (1.2K) + full format (1,200) enables tooltip workflow', () => { + const supply = 1200; + const displayValue = formatCompactNumber(supply); + const tooltipValue = formatNumber(supply); + + expect(displayValue).toBe('1.2K'); + expect(tooltipValue).toBe('1,200'); + // Both are present and different — enables title attribute pattern + expect(displayValue).not.toBe(tooltipValue); + }); + + it('large supply (4.5M compact, 4,500,000 full)', () => { + const supply = 4500000; + const displayValue = formatCompactNumber(supply); + const tooltipValue = formatNumber(supply); + + expect(displayValue).toBe('4.5M'); + expect(tooltipValue).toBe('4,500,000'); + }); + + it('small supply stays the same for both (reduces noise)', () => { + const supply = 42; + const displayValue = formatCompactNumber(supply); + const tooltipValue = formatNumber(supply); + + expect(displayValue).toBe('42'); + expect(tooltipValue).toBe('42'); + }); +}); From 46540da3e3ca2127cf7c1cba0f836c9660298e84 Mon Sep 17 00:00:00 2001 From: Marvell69 Date: Thu, 28 May 2026 18:35:03 +0100 Subject: [PATCH 2/2] test: add unit tests for number formatting utilities --- src/utils/__tests__/numberFormat.utils.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/__tests__/numberFormat.utils.test.ts b/src/utils/__tests__/numberFormat.utils.test.ts index 57e5904..b215130 100644 --- a/src/utils/__tests__/numberFormat.utils.test.ts +++ b/src/utils/__tests__/numberFormat.utils.test.ts @@ -4,7 +4,6 @@ import { formatCompactNumber, formatFollowerCount, formatPercent, - type FormatNumberOptions, } from '../numberFormat.utils'; // ---------------------------------------------------------------------------