Rename usePoint (real meaning useComma) and refactoring

This commit is contained in:
Vesa Luusua 2017-03-22 18:34:16 +02:00
parent 9344bdc00d
commit ae7b999774
7 changed files with 104 additions and 57 deletions

View file

@ -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',
};

View file

@ -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,

View file

@ -169,6 +169,7 @@ class EditListingForm extends Component {
const descriptionRequiredMessage = intl.formatMessage({
id: 'EditListingForm.descriptionRequired',
});
const pricePlaceholderMessage = intl.formatMessage({ id: 'EditListingForm.pricePlaceholder' });
return (
<form onSubmit={handleSubmit}>
@ -185,7 +186,7 @@ class EditListingForm extends Component {
component={EnhancedCurrencyInput}
currencyConfig={config.currencyConfig}
validate={[required(priceRequiredMessage)]}
placeholder="10.0"
placeholder={pricePlaceholderMessage}
/>
<h3>Images</h3>

View file

@ -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.",

View file

@ -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()) {

View file

@ -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');

View file

@ -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,
};