Improve send token error ux.

feature/default_network_editable
Dan 6 years ago
parent e467eda4f7
commit 0796fc58e4
  1. 15
      ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
  2. 5
      ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
  3. 29
      ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js
  4. 18
      ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
  5. 8
      ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
  6. 5
      ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
  7. 9
      ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
  8. 5
      ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
  9. 6
      ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
  10. 33
      ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
  11. 19
      ui/app/components/send_/send.component.js
  12. 2
      ui/app/components/send_/send.container.js
  13. 33
      ui/app/components/send_/send.utils.js
  14. 75
      ui/app/components/send_/tests/send-component.test.js
  15. 12
      ui/app/components/send_/tests/send-container.test.js
  16. 46
      ui/app/components/send_/tests/send-utils.test.js
  17. 7
      ui/app/ducks/send.duck.js
  18. 10
      ui/app/ducks/tests/send-duck.test.js

@ -21,6 +21,7 @@ export default class SendAmountRow extends Component {
selectedToken: PropTypes.object, selectedToken: PropTypes.object,
setMaxModeTo: PropTypes.func, setMaxModeTo: PropTypes.func,
tokenBalance: PropTypes.string, tokenBalance: PropTypes.string,
updateGasFeeError: PropTypes.func,
updateSendAmount: PropTypes.func, updateSendAmount: PropTypes.func,
updateSendAmountError: PropTypes.func, updateSendAmountError: PropTypes.func,
updateGas: PropTypes.func, updateGas: PropTypes.func,
@ -35,6 +36,7 @@ export default class SendAmountRow extends Component {
primaryCurrency, primaryCurrency,
selectedToken, selectedToken,
tokenBalance, tokenBalance,
updateGasFeeError,
updateSendAmountError, updateSendAmountError,
} = this.props } = this.props
@ -48,6 +50,19 @@ export default class SendAmountRow extends Component {
selectedToken, selectedToken,
tokenBalance, tokenBalance,
}) })
if (selectedToken) {
updateGasFeeError({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
})
}
} }
updateAmount (amount) { updateAmount (amount) {

@ -13,7 +13,7 @@ import {
import { import {
sendAmountIsInError, sendAmountIsInError,
} from './send-amount-row.selectors' } from './send-amount-row.selectors'
import { getAmountErrorObject } from '../../send.utils' import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
import { import {
setMaxModeTo, setMaxModeTo,
updateSendAmount, updateSendAmount,
@ -44,6 +44,9 @@ function mapDispatchToProps (dispatch) {
return { return {
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)), updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
updateGasFeeError: (amountDataObject) => {
dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
},
updateSendAmountError: (amountDataObject) => { updateSendAmountError: (amountDataObject) => {
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject))) dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
}, },

@ -13,6 +13,7 @@ const propsMethodSpies = {
updateSendAmount: sinon.spy(), updateSendAmount: sinon.spy(),
updateSendAmountError: sinon.spy(), updateSendAmountError: sinon.spy(),
updateGas: sinon.spy(), updateGas: sinon.spy(),
updateGasFeeError: sinon.spy(),
} }
sinon.spy(SendAmountRow.prototype, 'updateAmount') sinon.spy(SendAmountRow.prototype, 'updateAmount')
@ -36,6 +37,7 @@ describe('SendAmountRow Component', function () {
selectedToken={ { address: 'mockTokenAddress' } } selectedToken={ { address: 'mockTokenAddress' } }
setMaxModeTo={propsMethodSpies.setMaxModeTo} setMaxModeTo={propsMethodSpies.setMaxModeTo}
tokenBalance={'mockTokenBalance'} tokenBalance={'mockTokenBalance'}
updateGasFeeError={propsMethodSpies.updateGasFeeError}
updateSendAmount={propsMethodSpies.updateSendAmount} updateSendAmount={propsMethodSpies.updateSendAmount}
updateSendAmountError={propsMethodSpies.updateSendAmountError} updateSendAmountError={propsMethodSpies.updateSendAmountError}
updateGas={propsMethodSpies.updateGas} updateGas={propsMethodSpies.updateGas}
@ -47,6 +49,7 @@ describe('SendAmountRow Component', function () {
propsMethodSpies.setMaxModeTo.resetHistory() propsMethodSpies.setMaxModeTo.resetHistory()
propsMethodSpies.updateSendAmount.resetHistory() propsMethodSpies.updateSendAmount.resetHistory()
propsMethodSpies.updateSendAmountError.resetHistory() propsMethodSpies.updateSendAmountError.resetHistory()
propsMethodSpies.updateGasFeeError.resetHistory()
SendAmountRow.prototype.validateAmount.resetHistory() SendAmountRow.prototype.validateAmount.resetHistory()
SendAmountRow.prototype.updateAmount.resetHistory() SendAmountRow.prototype.updateAmount.resetHistory()
}) })
@ -72,6 +75,32 @@ describe('SendAmountRow Component', function () {
) )
}) })
it('should call updateGasFeeError if selectedToken is truthy', () => {
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
instance.validateAmount('someAmount')
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateGasFeeError.getCall(0).args,
[{
amount: 'someAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 7,
gasTotal: 'mockGasTotal',
primaryCurrency: 'mockPrimaryCurrency',
selectedToken: { address: 'mockTokenAddress' },
tokenBalance: 'mockTokenBalance',
}]
)
})
it('should call not updateGasFeeError if selectedToken is falsey', () => {
wrapper.setProps({ selectedToken: null })
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
instance.validateAmount('someAmount')
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
})
}) })
describe('updateAmount', () => { describe('updateAmount', () => {

@ -33,7 +33,10 @@ proxyquire('../send-amount-row.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`, getTokenBalance: (s) => `mockTokenBalance:${s}`,
}, },
'./send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` }, './send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` },
'../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }) }, '../../send.utils': {
getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
},
'../../../../actions': actionSpies, '../../../../actions': actionSpies,
'../../../../ducks/send.duck': duckActionSpies, '../../../../ducks/send.duck': duckActionSpies,
}) })
@ -66,6 +69,7 @@ describe('send-amount-row container', () => {
beforeEach(() => { beforeEach(() => {
dispatchSpy = sinon.spy() dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
duckActionSpies.updateSendErrors.resetHistory()
}) })
describe('setMaxModeTo()', () => { describe('setMaxModeTo()', () => {
@ -92,6 +96,18 @@ describe('send-amount-row container', () => {
}) })
}) })
describe('updateGasFeeError()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateGasFeeError({ some: 'data' })
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.updateSendErrors.calledOnce)
assert.deepEqual(
duckActionSpies.updateSendErrors.getCall(0).args[0],
{ some: 'data', mockGasFeeErrorChange: true }
)
})
})
describe('updateSendAmountError()', () => { describe('updateSendAmountError()', () => {
it('should dispatch an action', () => { it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendAmountError({ some: 'data' }) mapDispatchToPropsObject.updateSendAmountError({ some: 'data' })

@ -8,6 +8,7 @@ export default class SendGasRow extends Component {
static propTypes = { static propTypes = {
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string, convertedCurrency: PropTypes.string,
gasFeeError: PropTypes.bool,
gasLoadingError: PropTypes.bool, gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string, gasTotal: PropTypes.string,
showCustomizeGasModal: PropTypes.func, showCustomizeGasModal: PropTypes.func,
@ -19,11 +20,16 @@ export default class SendGasRow extends Component {
convertedCurrency, convertedCurrency,
gasLoadingError, gasLoadingError,
gasTotal, gasTotal,
gasFeeError,
showCustomizeGasModal, showCustomizeGasModal,
} = this.props } = this.props
return ( return (
<SendRowWrapper label={`${this.context.t('gasFee')}:`}> <SendRowWrapper
label={`${this.context.t('gasFee')}:`}
showError={gasFeeError}
errorType={'gasFee'}
>
<GasFeeDisplay <GasFeeDisplay
conversionRate={conversionRate} conversionRate={conversionRate}
convertedCurrency={convertedCurrency} convertedCurrency={convertedCurrency}

@ -4,7 +4,7 @@ import {
getCurrentCurrency, getCurrentCurrency,
getGasTotal, getGasTotal,
} from '../../send.selectors.js' } from '../../send.selectors.js'
import { sendGasIsInError } from './send-gas-row.selectors.js' import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
import { showModal } from '../../../../actions' import { showModal } from '../../../../actions'
import SendGasRow from './send-gas-row.component' import SendGasRow from './send-gas-row.component'
@ -15,7 +15,8 @@ function mapStateToProps (state) {
conversionRate: getConversionRate(state), conversionRate: getConversionRate(state),
convertedCurrency: getCurrentCurrency(state), convertedCurrency: getCurrentCurrency(state),
gasTotal: getGasTotal(state), gasTotal: getGasTotal(state),
gasLoadingError: sendGasIsInError(state), gasFeeError: gasFeeIsInError(state),
gasLoadingError: getGasLoadingError(state),
} }
} }

@ -1,9 +1,14 @@
const selectors = { const selectors = {
sendGasIsInError, gasFeeIsInError,
getGasLoadingError,
} }
module.exports = selectors module.exports = selectors
function sendGasIsInError (state) { function getGasLoadingError (state) {
return state.send.errors.gasLoading return state.send.errors.gasLoading
} }
function gasFeeIsInError (state) {
return Boolean(state.send.errors.gasFee)
}

@ -18,6 +18,7 @@ describe('SendGasRow Component', function () {
wrapper = shallow(<SendGasRow wrapper = shallow(<SendGasRow
conversionRate={20} conversionRate={20}
convertedCurrency={'mockConvertedCurrency'} convertedCurrency={'mockConvertedCurrency'}
gasFeeError={'mockGasFeeError'}
gasLoadingError={false} gasLoadingError={false}
gasTotal={'mockGasTotal'} gasTotal={'mockGasTotal'}
showCustomizeGasModal={propsMethodSpies.showCustomizeGasModal} showCustomizeGasModal={propsMethodSpies.showCustomizeGasModal}
@ -36,9 +37,13 @@ describe('SendGasRow Component', function () {
it('should pass the correct props to SendRowWrapper', () => { it('should pass the correct props to SendRowWrapper', () => {
const { const {
label, label,
showError,
errorType,
} = wrapper.find(SendRowWrapper).props() } = wrapper.find(SendRowWrapper).props()
assert.equal(label, 'gasFee_t:') assert.equal(label, 'gasFee_t:')
assert.equal(showError, 'mockGasFeeError')
assert.equal(errorType, 'gasFee')
}) })
it('should render a GasFeeDisplay as a child of the SendRowWrapper', () => { it('should render a GasFeeDisplay as a child of the SendRowWrapper', () => {

@ -22,7 +22,10 @@ proxyquire('../send-gas-row.container.js', {
getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`,
}, },
'./send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` }, './send-gas-row.selectors.js': {
getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
},
'../../../../actions': actionSpies, '../../../../actions': actionSpies,
}) })
@ -35,6 +38,7 @@ describe('send-gas-row container', () => {
conversionRate: 'mockConversionRate:mockState', conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState', convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState', gasTotal: 'mockGasTotal:mockState',
gasFeeError: 'mockGasFeeError:mockState',
gasLoadingError: 'mockGasLoadingError:mockState', gasLoadingError: 'mockGasLoadingError:mockState',
}) })
}) })

