Skip to content

Commit

Permalink
Merge pull request #2 from tncalvert/allow_negative
Browse files Browse the repository at this point in the history
Allow negative numbers in input.
  • Loading branch information
jsillitoe authored Dec 6, 2016
2 parents f7f41d2 + f44f1ed commit 0537a87
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,6 @@ All other attributes are applied to the input element. For example, you can int
| decimalSeparator | '.' | The decimal separator |
| thousandSeparator | ',' | The thousand separator |
| inputType | "text" | Input field tag type. You may want to use `number` or `tel` |
| allowNegative | false | Allows negative numbers in the input |
16 changes: 10 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const CurrencyInput = React.createClass({
decimalSeparator: PropTypes.string,
thousandSeparator: PropTypes.string,
precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
inputType: PropTypes.string
inputType: PropTypes.string,
allowNegative: PropTypes.bool
},


Expand All @@ -29,7 +30,7 @@ const CurrencyInput = React.createClass({
* Invoked once and cached when the class is created. Values in the mapping will be set on this.props if that
* prop is not specified by the parent component
*
* @returns {{onChange: onChange, value: string, decimalSeparator: string, thousandSeparator: string, precision: number}}
* @returns {{onChange: onChange, value: string, decimalSeparator: string, thousandSeparator: string, precision: number, inputType: string, allowNegative: boolean}}
*/
getDefaultProps(){
return {
Expand All @@ -38,7 +39,8 @@ const CurrencyInput = React.createClass({
decimalSeparator: ".",
thousandSeparator: ",",
precision: "2",
inputType: "text"
inputType: "text",
allowNegative: false
}
},

Expand All @@ -58,8 +60,9 @@ const CurrencyInput = React.createClass({
delete customProps.thousandSeparator;
delete customProps.precision;
delete customProps.inputType;
delete customProps.allowNegative;
return {
maskedValue: mask(this.props.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator),
maskedValue: mask(this.props.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator, this.props.allowNegative),
customProps: customProps
}
},
Expand All @@ -80,8 +83,9 @@ const CurrencyInput = React.createClass({
delete customProps.thousandSeparator;
delete customProps.precision;
delete customProps.inputType;
delete customProps.allowNegative;
this.setState({
maskedValue: mask(nextProps.value, nextProps.precision, nextProps.decimalSeparator, nextProps.thousandSeparator),
maskedValue: mask(nextProps.value, nextProps.precision, nextProps.decimalSeparator, nextProps.thousandSeparator, nextProps.allowNegative),
customProps: customProps
});
},
Expand All @@ -103,7 +107,7 @@ const CurrencyInput = React.createClass({
*/
handleChange(event){
event.preventDefault();
let maskedValue = mask(event.target.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator);
let maskedValue = mask(event.target.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator, this.props.allowNegative);
this.setState({maskedValue: maskedValue});
this.props.onChange(maskedValue);
},
Expand Down
33 changes: 32 additions & 1 deletion src/mask.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@

export default function mask(value, precision, decimalSeparator, thousandSeparator){
export default function mask(value, precision, decimalSeparator, thousandSeparator, allowNegative){
// provide some default values and arg validation.
if (decimalSeparator === undefined){decimalSeparator = ".";} // default to '.' as decimal separator
if (thousandSeparator === undefined){thousandSeparator = ",";} // default to ',' as thousand separator
if (allowNegative === undefined){allowNegative = false;} // default to not allowing negative numbers
if (precision === undefined){precision = 2;} // by default, 2 decimal places
if (precision < 0) {precision = 0;} // precision cannot be negative.
if (precision > 20) {precision = 20;} // precision cannot greater than 20

let numberIsNegative = false;
if (allowNegative) {
let negativeSignCount = (value.match(/-/g) || []).length;
// number will be negative if we have an odd number of "-"
// ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative
// and making a negative number positive, respectively)
numberIsNegative = negativeSignCount % 2 === 1;
}

// extract digits. if no digits, fill in a zero.
let digits = value.match(/\d/g) || ['0'];

if (allowNegative) {
// if every digit in the array is '0', then the number should
// never be negative
let allDigitsAreZero = true;
for(let idx=0; idx < digits.length; idx += 1) {
if(digits[idx] !== '0') {
allDigitsAreZero = false;
break;
}
}
if(allDigitsAreZero) {
numberIsNegative = false;
}
}

// zero-pad a input
while (digits.length <= precision) {digits.unshift('0')}

Expand All @@ -36,5 +61,11 @@ export default function mask(value, precision, decimalSeparator, thousandSeparat
digits.splice(x, 0, thousandSeparator);
}

// if the number is negative, insert a "-" to
// the front of the array
if (allowNegative && numberIsNegative) {
digits.unshift('-');
}

return digits.join('');
}
69 changes: 69 additions & 0 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,73 @@ describe('react-currency-input', function(){
});


describe('negative numbers', function() {

before('render and locate element', function() {
this.renderedComponent = ReactTestUtils.renderIntoDocument(
<CurrencyInput onChange={this.handleChange} value="0" allowNegative={true}/>
);

this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag(
this.renderedComponent,
'input'
);
});

beforeEach('reset value to 0', function() {
this.inputComponent.value = "0";
ReactTestUtils.Simulate.change(this.inputComponent);
});

it('should render 0 without negative sign', function() {
expect(this.renderedComponent.getMaskedValue()).to.equal('0.00');
this.inputComponent.value = "-0"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('0.00');
});

it('should render number with no or even number of "-" as positive', function() {
expect(this.renderedComponent.getMaskedValue()).to.equal('0.00');
this.inputComponent.value = "123456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "--123456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "123--456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "123456--"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "--123--456--"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "123456----"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
});

it('should render number with odd number of "-" as negative', function() {
expect(this.renderedComponent.getMaskedValue()).to.equal('0.00');
this.inputComponent.value = "-123456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
this.inputComponent.value = "123-456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
this.inputComponent.value = "123456-"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
this.inputComponent.value = "-123-456-"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
});

it('should correctly change between negative and positive numbers', function() {
expect(this.renderedComponent.getMaskedValue()).to.equal('0.00');
this.inputComponent.value = "123456"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "1,234.56-"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
this.inputComponent.value = "-1,234.56-"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
this.inputComponent.value = "1-,234.56"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56');
this.inputComponent.value = "-1,234.-56"; ReactTestUtils.Simulate.change(this.inputComponent);
expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56');
});

});


});
50 changes: 50 additions & 0 deletions test/mask.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,55 @@ describe('mask', function(){
});


describe('negative numbers', function(){

it('all "-" should be stripped out if allowNegative is false', function(){
expect(mask("123456")).to.equal("1,234.56");
expect(mask("-123456")).to.equal("1,234.56");
expect(mask("--123456")).to.equal("1,234.56");
expect(mask("--123--456")).to.equal("1,234.56");
expect(mask("--123--456--")).to.equal("1,234.56");
});

it('single "-" anywhere in the string should result in a negative number', function(){
expect(mask("-123456", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("123-456", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("123456-", "2", ".", ",", true)).to.equal("-1,234.56");
});

it('no or even number of "-" should result in a positive number', function(){
expect(mask("123456", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("--123456", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("123--456", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("123456--", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("--123456--", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("--123--456--", "2", ".", ",", true)).to.equal("1,234.56");
expect(mask("--1--234--56--", "2", ".", ",", true)).to.equal("1,234.56");
});

it('odd number of "-" should result in a negative number', function(){
expect(mask("-123456", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("123-456", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("123456-", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("-123-456-", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("-1-23-45-6-", "2", ".", ",", true)).to.equal("-1,234.56");
expect(mask("-1-2-3-4-5-6-", "2", ".", ",", true)).to.equal("-1,234.56");
});

it('0 is never negative', function(){
expect(mask("", "2", ".", ",", true)).to.equal("0.00");
expect(mask("0", "2", ".", ",", true)).to.equal("0.00");
expect(mask("-0", "2", ".", ",", true)).to.equal("0.00");
expect(mask("-0-", "2", ".", ",", true)).to.equal("0.00");
expect(mask("--0-", "2", ".", ",", true)).to.equal("0.00");
});

it('just "-" should result in 0.00', function(){
expect(mask("-", "2", ".", ",", true)).to.equal("0.00");
});

});



});

0 comments on commit 0537a87

Please sign in to comment.