Correction des tests pour CurrencyInput
Ajout d'un nouveau test pour le formatage des montants ; Ré-implémentation de l'activation de `onChange` (seulement quand le montant a changé — pas son formatage – et avec la valeur non formatée) ; Désactivation de l'évenement `onBlur` de redux-form, qui recalculait inutilement la saisie et était à l'origine d'un bug.pull/551/head
parent
0a6cd87c47
commit
2117e2b2cf
|
@ -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 (
|
||||
<div
|
||||
|
@ -55,14 +72,18 @@ class CurrencyInput extends Component {
|
|||
<NumberFormat
|
||||
{...forwardedProps}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
allowNegative={!this.state.valueHasChanged}
|
||||
className="currencyInput__input"
|
||||
inputMode="numeric"
|
||||
onValueChange={({ value }) => {
|
||||
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 && <> €</>}
|
||||
</div>
|
||||
|
|
|
@ -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(<CurrencyInput />)).to.have.length(1)
|
||||
|
@ -13,20 +13,36 @@ describe('CurrencyInput', () => {
|
|||
it('should accept both . and , as decimal separator', () => {
|
||||
let onChange = spy()
|
||||
const input = getInput(<CurrencyInput onChange={onChange} />)
|
||||
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(<CurrencyInput value={1000} language="fr" />)
|
||||
const input2 = getInput(<CurrencyInput value={1000} language="en" />)
|
||||
const input3 = getInput(<CurrencyInput value={1000.5} language="en" />)
|
||||
const input4 = getInput(<CurrencyInput value={1000000} language="en" />)
|
||||
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(<CurrencyInput value={0.5} language="fr" />)
|
||||
expect(input.instance().value).to.equal('0,5')
|
||||
})
|
||||
|
||||
it('should not accept negative number', () => {
|
||||
let onChange = spy()
|
||||
const input = getInput(<CurrencyInput onChange={onChange} />)
|
||||
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(<CurrencyInput onChange={onChange} />)
|
||||
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(<CurrencyInput autoFocus />)
|
||||
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(<CurrencyInput onChange={onChange} />)
|
||||
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(
|
||||
<CurrencyInput onChange={onChange} debounce={1000} />
|
||||
)
|
||||
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)
|
||||
|
|
|
@ -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 (
|
||||
<span className="targetInputOrValue">
|
||||
|
@ -275,10 +274,9 @@ let TargetInputOrValue = withLanguage(
|
|||
<Field
|
||||
name={target.dottedName}
|
||||
component={CurrencyField}
|
||||
normalizedValueRef={normalizedValueRef}
|
||||
onBlur={event => event.preventDefault()}
|
||||
{...(inputIsActive ? { autoFocus: true } : {})}
|
||||
language={language}
|
||||
normalize={(value) => normalizedValueRef.current || value}
|
||||
/>
|
||||
) : (
|
||||
<TargetValue
|
||||
|
|
|
@ -35,6 +35,11 @@ export default withLanguage(
|
|||
this.timeoutId = null
|
||||
}, 250)
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId)
|
||||
}
|
||||
}
|
||||
format = value => {
|
||||
return value == null
|
||||
? ''
|
||||
|
|
Loading…
Reference in New Issue