@ -1,11 +1,12 @@
import assert from 'assert' import assert from 'assert'
import { import {
sendGasIsInError, gasFeeIsInError,
getGasLoadingError,
} from '../send-gas-row.selectors.js' } from '../send-gas-row.selectors.js'
describe('send-gas-row selectors', () => { describe('send-gas-row selectors', () => {
describe('sendGasIsInError()', () => { describe('getGasLoadingError()', () => {
it('should return send.errors.gasLoading', () => { it('should return send.errors.gasLoading', () => {
const state = { const state = {
send: { send: {
@ -15,7 +16,33 @@ describe('send-gas-row selectors', () => {
}, },
} }
assert.equal(sendGasIsInError(state), 'abc') assert.equal(getGasLoadingError(state), 'abc')
})
})
describe('gasFeeIsInError()', () => {
it('should return true if send.errors.gasFee is truthy', () => {
const state = {
send: {
errors: {
gasFee: 'def',
},
},
}
assert.equal(gasFeeIsInError(state), true)
})
it('should return false send.errors.gasFee is falsely', () => {
const state = {
send: {
errors: {
gasFee: null,
},
},
}
assert.equal(gasFeeIsInError(state), false)
}) })
}) })

@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import PersistentForm from '../../../lib/persistent-form' import PersistentForm from '../../../lib/persistent-form'
import { import {
getAmountErrorObject, getAmountErrorObject,
getGasFeeErrorObject,
getToAddressForGasUpdate, getToAddressForGasUpdate,
doesAmountErrorRequireUpdate, doesAmountErrorRequireUpdate,
} from './send.utils' } from './send.utils'
@ -112,7 +113,19 @@ export default class SendTransactionScreen extends PersistentForm {
selectedToken, selectedToken,
tokenBalance, tokenBalance,
}) })
updateSendErrors(amountErrorObject) const gasFeeErrorObject = selectedToken
? getGasFeeErrorObject({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
})
: { gasFee: null }
updateSendErrors(Object.assign(amountErrorObject, gasFeeErrorObject))
} }
if (!uninitialized) { if (!uninitialized) {
@ -143,6 +156,10 @@ export default class SendTransactionScreen extends PersistentForm {
this.updateGas() this.updateGas()
} }
componentWillUnmount () {
this.props.resetSendState()
}
render () { render () {
const { history } = this.props const { history } = this.props

@ -28,6 +28,7 @@ import {
setGasTotal, setGasTotal,
} from '../../actions' } from '../../actions'
import { import {
resetSendState,
updateSendErrors, updateSendErrors,
} from '../../ducks/send.duck' } from '../../ducks/send.duck'
import { import {
@ -87,5 +88,6 @@ function mapDispatchToProps (dispatch) {
})) }))
}, },
updateSendErrors: newError => dispatch(updateSendErrors(newError)), updateSendErrors: newError => dispatch(updateSendErrors(newError)),
resetSendState: () => dispatch(resetSendState()),
} }
} }

