mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Improve send token error ux.
This commit is contained in:
parent
e467eda4f7
commit
0796fc58e4
@ -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,51 @@ 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()
|
propsMethodSpies.updateSendErrors.resetHistory()
|
||||||
|
wrapper.setProps({ selectedToken: null })
|
||||||
wrapper.instance().componentDidUpdate({
|
wrapper.instance().componentDidUpdate({
|
||||||
from: {
|
from: {
|
||||||
balance: 'balanceChanged',
|
balance: 'balanceChanged',
|
||||||
@ -143,7 +199,22 @@ 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: null }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call updateSendErrors with the expected params if selectedToken is truthy', () => {
|
||||||
|
propsMethodSpies.updateSendErrors.resetHistory()
|
||||||
|
wrapper.setProps({ selectedToken: 'someToken' })
|
||||||
|
wrapper.instance().componentDidUpdate({
|
||||||
|
from: {
|
||||||
|
balance: 'balanceChanged',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.equal(propsMethodSpies.updateSendErrors.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
propsMethodSpies.updateSendErrors.getCall(0).args[0],
|
||||||
|
{ 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…
Reference in New Issue
Block a user