From ae7b999774ff412c766a2082f1ac797ff50a627d Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 22 Mar 2017 18:34:16 +0200 Subject: [PATCH] Rename usePoint (real meaning useComma) and refactoring --- .../CurrencyInput/CurrencyInput.example.js | 4 +- src/components/CurrencyInput/CurrencyInput.js | 17 ++- .../EditListingForm/EditListingForm.js | 3 +- src/translations/en.json | 1 + src/util/currency.js | 119 +++++++++++------- src/util/currency.test.js | 6 +- src/util/test-data.js | 11 ++ 7 files changed, 104 insertions(+), 57 deletions(-) diff --git a/src/components/CurrencyInput/CurrencyInput.example.js b/src/components/CurrencyInput/CurrencyInput.example.js index c9e3fd1b..f21bb246 100644 --- a/src/components/CurrencyInput/CurrencyInput.example.js +++ b/src/components/CurrencyInput/CurrencyInput.example.js @@ -3,11 +3,11 @@ import React, { PropTypes } from 'react'; import { IntlProvider, addLocaleData } from 'react-intl'; import en from 'react-intl/locale-data/en'; import fi from 'react-intl/locale-data/fi'; -import { currencyDefaultConfig } from '../../util/currency'; +import { currencyConfig } from '../../util/test-data'; import CurrencyInput from './CurrencyInput'; const defaultConfig = { - ...currencyDefaultConfig, + ...currencyConfig, currency: 'USD', }; diff --git a/src/components/CurrencyInput/CurrencyInput.js b/src/components/CurrencyInput/CurrencyInput.js index 84d7e41f..96e36f1b 100644 --- a/src/components/CurrencyInput/CurrencyInput.js +++ b/src/components/CurrencyInput/CurrencyInput.js @@ -8,7 +8,6 @@ import { intlShape, injectIntl } from 'react-intl'; import { types } from '../../util/sdkLoader'; import { convertUnitToSubUnit, - currencyDefaultConfig, ensureDotSeparator, ensureSeparator, truncateToSubUnitPrecision, @@ -19,7 +18,7 @@ const allowedInputProps = allProps => { // Strip away props that are not passed to input element (or are overwritten) // eslint-disable-next-line no-unused-vars const { currencyConfig, defaultValue, intl, input, meta, ...inputProps } = allProps; - return inputProps || {}; + return inputProps; }; // Convert unformatted value (e.g. 10,00) to Money (or null) @@ -38,19 +37,19 @@ class CurrencyInput extends Component { super(props); const { currencyConfig, defaultValue, intl } = props; - // We need to handle number format - some locales use dots and some points as decimal separator + // We need to handle number format - some locales use dots and some commas as decimal separator // TODO Figure out if this could be digged from React-Intl directly somehow const testSubUnitFormat = intl.formatNumber('1.1', currencyConfig); - const usesPoint = testSubUnitFormat.indexOf(',') >= 0; + const usesComma = testSubUnitFormat.indexOf(',') >= 0; try { // whatever is passed as a default value, will be converted to currency string // Unformatted value is digits + localized sub unit separator ("9,99") const unformattedValue = defaultValue ? truncateToSubUnitPrecision( - ensureSeparator(defaultValue.toString(), usesPoint), + ensureSeparator(defaultValue.toString(), usesComma), currencyConfig.subUnitDivisor, - usesPoint + usesComma ) : ''; // Formatted value fully localized currency string ("$1,000.99") @@ -62,7 +61,7 @@ class CurrencyInput extends Component { formattedValue, unformattedValue, value: formattedValue, - usesPoint, + usesComma, }; } catch (e) { // Print error, if default value isn't supported (see specs: truncateToSubUnitPrecision). @@ -134,7 +133,7 @@ class CurrencyInput extends Component { const truncatedValueString = truncateToSubUnitPrecision( valueOrZero, currencyConfig.subUnitDivisor, - this.state.usesPoint + this.state.usesComma ); const unformattedValue = !isEmptyString ? truncatedValueString : ''; const formattedValue = !isEmptyString @@ -177,7 +176,7 @@ class CurrencyInput extends Component { } CurrencyInput.defaultProps = { - currencyConfig: currencyDefaultConfig, + currencyConfig: null, defaultValue: 0, input: null, placeholder: null, diff --git a/src/containers/EditListingForm/EditListingForm.js b/src/containers/EditListingForm/EditListingForm.js index 38917769..7a794791 100644 --- a/src/containers/EditListingForm/EditListingForm.js +++ b/src/containers/EditListingForm/EditListingForm.js @@ -169,6 +169,7 @@ class EditListingForm extends Component { const descriptionRequiredMessage = intl.formatMessage({ id: 'EditListingForm.descriptionRequired', }); + const pricePlaceholderMessage = intl.formatMessage({ id: 'EditListingForm.pricePlaceholder' }); return (
@@ -185,7 +186,7 @@ class EditListingForm extends Component { component={EnhancedCurrencyInput} currencyConfig={config.currencyConfig} validate={[required(priceRequiredMessage)]} - placeholder="10.0" + placeholder={pricePlaceholderMessage} />

Images

diff --git a/src/translations/en.json b/src/translations/en.json index d0342bc8..59ebcea9 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -11,6 +11,7 @@ "EditListingForm.maxLength": "Must be {maxLength} characters or less", "EditListingForm.locationRequired": "You need to provide a location", "EditListingForm.locationNotRecognized": "We didn't recognize this location. Please try another location.", + "EditListingForm.pricePlaceholder": "$0.00", "EditListingPage.titleCreateListing": "Create a listing", "EditListingPage.titleEditListing": "Edit listing", "EditListingForm.descriptionRequired": "You need to add a description.", diff --git a/src/util/currency.js b/src/util/currency.js index 7724364c..a06098e3 100644 --- a/src/util/currency.js +++ b/src/util/currency.js @@ -1,53 +1,64 @@ import { trimEnd } from 'lodash'; import Decimal from 'decimal.js'; -// Default config for currency formatting (for React-Intl). -export const currencyDefaultConfig = { - style: 'currency', - currency: 'USD', - currencyDisplay: 'symbol', - useGrouping: true, - minimumFractionDigits: 2, - maximumFractionDigits: 2, - subUnitDivisor: 100, -}; - ////////// Currency manipulation in string format ////////// -// Ensures that the given string uses only dots or points -// ensureSeparator('9999999,99', false) // => '9999999.99' -export const ensureSeparator = (str, usePoint = false) => { +/** + * Ensures that the given string uses only dots or commas + * e.g. ensureSeparator('9999999,99', false) // => '9999999.99' + * + * @param {String} str - string to be formatted + * + * @return {String} converted string + */ +export const ensureSeparator = (str, useComma = false) => { if (typeof str !== 'string') { - throw new Error('Parameter must be a string'); + throw new TypeError('Parameter must be a string'); } - return usePoint ? str.split('.').join(',') : str.split(',').join('.'); + return useComma ? str.replace(/\./g, ',') : str.replace(/,/g, '.'); }; -// Ensures that the given string uses only dots (e.g. JavaScript floats use dots) +/** + * Ensures that the given string uses only dots + * (e.g. JavaScript floats use dots) + * + * @param {String} str - string to be formatted + * + * @return {String} converted string + */ export const ensureDotSeparator = str => { return ensureSeparator(str, false); }; -// Convert string to Decimal object (from Decimal.js math library) +/** + * Convert string to Decimal object (from Decimal.js math library) + * Handles both dots and commas as decimal separators + * + * @param {String} str - string to be converted + * + * @return {Decimal} numeral value + */ export const convertToDecimal = str => { const dotFormattedStr = ensureDotSeparator(str); - try { - return new Decimal(dotFormattedStr); - } catch (e) { - throw e; - } + return new Decimal(dotFormattedStr); }; -// Converts Decimal object to string -export const convertDecimalToString = (decimalValue, usePoint = false) => { - try { - const d = new Decimal(decimalValue); - return ensureSeparator(d.toString(), usePoint); - } catch (e) { - throw e; - } +/** + * Converts Decimal value to a string (from Decimal.js math library) + * + * @param {Decimal|Number|String} decimalValue + * + * @param {boolean} useComma - optional. + * Specify if return value should use comma as separator + * + * @return {String} converted value + */ +export const convertDecimalToString = (decimalValue, useComma = false) => { + const d = new Decimal(decimalValue); + return ensureSeparator(d.toString(), useComma); }; +// Divisor can be positive value given as Decimal, Number, or String const convertDivisorToDecimal = divisor => { try { const divisorAsDecimal = new Decimal(divisor); @@ -60,15 +71,27 @@ const convertDivisorToDecimal = divisor => { } }; -// Limits value to sub-unit precision: "1.4567" -> "1.45" -// Useful in input fields so this doesn't use rounding. -export const truncateToSubUnitPrecision = (inputString, subUnitDivisor, usePoint = false) => { +/** + * Limits value to sub-unit precision: "1.4567" -> "1.45" + * Useful in input fields so this doesn't use rounding. + * + * @param {String} inputString - positive number presentation. + * + * @param {Decimal|Number|String} subUnitDivisor - a ratio between currency's + * main unit and sub units + * + * @param {boolean} useComma - optional. + * Specify if return value should use comma as separator + * + * @return {String} truncated value + */ +export const truncateToSubUnitPrecision = (inputString, subUnitDivisor, useComma = false) => { const subUnitDivisorAsDecimal = convertDivisorToDecimal(subUnitDivisor); // '10,' should be passed through, but that format is not supported as valid number - const trimmed = trimEnd(inputString, usePoint ? ',' : '.'); + const trimmed = trimEnd(inputString, useComma ? ',' : '.'); // create another instance and check if value is convertable - const value = convertToDecimal(trimmed, usePoint); + const value = convertToDecimal(trimmed, useComma); if (value.isNegative()) { throw new Error(`Parameter (${inputString}) must be a positive number.`); @@ -86,25 +109,37 @@ export const truncateToSubUnitPrecision = (inputString, subUnitDivisor, usePoint const decimalPrecisionMax2 = decimalCount2.length >= inputString.length ? inputString : value.toFixed(2); - return ensureSeparator(decimalPrecisionMax2, usePoint); + return ensureSeparator(decimalPrecisionMax2, useComma); } else { // truncate strings ('9.999' => '9.99') const truncated = amount.truncated().dividedBy(subUnitDivisorAsDecimal); - return convertDecimalToString(truncated, usePoint); + return convertDecimalToString(truncated, useComma); } }; ////////// Currency - Money helpers ////////// -// Converts given value to sub unit value and returns it as a number -export const convertUnitToSubUnit = (value, subUnitDivisor, usePoint = false) => { +/** + * Converts given value to sub unit value and returns it as a number + * + * @param {Number|String} value + * + * @param {Decimal|Number|String} subUnitDivisor - a ratio between currency's + * main unit and sub units + * + * @param {boolean} useComma - optional. + * Specify if return value should use comma as separator + * + * @return {number} converted value + */ +export const convertUnitToSubUnit = (value, subUnitDivisor, useComma = false) => { const subUnitDivisorAsDecimal = convertDivisorToDecimal(subUnitDivisor); if (!(typeof value === 'string' || typeof value === 'number')) { - throw new Error('Value must be either number or string'); + throw new TypeError('Value must be either number or string'); } - const val = typeof value === 'string' ? convertToDecimal(value, usePoint) : new Decimal(value); + const val = typeof value === 'string' ? convertToDecimal(value, useComma) : new Decimal(value); const amount = val.times(subUnitDivisorAsDecimal); if (amount.isInteger()) { diff --git a/src/util/currency.test.js b/src/util/currency.test.js index da68f528..f9373918 100644 --- a/src/util/currency.test.js +++ b/src/util/currency.test.js @@ -9,7 +9,7 @@ import { describe('currency utils', () => { describe('ensureSeparator(str)', () => { - it('changes points in string to dots ', () => { + it('changes commas in string to dots ', () => { expect(ensureSeparator('0')).toEqual('0'); expect(ensureSeparator('0.0')).toEqual('0.0'); expect(ensureSeparator('0.')).toEqual('0.'); @@ -19,7 +19,7 @@ describe('currency utils', () => { expect(ensureSeparator('asdf')).toEqual('asdf'); }); - it('changes dots in string to points ', () => { + it('changes dots in string to commas ', () => { expect(ensureSeparator('0', true)).toEqual('0'); expect(ensureSeparator('0.0', true)).toEqual('0,0'); expect(ensureSeparator('0.', true)).toEqual('0,'); @@ -98,7 +98,7 @@ describe('currency utils', () => { describe('truncateToSubUnitPrecision(valueStr, subUnitDivisor)', () => { const subUnitDivisor = 100; - it('positive values', () => { + it('Values with no truncation needed', () => { expect(truncateToSubUnitPrecision('0', subUnitDivisor)).toEqual('0'); expect(truncateToSubUnitPrecision('1', subUnitDivisor)).toEqual('1'); expect(truncateToSubUnitPrecision('10', subUnitDivisor)).toEqual('10'); diff --git a/src/util/test-data.js b/src/util/test-data.js index 7300f2ed..d7f361be 100644 --- a/src/util/test-data.js +++ b/src/util/test-data.js @@ -23,3 +23,14 @@ export const createListing = id => ({ geolocation: new LatLng(40, 60), }, }); + +// Default config for currency formatting in tests and examples. +export const currencyConfig = { + style: 'currency', + currency: 'USD', + currencyDisplay: 'symbol', + useGrouping: true, + minimumFractionDigits: 2, + maximumFractionDigits: 2, + subUnitDivisor: 100, +};