@ -30,6 +30,7 @@ module.exports = {
estimateGasPriceFromRecentBlocks, estimateGasPriceFromRecentBlocks,
generateTokenTransferData, generateTokenTransferData,
getAmountErrorObject, getAmountErrorObject,
getGasFeeErrorObject,
getToAddressForGasUpdate, getToAddressForGasUpdate,
isBalanceSufficient, isBalanceSufficient,
isTokenBalanceSufficient, isTokenBalanceSufficient,
@ -110,9 +111,9 @@ function getAmountErrorObject ({
tokenBalance, tokenBalance,
}) { }) {
let insufficientFunds = false let insufficientFunds = false
if (gasTotal && conversionRate) { if (gasTotal && conversionRate && !selectedToken) {
insufficientFunds = !isBalanceSufficient({ insufficientFunds = !isBalanceSufficient({
amount: selectedToken ? '0x0' : amount, amount,
amountConversionRate, amountConversionRate,
balance, balance,
conversionRate, conversionRate,
@ -149,6 +150,34 @@ function getAmountErrorObject ({
return { amount: amountError } return { amount: amountError }
} }
function getGasFeeErrorObject ({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
}) {
let gasFeeError = null
if (gasTotal && conversionRate) {
const insufficientFunds = !isBalanceSufficient({
amount: '0x0',
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
})
if (insufficientFunds) {
gasFeeError = INSUFFICIENT_FUNDS_ERROR
}
}
return { gasFee: gasFeeError }
}
function calcTokenBalance ({ selectedToken, usersToken }) { function calcTokenBalance ({ selectedToken, usersToken }) {
const { decimals } = selectedToken || {} const { decimals } = selectedToken || {}
return calcTokenAmount(usersToken.balance.toString(), decimals) + '' return calcTokenAmount(usersToken.balance.toString(), decimals) + ''

@ -12,9 +12,11 @@ const propsMethodSpies = {
updateAndSetGasTotal: sinon.spy(), updateAndSetGasTotal: sinon.spy(),
updateSendErrors: sinon.spy(), updateSendErrors: sinon.spy(),
updateSendTokenBalance: sinon.spy(), updateSendTokenBalance: sinon.spy(),
resetSendState: sinon.spy(),
} }
const utilsMethodStubs = { const utilsMethodStubs = {
getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }), getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }),
getGasFeeErrorObject: sinon.stub().returns({ gasFee: 'mockGasFeeError' }),
doesAmountErrorRequireUpdate: sinon.stub().callsFake(obj => obj.balance !== obj.prevBalance), doesAmountErrorRequireUpdate: sinon.stub().callsFake(obj => obj.balance !== obj.prevBalance),
} }
@ -50,6 +52,7 @@ describe('Send Component', function () {
updateAndSetGasTotal={propsMethodSpies.updateAndSetGasTotal} updateAndSetGasTotal={propsMethodSpies.updateAndSetGasTotal}
updateSendErrors={propsMethodSpies.updateSendErrors} updateSendErrors={propsMethodSpies.updateSendErrors}
updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance} updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance}
resetSendState={propsMethodSpies.resetSendState}
/>) />)
}) })
@ -58,6 +61,7 @@ describe('Send Component', function () {
SendTransactionScreen.prototype.updateGas.resetHistory() SendTransactionScreen.prototype.updateGas.resetHistory()
utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory() utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory()
utilsMethodStubs.getAmountErrorObject.resetHistory() utilsMethodStubs.getAmountErrorObject.resetHistory()
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
propsMethodSpies.updateAndSetGasTotal.resetHistory() propsMethodSpies.updateAndSetGasTotal.resetHistory()
propsMethodSpies.updateSendErrors.resetHistory() propsMethodSpies.updateSendErrors.resetHistory()
propsMethodSpies.updateSendTokenBalance.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory()
@ -77,6 +81,15 @@ describe('Send Component', function () {
}) })
}) })
describe('componentWillUnmount', () => {
it('should call this.props.resetSendState', () => {
propsMethodSpies.resetSendState.resetHistory()
assert.equal(propsMethodSpies.resetSendState.callCount, 0)
wrapper.instance().componentWillUnmount()
assert.equal(propsMethodSpies.resetSendState.callCount, 1)
})
})
describe('componentDidUpdate', () => { describe('componentDidUpdate', () => {
it('should call doesAmountErrorRequireUpdate with the expected params', () => { it('should call doesAmountErrorRequireUpdate with the expected params', () => {
utilsMethodStubs.getAmountErrorObject.resetHistory() utilsMethodStubs.getAmountErrorObject.resetHistory()
@ -133,8 +146,66 @@ describe('Send Component', function () {
) )
}) })
it('should call updateSendErrors with the expected params', () => { it('should call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true and selectedToken is truthy', () => {
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
wrapper.instance().componentDidUpdate({
from: {
balance: 'balanceChanged',
},
})
assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 1)
assert.deepEqual(
utilsMethodStubs.getGasFeeErrorObject.getCall(0).args[0],
{
amount: 'mockAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 10,
gasTotal: 'mockGasTotal',
primaryCurrency: 'mockPrimaryCurrency',
selectedToken: 'mockSelectedToken',
tokenBalance: 'mockTokenBalance',
}
)
})
it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns false', () => {
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
wrapper.instance().componentDidUpdate({
from: { address: 'mockAddress', balance: 'mockBalance' },
})
assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 0)
})
it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true but selectedToken is falsy', () => {
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
wrapper.setProps({ selectedToken: null })
wrapper.instance().componentDidUpdate({
from: {
balance: 'balanceChanged',
},
})
assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 0)
})
it('should call updateSendErrors with the expected params if selectedToken is falsy', () => {
propsMethodSpies.updateSendErrors.resetHistory()
wrapper.setProps({ selectedToken: null })
wrapper.instance().componentDidUpdate({
from: {
balance: 'balanceChanged',
},
})
assert.equal(propsMethodSpies.updateSendErrors.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateSendErrors.getCall(0).args[0],
{ amount: 'mockAmountError', gasFee: null }
)
})
it('should call updateSendErrors with the expected params if selectedToken is truthy', () => {
propsMethodSpies.updateSendErrors.resetHistory() propsMethodSpies.updateSendErrors.resetHistory()
wrapper.setProps({ selectedToken: 'someToken' })
wrapper.instance().componentDidUpdate({ wrapper.instance().componentDidUpdate({
from: { from: {
balance: 'balanceChanged', balance: 'balanceChanged',
@ -143,7 +214,7 @@ describe('Send Component', function () {
assert.equal(propsMethodSpies.updateSendErrors.callCount, 1) assert.equal(propsMethodSpies.updateSendErrors.callCount, 1)
assert.deepEqual( assert.deepEqual(
propsMethodSpies.updateSendErrors.getCall(0).args[0], propsMethodSpies.updateSendErrors.getCall(0).args[0],
{ amount: 'mockAmountError'} { amount: 'mockAmountError', gasFee: 'mockGasFeeError' }
) )
}) })

@ -12,6 +12,7 @@ const actionSpies = {
} }
const duckActionSpies = { const duckActionSpies = {
updateSendErrors: sinon.spy(), updateSendErrors: sinon.spy(),
resetSendState: sinon.spy(),
} }
proxyquire('../send.container.js', { proxyquire('../send.container.js', {
@ -152,6 +153,17 @@ describe('send container', () => {
}) })
}) })
describe('resetSendState()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.resetSendState()
assert(dispatchSpy.calledOnce)
assert.equal(
duckActionSpies.resetSendState.getCall(0).args.length,
0
)
})
})
}) })
}) })

