Skip to content

Commit a4fa93f

Browse files
authored
Merge pull request #312 from cladiousdoff-glitch/feat/add-number-format
Add unit tests for number formatting utilities
2 parents e26f53a + 46540da commit a4fa93f

1 file changed

Lines changed: 215 additions & 0 deletions

File tree

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
formatNumber,
4+
formatCompactNumber,
5+
formatFollowerCount,
6+
formatPercent,
7+
} from '../numberFormat.utils';
8+
9+
// ---------------------------------------------------------------------------
10+
// Feature: Compact number formatting for supply values
11+
// Validates: Acceptance Criteria 1, 2, 3
12+
// ---------------------------------------------------------------------------
13+
describe('formatCompactNumber: Supply Value Formatting', () => {
14+
describe('Large supply values are abbreviated', () => {
15+
it('formats 1,200 as "1.2K"', () => {
16+
expect(formatCompactNumber(1200)).toBe('1.2K');
17+
});
18+
19+
it('formats 4,500 as "4.5K"', () => {
20+
expect(formatCompactNumber(4500)).toBe('4.5K');
21+
});
22+
23+
it('formats 1,000,000 as "1M"', () => {
24+
expect(formatCompactNumber(1000000)).toBe('1M');
25+
});
26+
27+
it('formats 4,500,000 as "4.5M"', () => {
28+
expect(formatCompactNumber(4500000)).toBe('4.5M');
29+
});
30+
31+
it('formats 1,200,000,000 as "1.2B"', () => {
32+
expect(formatCompactNumber(1200000000)).toBe('1.2B');
33+
});
34+
});
35+
36+
describe('Small supply values display as-is', () => {
37+
it('formats 0 as "0"', () => {
38+
expect(formatCompactNumber(0)).toBe('0');
39+
});
40+
41+
it('formats 1 as "1"', () => {
42+
expect(formatCompactNumber(1)).toBe('1');
43+
});
44+
45+
it('formats 999 as "999"', () => {
46+
expect(formatCompactNumber(999)).toBe('999');
47+
});
48+
49+
it('formats 500 as "500"', () => {
50+
expect(formatCompactNumber(500)).toBe('500');
51+
});
52+
});
53+
54+
describe('Edge cases and missing data', () => {
55+
it('returns "—" for null', () => {
56+
expect(formatCompactNumber(null)).toBe('—');
57+
});
58+
59+
it('returns "—" for undefined', () => {
60+
expect(formatCompactNumber(undefined)).toBe('—');
61+
});
62+
63+
it('returns "—" for NaN', () => {
64+
expect(formatCompactNumber(NaN)).toBe('—');
65+
});
66+
67+
it('returns "—" for Infinity', () => {
68+
expect(formatCompactNumber(Infinity)).toBe('—');
69+
});
70+
71+
it('returns "—" for negative Infinity', () => {
72+
expect(formatCompactNumber(-Infinity)).toBe('—');
73+
});
74+
});
75+
});
76+
77+
// ---------------------------------------------------------------------------
78+
// Feature: Full number formatting with tooltip values
79+
// Validates: Acceptance Criteria 2 (full value accessible via tooltip)
80+
// ---------------------------------------------------------------------------
81+
describe('formatNumber: Full Value Display for Tooltips', () => {
82+
it('formats 1,200 as "1,200" in full style', () => {
83+
expect(formatNumber(1200, { style: 'full' })).toBe('1,200');
84+
});
85+
86+
it('formats 4,500,000 as "4,500,000" in full style', () => {
87+
expect(formatNumber(4500000, { style: 'full' })).toBe('4,500,000');
88+
});
89+
90+
it('default style is full, returns "1,200"', () => {
91+
expect(formatNumber(1200)).toBe('1,200');
92+
});
93+
94+
it('returns "—" for null', () => {
95+
expect(formatNumber(null)).toBe('—');
96+
});
97+
98+
it('returns "—" for undefined', () => {
99+
expect(formatNumber(undefined)).toBe('—');
100+
});
101+
102+
it('handles negative numbers: -1200 → "-1,200"', () => {
103+
expect(formatNumber(-1200)).toBe('-1,200');
104+
});
105+
});
106+
107+
// ---------------------------------------------------------------------------
108+
// Feature: Configurable fraction digits
109+
// Validates: Precision control for different use cases
110+
// ---------------------------------------------------------------------------
111+
describe('formatCompactNumber: Configurable precision', () => {
112+
it('respects maximumFractionDigits option', () => {
113+
expect(formatCompactNumber(1234, { maximumFractionDigits: 0 })).toBe('1K');
114+
expect(formatCompactNumber(1234, { maximumFractionDigits: 2 })).toBe('1.23K');
115+
});
116+
117+
it('respects minimumFractionDigits option', () => {
118+
expect(formatCompactNumber(1000000, { minimumFractionDigits: 2 })).toBe('1.00M');
119+
});
120+
});
121+
122+
// ---------------------------------------------------------------------------
123+
// Feature: Follower count formatting (legacy helper)
124+
// Validates: Backward compatibility
125+
// ---------------------------------------------------------------------------
126+
describe('formatFollowerCount: Legacy follower abbreviation', () => {
127+
it('formats 1,200 as "1.2K"', () => {
128+
expect(formatFollowerCount(1200)).toBe('1.2K');
129+
});
130+
131+
it('formats 1,000,000 as "1M"', () => {
132+
expect(formatFollowerCount(1000000)).toBe('1M');
133+
});
134+
135+
it('formats 999 as "999"', () => {
136+
expect(formatFollowerCount(999)).toBe('999');
137+
});
138+
139+
it('formats 0 as "0"', () => {
140+
expect(formatFollowerCount(0)).toBe('0');
141+
});
142+
143+
it('removes ".0" suffix: 1,000 → "1K" not "1.0K"', () => {
144+
expect(formatFollowerCount(1000)).toBe('1K');
145+
});
146+
});
147+
148+
// ---------------------------------------------------------------------------
149+
// Feature: Percentage formatting
150+
// Validates: Acceptance Criteria for badge display
151+
// ---------------------------------------------------------------------------
152+
describe('formatPercent: Percentage badge formatting', () => {
153+
it('formats 12.5 as "12.5%"', () => {
154+
expect(formatPercent(12.5)).toBe('12.5%');
155+
});
156+
157+
it('formats positive value with sign option: +12.5%', () => {
158+
expect(formatPercent(12.5, { signed: true })).toBe('+12.5%');
159+
});
160+
161+
it('formats negative value with sign option: -12.5%', () => {
162+
expect(formatPercent(-12.5, { signed: true })).toBe('-12.5%');
163+
});
164+
165+
it('returns "—" for null', () => {
166+
expect(formatPercent(null)).toBe('—');
167+
});
168+
169+
it('returns "—" for undefined', () => {
170+
expect(formatPercent(undefined)).toBe('—');
171+
});
172+
173+
it('returns custom placeholder when provided', () => {
174+
expect(formatPercent(null, { emptyPlaceholder: 'N/A' })).toBe('N/A');
175+
});
176+
177+
it('respects precision options', () => {
178+
expect(formatPercent(12.567, { maximumFractionDigits: 1 })).toBe('12.6%');
179+
});
180+
});
181+
182+
// ---------------------------------------------------------------------------
183+
// Integration: Verify compact display + full tooltip pattern
184+
// Validates: Complete acceptance criteria
185+
// ---------------------------------------------------------------------------
186+
describe('Integration: Compact display with full tooltip pattern', () => {
187+
it('compact format (1.2K) + full format (1,200) enables tooltip workflow', () => {
188+
const supply = 1200;
189+
const displayValue = formatCompactNumber(supply);
190+
const tooltipValue = formatNumber(supply);
191+
192+
expect(displayValue).toBe('1.2K');
193+
expect(tooltipValue).toBe('1,200');
194+
// Both are present and different — enables title attribute pattern
195+
expect(displayValue).not.toBe(tooltipValue);
196+
});
197+
198+
it('large supply (4.5M compact, 4,500,000 full)', () => {
199+
const supply = 4500000;
200+
const displayValue = formatCompactNumber(supply);
201+
const tooltipValue = formatNumber(supply);
202+
203+
expect(displayValue).toBe('4.5M');
204+
expect(tooltipValue).toBe('4,500,000');
205+
});
206+
207+
it('small supply stays the same for both (reduces noise)', () => {
208+
const supply = 42;
209+
const displayValue = formatCompactNumber(supply);
210+
const tooltipValue = formatNumber(supply);
211+
212+
expect(displayValue).toBe('42');
213+
expect(tooltipValue).toBe('42');
214+
});
215+
});

0 commit comments

Comments
 (0)