From c60b9d81c8af1bdf830c907082b82a8261ed7631 Mon Sep 17 00:00:00 2001 From: Mikhail Bashkirov Date: Wed, 26 Jun 2019 17:49:28 +0200 Subject: [PATCH 1/5] chore(localize): split number related functions into their own files --- packages/localize/index.js | 12 +- packages/localize/src/formatNumber.js | 403 -------------- .../src/number/emptyStringWhenNumberNan.js | 12 + .../src/number/forceAddGroupSeparators.js | 44 ++ .../localize/src/number/forceCurrencyToEnd.js | 20 + .../localize/src/number/forceNormalSpaces.js | 16 + .../forceSpaceBetweenCurrencyCodeAndNumber.js | 31 ++ .../localize/src/number/forceYenSymbol.js | 14 + packages/localize/src/number/formatNumber.js | 28 + .../src/number/formatNumberToParts.js | 123 +++++ .../src/number/getDecimalSeparator.js | 16 + .../localize/src/number/getFractionDigits.js | 17 + .../localize/src/number/getGroupSeparator.js | 17 + packages/localize/src/number/getLocale.js | 17 + packages/localize/src/number/normalSpaces.js | 8 + packages/localize/src/number/normalizeIntl.js | 37 ++ packages/localize/src/number/roundNumber.js | 19 + packages/localize/test/formatNumber.test.js | 498 ------------------ .../localize/test/number/formatNumber.test.js | 258 +++++++++ .../test/number/formatNumberToParts.test.js | 215 ++++++++ .../test/number/getDecimalSeparator.test.js | 11 + .../test/number/getFractionDigits.test.js | 11 + .../test/number/getGroupSeparator.test.js | 11 + 23 files changed, 930 insertions(+), 908 deletions(-) delete mode 100644 packages/localize/src/formatNumber.js create mode 100644 packages/localize/src/number/emptyStringWhenNumberNan.js create mode 100644 packages/localize/src/number/forceAddGroupSeparators.js create mode 100644 packages/localize/src/number/forceCurrencyToEnd.js create mode 100644 packages/localize/src/number/forceNormalSpaces.js create mode 100644 packages/localize/src/number/forceSpaceBetweenCurrencyCodeAndNumber.js create mode 100644 packages/localize/src/number/forceYenSymbol.js create mode 100644 packages/localize/src/number/formatNumber.js create mode 100644 packages/localize/src/number/formatNumberToParts.js create mode 100644 packages/localize/src/number/getDecimalSeparator.js create mode 100644 packages/localize/src/number/getFractionDigits.js create mode 100644 packages/localize/src/number/getGroupSeparator.js create mode 100644 packages/localize/src/number/getLocale.js create mode 100644 packages/localize/src/number/normalSpaces.js create mode 100644 packages/localize/src/number/normalizeIntl.js create mode 100644 packages/localize/src/number/roundNumber.js delete mode 100644 packages/localize/test/formatNumber.test.js create mode 100644 packages/localize/test/number/formatNumber.test.js create mode 100644 packages/localize/test/number/formatNumberToParts.test.js create mode 100644 packages/localize/test/number/getDecimalSeparator.test.js create mode 100644 packages/localize/test/number/getFractionDigits.test.js create mode 100644 packages/localize/test/number/getGroupSeparator.test.js diff --git a/packages/localize/index.js b/packages/localize/index.js index 1efbe8f3b..696dc5379 100644 --- a/packages/localize/index.js +++ b/packages/localize/index.js @@ -3,13 +3,11 @@ export { getDateFormatBasedOnLocale } from './src/date/getDateFormatBasedOnLocal export { getMonthNames } from './src/date/getMonthNames.js'; export { getWeekdayNames } from './src/date/getWeekdayNames.js'; export { parseDate } from './src/date/parseDate.js'; -export { - formatNumber, - formatNumberToParts, - getFractionDigits, - getDecimalSeparator, - getGroupSeparator, -} from './src/formatNumber.js'; +export { formatNumber } from './src/number/formatNumber.js'; +export { formatNumberToParts } from './src/number/formatNumberToParts.js'; +export { getDecimalSeparator } from './src/number/getDecimalSeparator.js'; +export { getFractionDigits } from './src/number/getFractionDigits.js'; +export { getGroupSeparator } from './src/number/getGroupSeparator.js'; export { localize, setLocalize } from './src/localize.js'; export { LocalizeManager } from './src/LocalizeManager.js'; export { LocalizeMixin } from './src/LocalizeMixin.js'; diff --git a/packages/localize/src/formatNumber.js b/packages/localize/src/formatNumber.js deleted file mode 100644 index fdc34dcf8..000000000 --- a/packages/localize/src/formatNumber.js +++ /dev/null @@ -1,403 +0,0 @@ -import { localize } from './localize.js'; - -/** - * Gets the locale to use - * - * @param {string} locale Locale to override browser locale - * @returns {string} - */ -function getLocale(locale) { - if (locale) { - return locale; - } - if (localize && localize.locale) { - return localize.locale; - } - return 'en-GB'; -} - -/** - * Round the number based on the options - * - * @param {number} number - * @param {string} roundMode - * @returns {*} - */ -function roundNumber(number, roundMode) { - switch (roundMode) { - case 'floor': - return Math.floor(number); - case 'ceiling': - return Math.ceil(number); - case 'round': - return Math.round(number); - default: - throw new Error('roundMode can only be round|floor|ceiling'); - } -} -/** - * @param {Array} value - * @return {Array} value with forced "normal" space - */ -export function normalSpaces(value) { - // If non-breaking space (160) or narrow non-breaking space (8239) then return ' ' - return value.charCodeAt(0) === 160 || value.charCodeAt(0) === 8239 ? ' ' : value; -} - -/** - * To get the group separator - * - * @param {string} locale To override the browser locale - * @returns {Object} the separator - */ -export function getGroupSeparator(locale) { - const computedLocale = getLocale(locale); - const formattedNumber = Intl.NumberFormat(computedLocale, { - style: 'decimal', - minimumFractionDigits: 0, - }).format('1000'); - return normalSpaces(formattedNumber[1]); -} - -/** - * To get the decimal separator - * - * @param {string} locale To override the browser locale - * @returns {Object} the separator - */ -export function getDecimalSeparator(locale) { - const computedLocale = getLocale(locale); - const formattedNumber = Intl.NumberFormat(computedLocale, { - style: 'decimal', - minimumFractionDigits: 1, - }).format('1'); - return formattedNumber[1]; -} - -/** - * When number is NaN we should return an empty string or returnIfNaN param - * - * @param {string} returnIfNaN - * @returns {*} - */ -function emptyStringWhenNumberNan(returnIfNaN) { - const stringToReturn = returnIfNaN || localize.formatNumberOptions.returnIfNaN; - return stringToReturn; -} - -/** - * For Dutch and Belgian amounts the currency should be at the end of the string - * - * @param {Array} formattedParts - * @returns {Array} - */ -function forceCurrencyToEnd(formattedParts) { - if (formattedParts[0].type === 'currency') { - const moveCur = formattedParts.splice(0, 1); - const moveLit = formattedParts.splice(0, 1); - formattedParts.push(moveLit[0]); - formattedParts.push(moveCur[0]); - } else if (formattedParts[0].type === 'minusSign' && formattedParts[1].type === 'currency') { - const moveCur = formattedParts.splice(1, 1); - const moveLit = formattedParts.splice(1, 1); - formattedParts.push(moveLit[0]); - formattedParts.push(moveCur[0]); - } - return formattedParts; -} - -/** - * When in some locales there is no space between currency and amount it is added - * - * @param {Array} formattedParts - * @param {Object} options - * @returns {*} - */ -function forceSpaceBetweenCurrencyCodeAndNumber(formattedParts, options) { - const numberOfParts = formattedParts.length; - const literalObject = { type: 'literal', value: ' ' }; - if (numberOfParts > 1 && options && options.currency && options.currencyDisplay === 'code') { - if (formattedParts[0].type === 'currency' && formattedParts[1].type !== 'literal') { - // currency in front of a number: EUR 1.00 - formattedParts.splice(1, 0, literalObject); - } else if ( - formattedParts[0].type === 'minusSign' && - formattedParts[1].type === 'currency' && - formattedParts[2].type !== 'literal' - ) { - // currency in front of a negative number: -EUR 1.00 - formattedParts.splice(2, 0, literalObject); - } else if ( - formattedParts[numberOfParts - 1].type === 'currency' && - formattedParts[numberOfParts - 2].type !== 'literal' - ) { - // currency in behind a number: 1.00 EUR || -1.00 EUR - formattedParts.splice(numberOfParts - 1, 0, literalObject); - } - } - return formattedParts; -} - -/** - * Add separators when they are not present - * - * @param {Array} formattedParts - * @param {string} groupSeparator - * @returns {Array} - */ -function forceAddGroupSeparators(formattedParts, groupSeparator) { - let concatArray = []; - if (formattedParts[0].type === 'integer') { - const getInteger = formattedParts.splice(0, 1); - const numberOfDigits = getInteger[0].value.length; - const mod3 = numberOfDigits % 3; - const groups = Math.floor(numberOfDigits / 3); - const numberArray = []; - let numberOfGroups = 0; - let numberPart = ''; - let firstGroup = false; - // Loop through the integer - for (let i = 0; i < numberOfDigits; i += 1) { - numberPart += getInteger[0].value[i]; - // Create first grouping which is < 3 - if (numberPart.length === mod3 && firstGroup === false) { - numberArray.push({ type: 'integer', value: numberPart }); - if (numberOfDigits > 3) { - numberArray.push({ type: 'group', value: groupSeparator }); - } - numberPart = ''; - firstGroup = true; - // Create groupings of 3 - } else if (numberPart.length === 3 && i < numberOfDigits - 1) { - numberOfGroups += 1; - numberArray.push({ type: 'integer', value: numberPart }); - if (numberOfGroups !== groups) { - numberArray.push({ type: 'group', value: groupSeparator }); - } - numberPart = ''; - } - } - numberArray.push({ type: 'integer', value: numberPart }); - concatArray = numberArray.concat(formattedParts); - } - return concatArray; -} - -/** - * @param {Array} formattedParts - * @return {Array} parts with forced "normal" spaces - */ -function forceNormalSpaces(formattedParts) { - const result = []; - formattedParts.forEach(part => { - result.push({ - type: part.type, - value: normalSpaces(part.value), - }); - }); - return result; -} - -function forceYenSymbol(formattedParts, options) { - const result = formattedParts; - const numberOfParts = result.length; - // Change the symbol from JPY to ¥, due to bug in Chrome - if ( - numberOfParts > 1 && - options && - options.currency === 'JPY' && - options.currencyDisplay === 'symbol' - ) { - result[numberOfParts - 1].value = '¥'; - } - return result; -} - -/** - * Function with all fixes on localize - * - * @param {Array} formattedParts - * @param {Object} options - * @param {string} _locale - * @returns {*} - */ -function normalizeIntl(formattedParts, options, _locale) { - let normalize = forceNormalSpaces(formattedParts, options); - // Dutch and Belgian currency must be moved to end of number - if (options && options.style === 'currency') { - if (_locale === 'nl-NL' || _locale.slice(-2) === 'BE') { - normalize = forceCurrencyToEnd(normalize); - } - // Add group separator for Bulgarian locale - if (_locale === 'bg-BG') { - normalize = forceAddGroupSeparators(normalize, getGroupSeparator()); - } - // Force space between currency code and number - if (_locale === 'en-GB' || _locale === 'en-US' || _locale === 'en-AU') { - normalize = forceSpaceBetweenCurrencyCodeAndNumber(normalize, options); - } - // Force missing Japanese Yen symbol - if (_locale === 'fr-FR' || _locale === 'fr-BE') { - normalize = forceYenSymbol(normalize, options); - } - } - return normalize; -} - -/** - * Splits a number up in parts for integer, fraction, group, literal, decimal and currency. - * - * @param {number} number Number to split up - * @param {Object} options Intl options are available extended by roundMode - * @returns {Array} Array with parts - */ -export function formatNumberToParts(number, options) { - let parsedNumber = typeof number === 'string' ? parseFloat(number) : number; - const computedLocale = getLocale(options && options.locale); - // when parsedNumber is not a number we should return an empty string or returnIfNaN - if (Number.isNaN(parsedNumber)) { - return emptyStringWhenNumberNan(options && options.returnIfNaN); - } - // If roundMode is given the number is rounded based upon the mode - if (options && options.roundMode) { - parsedNumber = roundNumber(number, options.roundMode); - } - let formattedParts = []; - const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber); - const regexSymbol = /[A-Z.,\s0-9]/; - const regexCode = /[A-Z]/; - const regexMinusSign = /[-]/; - const regexNum = /[0-9]/; - const regexSeparator = /[.,]/; - const regexSpace = /[\s]/; - let currencyCode = ''; - let numberPart = ''; - let fraction = false; - for (let i = 0; i < formattedNumber.length; i += 1) { - // detect minusSign - if (regexMinusSign.test(formattedNumber[i])) { - formattedParts.push({ type: 'minusSign', value: formattedNumber[i] }); - } - // detect numbers - if (regexNum.test(formattedNumber[i])) { - numberPart += formattedNumber[i]; - } - // detect currency symbol - if (!regexSymbol.test(formattedNumber[i]) && !regexMinusSign.test(formattedNumber[i])) { - // Write number grouping - if (numberPart && !fraction) { - formattedParts.push({ type: 'integer', value: numberPart }); - numberPart = ''; - } else if (numberPart) { - formattedParts.push({ type: 'fraction', value: numberPart }); - numberPart = ''; - } - formattedParts.push({ type: 'currency', value: formattedNumber[i] }); - } - // detect currency code - if (regexCode.test(formattedNumber[i])) { - currencyCode += formattedNumber[i]; - // Write number grouping - if (numberPart && !fraction) { - formattedParts.push({ type: 'integer', value: numberPart }); - numberPart = ''; - } else if (numberPart) { - formattedParts.push({ type: 'fraction', value: numberPart }); - numberPart = ''; - } - if (currencyCode.length === 3) { - formattedParts.push({ type: 'currency', value: currencyCode }); - currencyCode = ''; - } - } - // detect dot and comma separators - if (regexSeparator.test(formattedNumber[i])) { - // Write number grouping - if (numberPart) { - formattedParts.push({ type: 'integer', value: numberPart }); - numberPart = ''; - } - const decimal = getDecimalSeparator(); - if (formattedNumber[i] === decimal) { - formattedParts.push({ type: 'decimal', value: formattedNumber[i] }); - fraction = true; - } else { - formattedParts.push({ type: 'group', value: formattedNumber[i] }); - } - } - // detect literals (empty spaces) or space group separator - if (regexSpace.test(formattedNumber[i])) { - const group = getGroupSeparator(); - const hasNumberPart = !!numberPart; - // Write number grouping - if (numberPart && !fraction) { - formattedParts.push({ type: 'integer', value: numberPart }); - numberPart = ''; - } else if (numberPart) { - formattedParts.push({ type: 'fraction', value: numberPart }); - numberPart = ''; - } - // If space equals the group separator it gets type group - if (normalSpaces(formattedNumber[i]) === group && hasNumberPart && !fraction) { - formattedParts.push({ type: 'group', value: formattedNumber[i] }); - } else { - formattedParts.push({ type: 'literal', value: formattedNumber[i] }); - } - } - // Numbers after the decimal sign are fractions, write the last - // fractions at the end of the number - if (fraction === true && i === formattedNumber.length - 1) { - // write last number part - if (numberPart) { - formattedParts.push({ type: 'fraction', value: numberPart }); - } - // If there are no fractions but we reached the end write the numberpart as integer - } else if (i === formattedNumber.length - 1 && numberPart) { - formattedParts.push({ type: 'integer', value: numberPart }); - } - } - formattedParts = normalizeIntl(formattedParts, options, computedLocale); - return formattedParts; -} - -/** - * @example - * getFractionDigits('JOD'); // return 3 - * - * @param {string} currency Currency code e.g. EUR - * @return {number} fraction for the given currency - */ -export function getFractionDigits(currency = 'EUR') { - const parts = formatNumberToParts(123, { - style: 'currency', - currency, - }); - const [fractionPart] = parts.filter(part => part.type === 'fraction'); - return fractionPart ? fractionPart.value.length : 0; -} - -/** - * Formats a number based on locale and options. It uses Intl for the formatting. - * - * @param {number} number Number to be formatted - * @param {Object} options Intl options are available extended by roundMode - * @returns {*} Formatted number - */ -export function formatNumber(number, options) { - if (number === undefined || number === null) return ''; - const formattedToParts = formatNumberToParts(number, options); - // If number is not a number - if ( - formattedToParts === (options && options.returnIfNaN) || - formattedToParts === localize.formatNumberOptions.returnIfNaN - ) { - return formattedToParts; - } - let printNumberOfParts = ''; - // update numberOfParts because there may be some parts added - const numberOfParts = formattedToParts && formattedToParts.length; - for (let i = 0; i < numberOfParts; i += 1) { - printNumberOfParts += formattedToParts[i].value; - } - return printNumberOfParts; -} diff --git a/packages/localize/src/number/emptyStringWhenNumberNan.js b/packages/localize/src/number/emptyStringWhenNumberNan.js new file mode 100644 index 000000000..427a72ef1 --- /dev/null +++ b/packages/localize/src/number/emptyStringWhenNumberNan.js @@ -0,0 +1,12 @@ +import { localize } from '../localize.js'; + +/** + * When number is NaN we should return an empty string or returnIfNaN param + * + * @param {string} returnIfNaN + * @returns {*} + */ +export function emptyStringWhenNumberNan(returnIfNaN) { + const stringToReturn = returnIfNaN || localize.formatNumberOptions.returnIfNaN; + return stringToReturn; +} diff --git a/packages/localize/src/number/forceAddGroupSeparators.js b/packages/localize/src/number/forceAddGroupSeparators.js new file mode 100644 index 000000000..dd00427e3 --- /dev/null +++ b/packages/localize/src/number/forceAddGroupSeparators.js @@ -0,0 +1,44 @@ +/** + * Add separators when they are not present + * + * @param {Array} formattedParts + * @param {string} groupSeparator + * @returns {Array} + */ +export function forceAddGroupSeparators(formattedParts, groupSeparator) { + let concatArray = []; + if (formattedParts[0].type === 'integer') { + const getInteger = formattedParts.splice(0, 1); + const numberOfDigits = getInteger[0].value.length; + const mod3 = numberOfDigits % 3; + const groups = Math.floor(numberOfDigits / 3); + const numberArray = []; + let numberOfGroups = 0; + let numberPart = ''; + let firstGroup = false; + // Loop through the integer + for (let i = 0; i < numberOfDigits; i += 1) { + numberPart += getInteger[0].value[i]; + // Create first grouping which is < 3 + if (numberPart.length === mod3 && firstGroup === false) { + numberArray.push({ type: 'integer', value: numberPart }); + if (numberOfDigits > 3) { + numberArray.push({ type: 'group', value: groupSeparator }); + } + numberPart = ''; + firstGroup = true; + // Create groupings of 3 + } else if (numberPart.length === 3 && i < numberOfDigits - 1) { + numberOfGroups += 1; + numberArray.push({ type: 'integer', value: numberPart }); + if (numberOfGroups !== groups) { + numberArray.push({ type: 'group', value: groupSeparator }); + } + numberPart = ''; + } + } + numberArray.push({ type: 'integer', value: numberPart }); + concatArray = numberArray.concat(formattedParts); + } + return concatArray; +} diff --git a/packages/localize/src/number/forceCurrencyToEnd.js b/packages/localize/src/number/forceCurrencyToEnd.js new file mode 100644 index 000000000..101cf9dcd --- /dev/null +++ b/packages/localize/src/number/forceCurrencyToEnd.js @@ -0,0 +1,20 @@ +/** + * For Dutch and Belgian amounts the currency should be at the end of the string + * + * @param {Array} formattedParts + * @returns {Array} + */ +export function forceCurrencyToEnd(formattedParts) { + if (formattedParts[0].type === 'currency') { + const moveCur = formattedParts.splice(0, 1); + const moveLit = formattedParts.splice(0, 1); + formattedParts.push(moveLit[0]); + formattedParts.push(moveCur[0]); + } else if (formattedParts[0].type === 'minusSign' && formattedParts[1].type === 'currency') { + const moveCur = formattedParts.splice(1, 1); + const moveLit = formattedParts.splice(1, 1); + formattedParts.push(moveLit[0]); + formattedParts.push(moveCur[0]); + } + return formattedParts; +} diff --git a/packages/localize/src/number/forceNormalSpaces.js b/packages/localize/src/number/forceNormalSpaces.js new file mode 100644 index 000000000..444d824f7 --- /dev/null +++ b/packages/localize/src/number/forceNormalSpaces.js @@ -0,0 +1,16 @@ +import { normalSpaces } from './normalSpaces.js'; + +/** + * @param {Array} formattedParts + * @return {Array} parts with forced "normal" spaces + */ +export function forceNormalSpaces(formattedParts) { + const result = []; + formattedParts.forEach(part => { + result.push({ + type: part.type, + value: normalSpaces(part.value), + }); + }); + return result; +} diff --git a/packages/localize/src/number/forceSpaceBetweenCurrencyCodeAndNumber.js b/packages/localize/src/number/forceSpaceBetweenCurrencyCodeAndNumber.js new file mode 100644 index 000000000..cbe9fbde6 --- /dev/null +++ b/packages/localize/src/number/forceSpaceBetweenCurrencyCodeAndNumber.js @@ -0,0 +1,31 @@ +/** + * When in some locales there is no space between currency and amount it is added + * + * @param {Array} formattedParts + * @param {Object} options + * @returns {*} + */ +export function forceSpaceBetweenCurrencyCodeAndNumber(formattedParts, options) { + const numberOfParts = formattedParts.length; + const literalObject = { type: 'literal', value: ' ' }; + if (numberOfParts > 1 && options && options.currency && options.currencyDisplay === 'code') { + if (formattedParts[0].type === 'currency' && formattedParts[1].type !== 'literal') { + // currency in front of a number: EUR 1.00 + formattedParts.splice(1, 0, literalObject); + } else if ( + formattedParts[0].type === 'minusSign' && + formattedParts[1].type === 'currency' && + formattedParts[2].type !== 'literal' + ) { + // currency in front of a negative number: -EUR 1.00 + formattedParts.splice(2, 0, literalObject); + } else if ( + formattedParts[numberOfParts - 1].type === 'currency' && + formattedParts[numberOfParts - 2].type !== 'literal' + ) { + // currency in behind a number: 1.00 EUR || -1.00 EUR + formattedParts.splice(numberOfParts - 1, 0, literalObject); + } + } + return formattedParts; +} diff --git a/packages/localize/src/number/forceYenSymbol.js b/packages/localize/src/number/forceYenSymbol.js new file mode 100644 index 000000000..ac01af76f --- /dev/null +++ b/packages/localize/src/number/forceYenSymbol.js @@ -0,0 +1,14 @@ +export function forceYenSymbol(formattedParts, options) { + const result = formattedParts; + const numberOfParts = result.length; + // Change the symbol from JPY to ¥, due to bug in Chrome + if ( + numberOfParts > 1 && + options && + options.currency === 'JPY' && + options.currencyDisplay === 'symbol' + ) { + result[numberOfParts - 1].value = '¥'; + } + return result; +} diff --git a/packages/localize/src/number/formatNumber.js b/packages/localize/src/number/formatNumber.js new file mode 100644 index 000000000..60f227155 --- /dev/null +++ b/packages/localize/src/number/formatNumber.js @@ -0,0 +1,28 @@ +import { localize } from '../localize.js'; +import { formatNumberToParts } from './formatNumberToParts.js'; + +/** + * Formats a number based on locale and options. It uses Intl for the formatting. + * + * @param {number} number Number to be formatted + * @param {Object} options Intl options are available extended by roundMode + * @returns {*} Formatted number + */ +export function formatNumber(number, options) { + if (number === undefined || number === null) return ''; + const formattedToParts = formatNumberToParts(number, options); + // If number is not a number + if ( + formattedToParts === (options && options.returnIfNaN) || + formattedToParts === localize.formatNumberOptions.returnIfNaN + ) { + return formattedToParts; + } + let printNumberOfParts = ''; + // update numberOfParts because there may be some parts added + const numberOfParts = formattedToParts && formattedToParts.length; + for (let i = 0; i < numberOfParts; i += 1) { + printNumberOfParts += formattedToParts[i].value; + } + return printNumberOfParts; +} diff --git a/packages/localize/src/number/formatNumberToParts.js b/packages/localize/src/number/formatNumberToParts.js new file mode 100644 index 000000000..1a7f6f7fe --- /dev/null +++ b/packages/localize/src/number/formatNumberToParts.js @@ -0,0 +1,123 @@ +import { emptyStringWhenNumberNan } from './emptyStringWhenNumberNan.js'; +import { getDecimalSeparator } from './getDecimalSeparator.js'; +import { getGroupSeparator } from './getGroupSeparator.js'; +import { getLocale } from './getLocale.js'; +import { normalizeIntl } from './normalizeIntl.js'; +import { normalSpaces } from './normalSpaces.js'; +import { roundNumber } from './roundNumber.js'; + +/** + * Splits a number up in parts for integer, fraction, group, literal, decimal and currency. + * + * @param {number} number Number to split up + * @param {Object} options Intl options are available extended by roundMode + * @returns {Array} Array with parts + */ +export function formatNumberToParts(number, options) { + let parsedNumber = typeof number === 'string' ? parseFloat(number) : number; + const computedLocale = getLocale(options && options.locale); + // when parsedNumber is not a number we should return an empty string or returnIfNaN + if (Number.isNaN(parsedNumber)) { + return emptyStringWhenNumberNan(options && options.returnIfNaN); + } + // If roundMode is given the number is rounded based upon the mode + if (options && options.roundMode) { + parsedNumber = roundNumber(number, options.roundMode); + } + let formattedParts = []; + const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber); + const regexSymbol = /[A-Z.,\s0-9]/; + const regexCode = /[A-Z]/; + const regexMinusSign = /[-]/; + const regexNum = /[0-9]/; + const regexSeparator = /[.,]/; + const regexSpace = /[\s]/; + let currencyCode = ''; + let numberPart = ''; + let fraction = false; + for (let i = 0; i < formattedNumber.length; i += 1) { + // detect minusSign + if (regexMinusSign.test(formattedNumber[i])) { + formattedParts.push({ type: 'minusSign', value: formattedNumber[i] }); + } + // detect numbers + if (regexNum.test(formattedNumber[i])) { + numberPart += formattedNumber[i]; + } + // detect currency symbol + if (!regexSymbol.test(formattedNumber[i]) && !regexMinusSign.test(formattedNumber[i])) { + // Write number grouping + if (numberPart && !fraction) { + formattedParts.push({ type: 'integer', value: numberPart }); + numberPart = ''; + } else if (numberPart) { + formattedParts.push({ type: 'fraction', value: numberPart }); + numberPart = ''; + } + formattedParts.push({ type: 'currency', value: formattedNumber[i] }); + } + // detect currency code + if (regexCode.test(formattedNumber[i])) { + currencyCode += formattedNumber[i]; + // Write number grouping + if (numberPart && !fraction) { + formattedParts.push({ type: 'integer', value: numberPart }); + numberPart = ''; + } else if (numberPart) { + formattedParts.push({ type: 'fraction', value: numberPart }); + numberPart = ''; + } + if (currencyCode.length === 3) { + formattedParts.push({ type: 'currency', value: currencyCode }); + currencyCode = ''; + } + } + // detect dot and comma separators + if (regexSeparator.test(formattedNumber[i])) { + // Write number grouping + if (numberPart) { + formattedParts.push({ type: 'integer', value: numberPart }); + numberPart = ''; + } + const decimal = getDecimalSeparator(); + if (formattedNumber[i] === decimal) { + formattedParts.push({ type: 'decimal', value: formattedNumber[i] }); + fraction = true; + } else { + formattedParts.push({ type: 'group', value: formattedNumber[i] }); + } + } + // detect literals (empty spaces) or space group separator + if (regexSpace.test(formattedNumber[i])) { + const group = getGroupSeparator(); + const hasNumberPart = !!numberPart; + // Write number grouping + if (numberPart && !fraction) { + formattedParts.push({ type: 'integer', value: numberPart }); + numberPart = ''; + } else if (numberPart) { + formattedParts.push({ type: 'fraction', value: numberPart }); + numberPart = ''; + } + // If space equals the group separator it gets type group + if (normalSpaces(formattedNumber[i]) === group && hasNumberPart && !fraction) { + formattedParts.push({ type: 'group', value: formattedNumber[i] }); + } else { + formattedParts.push({ type: 'literal', value: formattedNumber[i] }); + } + } + // Numbers after the decimal sign are fractions, write the last + // fractions at the end of the number + if (fraction === true && i === formattedNumber.length - 1) { + // write last number part + if (numberPart) { + formattedParts.push({ type: 'fraction', value: numberPart }); + } + // If there are no fractions but we reached the end write the numberpart as integer + } else if (i === formattedNumber.length - 1 && numberPart) { + formattedParts.push({ type: 'integer', value: numberPart }); + } + } + formattedParts = normalizeIntl(formattedParts, options, computedLocale); + return formattedParts; +} diff --git a/packages/localize/src/number/getDecimalSeparator.js b/packages/localize/src/number/getDecimalSeparator.js new file mode 100644 index 000000000..8f2e730a6 --- /dev/null +++ b/packages/localize/src/number/getDecimalSeparator.js @@ -0,0 +1,16 @@ +import { getLocale } from './getLocale.js'; + +/** + * To get the decimal separator + * + * @param {string} locale To override the browser locale + * @returns {Object} the separator + */ +export function getDecimalSeparator(locale) { + const computedLocale = getLocale(locale); + const formattedNumber = Intl.NumberFormat(computedLocale, { + style: 'decimal', + minimumFractionDigits: 1, + }).format('1'); + return formattedNumber[1]; +} diff --git a/packages/localize/src/number/getFractionDigits.js b/packages/localize/src/number/getFractionDigits.js new file mode 100644 index 000000000..c2331b0c9 --- /dev/null +++ b/packages/localize/src/number/getFractionDigits.js @@ -0,0 +1,17 @@ +import { formatNumberToParts } from './formatNumberToParts.js'; + +/** + * @example + * getFractionDigits('JOD'); // return 3 + * + * @param {string} currency Currency code e.g. EUR + * @return {number} fraction for the given currency + */ +export function getFractionDigits(currency = 'EUR') { + const parts = formatNumberToParts(123, { + style: 'currency', + currency, + }); + const [fractionPart] = parts.filter(part => part.type === 'fraction'); + return fractionPart ? fractionPart.value.length : 0; +} diff --git a/packages/localize/src/number/getGroupSeparator.js b/packages/localize/src/number/getGroupSeparator.js new file mode 100644 index 000000000..4a1cebb07 --- /dev/null +++ b/packages/localize/src/number/getGroupSeparator.js @@ -0,0 +1,17 @@ +import { getLocale } from './getLocale.js'; +import { normalSpaces } from './normalSpaces.js'; + +/** + * To get the group separator + * + * @param {string} locale To override the browser locale + * @returns {Object} the separator + */ +export function getGroupSeparator(locale) { + const computedLocale = getLocale(locale); + const formattedNumber = Intl.NumberFormat(computedLocale, { + style: 'decimal', + minimumFractionDigits: 0, + }).format('1000'); + return normalSpaces(formattedNumber[1]); +} diff --git a/packages/localize/src/number/getLocale.js b/packages/localize/src/number/getLocale.js new file mode 100644 index 000000000..a8e96b988 --- /dev/null +++ b/packages/localize/src/number/getLocale.js @@ -0,0 +1,17 @@ +import { localize } from '../localize.js'; + +/** + * Gets the locale to use + * + * @param {string} locale Locale to override browser locale + * @returns {string} + */ +export function getLocale(locale) { + if (locale) { + return locale; + } + if (localize && localize.locale) { + return localize.locale; + } + return 'en-GB'; +} diff --git a/packages/localize/src/number/normalSpaces.js b/packages/localize/src/number/normalSpaces.js new file mode 100644 index 000000000..80e8eeef6 --- /dev/null +++ b/packages/localize/src/number/normalSpaces.js @@ -0,0 +1,8 @@ +/** + * @param {Array} value + * @return {Array} value with forced "normal" space + */ +export function normalSpaces(value) { + // If non-breaking space (160) or narrow non-breaking space (8239) then return ' ' + return value.charCodeAt(0) === 160 || value.charCodeAt(0) === 8239 ? ' ' : value; +} diff --git a/packages/localize/src/number/normalizeIntl.js b/packages/localize/src/number/normalizeIntl.js new file mode 100644 index 000000000..d63ea9b8d --- /dev/null +++ b/packages/localize/src/number/normalizeIntl.js @@ -0,0 +1,37 @@ +import { getGroupSeparator } from './getGroupSeparator.js'; +import { forceAddGroupSeparators } from './forceAddGroupSeparators.js'; +import { forceCurrencyToEnd } from './forceCurrencyToEnd.js'; +import { forceNormalSpaces } from './forceNormalSpaces.js'; +import { forceSpaceBetweenCurrencyCodeAndNumber } from './forceSpaceBetweenCurrencyCodeAndNumber.js'; +import { forceYenSymbol } from './forceYenSymbol.js'; + +/** + * Function with all fixes on localize + * + * @param {Array} formattedParts + * @param {Object} options + * @param {string} _locale + * @returns {*} + */ +export function normalizeIntl(formattedParts, options, _locale) { + let normalize = forceNormalSpaces(formattedParts, options); + // Dutch and Belgian currency must be moved to end of number + if (options && options.style === 'currency') { + if (_locale === 'nl-NL' || _locale.slice(-2) === 'BE') { + normalize = forceCurrencyToEnd(normalize); + } + // Add group separator for Bulgarian locale + if (_locale === 'bg-BG') { + normalize = forceAddGroupSeparators(normalize, getGroupSeparator()); + } + // Force space between currency code and number + if (_locale === 'en-GB' || _locale === 'en-US' || _locale === 'en-AU') { + normalize = forceSpaceBetweenCurrencyCodeAndNumber(normalize, options); + } + // Force missing Japanese Yen symbol + if (_locale === 'fr-FR' || _locale === 'fr-BE') { + normalize = forceYenSymbol(normalize, options); + } + } + return normalize; +} diff --git a/packages/localize/src/number/roundNumber.js b/packages/localize/src/number/roundNumber.js new file mode 100644 index 000000000..154787a65 --- /dev/null +++ b/packages/localize/src/number/roundNumber.js @@ -0,0 +1,19 @@ +/** + * Round the number based on the options + * + * @param {number} number + * @param {string} roundMode + * @returns {*} + */ +export function roundNumber(number, roundMode) { + switch (roundMode) { + case 'floor': + return Math.floor(number); + case 'ceiling': + return Math.ceil(number); + case 'round': + return Math.round(number); + default: + throw new Error('roundMode can only be round|floor|ceiling'); + } +} diff --git a/packages/localize/test/formatNumber.test.js b/packages/localize/test/formatNumber.test.js deleted file mode 100644 index 12df960df..000000000 --- a/packages/localize/test/formatNumber.test.js +++ /dev/null @@ -1,498 +0,0 @@ -import { expect } from '@open-wc/testing'; - -import { localize } from '../src/localize.js'; - -import { - formatNumber, - formatNumberToParts, - getGroupSeparator, - getDecimalSeparator, - getFractionDigits, -} from '../src/formatNumber.js'; - -describe('formatNumber', () => { - afterEach(() => { - // makes sure that between tests the localization is reset to default state - document.documentElement.lang = 'en-GB'; - }); - - it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', async () => { - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); - localize.locale = 'fr-FR'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123 456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123 457 ¥'); - localize.locale = 'de-DE'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); - }); - - it('can display currency as code', async () => { - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - '123.456,79 USD', - ); - }); - - it('can display currency as symbol', async () => { - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '123.456,79 €', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '123.456,79 $', - ); - }); - - it('uses minus (and not dash) to indicate negative numbers ', async () => { - expect(formatNumber(-12, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); - }); - - it('rounds (negative) numbers e.g. `roundMode: round`', async () => { - expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); - expect(formatNumber(12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('13'); - - expect(formatNumber(-12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); - expect(formatNumber(-12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-13'); - }); - - it("rounds (negative) numbers up when `roundMode: 'ceiling'`", async () => { - expect(formatNumber(12.4, { roundMode: 'ceiling' })).to.equal('13'); - expect(formatNumber(12.6, { roundMode: 'ceiling' })).to.equal('13'); - - expect(formatNumber(-12.4, { roundMode: 'ceiling' })).to.equal('-12'); - expect(formatNumber(-12.6, { roundMode: 'ceiling' })).to.equal('-12'); - }); - - it('rounds (negative) numbers down when `roundMode: floor`', async () => { - expect(formatNumber(12.4, { roundMode: 'floor' })).to.equal('12'); - expect(formatNumber(12.6, { roundMode: 'floor' })).to.equal('12'); - - expect(formatNumber(-12.4, { roundMode: 'floor' })).to.equal('-13'); - expect(formatNumber(-12.6, { roundMode: 'floor' })).to.equal('-13'); - }); - - it('returns empty string when NaN', async () => { - expect(formatNumber('foo')).to.equal(''); - }); - - it('returns empty string when number is undefined', async () => { - expect(formatNumber(undefined)).to.equal(''); - }); - - it('uses `localize.formatNumberOptions.returnIfNaN`', async () => { - localize.formatNumberOptions.returnIfNaN = '-'; - expect(formatNumber('foo')).to.equal('-'); - }); - - it("can set what to returns when NaN via `returnIfNaN: 'foo'`", async () => { - expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-'); - }); - - it('uses `localize.locale`', async () => { - expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( - '123,456.79', - ); - localize.locale = 'de-DE'; - expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( - '123.456,79', - ); - }); - - it('can set locale to use', async () => { - expect( - formatNumber(123456.789, { locale: 'en-GB', style: 'decimal', maximumFractionDigits: 2 }), - ).to.equal('123,456.79'); - expect( - formatNumber(123456.789, { locale: 'de-DE', style: 'decimal', maximumFractionDigits: 2 }), - ).to.equal('123.456,79'); - }); - - it('can specify max decimal places by `maximumFractionDigits: 3`', async () => { - expect(formatNumber(123456.789)).to.equal('123,456.789'); - expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 3 })).to.equal( - '123,456.789', - ); - expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 1 })).to.equal( - '123,456.8', - ); - }); - - it('can specify min decimal places by `minimumFractionDigits: 3`', async () => { - expect(formatNumber(12.3)).to.equal('12.3'); - expect(formatNumber(12.3456, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( - '12.346', - ); - expect(formatNumber(12.3, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12.300'); - }); - - it('can specify to show at least x digits by `minimumIntegerDigits: 5`', async () => { - expect(formatNumber(123)).to.equal('123'); - expect(formatNumber(123, { minimumIntegerDigits: 5 })).to.equal('00,123'); - }); - - it('can display 0 decimal places', async () => { - expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); - }); - - it('can give separators via getGroupSeparator() or getDecimalSeparator()', async () => { - expect(getGroupSeparator('en-GB')).to.equal(','); - expect(getGroupSeparator('nl-NL')).to.equal('.'); - expect(getGroupSeparator('fr-FR')).to.equal(' '); - - expect(getDecimalSeparator('en-GB')).to.equal('.'); - expect(getDecimalSeparator('nl-NL')).to.equal(','); - }); - - it('can give number of fraction digits for a certain currency via getFractionDigits()', async () => { - expect(getFractionDigits('JOD')).to.equal(3); - expect(getFractionDigits('EUR')).to.equal(2); - }); - - it('formats numbers correctly', async () => { - localize.locale = 'nl-NL'; - expect(formatNumber(0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,00'); - expect(formatNumber(0.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,10'); - expect(formatNumber(0.12, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,12'); - expect( - formatNumber(0.123, { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 }), - ).to.equal('0,12'); - expect( - formatNumber(0.1234, { - style: 'decimal', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - ).to.equal('0,12'); - expect( - formatNumber(0.123456789, { - style: 'decimal', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - ).to.equal('0,12'); - expect(formatNumber(1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,00'); - expect(formatNumber(1.0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,00'); - expect(formatNumber(1.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,10'); - expect(formatNumber(1.12, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,12'); - expect(formatNumber(1.123, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,123'); - expect(formatNumber(1.1234, { style: 'decimal', maximumFractionDigits: 2 })).to.equal('1,12'); - expect( - formatNumber(1.12345678, { - style: 'decimal', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - ).to.equal('1,12'); - expect(formatNumber(1000, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1.000,00'); - expect( - formatNumber(112345678, { - style: 'decimal', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - ).to.equal('112.345.678,00'); - }); - - it('formats 2-digit decimals correctly', async () => { - localize.locale = 'nl-NL'; - Array.from(new Array(100), (val, index) => index).forEach(i => { - const iString = `${i}`; - let number = 0.0; - number += i * 0.01; - expect(formatNumber(number, { style: 'decimal', minimumFractionDigits: 2 })).to.equal( - `0,${iString.padStart(2, '0')}`, - ); - }); - }); -}); - -describe('normalizeIntl()', () => { - afterEach(() => { - // makes sure that between tests the localization is reset to default state - document.documentElement.lang = 'en-GB'; - }); - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; - - it('supports British locale', async () => { - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - 'USD 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '$123,456.79', - ); - }); - - it('supports US locale', async () => { - localize.locale = 'en-US'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - 'USD 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '$123,456.79', - ); - }); - - it('supports Bulgarian locale', async () => { - localize.locale = 'bg-BG'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123 456,79 EUR', - ); - expect(formatNumber(1234567890.789, { currency: 'USD', ...currencyCode })).to.equal( - '1 234 567 890,79 USD', - ); - expect(formatNumber(12.789, { currency: 'EUR', ...currencyCode })).to.equal('12,79 EUR'); - expect(formatNumber(12, { currency: 'USD', ...currencyCode })).to.equal('12,00 USD'); - expect(formatNumber(12.789, { style: 'decimal' })).to.equal('12,789'); - expect(formatNumber(12, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12,000'); - expect(formatNumber(20000, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( - '20 000,000', - ); - }); -}); - -describe('formatNumberToParts', () => { - afterEach(() => { - // makes sure that between tests the localization is reset to default state - document.documentElement.lang = 'en-GB'; - }); - - describe('formats based on ISO standards', () => { - const specs = [ - ['nl-NL', 'EUR', 1234.5, '1.234,50 EUR'], - ['nl-NL', 'USD', 1234.5, '1.234,50 USD'], - ['nl-NL', 'EUR', -1234.5, '-1.234,50 EUR'], - ['nl-BE', 'EUR', 1234.5, '1.234,50 EUR'], - ['nl-BE', 'USD', 1234.5, '1.234,50 USD'], - ['nl-BE', 'EUR', -1234.5, '-1.234,50 EUR'], - ['en-GB', 'EUR', 1234.5, 'EUR 1,234.50'], - ['en-GB', 'USD', 1234.5, 'USD 1,234.50'], - ['en-GB', 'EUR', -1234.5, '-EUR 1,234.50'], - ['de-DE', 'EUR', 1234.5, '1.234,50 EUR'], - ['de-DE', 'USD', 1234.5, '1.234,50 USD'], - ['de-DE', 'EUR', -1234.5, '-1.234,50 EUR'], - ['fr-BE', 'EUR', 1234.5, '1 234,50 EUR'], - ['fr-BE', 'USD', 1234.5, '1 234,50 USD'], - ['fr-BE', 'EUR', -1234.5, '-1 234,50 EUR'], - ]; - - specs.forEach(spec => { - const [locale, currency, amount, expectedResult] = spec; - - it(`formats ${locale} ${currency} ${amount} as ${expectedResult}`, () => { - localize.locale = locale; - const parts = formatNumberToParts(amount, { - style: 'currency', - currency, - currencyDisplay: 'code', - }); - const joinedParts = parts.map(p => p.value).join(''); - expect(joinedParts).to.equal(expectedResult); - }); - }); - }); - - it('supports currency symbol with dutch locale', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', - }); - expect(formattedToParts).to.eql([ - { type: 'integer', value: '3' }, - { type: 'group', value: '.' }, - { type: 'integer', value: '500' }, - { type: 'decimal', value: ',' }, - { type: 'fraction', value: '00' }, - { type: 'literal', value: ' ' }, - { type: 'currency', value: '€' }, - ]); - }); - - it('supports currency symbol with french locale', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[0].type).to.equal('integer'); - expect(formattedToParts[0].value).to.equal('3'); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[5].type).to.equal('literal'); - expect(formattedToParts[5].value).to.equal(' '); - expect(formattedToParts[6].type).to.equal('currency'); - expect(formattedToParts[6].value).to.equal('€'); - }); - - it('supports currency symbol with British locale', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', - }); - expect(Object.keys(formattedToParts).length).to.equal(6); - expect(formattedToParts[2].type).to.equal('group'); - expect(formattedToParts[2].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('decimal'); - expect(formattedToParts[4].value).to.equal('.'); - expect(formattedToParts[5].type).to.equal('fraction'); - expect(formattedToParts[5].value).to.equal('00'); - }); - - it('supports currency code with dutch locale', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[5].type).to.equal('literal'); - expect(formattedToParts[5].value).to.equal(' '); - expect(formattedToParts[6].type).to.equal('currency'); - expect(formattedToParts[6].value).to.equal('EUR'); - }); - - it('supports currency code with french locale', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('fraction'); - expect(formattedToParts[4].value).to.equal('00'); - }); - - it('supports currency code with British locale', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[3].type).to.equal('group'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[5].type).to.equal('decimal'); - expect(formattedToParts[5].value).to.equal('.'); - }); - - it('supports currency with dutch locale and 2 decimals', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[0].type).to.equal('integer'); - expect(formattedToParts[0].value).to.equal('3'); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[2].type).to.equal('integer'); - expect(formattedToParts[2].value).to.equal('500'); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('fraction'); - expect(formattedToParts[4].value).to.equal('00'); - }); - - it('supports currency with french locale and 2 decimals', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - }); - - it('supports currency with british locale and 2 decimals', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(','); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal('.'); - }); - - it('supports currency with dutch locale without decimals', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[2].type).to.equal('integer'); - expect(formattedToParts[2].value).to.equal('500'); - }); - - it('supports currency with french locale without decimals', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - }); - - it('supports currency with british locale without decimals', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(','); - }); -}); diff --git a/packages/localize/test/number/formatNumber.test.js b/packages/localize/test/number/formatNumber.test.js new file mode 100644 index 000000000..ff99ceeed --- /dev/null +++ b/packages/localize/test/number/formatNumber.test.js @@ -0,0 +1,258 @@ +import { expect } from '@open-wc/testing'; +import { localize } from '../../src/localize.js'; +import { localizeTearDown } from '../../test-helpers.js'; + +import { formatNumber } from '../../src/number/formatNumber.js'; + +describe('formatNumber', () => { + afterEach(localizeTearDown); + + it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', async () => { + const currencyCode = { style: 'currency', currencyDisplay: 'code' }; + const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + 'EUR 123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( + '€123,456.79', + ); + localize.locale = 'nl-NL'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + '123.456,79 EUR', + ); + expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); + localize.locale = 'fr-FR'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + '123 456,79 EUR', + ); + expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123 457 ¥'); + localize.locale = 'de-DE'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + '123.456,79 EUR', + ); + expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); + }); + + it('can display currency as code', async () => { + const currencyCode = { style: 'currency', currencyDisplay: 'code' }; + localize.locale = 'nl-NL'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + '123.456,79 EUR', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( + '123.456,79 USD', + ); + }); + + it('can display currency as symbol', async () => { + const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; + localize.locale = 'nl-NL'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( + '123.456,79 €', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( + '123.456,79 $', + ); + }); + + it('uses minus (and not dash) to indicate negative numbers ', async () => { + expect(formatNumber(-12, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); + }); + + it('rounds (negative) numbers e.g. `roundMode: round`', async () => { + expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); + expect(formatNumber(12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('13'); + + expect(formatNumber(-12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); + expect(formatNumber(-12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-13'); + }); + + it("rounds (negative) numbers up when `roundMode: 'ceiling'`", async () => { + expect(formatNumber(12.4, { roundMode: 'ceiling' })).to.equal('13'); + expect(formatNumber(12.6, { roundMode: 'ceiling' })).to.equal('13'); + + expect(formatNumber(-12.4, { roundMode: 'ceiling' })).to.equal('-12'); + expect(formatNumber(-12.6, { roundMode: 'ceiling' })).to.equal('-12'); + }); + + it('rounds (negative) numbers down when `roundMode: floor`', async () => { + expect(formatNumber(12.4, { roundMode: 'floor' })).to.equal('12'); + expect(formatNumber(12.6, { roundMode: 'floor' })).to.equal('12'); + + expect(formatNumber(-12.4, { roundMode: 'floor' })).to.equal('-13'); + expect(formatNumber(-12.6, { roundMode: 'floor' })).to.equal('-13'); + }); + + it('returns empty string when NaN', async () => { + expect(formatNumber('foo')).to.equal(''); + }); + + it('returns empty string when number is undefined', async () => { + expect(formatNumber(undefined)).to.equal(''); + }); + + it('uses `localize.formatNumberOptions.returnIfNaN`', async () => { + localize.formatNumberOptions.returnIfNaN = '-'; + expect(formatNumber('foo')).to.equal('-'); + }); + + it("can set what to returns when NaN via `returnIfNaN: 'foo'`", async () => { + expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-'); + }); + + it('uses `localize.locale`', async () => { + expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( + '123,456.79', + ); + localize.locale = 'de-DE'; + expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( + '123.456,79', + ); + }); + + it('can set locale to use', async () => { + expect( + formatNumber(123456.789, { locale: 'en-GB', style: 'decimal', maximumFractionDigits: 2 }), + ).to.equal('123,456.79'); + expect( + formatNumber(123456.789, { locale: 'de-DE', style: 'decimal', maximumFractionDigits: 2 }), + ).to.equal('123.456,79'); + }); + + it('can specify max decimal places by `maximumFractionDigits: 3`', async () => { + expect(formatNumber(123456.789)).to.equal('123,456.789'); + expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 3 })).to.equal( + '123,456.789', + ); + expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 1 })).to.equal( + '123,456.8', + ); + }); + + it('can specify min decimal places by `minimumFractionDigits: 3`', async () => { + expect(formatNumber(12.3)).to.equal('12.3'); + expect(formatNumber(12.3456, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( + '12.346', + ); + expect(formatNumber(12.3, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12.300'); + }); + + it('can specify to show at least x digits by `minimumIntegerDigits: 5`', async () => { + expect(formatNumber(123)).to.equal('123'); + expect(formatNumber(123, { minimumIntegerDigits: 5 })).to.equal('00,123'); + }); + + it('can display 0 decimal places', async () => { + expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); + }); + + it('formats numbers correctly', async () => { + localize.locale = 'nl-NL'; + expect(formatNumber(0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,00'); + expect(formatNumber(0.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,10'); + expect(formatNumber(0.12, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,12'); + expect( + formatNumber(0.123, { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 }), + ).to.equal('0,12'); + expect( + formatNumber(0.1234, { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + ).to.equal('0,12'); + expect( + formatNumber(0.123456789, { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + ).to.equal('0,12'); + expect(formatNumber(1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,00'); + expect(formatNumber(1.0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,00'); + expect(formatNumber(1.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,10'); + expect(formatNumber(1.12, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,12'); + expect(formatNumber(1.123, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1,123'); + expect(formatNumber(1.1234, { style: 'decimal', maximumFractionDigits: 2 })).to.equal('1,12'); + expect( + formatNumber(1.12345678, { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + ).to.equal('1,12'); + expect(formatNumber(1000, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('1.000,00'); + expect( + formatNumber(112345678, { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + ).to.equal('112.345.678,00'); + }); + + it('formats 2-digit decimals correctly', async () => { + localize.locale = 'nl-NL'; + Array.from(new Array(100), (val, index) => index).forEach(i => { + const iString = `${i}`; + let number = 0.0; + number += i * 0.01; + expect(formatNumber(number, { style: 'decimal', minimumFractionDigits: 2 })).to.equal( + `0,${iString.padStart(2, '0')}`, + ); + }); + }); + + describe('normalization', () => { + const currencyCode = { style: 'currency', currencyDisplay: 'code' }; + const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; + + it('supports British locale', async () => { + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + 'EUR 123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( + 'USD 123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( + '€123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( + '$123,456.79', + ); + }); + + it('supports US locale', async () => { + localize.locale = 'en-US'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + 'EUR 123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( + 'USD 123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( + '€123,456.79', + ); + expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( + '$123,456.79', + ); + }); + + it('supports Bulgarian locale', async () => { + localize.locale = 'bg-BG'; + expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( + '123 456,79 EUR', + ); + expect(formatNumber(1234567890.789, { currency: 'USD', ...currencyCode })).to.equal( + '1 234 567 890,79 USD', + ); + expect(formatNumber(12.789, { currency: 'EUR', ...currencyCode })).to.equal('12,79 EUR'); + expect(formatNumber(12, { currency: 'USD', ...currencyCode })).to.equal('12,00 USD'); + expect(formatNumber(12.789, { style: 'decimal' })).to.equal('12,789'); + expect(formatNumber(12, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12,000'); + expect(formatNumber(20000, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( + '20 000,000', + ); + }); + }); +}); diff --git a/packages/localize/test/number/formatNumberToParts.test.js b/packages/localize/test/number/formatNumberToParts.test.js new file mode 100644 index 000000000..7c40184c6 --- /dev/null +++ b/packages/localize/test/number/formatNumberToParts.test.js @@ -0,0 +1,215 @@ +import { expect } from '@open-wc/testing'; +import { localize } from '../../src/localize.js'; +import { localizeTearDown } from '../../test-helpers.js'; + +import { formatNumberToParts } from '../../src/number/formatNumberToParts.js'; + +describe('formatNumberToParts', () => { + afterEach(localizeTearDown); + + describe('formats based on ISO standards', () => { + const specs = [ + ['nl-NL', 'EUR', 1234.5, '1.234,50 EUR'], + ['nl-NL', 'USD', 1234.5, '1.234,50 USD'], + ['nl-NL', 'EUR', -1234.5, '-1.234,50 EUR'], + ['nl-BE', 'EUR', 1234.5, '1.234,50 EUR'], + ['nl-BE', 'USD', 1234.5, '1.234,50 USD'], + ['nl-BE', 'EUR', -1234.5, '-1.234,50 EUR'], + ['en-GB', 'EUR', 1234.5, 'EUR 1,234.50'], + ['en-GB', 'USD', 1234.5, 'USD 1,234.50'], + ['en-GB', 'EUR', -1234.5, '-EUR 1,234.50'], + ['de-DE', 'EUR', 1234.5, '1.234,50 EUR'], + ['de-DE', 'USD', 1234.5, '1.234,50 USD'], + ['de-DE', 'EUR', -1234.5, '-1.234,50 EUR'], + ['fr-BE', 'EUR', 1234.5, '1 234,50 EUR'], + ['fr-BE', 'USD', 1234.5, '1 234,50 USD'], + ['fr-BE', 'EUR', -1234.5, '-1 234,50 EUR'], + ]; + + specs.forEach(spec => { + const [locale, currency, amount, expectedResult] = spec; + + it(`formats ${locale} ${currency} ${amount} as ${expectedResult}`, () => { + localize.locale = locale; + const parts = formatNumberToParts(amount, { + style: 'currency', + currency, + currencyDisplay: 'code', + }); + const joinedParts = parts.map(p => p.value).join(''); + expect(joinedParts).to.equal(expectedResult); + }); + }); + }); + + it('supports currency symbol with dutch locale', async () => { + localize.locale = 'nl-NL'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'symbol', + }); + expect(formattedToParts).to.eql([ + { type: 'integer', value: '3' }, + { type: 'group', value: '.' }, + { type: 'integer', value: '500' }, + { type: 'decimal', value: ',' }, + { type: 'fraction', value: '00' }, + { type: 'literal', value: ' ' }, + { type: 'currency', value: '€' }, + ]); + }); + + it('supports currency symbol with french locale', async () => { + localize.locale = 'fr-FR'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'symbol', + }); + expect(Object.keys(formattedToParts).length).to.equal(7); + expect(formattedToParts[0].type).to.equal('integer'); + expect(formattedToParts[0].value).to.equal('3'); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(' '); + expect(formattedToParts[5].type).to.equal('literal'); + expect(formattedToParts[5].value).to.equal(' '); + expect(formattedToParts[6].type).to.equal('currency'); + expect(formattedToParts[6].value).to.equal('€'); + }); + + it('supports currency symbol with British locale', async () => { + localize.locale = 'en-GB'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'symbol', + }); + expect(Object.keys(formattedToParts).length).to.equal(6); + expect(formattedToParts[2].type).to.equal('group'); + expect(formattedToParts[2].value).to.equal(','); + expect(formattedToParts[4].type).to.equal('decimal'); + expect(formattedToParts[4].value).to.equal('.'); + expect(formattedToParts[5].type).to.equal('fraction'); + expect(formattedToParts[5].value).to.equal('00'); + }); + + it('supports currency code with dutch locale', async () => { + localize.locale = 'nl-NL'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'code', + }); + expect(Object.keys(formattedToParts).length).to.equal(7); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal('.'); + expect(formattedToParts[3].type).to.equal('decimal'); + expect(formattedToParts[3].value).to.equal(','); + expect(formattedToParts[5].type).to.equal('literal'); + expect(formattedToParts[5].value).to.equal(' '); + expect(formattedToParts[6].type).to.equal('currency'); + expect(formattedToParts[6].value).to.equal('EUR'); + }); + + it('supports currency code with french locale', async () => { + localize.locale = 'fr-FR'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'code', + }); + expect(Object.keys(formattedToParts).length).to.equal(7); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(' '); + expect(formattedToParts[3].type).to.equal('decimal'); + expect(formattedToParts[3].value).to.equal(','); + expect(formattedToParts[4].type).to.equal('fraction'); + expect(formattedToParts[4].value).to.equal('00'); + }); + + it('supports currency code with British locale', async () => { + localize.locale = 'en-GB'; + const formattedToParts = formatNumberToParts(3500, { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'code', + }); + expect(Object.keys(formattedToParts).length).to.equal(7); + expect(formattedToParts[3].type).to.equal('group'); + expect(formattedToParts[3].value).to.equal(','); + expect(formattedToParts[5].type).to.equal('decimal'); + expect(formattedToParts[5].value).to.equal('.'); + }); + + it('supports currency with dutch locale and 2 decimals', async () => { + localize.locale = 'nl-NL'; + const formattedToParts = formatNumberToParts(3500, { + style: 'decimal', + minimumFractionDigits: 2, + }); + expect(Object.keys(formattedToParts).length).to.equal(5); + expect(formattedToParts[0].type).to.equal('integer'); + expect(formattedToParts[0].value).to.equal('3'); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal('.'); + expect(formattedToParts[2].type).to.equal('integer'); + expect(formattedToParts[2].value).to.equal('500'); + expect(formattedToParts[3].type).to.equal('decimal'); + expect(formattedToParts[3].value).to.equal(','); + expect(formattedToParts[4].type).to.equal('fraction'); + expect(formattedToParts[4].value).to.equal('00'); + }); + + it('supports currency with french locale and 2 decimals', async () => { + localize.locale = 'fr-FR'; + const formattedToParts = formatNumberToParts(3500, { + style: 'decimal', + minimumFractionDigits: 2, + }); + expect(Object.keys(formattedToParts).length).to.equal(5); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(' '); + expect(formattedToParts[3].type).to.equal('decimal'); + expect(formattedToParts[3].value).to.equal(','); + }); + + it('supports currency with british locale and 2 decimals', async () => { + localize.locale = 'en-GB'; + const formattedToParts = formatNumberToParts(3500, { + style: 'decimal', + minimumFractionDigits: 2, + }); + expect(Object.keys(formattedToParts).length).to.equal(5); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(','); + expect(formattedToParts[3].type).to.equal('decimal'); + expect(formattedToParts[3].value).to.equal('.'); + }); + + it('supports currency with dutch locale without decimals', async () => { + localize.locale = 'nl-NL'; + const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); + expect(Object.keys(formattedToParts).length).to.equal(3); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal('.'); + expect(formattedToParts[2].type).to.equal('integer'); + expect(formattedToParts[2].value).to.equal('500'); + }); + + it('supports currency with french locale without decimals', async () => { + localize.locale = 'fr-FR'; + const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); + expect(Object.keys(formattedToParts).length).to.equal(3); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(' '); + }); + + it('supports currency with british locale without decimals', async () => { + localize.locale = 'en-GB'; + const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); + expect(Object.keys(formattedToParts).length).to.equal(3); + expect(formattedToParts[1].type).to.equal('group'); + expect(formattedToParts[1].value).to.equal(','); + }); +}); diff --git a/packages/localize/test/number/getDecimalSeparator.test.js b/packages/localize/test/number/getDecimalSeparator.test.js new file mode 100644 index 000000000..9e39f0624 --- /dev/null +++ b/packages/localize/test/number/getDecimalSeparator.test.js @@ -0,0 +1,11 @@ +import { expect } from '@open-wc/testing'; + +import { getDecimalSeparator } from '../../src/number/getDecimalSeparator.js'; + +describe('getDecimalSeparator', () => { + it('returns decimal separator for locale', async () => { + expect(getDecimalSeparator('en-GB')).to.equal('.'); + expect(getDecimalSeparator('nl-NL')).to.equal(','); + expect(getDecimalSeparator('fr-FR')).to.equal(','); + }); +}); diff --git a/packages/localize/test/number/getFractionDigits.test.js b/packages/localize/test/number/getFractionDigits.test.js new file mode 100644 index 000000000..e9ff9478a --- /dev/null +++ b/packages/localize/test/number/getFractionDigits.test.js @@ -0,0 +1,11 @@ +import { expect } from '@open-wc/testing'; + +import { getFractionDigits } from '../../src/number/getFractionDigits.js'; + +describe('getFractionDigits', () => { + it('returns number of fraction digits for currency', async () => { + expect(getFractionDigits('JPY')).to.equal(0); + expect(getFractionDigits('EUR')).to.equal(2); + expect(getFractionDigits('BHD')).to.equal(3); + }); +}); diff --git a/packages/localize/test/number/getGroupSeparator.test.js b/packages/localize/test/number/getGroupSeparator.test.js new file mode 100644 index 000000000..78249c0bc --- /dev/null +++ b/packages/localize/test/number/getGroupSeparator.test.js @@ -0,0 +1,11 @@ +import { expect } from '@open-wc/testing'; + +import { getGroupSeparator } from '../../src/number/getGroupSeparator.js'; + +describe('getGroupSeparator', () => { + it('returns group separator for locale', async () => { + expect(getGroupSeparator('en-GB')).to.equal(','); + expect(getGroupSeparator('nl-NL')).to.equal('.'); + expect(getGroupSeparator('fr-FR')).to.equal(' '); + }); +}); From 2f352008b159b85d658c3ce391a433c214ac437c Mon Sep 17 00:00:00 2001 From: Mikhail Bashkirov Date: Wed, 26 Jun 2019 18:11:47 +0200 Subject: [PATCH 2/5] chore(localize): make currency tests more readable --- .../localize/test/number/formatNumber.test.js | 96 ++++++------------- 1 file changed, 27 insertions(+), 69 deletions(-) diff --git a/packages/localize/test/number/formatNumber.test.js b/packages/localize/test/number/formatNumber.test.js index ff99ceeed..acfc80240 100644 --- a/packages/localize/test/number/formatNumber.test.js +++ b/packages/localize/test/number/formatNumber.test.js @@ -4,55 +4,36 @@ import { localizeTearDown } from '../../test-helpers.js'; import { formatNumber } from '../../src/number/formatNumber.js'; +const currencyCode = currency => ({ style: 'currency', currencyDisplay: 'code', currency }); +const currencySymbol = currency => ({ style: 'currency', currencyDisplay: 'symbol', currency }); + describe('formatNumber', () => { afterEach(localizeTearDown); it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', async () => { - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); localize.locale = 'fr-FR'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123 456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123 457 ¥'); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123 457 ¥'); localize.locale = 'de-DE'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'JPY', ...currencySymbol })).to.equal('123.457 ¥'); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); }); it('can display currency as code', async () => { - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123.456,79 EUR', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - '123.456,79 USD', - ); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); }); it('can display currency as symbol', async () => { - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '123.456,79 €', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '123.456,79 $', - ); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); }); it('uses minus (and not dash) to indicate negative numbers ', async () => { @@ -204,50 +185,27 @@ describe('formatNumber', () => { }); describe('normalization', () => { - const currencyCode = { style: 'currency', currencyDisplay: 'code' }; - const currencySymbol = { style: 'currency', currencyDisplay: 'symbol' }; - it('supports British locale', async () => { - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - 'USD 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '$123,456.79', - ); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); }); it('supports US locale', async () => { localize.locale = 'en-US'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - 'EUR 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencyCode })).to.equal( - 'USD 123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'EUR', ...currencySymbol })).to.equal( - '€123,456.79', - ); - expect(formatNumber(123456.789, { currency: 'USD', ...currencySymbol })).to.equal( - '$123,456.79', - ); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); }); it('supports Bulgarian locale', async () => { localize.locale = 'bg-BG'; - expect(formatNumber(123456.789, { currency: 'EUR', ...currencyCode })).to.equal( - '123 456,79 EUR', - ); - expect(formatNumber(1234567890.789, { currency: 'USD', ...currencyCode })).to.equal( - '1 234 567 890,79 USD', - ); - expect(formatNumber(12.789, { currency: 'EUR', ...currencyCode })).to.equal('12,79 EUR'); - expect(formatNumber(12, { currency: 'USD', ...currencyCode })).to.equal('12,00 USD'); + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); + expect(formatNumber(1234567890.789, currencyCode('USD'))).to.equal('1 234 567 890,79 USD'); + expect(formatNumber(12.789, currencyCode('EUR'))).to.equal('12,79 EUR'); + expect(formatNumber(12, currencyCode('USD'))).to.equal('12,00 USD'); expect(formatNumber(12.789, { style: 'decimal' })).to.equal('12,789'); expect(formatNumber(12, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12,000'); expect(formatNumber(20000, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( From 86c9824f07c07726ce17993f68f9c2d328026209 Mon Sep 17 00:00:00 2001 From: Mikhail Bashkirov Date: Thu, 27 Jun 2019 09:57:51 +0200 Subject: [PATCH 3/5] chore(localize): make number formatting tests more scalable --- .../localize/test/number/formatNumber.test.js | 209 ++++++++++---- .../test/number/formatNumberToParts.test.js | 273 +++++------------- .../test/number/getDecimalSeparator.test.js | 2 +- .../test/number/getFractionDigits.test.js | 2 +- .../test/number/getGroupSeparator.test.js | 2 +- 5 files changed, 228 insertions(+), 260 deletions(-) diff --git a/packages/localize/test/number/formatNumber.test.js b/packages/localize/test/number/formatNumber.test.js index acfc80240..4cdbb66d4 100644 --- a/packages/localize/test/number/formatNumber.test.js +++ b/packages/localize/test/number/formatNumber.test.js @@ -10,45 +10,36 @@ const currencySymbol = currency => ({ style: 'currency', currencyDisplay: 'symbo describe('formatNumber', () => { afterEach(localizeTearDown); - it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', async () => { + it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', () => { + const clean = str => str.replace(/[a-zA-Z]+/g, '').trim(); + expect(clean(formatNumber(123456.789, currencyCode('JPY')))).to.equal('123,457'); + expect(clean(formatNumber(123456.789, currencyCode('EUR')))).to.equal('123,456.79'); + expect(clean(formatNumber(123456.789, currencyCode('BHD')))).to.equal('123,456.789'); + }); + + it('can display currency as code', () => { expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + }); + + it('can display currency as symbol', () => { expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); - expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); - localize.locale = 'fr-FR'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); - expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123 457 ¥'); - localize.locale = 'de-DE'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); - expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); }); - it('can display currency as code', async () => { - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); - expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); - }); - - it('can display currency as symbol', async () => { - localize.locale = 'nl-NL'; - expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); - expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); - }); - - it('uses minus (and not dash) to indicate negative numbers ', async () => { + it('uses minus (and not dash) to indicate negative numbers ', () => { expect(formatNumber(-12, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); }); - it('rounds (negative) numbers e.g. `roundMode: round`', async () => { - expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); - expect(formatNumber(12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('13'); + it('rounds (negative) numbers e.g. `roundMode: round`', () => { + expect(formatNumber(12.4, { roundMode: 'round' })).to.equal('12'); + expect(formatNumber(12.6, { roundMode: 'round' })).to.equal('13'); - expect(formatNumber(-12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-12'); - expect(formatNumber(-12.6, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('-13'); + expect(formatNumber(-12.4, { roundMode: 'round' })).to.equal('-12'); + expect(formatNumber(-12.6, { roundMode: 'round' })).to.equal('-13'); }); - it("rounds (negative) numbers up when `roundMode: 'ceiling'`", async () => { + it("rounds (negative) numbers up when `roundMode: 'ceiling'`", () => { expect(formatNumber(12.4, { roundMode: 'ceiling' })).to.equal('13'); expect(formatNumber(12.6, { roundMode: 'ceiling' })).to.equal('13'); @@ -56,7 +47,7 @@ describe('formatNumber', () => { expect(formatNumber(-12.6, { roundMode: 'ceiling' })).to.equal('-12'); }); - it('rounds (negative) numbers down when `roundMode: floor`', async () => { + it('rounds (negative) numbers down when `roundMode: floor`', () => { expect(formatNumber(12.4, { roundMode: 'floor' })).to.equal('12'); expect(formatNumber(12.6, { roundMode: 'floor' })).to.equal('12'); @@ -64,24 +55,28 @@ describe('formatNumber', () => { expect(formatNumber(-12.6, { roundMode: 'floor' })).to.equal('-13'); }); - it('returns empty string when NaN', async () => { + it('returns empty string when NaN', () => { expect(formatNumber('foo')).to.equal(''); }); - it('returns empty string when number is undefined', async () => { + it('returns empty string when number is undefined', () => { expect(formatNumber(undefined)).to.equal(''); }); - it('uses `localize.formatNumberOptions.returnIfNaN`', async () => { + it('uses `localize.formatNumberOptions.returnIfNaN`', () => { + const savedReturnIfNaN = localize.formatNumberOptions.returnIfNaN; + localize.formatNumberOptions.returnIfNaN = '-'; expect(formatNumber('foo')).to.equal('-'); + + localize.formatNumberOptions.returnIfNaN = savedReturnIfNaN; }); - it("can set what to returns when NaN via `returnIfNaN: 'foo'`", async () => { + it("can set what to returns when NaN via `returnIfNaN: 'foo'`", () => { expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-'); }); - it('uses `localize.locale`', async () => { + it('uses `localize.locale`', () => { expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( '123,456.79', ); @@ -91,7 +86,7 @@ describe('formatNumber', () => { ); }); - it('can set locale to use', async () => { + it('can set locale to use', () => { expect( formatNumber(123456.789, { locale: 'en-GB', style: 'decimal', maximumFractionDigits: 2 }), ).to.equal('123,456.79'); @@ -100,7 +95,7 @@ describe('formatNumber', () => { ).to.equal('123.456,79'); }); - it('can specify max decimal places by `maximumFractionDigits: 3`', async () => { + it('can specify max decimal places by `maximumFractionDigits: 3`', () => { expect(formatNumber(123456.789)).to.equal('123,456.789'); expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 3 })).to.equal( '123,456.789', @@ -110,7 +105,7 @@ describe('formatNumber', () => { ); }); - it('can specify min decimal places by `minimumFractionDigits: 3`', async () => { + it('can specify min decimal places by `minimumFractionDigits: 3`', () => { expect(formatNumber(12.3)).to.equal('12.3'); expect(formatNumber(12.3456, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( '12.346', @@ -118,16 +113,16 @@ describe('formatNumber', () => { expect(formatNumber(12.3, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12.300'); }); - it('can specify to show at least x digits by `minimumIntegerDigits: 5`', async () => { + it('can specify to show at least x digits by `minimumIntegerDigits: 5`', () => { expect(formatNumber(123)).to.equal('123'); expect(formatNumber(123, { minimumIntegerDigits: 5 })).to.equal('00,123'); }); - it('can display 0 decimal places', async () => { + it('can display 0 decimal places', () => { expect(formatNumber(12.4, { style: 'decimal', maximumFractionDigits: 0 })).to.equal('12'); }); - it('formats numbers correctly', async () => { + it('formats numbers correctly', () => { localize.locale = 'nl-NL'; expect(formatNumber(0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,00'); expect(formatNumber(0.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,10'); @@ -172,7 +167,7 @@ describe('formatNumber', () => { ).to.equal('112.345.678,00'); }); - it('formats 2-digit decimals correctly', async () => { + it('formats 2-digit decimals correctly', () => { localize.locale = 'nl-NL'; Array.from(new Array(100), (val, index) => index).forEach(i => { const iString = `${i}`; @@ -185,32 +180,118 @@ describe('formatNumber', () => { }); describe('normalization', () => { - it('supports British locale', async () => { - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); - expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); - expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); - expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); + describe('en-GB', () => { + it('supports basics', () => { + localize.locale = 'en-GB'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥123,457'); + }); }); - it('supports US locale', async () => { - localize.locale = 'en-US'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); - expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); - expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); - expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); + describe('en-US', () => { + it('supports basics', () => { + localize.locale = 'en-US'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥123,457'); + }); }); - it('supports Bulgarian locale', async () => { - localize.locale = 'bg-BG'; - expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); - expect(formatNumber(1234567890.789, currencyCode('USD'))).to.equal('1 234 567 890,79 USD'); - expect(formatNumber(12.789, currencyCode('EUR'))).to.equal('12,79 EUR'); - expect(formatNumber(12, currencyCode('USD'))).to.equal('12,00 USD'); - expect(formatNumber(12.789, { style: 'decimal' })).to.equal('12,789'); - expect(formatNumber(12, { style: 'decimal', minimumFractionDigits: 3 })).to.equal('12,000'); - expect(formatNumber(20000, { style: 'decimal', minimumFractionDigits: 3 })).to.equal( - '20 000,000', - ); + describe('en-AU', () => { + it('supports basics', () => { + localize.locale = 'en-AU'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); + // expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); // TODO: fix + // expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); // TODO: fix + // expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥123,457'); // TODO: fix + }); + }); + + describe('en-PH', () => { + it('supports basics', () => { + localize.locale = 'en-PH'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€123,456.79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$123,456.79'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥123,457'); + }); + }); + + describe('nl-NL', () => { + it('supports basics', () => { + localize.locale = 'nl-NL'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); + }); + }); + + describe('nl-BE', () => { + it('supports basics', () => { + localize.locale = 'nl-BE'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); + }); + }); + + describe('fr-FR', () => { + it('supports basics', () => { + localize.locale = 'fr-FR'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123 456,79 €'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123 456,79 $'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123 457 ¥'); + }); + }); + + describe('fr-BE', () => { + it('supports basics', () => { + localize.locale = 'fr-FR'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123 456,79 €'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123 456,79 $'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123 457 ¥'); + }); + }); + + describe('bg-BG', () => { + it('supports basics', () => { + localize.locale = 'bg-BG'; + expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); + expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); + expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123 456,79 €'); + // expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123 456,79 $'); // TODO: fix + // expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123 457 ¥'); // TODO: fix + }); + + it('normalizes group separator', () => { + localize.locale = 'bg-BG'; + expect(formatNumber(1.234, currencyCode('EUR'))).to.equal('1,23 EUR'); + expect(formatNumber(1234.567, currencyCode('EUR'))).to.equal('1 234,57 EUR'); + }); }); }); }); diff --git a/packages/localize/test/number/formatNumberToParts.test.js b/packages/localize/test/number/formatNumberToParts.test.js index 7c40184c6..f9ffd103e 100644 --- a/packages/localize/test/number/formatNumberToParts.test.js +++ b/packages/localize/test/number/formatNumberToParts.test.js @@ -4,212 +4,99 @@ import { localizeTearDown } from '../../test-helpers.js'; import { formatNumberToParts } from '../../src/number/formatNumberToParts.js'; +const c = v => ({ type: 'currency', value: v }); +const d = v => ({ type: 'decimal', value: v }); +const i = v => ({ type: 'integer', value: v }); +const f = v => ({ type: 'fraction', value: v }); +const g = v => ({ type: 'group', value: v }); +const l = v => ({ type: 'literal', value: v }); +const m = { type: 'minusSign', value: '-' }; + +const stringifyParts = parts => parts.map(part => part.value).join(''); + describe('formatNumberToParts', () => { afterEach(localizeTearDown); - describe('formats based on ISO standards', () => { + describe("style: 'currency'", () => { const specs = [ - ['nl-NL', 'EUR', 1234.5, '1.234,50 EUR'], - ['nl-NL', 'USD', 1234.5, '1.234,50 USD'], - ['nl-NL', 'EUR', -1234.5, '-1.234,50 EUR'], - ['nl-BE', 'EUR', 1234.5, '1.234,50 EUR'], - ['nl-BE', 'USD', 1234.5, '1.234,50 USD'], - ['nl-BE', 'EUR', -1234.5, '-1.234,50 EUR'], - ['en-GB', 'EUR', 1234.5, 'EUR 1,234.50'], - ['en-GB', 'USD', 1234.5, 'USD 1,234.50'], - ['en-GB', 'EUR', -1234.5, '-EUR 1,234.50'], - ['de-DE', 'EUR', 1234.5, '1.234,50 EUR'], - ['de-DE', 'USD', 1234.5, '1.234,50 USD'], - ['de-DE', 'EUR', -1234.5, '-1.234,50 EUR'], - ['fr-BE', 'EUR', 1234.5, '1 234,50 EUR'], - ['fr-BE', 'USD', 1234.5, '1 234,50 USD'], - ['fr-BE', 'EUR', -1234.5, '-1 234,50 EUR'], + ['en-GB', 'EUR', 1234.5, [c('EUR'), l(' '), i('1'), g(','), i('234'), d('.'), f('50')]], + ['en-GB', 'EUR', -1234.5, [m, c('EUR'), l(' '), i('1'), g(','), i('234'), d('.'), f('50')]], + ['nl-NL', 'EUR', 1234.5, [i('1'), g('.'), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['nl-NL', 'EUR', -1234.5, [m, i('1'), g('.'), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['nl-BE', 'EUR', 1234.5, [i('1'), g('.'), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['nl-BE', 'EUR', -1234.5, [m, i('1'), g('.'), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['fr-FR', 'EUR', 1234.5, [i('1'), g(' '), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['fr-FR', 'EUR', -1234.5, [m, i('1'), g(' '), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['fr-BE', 'EUR', 1234.5, [i('1'), g(' '), i('234'), d(','), f('50'), l(' '), c('EUR')]], + ['fr-BE', 'EUR', -1234.5, [m, i('1'), g(' '), i('234'), d(','), f('50'), l(' '), c('EUR')]], ]; - specs.forEach(spec => { - const [locale, currency, amount, expectedResult] = spec; - - it(`formats ${locale} ${currency} ${amount} as ${expectedResult}`, () => { + specs.forEach(([locale, currency, amount, expectedResult]) => { + it(`formats ${locale} ${currency} ${amount} as "${stringifyParts(expectedResult)}"`, () => { localize.locale = locale; - const parts = formatNumberToParts(amount, { - style: 'currency', - currency, - currencyDisplay: 'code', - }); - const joinedParts = parts.map(p => p.value).join(''); - expect(joinedParts).to.equal(expectedResult); + expect( + formatNumberToParts(amount, { + style: 'currency', + currencyDisplay: 'code', + currency, + }), + ).to.deep.equal(expectedResult); }); }); }); - it('supports currency symbol with dutch locale', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', + describe("style: 'decimal'", () => { + describe('no minimumFractionDigits', () => { + const specs = [ + ['en-GB', 3500, [i('3'), g(','), i('500')]], + ['en-GB', -3500, [m, i('3'), g(','), i('500')]], + ['nl-NL', 3500, [i('3'), g('.'), i('500')]], + ['nl-NL', -3500, [m, i('3'), g('.'), i('500')]], + ['nl-BE', 3500, [i('3'), g('.'), i('500')]], + ['nl-BE', -3500, [m, i('3'), g('.'), i('500')]], + ['fr-FR', 3500, [i('3'), g(' '), i('500')]], + ['fr-FR', -3500, [m, i('3'), g(' '), i('500')]], + ['fr-BE', 3500, [i('3'), g(' '), i('500')]], + ['fr-BE', -3500, [m, i('3'), g(' '), i('500')]], + ]; + + specs.forEach(([locale, amount, expectedResult]) => { + it(`formats ${locale} ${amount} as "${stringifyParts(expectedResult)}"`, () => { + localize.locale = locale; + expect( + formatNumberToParts(amount, { + style: 'decimal', + }), + ).to.deep.equal(expectedResult); + }); + }); }); - expect(formattedToParts).to.eql([ - { type: 'integer', value: '3' }, - { type: 'group', value: '.' }, - { type: 'integer', value: '500' }, - { type: 'decimal', value: ',' }, - { type: 'fraction', value: '00' }, - { type: 'literal', value: ' ' }, - { type: 'currency', value: '€' }, - ]); - }); - it('supports currency symbol with french locale', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', + describe('minimumFractionDigits: 2', () => { + const specs = [ + ['en-GB', 3500, [i('3'), g(','), i('500'), d('.'), f('00')]], + ['en-GB', -3500, [m, i('3'), g(','), i('500'), d('.'), f('00')]], + ['nl-NL', 3500, [i('3'), g('.'), i('500'), d(','), f('00')]], + ['nl-NL', -3500, [m, i('3'), g('.'), i('500'), d(','), f('00')]], + ['nl-BE', 3500, [i('3'), g('.'), i('500'), d(','), f('00')]], + ['nl-BE', -3500, [m, i('3'), g('.'), i('500'), d(','), f('00')]], + ['fr-FR', 3500, [i('3'), g(' '), i('500'), d(','), f('00')]], + ['fr-FR', -3500, [m, i('3'), g(' '), i('500'), d(','), f('00')]], + ['fr-BE', 3500, [i('3'), g(' '), i('500'), d(','), f('00')]], + ['fr-BE', -3500, [m, i('3'), g(' '), i('500'), d(','), f('00')]], + ]; + + specs.forEach(([locale, amount, expectedResult]) => { + it(`formats ${locale} ${amount} as "${stringifyParts(expectedResult)}"`, () => { + localize.locale = locale; + expect( + formatNumberToParts(amount, { + style: 'decimal', + minimumFractionDigits: 2, + }), + ).to.deep.equal(expectedResult); + }); + }); }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[0].type).to.equal('integer'); - expect(formattedToParts[0].value).to.equal('3'); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[5].type).to.equal('literal'); - expect(formattedToParts[5].value).to.equal(' '); - expect(formattedToParts[6].type).to.equal('currency'); - expect(formattedToParts[6].value).to.equal('€'); - }); - - it('supports currency symbol with British locale', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'symbol', - }); - expect(Object.keys(formattedToParts).length).to.equal(6); - expect(formattedToParts[2].type).to.equal('group'); - expect(formattedToParts[2].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('decimal'); - expect(formattedToParts[4].value).to.equal('.'); - expect(formattedToParts[5].type).to.equal('fraction'); - expect(formattedToParts[5].value).to.equal('00'); - }); - - it('supports currency code with dutch locale', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[5].type).to.equal('literal'); - expect(formattedToParts[5].value).to.equal(' '); - expect(formattedToParts[6].type).to.equal('currency'); - expect(formattedToParts[6].value).to.equal('EUR'); - }); - - it('supports currency code with french locale', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('fraction'); - expect(formattedToParts[4].value).to.equal('00'); - }); - - it('supports currency code with British locale', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'currency', - currency: 'EUR', - currencyDisplay: 'code', - }); - expect(Object.keys(formattedToParts).length).to.equal(7); - expect(formattedToParts[3].type).to.equal('group'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[5].type).to.equal('decimal'); - expect(formattedToParts[5].value).to.equal('.'); - }); - - it('supports currency with dutch locale and 2 decimals', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[0].type).to.equal('integer'); - expect(formattedToParts[0].value).to.equal('3'); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[2].type).to.equal('integer'); - expect(formattedToParts[2].value).to.equal('500'); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - expect(formattedToParts[4].type).to.equal('fraction'); - expect(formattedToParts[4].value).to.equal('00'); - }); - - it('supports currency with french locale and 2 decimals', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal(','); - }); - - it('supports currency with british locale and 2 decimals', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { - style: 'decimal', - minimumFractionDigits: 2, - }); - expect(Object.keys(formattedToParts).length).to.equal(5); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(','); - expect(formattedToParts[3].type).to.equal('decimal'); - expect(formattedToParts[3].value).to.equal('.'); - }); - - it('supports currency with dutch locale without decimals', async () => { - localize.locale = 'nl-NL'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal('.'); - expect(formattedToParts[2].type).to.equal('integer'); - expect(formattedToParts[2].value).to.equal('500'); - }); - - it('supports currency with french locale without decimals', async () => { - localize.locale = 'fr-FR'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(' '); - }); - - it('supports currency with british locale without decimals', async () => { - localize.locale = 'en-GB'; - const formattedToParts = formatNumberToParts(3500, { style: 'decimal' }); - expect(Object.keys(formattedToParts).length).to.equal(3); - expect(formattedToParts[1].type).to.equal('group'); - expect(formattedToParts[1].value).to.equal(','); }); }); diff --git a/packages/localize/test/number/getDecimalSeparator.test.js b/packages/localize/test/number/getDecimalSeparator.test.js index 9e39f0624..a9ec4dbea 100644 --- a/packages/localize/test/number/getDecimalSeparator.test.js +++ b/packages/localize/test/number/getDecimalSeparator.test.js @@ -3,7 +3,7 @@ import { expect } from '@open-wc/testing'; import { getDecimalSeparator } from '../../src/number/getDecimalSeparator.js'; describe('getDecimalSeparator', () => { - it('returns decimal separator for locale', async () => { + it('returns decimal separator for locale', () => { expect(getDecimalSeparator('en-GB')).to.equal('.'); expect(getDecimalSeparator('nl-NL')).to.equal(','); expect(getDecimalSeparator('fr-FR')).to.equal(','); diff --git a/packages/localize/test/number/getFractionDigits.test.js b/packages/localize/test/number/getFractionDigits.test.js index e9ff9478a..87599a5d1 100644 --- a/packages/localize/test/number/getFractionDigits.test.js +++ b/packages/localize/test/number/getFractionDigits.test.js @@ -3,7 +3,7 @@ import { expect } from '@open-wc/testing'; import { getFractionDigits } from '../../src/number/getFractionDigits.js'; describe('getFractionDigits', () => { - it('returns number of fraction digits for currency', async () => { + it('returns number of fraction digits for currency', () => { expect(getFractionDigits('JPY')).to.equal(0); expect(getFractionDigits('EUR')).to.equal(2); expect(getFractionDigits('BHD')).to.equal(3); diff --git a/packages/localize/test/number/getGroupSeparator.test.js b/packages/localize/test/number/getGroupSeparator.test.js index 78249c0bc..c8a14e47e 100644 --- a/packages/localize/test/number/getGroupSeparator.test.js +++ b/packages/localize/test/number/getGroupSeparator.test.js @@ -3,7 +3,7 @@ import { expect } from '@open-wc/testing'; import { getGroupSeparator } from '../../src/number/getGroupSeparator.js'; describe('getGroupSeparator', () => { - it('returns group separator for locale', async () => { + it('returns group separator for locale', () => { expect(getGroupSeparator('en-GB')).to.equal(','); expect(getGroupSeparator('nl-NL')).to.equal('.'); expect(getGroupSeparator('fr-FR')).to.equal(' '); From 0afb0e127265575eb52b245f7d552d0a2a2d77e5 Mon Sep 17 00:00:00 2001 From: Mikhail Bashkirov Date: Thu, 27 Jun 2019 09:59:54 +0200 Subject: [PATCH 4/5] fix(localize): remove unnecessary normalization for Belgium Belgium does not need to be normalized always (e.g. not in "fr-BE"). But Dutch language does need normalization always, because this is where Intl really makes it wrong, both for "nl-Nl" and "nl-BE". --- packages/localize/src/number/normalizeIntl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/localize/src/number/normalizeIntl.js b/packages/localize/src/number/normalizeIntl.js index d63ea9b8d..cabd6a143 100644 --- a/packages/localize/src/number/normalizeIntl.js +++ b/packages/localize/src/number/normalizeIntl.js @@ -17,7 +17,7 @@ export function normalizeIntl(formattedParts, options, _locale) { let normalize = forceNormalSpaces(formattedParts, options); // Dutch and Belgian currency must be moved to end of number if (options && options.style === 'currency') { - if (_locale === 'nl-NL' || _locale.slice(-2) === 'BE') { + if (_locale.slice(0, 2) === 'nl') { normalize = forceCurrencyToEnd(normalize); } // Add group separator for Bulgarian locale From 8fb70c2080ca7b00aa5f4443b3d0d83ae6ae0ae7 Mon Sep 17 00:00:00 2001 From: bverhoef Date: Thu, 27 Jun 2019 10:43:31 +0200 Subject: [PATCH 5/5] fix(localize): return sign to the front of the currency formatted value in Dutch --- packages/localize/src/number/normalizeIntl.js | 2 +- packages/localize/test/number/formatNumber.test.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/localize/src/number/normalizeIntl.js b/packages/localize/src/number/normalizeIntl.js index cabd6a143..1c1a7f869 100644 --- a/packages/localize/src/number/normalizeIntl.js +++ b/packages/localize/src/number/normalizeIntl.js @@ -17,7 +17,7 @@ export function normalizeIntl(formattedParts, options, _locale) { let normalize = forceNormalSpaces(formattedParts, options); // Dutch and Belgian currency must be moved to end of number if (options && options.style === 'currency') { - if (_locale.slice(0, 2) === 'nl') { + if (options.currencyDisplay === 'code' && _locale.slice(0, 2) === 'nl') { normalize = forceCurrencyToEnd(normalize); } // Add group separator for Bulgarian locale diff --git a/packages/localize/test/number/formatNumber.test.js b/packages/localize/test/number/formatNumber.test.js index 4cdbb66d4..c5eb071ee 100644 --- a/packages/localize/test/number/formatNumber.test.js +++ b/packages/localize/test/number/formatNumber.test.js @@ -234,9 +234,9 @@ describe('formatNumber', () => { expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); - expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); - expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); - expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€ 123.456,79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$ 123.456,79'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥ 123.457'); }); }); @@ -246,9 +246,9 @@ describe('formatNumber', () => { expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); - expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('123.456,79 €'); - expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('123.456,79 $'); - expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('123.457 ¥'); + expect(formatNumber(123456.789, currencySymbol('EUR'))).to.equal('€ 123.456,79'); + expect(formatNumber(123456.789, currencySymbol('USD'))).to.equal('$ 123.456,79'); + expect(formatNumber(123456.789, currencySymbol('JPY'))).to.equal('¥ 123.457'); }); });