From 6389f6eee53d384e6895c038343dc7d614dbeea9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 18 Nov 2021 16:51:15 +0000 Subject: [PATCH 1/2] Format with decimal and thousands, with tests --- packages/prices/utils/price.ts | 54 ++++++++++++++++++++++++++--- packages/prices/utils/test/price.js | 47 +++++++++++++++++++------ 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/packages/prices/utils/price.ts b/packages/prices/utils/price.ts index 9fc85922363..37fe1aa722b 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,11 @@ export const getCurrencyFromPriceResponse = ( | Record< string, never > | CartShippingPackageShippingRate ): Currency => { - if ( ! currencyData || typeof currencyData !== 'object' ) { + if ( + ! currencyData || + typeof currencyData !== 'object' || + ! currencyData?.currency_code + ) { return siteCurrencySettings; } @@ -113,6 +119,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 +162,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 ); From c600de428c2d1f1a0e6620f049f47e915c4181fa Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 19 Nov 2021 11:10:00 +0000 Subject: [PATCH 2/2] Update packages/prices/utils/price.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Albert Juhé Lluveras --- packages/prices/utils/price.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/prices/utils/price.ts b/packages/prices/utils/price.ts index 37fe1aa722b..58669ded8e1 100644 --- a/packages/prices/utils/price.ts +++ b/packages/prices/utils/price.ts @@ -76,11 +76,7 @@ export const getCurrencyFromPriceResponse = ( | Record< string, never > | CartShippingPackageShippingRate ): Currency => { - if ( - ! currencyData || - typeof currencyData !== 'object' || - ! currencyData?.currency_code - ) { + if ( ! currencyData?.currency_code ) { return siteCurrencySettings; }