diff --git a/source/components/CurrencyInput/CurrencyInput.js b/source/components/CurrencyInput/CurrencyInput.js index 5cb641353..0203aaef0 100644 --- a/source/components/CurrencyInput/CurrencyInput.js +++ b/source/components/CurrencyInput/CurrencyInput.js @@ -15,35 +15,52 @@ let currencyFormat = language => ({ thousandSeparator: Intl.NumberFormat(language) .format(1000) + .charAt(1), + + decimalSeparator: Intl.NumberFormat(language) + .format(0.1) .charAt(1) }) class CurrencyInput extends Component { state = { - value: this.props.storeValue + value: this.props.value || '', + valueHasChanged: false, } + onChange = this.props.debounce ? debounce(this.props.debounce, this.props.onChange) : this.props.onChange - componentDidUpdate(prevProps) { - if ( - prevProps.storeValue !== this.props.storeValue && - this.props.storeValue !== this.state.value - ) { - this.setState({ value: this.props.storeValue }) + handleNextChange = false + value = undefined + handleChange = event => { + // Only trigger the `onChange` event if the value has changed -- and not + // only its formating, we don't want to call it when a dot is added in `12.` + // for instance + if (!this.handleNextChange) { + return } + this.handleNextChange = false + event.persist() + event.target = { + ...event.target, + value: this.value + } + this.onChange(event) } render() { let forwardedProps = omit( - ['onChange', 'defaultValue', 'language', 'className', 'value', 'normalizedValueRef'], + ['onChange', 'defaultValue', 'language', 'className', 'value'], this.props ) - const { isCurrencyPrefixed, thousandSeparator } = currencyFormat( - this.props.language - ) + const { + isCurrencyPrefixed, + thousandSeparator, + decimalSeparator + } = currencyFormat(this.props.language) return (
{ - this.setState({ value }) - this.props.normalizedValueRef.current = value + this.setState({valueHasChanged: true, value}) + this.value = value.toString().replace(/^\-/,'') + this.handleNextChange = true }} - onChange={this.onChange} - value={this.state.value} + onChange={this.handleChange} + value={this.state.value.toString() + .replace('.', decimalSeparator)} /> {!isCurrencyPrefixed && <> €}
diff --git a/source/components/CurrencyInput/CurrencyInput.test.js b/source/components/CurrencyInput/CurrencyInput.test.js index f1e0f6eb4..cdee61c95 100644 --- a/source/components/CurrencyInput/CurrencyInput.test.js +++ b/source/components/CurrencyInput/CurrencyInput.test.js @@ -1,10 +1,10 @@ import { expect } from 'chai' -import { shallow } from 'enzyme' +import { shallow, mount } from 'enzyme' import React from 'react' import { match, spy, useFakeTimers } from 'sinon' import CurrencyInput from './CurrencyInput' -let getInput = component => shallow(component).find('input') +let getInput = component => mount(component).find('input') describe('CurrencyInput', () => { it('should render an input', () => { expect(getInput()).to.have.length(1) @@ -13,20 +13,36 @@ describe('CurrencyInput', () => { it('should accept both . and , as decimal separator', () => { let onChange = spy() const input = getInput() - input.simulate('change', { target: { value: '12.1' } }) + input.simulate('change', { target: { value: '12.1', focus: () => {} } }) expect(onChange).to.have.been.calledWith( match.hasNested('target.value', '12.1') ) - input.simulate('change', { target: { value: '12,1' } }) + input.simulate('change', { target: { value: '12,1', focus: () => {} } }) expect(onChange).to.have.been.calledWith( match.hasNested('target.value', '12.1') ) }) + it('should separate thousand groups', () => { + const input1 = getInput() + const input2 = getInput() + const input3 = getInput() + const input4 = getInput() + expect(input1.instance().value).to.equal('1 000') + expect(input2.instance().value).to.equal('1,000') + expect(input3.instance().value).to.equal('1,000.5') + expect(input4.instance().value).to.equal('1,000,000') + }) + + it('should handle decimal separator', () => { + const input = getInput() + expect(input.instance().value).to.equal('0,5') + }) + it('should not accept negative number', () => { let onChange = spy() const input = getInput() - input.simulate('change', { target: { value: '-12' } }) + input.simulate('change', { target: { value: '-12', focus: () => {} } }) expect(onChange).to.have.been.calledWith( match.hasNested('target.value', '12') ) @@ -35,19 +51,21 @@ describe('CurrencyInput', () => { it('should not accept anything else than number', () => { let onChange = spy() const input = getInput() - input.simulate('change', { target: { value: '*1/2abc3' } }) + input.simulate('change', { target: { value: '*1/2abc3', focus: () => {} } }) expect(onChange).to.have.been.calledWith( match.hasNested('target.value', '123') ) }) + it('should pass other props to the input', () => { const input = getInput() expect(input.prop('autoFocus')).to.be.true }) + it('should not call onChange while the decimal part is being written', () => { let onChange = spy() const input = getInput() - input.simulate('change', { target: { value: '111,' } }) + input.simulate('change', { target: { value: '111,', focus: () => {} } }) expect(onChange).not.to.have.been.called }) @@ -74,10 +92,10 @@ describe('CurrencyInput', () => { const input = getInput( ) - input.simulate('change', { target: { value: '1' } }) + input.simulate('change', { target: { value: '1', focus: () => {} } }) expect(onChange).not.to.have.been.called clock.tick(500) - input.simulate('change', { target: { value: '12' } }) + input.simulate('change', { target: { value: '12', focus: () => {} } }) clock.tick(600) expect(onChange).not.to.have.been.called clock.tick(400) diff --git a/source/components/TargetSelection.js b/source/components/TargetSelection.js index f496113df..2648af962 100644 --- a/source/components/TargetSelection.js +++ b/source/components/TargetSelection.js @@ -250,7 +250,7 @@ let CurrencyField = withColours(props => { }} debounce={600} className="targetInput" - storeValue={props.input.value} + value={props.input.value} {...props.input} {...props} /> @@ -267,7 +267,6 @@ let TargetInputOrValue = withLanguage( firstStepCompleted, inversionFail }) => { - let normalizedValueRef = useRef(null) let inputIsActive = activeInput === target.dottedName return ( @@ -275,10 +274,9 @@ let TargetInputOrValue = withLanguage( event.preventDefault()} {...(inputIsActive ? { autoFocus: true } : {})} language={language} - normalize={(value) => normalizedValueRef.current || value} /> ) : ( { return value == null ? ''