@ -17,7 +17,11 @@ const {
} = require('../send.constants') } = require('../send.constants')
const stubs = { const stubs = {
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b), addCurrencies: sinon.stub().callsFake((a, b, obj) => {
if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2))
if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2))
return a + b
}),
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)), conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value), conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`), multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
@ -49,6 +53,7 @@ const {
estimateGasPriceFromRecentBlocks, estimateGasPriceFromRecentBlocks,
generateTokenTransferData, generateTokenTransferData,
getAmountErrorObject, getAmountErrorObject,
getGasFeeErrorObject,
getToAddressForGasUpdate, getToAddressForGasUpdate,
calcTokenBalance, calcTokenBalance,
isBalanceSufficient, isBalanceSufficient,
@ -143,6 +148,18 @@ describe('send utils', () => {
primaryCurrency: 'ABC', primaryCurrency: 'ABC',
expectedResult: { amount: INSUFFICIENT_FUNDS_ERROR }, expectedResult: { amount: INSUFFICIENT_FUNDS_ERROR },
}, },
'should not return insufficientFunds error if selectedToken is truthy': {
amount: '0x0',
amountConversionRate: 2,
balance: 1,
conversionRate: 3,
gasTotal: 17,
primaryCurrency: 'ABC',
selectedToken: { symbole: 'DEF', decimals: 0 },
decimals: 0,
tokenBalance: 'sometokenbalance',
expectedResult: { amount: null },
},
'should return insufficientTokens error if token is selected and isTokenBalanceSufficient returns false': { 'should return insufficientTokens error if token is selected and isTokenBalanceSufficient returns false': {
amount: '0x10', amount: '0x10',
amountConversionRate: 2, amountConversionRate: 2,
@ -163,6 +180,32 @@ describe('send utils', () => {
}) })
}) })
describe('getGasFeeErrorObject()', () => {
const config = {
'should return insufficientFunds error if isBalanceSufficient returns false': {
amountConversionRate: 2,
balance: 16,
conversionRate: 3,
gasTotal: 17,
primaryCurrency: 'ABC',
expectedResult: { gasFee: INSUFFICIENT_FUNDS_ERROR },
},
'should return null error if isBalanceSufficient returns true': {
amountConversionRate: 2,
balance: 16,
conversionRate: 3,
gasTotal: 15,
primaryCurrency: 'ABC',
expectedResult: { gasFee: null },
},
}
Object.entries(config).map(([description, obj]) => {
it(description, () => {
assert.deepEqual(getGasFeeErrorObject(obj), obj.expectedResult)
})
})
})
describe('calcTokenBalance()', () => { describe('calcTokenBalance()', () => {
it('should return the calculated token blance', () => { it('should return the calculated token blance', () => {
assert.equal(calcTokenBalance({ assert.equal(calcTokenBalance({
@ -222,6 +265,7 @@ describe('send utils', () => {
describe('isTokenBalanceSufficient()', () => { describe('isTokenBalanceSufficient()', () => {
it('should correctly call conversionUtil and return the result of calling conversionGTE', () => { it('should correctly call conversionUtil and return the result of calling conversionGTE', () => {
stubs.conversionGTE.resetHistory() stubs.conversionGTE.resetHistory()
stubs.conversionUtil.resetHistory()
const result = isTokenBalanceSufficient({ const result = isTokenBalanceSufficient({
amount: '0x10', amount: '0x10',
tokenBalance: 123, tokenBalance: 123,

@ -6,6 +6,7 @@ const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern // TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = { const initState = {
@ -42,6 +43,8 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
...action.value, ...action.value,
}, },
}) })
case RESET_SEND_STATE:
return extend({}, initState)
default: default:
return newState return newState
} }
@ -70,3 +73,7 @@ export function updateSendErrors (errorObject) {
value: errorObject, value: errorObject,
} }
} }
export function resetSendState () {
return { type: RESET_SEND_STATE }
}

@ -24,6 +24,7 @@ describe('Send Duck', () => {
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN' const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN' const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS' const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
describe('SendReducer()', () => { describe('SendReducer()', () => {
it('should initialize state', () => { it('should initialize state', () => {
@ -105,6 +106,15 @@ describe('Send Duck', () => {
}) })
) )
}) })
it('should return the initial state in response to a RESET_SEND_STATE action', () => {
assert.deepEqual(
SendReducer(mockState, {
type: RESET_SEND_STATE,
}),
Object.assign({}, initState)
)
})
}) })
describe('openFromDropdown', () => { describe('openFromDropdown', () => {

Loading…
Cancel
Save