Skip to content

Commit 0e1ca8d

Browse files
committed
fix(popup): don't crash when not using 3-letter currencies
1 parent b56eca8 commit 0e1ca8d

File tree

2 files changed

+79
-4
lines changed

2 files changed

+79
-4
lines changed

src/pages/shared/lib/utils.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { formatNumber } from '@/pages/shared/lib/utils';
1+
import {
2+
formatNumber,
3+
formatCurrency,
4+
getCurrencySymbol,
5+
} from '@/pages/shared/lib/utils';
26

37
describe('formatNumber', () => {
48
it('should display right format for integers', () => {
@@ -43,3 +47,52 @@ describe('formatNumber', () => {
4347
expect(formatNumber(0.000100009, 9)).toEqual('0.000100009');
4448
});
4549
});
50+
51+
describe('getCurrencySymbol', () => {
52+
it('should return currency symbol for common cases', () => {
53+
expect(getCurrencySymbol('USD')).toEqual('$');
54+
expect(getCurrencySymbol('EUR')).toEqual('€');
55+
expect(getCurrencySymbol('MXN')).toEqual('MX$');
56+
});
57+
58+
it('should return currency symbol for non-standard currencies', () => {
59+
expect(getCurrencySymbol('abc')).toEqual('ABC');
60+
expect(getCurrencySymbol('ZZZ')).toEqual('ZZZ');
61+
expect(getCurrencySymbol('AB')).toEqual('AB');
62+
expect(getCurrencySymbol('ABCD')).toEqual('ABCD');
63+
expect(getCurrencySymbol('ABcDe')).toEqual('ABCDE');
64+
});
65+
});
66+
67+
describe('formatCurrency', () => {
68+
it('should display right format for common cases', () => {
69+
expect(formatCurrency(5, 'USD')).toEqual('$5.00');
70+
expect(formatCurrency(0.5, 'USD')).toEqual('$0.50');
71+
expect(formatCurrency(5.34, 'USD')).toEqual('$5.34');
72+
expect(formatCurrency(5.34, 'EUR')).toEqual('€5.34');
73+
expect(formatCurrency(5.34, 'MXN')).toEqual('MX$5.34');
74+
});
75+
76+
it('should support custom precision', () => {
77+
expect(formatCurrency(5.12, 'USD', 2)).toEqual('$5.12');
78+
expect(formatCurrency(5.12, 'USD', 4)).toEqual('$5.12');
79+
expect(formatCurrency(5.19, 'EUR', 1)).toEqual('€5.2');
80+
expect(formatCurrency(5.12058, 'USD', 4)).toEqual('$5.1206');
81+
});
82+
83+
it('should display right format in different locales', () => {
84+
expect(formatCurrency(5.12, 'USD', 2, 'en-US')).toEqual('$5.12');
85+
expect(formatCurrency(5.12, 'USD', 2, 'en-IN')).toEqual('$5.12');
86+
expect(formatCurrency(5.34, 'USD', 2, 'fr')).toEqual('5,34\xa0$US'); // '\xa0' is a normal non-breaking space
87+
expect(formatCurrency(5.45, 'EUR', 2, 'en-US')).toEqual('€5.45');
88+
expect(formatCurrency(5.67, 'EUR', 2, 'fr-FR')).toEqual('5,67\xa0€');
89+
expect(formatCurrency(5.89, 'MXN', 2, 'es-MX')).toEqual('$5.89');
90+
});
91+
92+
it('should support non-standard currencies', () => {
93+
expect(formatCurrency(5.12, 'ABC')).toEqual('ABC\xa05.12');
94+
expect(formatCurrency(5.12, 'ZZZ')).toEqual('ZZZ\xa05.12');
95+
expect(formatCurrency(5.12, 'AB')).toEqual('AB\xa05.12');
96+
expect(formatCurrency(5.12, 'ABCDE')).toEqual('ABCDE\xa05.12');
97+
});
98+
});

src/pages/shared/lib/utils.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { cx, type CxOptions } from 'class-variance-authority';
22
import { twMerge } from 'tailwind-merge';
33

44
export const getCurrencySymbol = (assetCode: string): string => {
5+
if (!isISO4217Code(assetCode)) {
6+
return assetCode.toUpperCase();
7+
}
58
return new Intl.NumberFormat('en-US', {
69
currency: assetCode,
710
style: 'currency',
@@ -20,11 +23,30 @@ export const formatCurrency = (
2023
maximumFractionDigits = 2,
2124
locale?: string,
2225
): string => {
23-
return new Intl.NumberFormat(locale, {
26+
const isCurrencyCode = isISO4217Code(currency);
27+
const PLACEHOLDER_CURRENCY_CODE = 'ABC';
28+
const fmt = new Intl.NumberFormat(locale, {
2429
style: 'currency',
25-
currency,
30+
currency: isCurrencyCode ? currency : PLACEHOLDER_CURRENCY_CODE,
2631
maximumFractionDigits,
27-
}).format(Number(value));
32+
});
33+
34+
if (isCurrencyCode) {
35+
return fmt.format(Number(value));
36+
}
37+
return fmt
38+
.formatToParts(Number(value))
39+
.map((e) => (e.type === 'currency' ? currency.toUpperCase() : e.value))
40+
.join('');
41+
};
42+
43+
/**
44+
* Currency identifiers are three-letter uppercase codes defined in ISO 4217.
45+
* Intl.NumberFormat API allows any 3-letter code to be used without throwing a RangeError.
46+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf#supported_currency_identifiers
47+
*/
48+
export const isISO4217Code = (code: string): boolean => {
49+
return code.length === 3;
2850
};
2951

3052
export function roundWithPrecision(num: number, precision: number) {

0 commit comments

Comments
 (0)