diff --git a/packages/prices/utils/price.ts b/packages/prices/utils/price.ts index 9fc85922363..58669ded8e1 100644 --- a/packages/prices/utils/price.ts +++ b/packages/prices/utils/price.ts @@ -66,6 +66,8 @@ const siteCurrencySettings: Currency = { /** * Gets currency information in normalized format from an API response or the server. + * + * If no currency was provided, or currency_code is empty, the default store currency will be used. */ export const getCurrencyFromPriceResponse = ( // Currency data object, for example an API response containing currency formatting data. @@ -74,7 +76,7 @@ export const getCurrencyFromPriceResponse = ( | Record< string, never > | CartShippingPackageShippingRate ): Currency => { - if ( ! currencyData || typeof currencyData !== 'object' ) { + if ( ! currencyData?.currency_code ) { return siteCurrencySettings; } @@ -113,6 +115,28 @@ export const getCurrency = ( }; }; +const applyThousandSeparator = ( + numberString: string, + thousandSeparator: string +): string => { + return numberString.replace( /\B(?=(\d{3})+(?!\d))/g, thousandSeparator ); +}; + +const splitDecimal = ( + numberString: string +): { + beforeDecimal: string; + afterDecimal: string; +} => { + const parts = numberString.split( '.' ); + const beforeDecimal = parts[ 0 ]; + const afterDecimal = parts[ 1 ] || ''; + return { + beforeDecimal, + afterDecimal, + }; +}; + /** * Format a price, provided using the smallest unit of the currency, as a * decimal complete with currency symbols using current store settings. @@ -134,9 +158,27 @@ export const formatPrice = ( } const currency: Currency = getCurrency( currencyData ); - const formattedPrice: number = priceInt / 10 ** currency.minorUnit; - const formattedValue: string = - currency.prefix + formattedPrice + currency.suffix; + + const { + minorUnit, + prefix, + suffix, + decimalSeparator, + thousandSeparator, + } = currency; + + const formattedPrice: number = priceInt / 10 ** minorUnit; + + const { beforeDecimal, afterDecimal } = splitDecimal( + formattedPrice.toString() + ); + + const formattedValue = `${ prefix }${ applyThousandSeparator( + beforeDecimal, + thousandSeparator + ) }${ + afterDecimal ? `${ decimalSeparator }${ afterDecimal }` : '' + }${ suffix }`; // This uses a textarea to magically decode HTML currency symbols. const txt = document.createElement( 'textarea' ); diff --git a/packages/prices/utils/test/price.js b/packages/prices/utils/test/price.js index c7c96b20cb6..bc28c6d9c08 100644 --- a/packages/prices/utils/test/price.js +++ b/packages/prices/utils/test/price.js @@ -5,17 +5,20 @@ import { formatPrice, getCurrency } from '../price'; describe( 'formatPrice', () => { test.each` - value | prefix | suffix | expected - ${ 1000 } | ${ '€' } | ${ '' } | ${ '€10' } - ${ 1000 } | ${ '' } | ${ '€' } | ${ '10€' } - ${ 1000 } | ${ '' } | ${ '$' } | ${ '10$' } - ${ '1000' } | ${ '€' } | ${ '' } | ${ '€10' } - ${ 0 } | ${ '€' } | ${ '' } | ${ '€0' } - ${ '' } | ${ '€' } | ${ '' } | ${ '' } - ${ null } | ${ '€' } | ${ '' } | ${ '' } - ${ undefined } | ${ '€' } | ${ '' } | ${ '' } + value | prefix | suffix | expected + ${ 1000 } | ${ '€' } | ${ '' } | ${ '€10' } + ${ 1000 } | ${ '' } | ${ '€' } | ${ '10€' } + ${ 1000 } | ${ '' } | ${ '$' } | ${ '10$' } + ${ '1000' } | ${ '€' } | ${ '' } | ${ '€10' } + ${ 0 } | ${ '€' } | ${ '' } | ${ '€0' } + ${ '' } | ${ '€' } | ${ '' } | ${ '' } + ${ null } | ${ '€' } | ${ '' } | ${ '' } + ${ undefined } | ${ '€' } | ${ '' } | ${ '' } + ${ 100000 } | ${ '€' } | ${ '' } | ${ '€1,000' } + ${ 1000000 } | ${ '€' } | ${ '' } | ${ '€10,000' } + ${ 1000000000 } | ${ '€' } | ${ '' } | ${ '€10,000,000' } `( - 'correctly formats price given "$value", "$prefix" prefix, and "$suffix" suffix', + 'correctly formats price given "$value", "$prefix" prefix, and "$suffix" suffix as "$expected"', ( { value, prefix, suffix, expected } ) => { const formattedPrice = formatPrice( value, @@ -26,6 +29,28 @@ describe( 'formatPrice', () => { } ); + test.each` + value | prefix | decimalSeparator | thousandSeparator | expected + ${ 1000000099 } | ${ '$' } | ${ '.' } | ${ ',' } | ${ '$10,000,000.99' } + ${ 1000000099 } | ${ '$' } | ${ ',' } | ${ '.' } | ${ '$10.000.000,99' } + `( + 'correctly formats price given "$value", "$prefix" prefix, "$decimalSeparator" decimal separator, "$thousandSeparator" thousand separator as "$expected"', + ( { + value, + prefix, + decimalSeparator, + thousandSeparator, + expected, + } ) => { + const formattedPrice = formatPrice( + value, + getCurrency( { prefix, decimalSeparator, thousandSeparator } ) + ); + + expect( formattedPrice ).toEqual( expected ); + } + ); + test.each` value | expected ${ 1000 } | ${ '$10' } @@ -34,7 +59,7 @@ describe( 'formatPrice', () => { ${ null } | ${ '' } ${ undefined } | ${ '' } `( - 'correctly formats price given "$value" only', + 'correctly formats price given "$value" only as "$expected"', ( { value, expected } ) => { const formattedPrice = formatPrice( value );