1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Unit tests for containers, utils and selectors in send_/

This commit is contained in:
Dan 2018-05-05 11:11:53 -04:00
parent e869d09c79
commit 7c49009854
46 changed files with 3223 additions and 128 deletions

View File

@ -9,7 +9,7 @@
"dist": "gulp dist",
"doc": "jsdoc -c development/tools/.jsdoc.json",
"test": "npm run test:unit && npm run test:integration && npm run lint",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
"test:integration:build": "gulp build:scss",
@ -258,6 +258,7 @@
"open": "0.0.5",
"png-file-stream": "^1.0.0",
"prompt": "^1.0.0",
"proxyquire": "2.0.1",
"qs": "^6.2.0",
"qunitjs": "^2.4.1",
"radgrad-jsdoc-template": "^1.1.3",

View File

@ -16,11 +16,11 @@ const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
} = require('../send/send-constants')
} = require('../send_/send.constants')
const {
isBalanceSufficient,
} = require('../send/send-utils')
} = require('../send_/send.utils')
const {
conversionUtil,

View File

@ -149,10 +149,10 @@ AddTokenScreen.prototype.validate = function () {
errors.customAddress = this.context.t('invalidAddress')
}
const validDecimals = customDecimals !== null
&& customDecimals !== ''
&& customDecimals >= 0
&& customDecimals < 36
const validDecimals = customDecimals !== null &&
customDecimals !== '' &&
customDecimals >= 0 &&
customDecimals < 36
if (!validDecimals) {
errors.customDecimals = this.context.t('decimalsMustZerotoTen')
}

View File

@ -11,7 +11,7 @@ const { conversionUtil } = require('../../conversion-util')
const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants')
class ConfirmDeployContract extends Component {
constructor (props) {

View File

@ -17,16 +17,16 @@ const {
multiplyCurrencies,
} = require('../../conversion-util')
const {
getGasTotal,
calcGasTotal,
isBalanceSufficient,
} = require('../send/send-utils')
} = require('../send_/send.utils')
const GasFeeDisplay = require('../send/gas-fee-display-v2')
const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
import {
@ -590,7 +590,7 @@ ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
value: amount,
},
} = txMeta
const gasTotal = getGasTotal(gas, gasPrice)
const gasTotal = calcGasTotal(gas, gasPrice)
return isBalanceSufficient({
amount,

View File

@ -20,9 +20,9 @@ const {
addCurrencies,
} = require('../../conversion-util')
const {
getGasTotal,
calcGasTotal,
isBalanceSufficient,
} = require('../send/send-utils')
} = require('../send_/send.utils')
const {
calcTokenAmount,
} = require('../../token-util')
@ -30,7 +30,7 @@ const classnames = require('classnames')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants')
const {
getTokenExchangeRate,
@ -580,7 +580,7 @@ ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) {
gasPrice,
},
} = txMeta
const gasTotal = getGasTotal(gas, gasPrice)
const gasTotal = calcGasTotal(gas, gasPrice)
return isBalanceSufficient({
amount: '0',

View File

@ -0,0 +1,32 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps
proxyquire('../account-list-item.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
return () => ({})
},
},
'../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
getConvertedCurrency: (s) => `mockCurrentCurrency:${s}`,
},
})
describe('account-list-item container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
conversionRate: 'mockConversionRate:mockState',
currentCurrency: 'mockCurrentCurrency:mockState',
})
})
})
})

View File

@ -0,0 +1,91 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
setMaxModeTo: sinon.spy(),
updateSendAmount: sinon.spy(),
}
const duckActionSpies = {
updateSendErrors: sinon.spy(),
}
proxyquire('../amount-max-button.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../../send.selectors.js': {
getGasTotal: (s) => `mockGasTotal:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSendFromBalance: (s) => `mockBalance:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`,
},
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
'../../../../../actions': actionSpies,
'../../../../../ducks/send': duckActionSpies,
})
describe('amount-max-button container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
balance: 'mockBalance:mockState',
gasTotal: 'mockGasTotal:mockState',
maxModeOn: 'mockMaxModeOn:mockState',
selectedToken: 'mockSelectedToken:mockState',
tokenBalance: 'mockTokenBalance:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('setAmountToMax()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' })
assert(dispatchSpy.calledTwice)
assert(duckActionSpies.updateSendErrors.calledOnce)
assert.deepEqual(
duckActionSpies.updateSendErrors.getCall(0).args[0],
{ amount: null }
)
assert(actionSpies.updateSendAmount.calledOnce)
assert.equal(
actionSpies.updateSendAmount.getCall(0).args[0],
12
)
})
})
describe('setMaxModeTo()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.setMaxModeTo('mockVal')
assert(dispatchSpy.calledOnce)
assert.equal(
actionSpies.setMaxModeTo.getCall(0).args[0],
'mockVal'
)
})
})
})
})

View File

@ -0,0 +1,22 @@
import assert from 'assert'
import {
getMaxModeOn,
} from '../amount-max-button.selectors.js'
describe('amount-max-button selectors', () => {
describe('getMaxModeOn()', () => {
it('should', () => {
const state = {
metamask: {
send: {
maxModeOn: null,
},
},
}
assert.equal(getMaxModeOn(state), null)
})
})
})

View File

@ -0,0 +1,27 @@
import assert from 'assert'
import {
calcMaxAmount,
} from '../amount-max-button.utils.js'
describe('amount-max-button utils', () => {
describe('calcMaxAmount()', () => {
it('should calculate the correct amount when no selectedToken defined', () => {
assert.deepEqual(calcMaxAmount({
balance: 'ffffff',
gasTotal: 'ff',
selectedToken: false,
}), 'ffff00')
})
it('should calculate the correct amount when a selectedToken is defined', () => {
assert.deepEqual(calcMaxAmount({
selectedToken: {
decimals: 10,
},
tokenBalance: 100,
}), 'e8d4a51000')
})
})
})

View File

@ -0,0 +1,109 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
setMaxModeTo: sinon.spy(),
updateSendAmount: sinon.spy(),
}
const duckActionSpies = {
updateSendErrors: sinon.spy(),
}
proxyquire('../send-amount-row.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getConversionRate: (s) => `mockConversionRate:${s}`,
getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSendAmount: (s) => `mockAmount:${s}`,
getSendFromBalance: (s) => `mockBalance:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`,
},
'./send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` },
'../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }) },
'../../../../actions': actionSpies,
'../../../../ducks/send': duckActionSpies,
})
describe('send-amount-row container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
amount: 'mockAmount:mockState',
amountConversionRate: 'mockAmountConversionRate:mockState',
balance: 'mockBalance:mockState',
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
inError: 'mockInError:mockState',
primaryCurrency: 'mockPrimaryCurrency:mockState',
selectedToken: 'mockSelectedToken:mockState',
tokenBalance: 'mockTokenBalance:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('setMaxModeTo()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.setMaxModeTo('mockBool')
assert(dispatchSpy.calledOnce)
assert(actionSpies.setMaxModeTo.calledOnce)
assert.equal(
actionSpies.setMaxModeTo.getCall(0).args[0],
'mockBool'
)
})
})
describe('updateSendAmount()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendAmount('mockAmount')
assert(dispatchSpy.calledOnce)
assert(actionSpies.updateSendAmount.calledOnce)
assert.equal(
actionSpies.updateSendAmount.getCall(0).args[0],
'mockAmount'
)
})
})
describe('updateSendAmountError()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendAmountError({ some: 'data' })
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.updateSendErrors.calledOnce)
assert.deepEqual(
duckActionSpies.updateSendErrors.getCall(0).args[0],
{ some: 'data', mockChange: true }
)
})
})
})
})

View File

@ -0,0 +1,34 @@
import assert from 'assert'
import {
sendAmountIsInError,
} from '../send-amount-row.selectors.js'
describe('send-amount-row selectors', () => {
describe('sendAmountIsInError()', () => {
it('should return true if send.errors.amount is truthy', () => {
const state = {
send: {
errors: {
amount: 'abc',
},
},
}
assert.equal(sendAmountIsInError(state), true)
})
it('should return false if send.errors.amount is falsy', () => {
const state = {
send: {
errors: {
amount: null,
},
},
}
assert.equal(sendAmountIsInError(state), false)
})
})
})

View File

@ -8,7 +8,7 @@ import {
import {
getFromDropdownOpen,
} from './send-from-row.selectors.js'
import { calcTokenUpdateAmount } from './send-from-row.utils.js'
import { calcTokenBalance } from '../../send.utils.js'
import {
updateSendFrom,
setSendTokenBalance,
@ -39,7 +39,7 @@ function mapDispatchToProps (dispatch) {
setSendTokenBalance: (usersToken, selectedToken) => {
if (!usersToken) return
const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken)
const tokenBalance = calcTokenBalance(usersToken, selectedToken)
dispatch(setSendTokenBalance(tokenBalance))
},
}

View File

@ -1,12 +0,0 @@
const {
calcTokenAmount,
} = require('../../../../token-util')
function calcTokenUpdateAmount (usersToken, selectedToken) {
const { decimals } = selectedToken || {}
return calcTokenAmount(usersToken.balance.toString(), decimals)
}
module.exports = {
calcTokenUpdateAmount,
}

View File

@ -0,0 +1,110 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
updateSendFrom: sinon.spy(),
setSendTokenBalance: sinon.spy(),
}
const duckActionSpies = {
closeFromDropdown: sinon.spy(),
openFromDropdown: sinon.spy(),
}
proxyquire('../send-from-row.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../send.selectors.js': {
accountsWithSendEtherInfoSelector: (s) => `mockFromAccounts:${s}`,
getConversionRate: (s) => `mockConversionRate:${s}`,
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
getSendFromObject: (s) => `mockFrom:${s}`,
},
'./send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` },
'../../send.utils.js': { calcTokenBalance: (a, b) => a + b },
'../../../../actions': actionSpies,
'../../../../ducks/send': duckActionSpies,
})
describe('send-from-row container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
conversionRate: 'mockConversionRate:mockState',
from: 'mockFrom:mockState',
fromAccounts: 'mockFromAccounts:mockState',
fromDropdownOpen: 'mockFromDropdownOpen:mockState',
tokenContract: 'mockTokenContract:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('closeFromDropdown()', () => {
it('should dispatch a closeFromDropdown action', () => {
mapDispatchToPropsObject.closeFromDropdown()
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.closeFromDropdown.calledOnce)
assert.equal(
duckActionSpies.closeFromDropdown.getCall(0).args[0],
undefined
)
})
})
describe('openFromDropdown()', () => {
it('should dispatch a openFromDropdown action', () => {
mapDispatchToPropsObject.openFromDropdown()
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.openFromDropdown.calledOnce)
assert.equal(
duckActionSpies.openFromDropdown.getCall(0).args[0],
undefined
)
})
})
describe('updateSendFrom()', () => {
it('should dispatch an updateSendFrom action', () => {
mapDispatchToPropsObject.updateSendFrom('mockFrom')
assert(dispatchSpy.calledOnce)
assert.equal(
actionSpies.updateSendFrom.getCall(0).args[0],
'mockFrom'
)
})
})
describe('setSendTokenBalance()', () => {
it('should dispatch an setSendTokenBalance action', () => {
mapDispatchToPropsObject.setSendTokenBalance('mockUsersToken', 'mockSelectedToken')
assert(dispatchSpy.calledOnce)
assert.equal(
actionSpies.setSendTokenBalance.getCall(0).args[0],
'mockUsersTokenmockSelectedToken'
)
})
})
})
})

View File

@ -0,0 +1,20 @@
import assert from 'assert'
import {
getFromDropdownOpen,
} from '../send-from-row.selectors.js'
describe('send-from-row selectors', () => {
describe('getFromDropdownOpen()', () => {
it('should get send.fromDropdownOpen', () => {
const state = {
send: {
fromDropdownOpen: null,
},
}
assert.equal(getFromDropdownOpen(state), null)
})
})
})

View File

@ -5,5 +5,5 @@ const selectors = {
module.exports = selectors
function sendGasIsInError (state) {
return state.metamask.send.errors.gasLoading
return state.send.errors.gasLoading
}

View File

@ -0,0 +1,66 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
showModal: sinon.spy(),
}
proxyquire('../send-gas-row.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
},
'./send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` },
'../../../../actions': actionSpies,
})
describe('send-gas-row container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
gasLoadingError: 'mockGasLoadingError:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('showCustomizeGasModal()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.showCustomizeGasModal()
assert(dispatchSpy.calledOnce)
assert.deepEqual(
actionSpies.showModal.getCall(0).args[0],
{ name: 'CUSTOMIZE_GAS' }
)
})
})
})
})

View File

@ -0,0 +1,22 @@
import assert from 'assert'
import {
sendGasIsInError,
} from '../send-gas-row.selectors.js'
describe('send-gas-row selectors', () => {
describe('sendGasIsInError()', () => {
it('should return send.errors.gasLoading', () => {
const state = {
send: {
errors: {
gasLoading: 'abc',
},
},
}
assert.equal(sendGasIsInError(state), 'abc')
})
})
})

View File

@ -0,0 +1,28 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps
proxyquire('../send-row-error-message.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
return () => ({})
},
},
'../../../send.selectors': { getSendErrors: (s) => `mockErrors:${s}` },
})
describe('send-row-error-message container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState', { errorType: 'someType' }), {
errors: 'mockErrors:mockState',
errorType: 'someType' })
})
})
})

View File

@ -0,0 +1,114 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
updateSendTo: sinon.spy(),
}
const duckActionSpies = {
closeToDropdown: sinon.spy(),
openToDropdown: sinon.spy(),
updateSendErrors: sinon.spy(),
}
proxyquire('../send-to-row.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../send.selectors.js': {
getCurrentNetwork: (s) => `mockNetwork:${s}`,
getSendTo: (s) => `mockTo:${s}`,
getSendToAccounts: (s) => `mockToAccounts:${s}`,
},
'./send-to-row.selectors.js': {
getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`,
sendToIsInError: (s) => `mockInError:${s}`,
},
'./send-to-row.utils.js': { getToErrorObject: (t) => `mockError:${t}` },
'../../../../actions': actionSpies,
'../../../../ducks/send': duckActionSpies,
})
describe('send-to-row container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
inError: 'mockInError:mockState',
network: 'mockNetwork:mockState',
to: 'mockTo:mockState',
toAccounts: 'mockToAccounts:mockState',
toDropdownOpen: 'mockToDropdownOpen:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('closeToDropdown()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.closeToDropdown()
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.closeToDropdown.calledOnce)
assert.equal(
duckActionSpies.closeToDropdown.getCall(0).args[0],
undefined
)
})
})
describe('openToDropdown()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.openToDropdown()
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.openToDropdown.calledOnce)
assert.equal(
duckActionSpies.openToDropdown.getCall(0).args[0],
undefined
)
})
})
describe('updateSendTo()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendTo('mockTo', 'mockNickname')
assert(dispatchSpy.calledOnce)
assert(actionSpies.updateSendTo.calledOnce)
assert.deepEqual(
actionSpies.updateSendTo.getCall(0).args,
['mockTo', 'mockNickname']
)
})
})
describe('updateSendToError()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendToError('mockTo')
assert(dispatchSpy.calledOnce)
assert(duckActionSpies.updateSendErrors.calledOnce)
assert.equal(
duckActionSpies.updateSendErrors.getCall(0).args[0],
'mockError:mockTo'
)
})
})
})
})

View File

@ -0,0 +1,47 @@
import assert from 'assert'
import {
getToDropdownOpen,
sendToIsInError,
} from '../send-to-row.selectors.js'
describe('send-to-row selectors', () => {
describe('getToDropdownOpen()', () => {
it('should return send.getToDropdownOpen', () => {
const state = {
send: {
toDropdownOpen: false,
},
}
assert.equal(getToDropdownOpen(state), false)
})
})
describe('sendToIsInError()', () => {
it('should return true if send.errors.to is truthy', () => {
const state = {
send: {
errors: {
to: 'abc',
},
},
}
assert.equal(sendToIsInError(state), true)
})
it('should return false if send.errors.to is falsy', () => {
const state = {
send: {
errors: {
to: null,
},
},
}
assert.equal(sendToIsInError(state), false)
})
})
})

View File

@ -0,0 +1,45 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
import {
REQUIRED_ERROR,
INVALID_RECIPIENT_ADDRESS_ERROR,
} from '../../../send.constants'
const stubs = {
isValidAddress: sinon.stub().callsFake(to => Boolean(to.match(/^[0xabcdef123456798]+$/))),
}
const toRowUtils = proxyquire('../send-to-row.utils.js', {
'../../../../util': {
isValidAddress: stubs.isValidAddress,
},
})
const {
getToErrorObject,
} = toRowUtils
describe('send-to-row utils', () => {
describe('getToErrorObject()', () => {
it('should return a required error if to is falsy', () => {
assert.deepEqual(getToErrorObject(null), {
to: REQUIRED_ERROR,
})
})
it('should return an invalid recipient error if to is truthy but invalid', () => {
assert.deepEqual(getToErrorObject('mockInvalidTo'), {
to: INVALID_RECIPIENT_ADDRESS_ERROR,
})
})
it('should return null if to is truthy and valid', () => {
assert.deepEqual(getToErrorObject('0xabc123'), {
to: null,
})
})
})
})

View File

@ -20,10 +20,8 @@ import {
getSendToAccounts,
getTokenBalance,
getUnapprovedTxs,
} from '../send.selectors'
import {
isSendFormInError,
} from './send-footer.selectors'
} from '../send.selectors'
import {
addressIsNew,
constructTxParams,

View File

@ -1,4 +1,4 @@
import { getSendErrors } from '../send.selectors'
const { getSendErrors } = require('../send.selectors')
const selectors = {
isSendFormInError,

View File

@ -1,6 +1,6 @@
import ethAbi from 'ethereumjs-abi'
import ethUtil from 'ethereumjs-util'
import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants'
const ethAbi = require('ethereumjs-abi')
const ethUtil = require('ethereumjs-util')
const { TOKEN_TRANSFER_FUNCTION_SIGNATURE } = require('../send.constants')
function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) {
const missingTokenBalance = selectedToken && !tokenBalance
@ -47,6 +47,7 @@ function constructUpdatedTx ({
}
if (selectedToken) {
console.log(`ethAbi.rawEncode`, ethAbi.rawEncode)
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
x => ('00' + x.toString(16)).slice(-2)
@ -70,6 +71,8 @@ function constructUpdatedTx ({
delete editingTx.txParams.data
}
}
return editingTx
}
function addressIsNew (toAccounts, newAddress) {
@ -81,4 +84,5 @@ module.exports = {
formShouldBeDisabled,
constructTxParams,
constructUpdatedTx,
addHexPrefixToObjectValues,
}

View File

@ -0,0 +1,202 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
addToAddressBook: sinon.spy(),
clearSend: sinon.spy(),
signTokenTx: sinon.spy(),
signTx: sinon.spy(),
updateTransaction: sinon.spy(),
}
const utilsStubs = {
addressIsNew: sinon.stub().returns(true),
constructTxParams: sinon.stub().returns('mockConstructedTxParams'),
constructUpdatedTx: sinon.stub().returns('mockConstructedUpdatedTxParams'),
formShouldBeDisabled: sinon.stub().returns('mockFormShouldBeDisabled'),
}
proxyquire('../send-footer.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../../actions': actionSpies,
'../send.selectors': {
getGasLimit: (s) => `mockGasLimit:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSendAmount: (s) => `mockAmount:${s}`,
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
getSendFromObject: (s) => `mockFromObject:${s}`,
getSendTo: (s) => `mockTo:${s}`,
getSendToAccounts: (s) => `mockToAccounts:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`,
getUnapprovedTxs: (s) => `mockUnapprovedTxs:${s}`,
isSendFormInError: (s) => `mockInError:${s}`,
},
'./send-footer.selectors': { isSendFormInError: () => {} },
'./send-footer.utils': utilsStubs,
})
describe('send-footer container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
amount: 'mockAmount:mockState',
disabled: 'mockFormShouldBeDisabled',
selectedToken: 'mockSelectedToken:mockState',
editingTransactionId: 'mockEditingTransactionId:mockState',
from: 'mockFromObject:mockState',
gasLimit: 'mockGasLimit:mockState',
gasPrice: 'mockGasPrice:mockState',
inError: 'mockInError:mockState',
isToken: true,
to: 'mockTo:mockState',
toAccounts: 'mockToAccounts:mockState',
unapprovedTxs: 'mockUnapprovedTxs:mockState',
})
assert.deepEqual(
utilsStubs.formShouldBeDisabled.getCall(0).args[0],
{
inError: 'mockInError:mockState',
selectedToken: 'mockSelectedToken:mockState',
tokenBalance: 'mockTokenBalance:mockState',
gasTotal: 'mockGasTotal:mockState',
}
)
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('clearSend()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.clearSend()
assert(dispatchSpy.calledOnce)
assert(actionSpies.clearSend.calledOnce)
})
})
describe('sign()', () => {
it('should dispatch a signTokenTx action if selectedToken is defined', () => {
mapDispatchToPropsObject.sign({
selectedToken: {
address: '0xabc',
},
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
})
assert(dispatchSpy.calledOnce)
assert.deepEqual(
utilsStubs.constructTxParams.getCall(0).args[0],
{
selectedToken: {
address: '0xabc',
},
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
}
)
assert.deepEqual(
actionSpies.signTokenTx.getCall(0).args,
[ '0xabc', 'mockTo', 'mockAmount', 'mockConstructedTxParams' ]
)
})
it('should dispatch a sign action if selectedToken is not defined', () => {
utilsStubs.constructTxParams.resetHistory()
mapDispatchToPropsObject.sign({
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
})
assert(dispatchSpy.calledOnce)
assert.deepEqual(
utilsStubs.constructTxParams.getCall(0).args[0],
{
selectedToken: undefined,
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
}
)
assert.deepEqual(
actionSpies.signTx.getCall(0).args,
[ 'mockConstructedTxParams' ]
)
})
})
describe('update()', () => {
it('should dispatch an updateTransaction action', () => {
mapDispatchToPropsObject.update({
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
editingTransactionId: 'mockEditingTransactionId',
selectedToken: 'mockSelectedToken',
unapprovedTxs: 'mockUnapprovedTxs',
})
assert(dispatchSpy.calledOnce)
assert.deepEqual(
utilsStubs.constructUpdatedTx.getCall(0).args[0],
{
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
editingTransactionId: 'mockEditingTransactionId',
selectedToken: 'mockSelectedToken',
unapprovedTxs: 'mockUnapprovedTxs',
}
)
assert.equal(actionSpies.updateTransaction.getCall(0).args[0], 'mockConstructedUpdatedTxParams')
})
})
describe('addToAddressBookIfNew()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.addToAddressBookIfNew('mockNewAddress', 'mockToAccounts', 'mockNickname')
assert(dispatchSpy.calledOnce)
assert.equal(utilsStubs.addressIsNew.getCall(0).args[0], 'mockToAccounts')
assert.deepEqual(
actionSpies.addToAddressBook.getCall(0).args,
[ '0xmockNewAddress', 'mockNickname' ]
)
})
})
})
})

View File

@ -0,0 +1,242 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
const { TOKEN_TRANSFER_FUNCTION_SIGNATURE } = require('../../send.constants')
const stubs = {
rawEncode: sinon.stub().callsFake((arr1, arr2) => {
return [ ...arr1, ...arr2 ]
}),
}
const sendUtils = proxyquire('../send-footer.utils.js', {
'ethereumjs-abi': {
rawEncode: stubs.rawEncode,
},
})
const {
addressIsNew,
formShouldBeDisabled,
constructTxParams,
constructUpdatedTx,
addHexPrefixToObjectValues,
} = sendUtils
describe('send-footer utils', () => {
describe('addHexPrefixToObjectValues()', () => {
it('should return a new object with the same properties with a 0x prefix', () => {
assert.deepEqual(
addHexPrefixToObjectValues({
prop1: '0x123',
prop2: '456',
prop3: 'x',
}),
{
prop1: '0x123',
prop2: '0x456',
prop3: '0xx',
}
)
})
})
describe('addressIsNew()', () => {
it('should return false if the address exists in toAccounts', () => {
assert.equal(
addressIsNew([
{ address: '0xabc' },
{ address: '0xdef' },
{ address: '0xghi' },
], '0xdef'),
false
)
})
it('should return true if the address does not exists in toAccounts', () => {
assert.equal(
addressIsNew([
{ address: '0xabc' },
{ address: '0xdef' },
{ address: '0xghi' },
], '0xxyz'),
true
)
})
})
describe('formShouldBeDisabled()', () => {
const config = {
'should return true if inError is truthy': {
inError: true,
expectedResult: true,
},
'should return true if gasTotal is falsy': {
inError: false,
gasTotal: false,
expectedResult: true,
},
'should return true if selectedToken is truthy and tokenBalance is falsy': {
selectedToken: true,
tokenBalance: null,
expectedResult: true,
},
'should return false if inError is false and all other params are truthy': {
inError: false,
gasTotal: '0x123',
selectedToken: true,
tokenBalance: 123,
expectedResult: false,
},
}
Object.entries(config).map(([description, obj]) => {
it(description, () => {
assert.equal(formShouldBeDisabled(obj), obj.expectedResult)
})
})
})
describe('constructTxParams()', () => {
it('should return a new txParams object with value and to properties if there is no selectedToken', () => {
assert.deepEqual(
constructTxParams({
selectedToken: false,
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
}),
{
to: '0xmockTo',
value: '0xmockAmount',
from: '0xmockFrom',
gas: '0xmockGas',
gasPrice: '0xmockGasPrice',
}
)
})
it('should return a new txParams object without a to property and a 0 value if there is a selectedToken', () => {
assert.deepEqual(
constructTxParams({
selectedToken: true,
to: 'mockTo',
amount: 'mockAmount',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
}),
{
value: '0x0',
from: '0xmockFrom',
gas: '0xmockGas',
gasPrice: '0xmockGasPrice',
}
)
})
})
describe('constructUpdatedTx()', () => {
it('should return a new object with an updated txParams', () => {
const result = constructUpdatedTx({
amount: 'mockAmount',
editingTransactionId: '0x456',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
selectedToken: false,
to: 'mockTo',
unapprovedTxs: {
'0x123': {},
'0x456': {
unapprovedTxParam: 'someOtherParam',
txParams: {
data: 'someData',
},
},
},
})
assert.deepEqual(result, {
unapprovedTxParam: 'someOtherParam',
txParams: {
from: '0xmockFrom',
gas: '0xmockGas',
gasPrice: '0xmockGasPrice',
value: '0xmockAmount',
to: '0xmockTo',
data: '0xsomeData',
},
})
})
it('should not have data property if there is non in the original tx', () => {
const result = constructUpdatedTx({
amount: 'mockAmount',
editingTransactionId: '0x456',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
selectedToken: false,
to: 'mockTo',
unapprovedTxs: {
'0x123': {},
'0x456': {
unapprovedTxParam: 'someOtherParam',
txParams: {
from: 'oldFrom',
gas: 'oldGas',
gasPrice: 'oldGasPrice',
},
},
},
})
assert.deepEqual(result, {
unapprovedTxParam: 'someOtherParam',
txParams: {
from: '0xmockFrom',
gas: '0xmockGas',
gasPrice: '0xmockGasPrice',
value: '0xmockAmount',
to: '0xmockTo',
},
})
})
it('should have token property values if selectedToken is truthy', () => {
const result = constructUpdatedTx({
amount: 'mockAmount',
editingTransactionId: '0x456',
from: 'mockFrom',
gas: 'mockGas',
gasPrice: 'mockGasPrice',
selectedToken: {
address: 'mockTokenAddress',
},
to: 'mockTo',
unapprovedTxs: {
'0x123': {},
'0x456': {
unapprovedTxParam: 'someOtherParam',
txParams: {},
},
},
})
assert.deepEqual(result, {
unapprovedTxParam: 'someOtherParam',
txParams: {
from: '0xmockFrom',
gas: '0xmockGas',
gasPrice: '0xmockGasPrice',
value: '0x0',
to: '0xmockTokenAddress',
data: `${TOKEN_TRANSFER_FUNCTION_SIGNATURE}ss56Tont`,
},
})
})
})
})

View File

@ -0,0 +1,55 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
clearSend: sinon.spy(),
}
proxyquire('../send-header.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'../../../actions': actionSpies,
'../../../selectors': { getSelectedToken: (s) => `mockSelectedToken:${s}` },
})
describe('send-header container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
isToken: true,
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('clearSend()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.clearSend()
assert(dispatchSpy.calledOnce)
assert(actionSpies.clearSend.calledOnce)
})
})
})
})

View File

@ -1,5 +1,4 @@
import { connect } from 'react-redux'
import abi from 'ethereumjs-abi'
import SendEther from './send.component'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
@ -46,7 +45,7 @@ function mapStateToProps (state) {
amount: getSendAmount(state),
amountConversionRate: getAmountConversionRate(state),
conversionRate: getConversionRate(state),
data: generateTokenTransferData(abi, selectedAddress, selectedToken),
data: generateTokenTransferData(selectedAddress, selectedToken),
editingTransactionId: getSendEditingTransactionId(state),
from: getSendFromObject(state),
gasLimit: getGasLimit(state),
@ -72,6 +71,7 @@ function mapDispatchToProps (dispatch) {
selectedAddress,
selectedToken,
}) => {
console.log(`editingTransactionId`, editingTransactionId)
!editingTransactionId
? dispatch(updateGasTotal({ selectedAddress, selectedToken, data }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))

View File

@ -1,12 +1,12 @@
import { valuesFor } from '../../util'
import abi from 'human-standard-token-abi'
import {
const { valuesFor } = require('../../util')
const abi = require('human-standard-token-abi')
const {
multiplyCurrencies,
} from '../../conversion-util'
} = require('../../conversion-util')
const selectors = {
accountsWithSendEtherInfoSelector,
autoAddToBetaUI,
// autoAddToBetaUI,
getAddressBook,
getAmountConversionRate,
getConversionRate,
@ -58,22 +58,22 @@ function accountsWithSendEtherInfoSelector (state) {
return accountsWithSendEtherInfo
}
function autoAddToBetaUI (state) {
const autoAddTransactionThreshold = 12
const autoAddAccountsThreshold = 2
const autoAddTokensThreshold = 1
// function autoAddToBetaUI (state) {
// const autoAddTransactionThreshold = 12
// const autoAddAccountsThreshold = 2
// const autoAddTokensThreshold = 1
const numberOfTransactions = state.metamask.selectedAddressTxList.length
const numberOfAccounts = Object.keys(state.metamask.accounts).length
const numberOfTokensAdded = state.metamask.tokens.length
// const numberOfTransactions = state.metamask.selectedAddressTxList.length
// const numberOfAccounts = Object.keys(state.metamask.accounts).length
// const numberOfTokensAdded = state.metamask.tokens.length
const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
(numberOfAccounts > autoAddAccountsThreshold) &&
(numberOfTokensAdded > autoAddTokensThreshold)
const userIsNotInBeta = !state.metamask.featureFlags.betaUI
// const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
// (numberOfAccounts > autoAddAccountsThreshold) &&
// (numberOfTokensAdded > autoAddTokensThreshold)
// const userIsNotInBeta = !state.metamask.featureFlags.betaUI
return userIsNotInBeta && userPassesThreshold
}
// return userIsNotInBeta && userPassesThreshold
// }
function getAddressBook (state) {
return state.metamask.addressBook
@ -117,14 +117,14 @@ function getForceGasMin (state) {
return state.metamask.send.forceGasMin
}
function getGasPrice (state) {
return state.metamask.send.gasPrice
}
function getGasLimit (state) {
return state.metamask.send.gasLimit
}
function getGasPrice (state) {
return state.metamask.send.gasPrice
}
function getGasTotal (state) {
return state.metamask.send.gasTotal
}

View File

@ -3,18 +3,17 @@ const {
conversionUtil,
conversionGTE,
multiplyCurrencies,
conversionGreaterThan,
} = require('../../conversion-util')
const {
calcTokenAmount,
} = require('../../token-util')
const {
conversionGreaterThan,
} = require('../../conversion-util')
const {
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
NEGATIVE_ETH_ERROR,
} = require('./send.constants')
const abi = require('ethereumjs-abi')
module.exports = {
calcGasTotal,
@ -179,8 +178,9 @@ function doesAmountErrorRequireUpdate ({
return amountErrorRequiresUpdate
}
function generateTokenTransferData (abi, selectedAddress, selectedToken) {
function generateTokenTransferData (selectedAddress, selectedToken) {
if (!selectedToken) return
console.log(`abi.rawEncode`, abi.rawEncode)
return Array.prototype.map.call(
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
x => ('00' + x.toString(16)).slice(-2)

View File

@ -0,0 +1,150 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
const actionSpies = {
updateSendTokenBalance: sinon.spy(),
updateGasTotal: sinon.spy(),
setGasTotal: sinon.spy(),
}
const duckActionSpies = {
updateSendErrors: sinon.spy(),
}
proxyquire('../send.container.js', {
'react-redux': {
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},
'react-router-dom': { withRouter: () => {} },
'recompose': { compose: (arg1, arg2) => () => arg2() },
'./send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getConversionRate: (s) => `mockConversionRate:${s}`,
getCurrentNetwork: (s) => `mockNetwork:${s}`,
getGasLimit: (s) => `mockGasLimit:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`,
getSendAmount: (s) => `mockAmount:${s}`,
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
getSendFromObject: (s) => `mockFrom:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`,
},
'../../actions': actionSpies,
'../../ducks/send': duckActionSpies,
'./send.utils.js': {
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
generateTokenTransferData: (a, b) => `mockData:${a + b}`,
},
})
describe('send container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
amount: 'mockAmount:mockState',
amountConversionRate: 'mockAmountConversionRate:mockState',
conversionRate: 'mockConversionRate:mockState',
data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState',
editingTransactionId: 'mockEditingTransactionId:mockState',
from: 'mockFrom:mockState',
gasLimit: 'mockGasLimit:mockState',
gasPrice: 'mockGasPrice:mockState',
gasTotal: 'mockGasTotal:mockState',
network: 'mockNetwork:mockState',
primaryCurrency: 'mockPrimaryCurrency:mockState',
selectedAddress: 'mockSelectedAddress:mockState',
selectedToken: 'mockSelectedToken:mockState',
tokenBalance: 'mockTokenBalance:mockState',
tokenContract: 'mockTokenContract:mockState',
tokenToFiatRate: 'mockTokenToFiatRate:mockState',
})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
describe('updateAndSetGasTotal()', () => {
const mockProps = {
data: '0x1',
editingTransactionId: '0x2',
gasLimit: '0x3',
gasPrice: '0x4',
selectedAddress: '0x4',
selectedToken: { address: '0x1' },
}
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => {
mapDispatchToPropsObject.updateAndSetGasTotal(mockProps)
assert(dispatchSpy.calledOnce)
assert.equal(
actionSpies.setGasTotal.getCall(0).args[0],
'0x30x4'
)
})
it('should dispatch an updateGasTotal action when editingTransactionId is falsy', () => {
const { selectedAddress, selectedToken, data } = mockProps
mapDispatchToPropsObject.updateAndSetGasTotal(
Object.assign(mockProps, {editingTransactionId: false})
)
assert(dispatchSpy.calledOnce)
assert.deepEqual(
actionSpies.updateGasTotal.getCall(0).args[0],
{ selectedAddress, selectedToken, data }
)
})
})
describe('updateSendTokenBalance()', () => {
const mockProps = {
address: '0x10',
tokenContract: '0x00a',
selectedToken: {address: '0x1'},
}
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendTokenBalance(Object.assign({}, mockProps))
assert(dispatchSpy.calledOnce)
assert.deepEqual(
actionSpies.updateSendTokenBalance.getCall(0).args[0],
mockProps
)
})
})
describe('updateSendErrors()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendErrors('mockError')
assert(dispatchSpy.calledOnce)
assert.equal(
duckActionSpies.updateSendErrors.getCall(0).args[0],
'mockError'
)
})
})
})
})

View File

@ -0,0 +1,191 @@
module.exports = {
'metamask': {
'isInitialized': true,
'isUnlocked': true,
'featureFlags': {'betaUI': true},
'rpcTarget': 'https://rawtestrpc.metamask.io/',
'identities': {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
'name': 'Send Account 1',
},
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': {
'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'name': 'Send Account 2',
},
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': {
'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
'name': 'Send Account 3',
},
'0xd85a4b6a394794842887b8284293d69163007bbb': {
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
},
},
'currentCurrency': 'USD',
'conversionRate': 1200.88200327,
'conversionDate': 1489013762,
'noActiveNotices': true,
'frequentRpcList': [],
'network': '3',
'accounts': {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
'code': '0x',
'balance': '0x47c9d71831c76efe',
'nonce': '0x1b',
'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
},
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': {
'code': '0x',
'balance': '0x37452b1315889f80',
'nonce': '0xa',
'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
},
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': {
'code': '0x',
'balance': '0x30c9d71831c76efe',
'nonce': '0x1c',
'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
},
'0xd85a4b6a394794842887b8284293d69163007bbb': {
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
},
},
'addressBook': [
{
'address': '0x06195827297c7a80a443b6894d3bdb8824b43896',
'name': 'Address Book Account 1',
},
],
'tokens': [
{
'address': '0x1a195821297c7a80a433b6894d3bdb8824b43896',
'decimals': 18,
'symbol': 'ABC',
},
{
'address': '0x8d6b81208414189a58339873ab429b6c47ab92d3',
'decimals': 4,
'symbol': 'DEF',
},
{
'address': '0xa42084c8d1d9a2198631988579bb36b48433a72b',
'decimals': 18,
'symbol': 'GHI',
},
],
'tokenExchangeRates': {
'def_eth': {
rate: 2.0,
},
'ghi_eth': {
rate: 31.01,
},
},
'transactions': {},
'selectedAddressTxList': [],
'selectedTokenAddress': '0x8d6b81208414189a58339873ab429b6c47ab92d3',
'unapprovedMsgs': {},
'unapprovedMsgCount': 0,
'unapprovedPersonalMsgs': {},
'unapprovedPersonalMsgCount': 0,
'keyringTypes': [
'Simple Key Pair',
'HD Key Tree',
],
'keyrings': [
{
'type': 'HD Key Tree',
'accounts': [
'fdea65c8e26263f6d9a1b5de9555d2931a33b825',
'c5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'2f8d4a878cfa04a6e60d46362f5644deab66572d',
],
},
{
'type': 'Simple Key Pair',
'accounts': [
'0xd85a4b6a394794842887b8284293d69163007bbb',
],
},
],
'selectedAddress': '0xd85a4b6a394794842887b8284293d69163007bbb',
'provider': {
'type': 'testnet',
},
'shapeShiftTxList': [],
'lostAccounts': [],
'send': {
'gasLimit': '0xFFFF',
'gasPrice': '0xaa',
'gasTotal': '0xb451dc41b578',
'tokenBalance': 3434,
'from': {
'address': '0xabcdefg',
'balance': '0x5f4e3d2c1',
},
'to': '0x987fedabc',
'amount': '0x080',
'memo': '',
'errors': {
'someError': null,
},
'maxModeOn': false,
'editingTransactionId': 97531,
'forceGasMin': true,
},
'unapprovedTxs': {
'4768706228115573': {
'id': 4768706228115573,
'time': 1487363153561,
'status': 'unapproved',
'gasMultiplier': 1,
'metamaskNetworkId': '3',
'txParams': {
'from': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'to': '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761',
'value': '0xde0b6b3a7640000',
'metamaskId': 4768706228115573,
'metamaskNetworkId': '3',
'gas': '0x5209',
},
'gasLimitSpecified': false,
'estimatedGas': '0x5209',
'txFee': '17e0186e60800',
'txValue': 'de0b6b3a7640000',
'maxCost': 'de234b52e4a0800',
'gasPrice': '4a817c800',
},
},
'currentLocale': 'en',
},
'appState': {
'menuOpen': false,
'currentView': {
'name': 'accountDetail',
'detailView': null,
'context': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
},
'accountDetail': {
'subview': 'transactions',
},
'modal': {
'modalState': {},
'previousModalState': {},
},
'transForward': true,
'isLoading': false,
'warning': null,
'scrollToBottom': false,
'forgottenPassword': null,
},
'identities': {},
'send': {
'fromDropdownOpen': false,
'toDropdownOpen': false,
'errors': { 'someError': null },
},
}

View File

@ -0,0 +1,572 @@
import assert from 'assert'
import selectors from '../send.selectors.js'
const {
accountsWithSendEtherInfoSelector,
// autoAddToBetaUI,
getAddressBook,
getAmountConversionRate,
getConversionRate,
getConvertedCurrency,
getCurrentAccountWithSendEtherInfo,
getCurrentCurrency,
getCurrentNetwork,
getCurrentViewContext,
getForceGasMin,
getGasLimit,
getGasPrice,
getGasTotal,
getPrimaryCurrency,
getSelectedAccount,
getSelectedAddress,
getSelectedIdentity,
getSelectedToken,
// getSelectedTokenContract,
getSelectedTokenExchangeRate,
getSelectedTokenToFiatRate,
getSendAmount,
getSendEditingTransactionId,
getSendErrors,
getSendFrom,
getSendFromBalance,
getSendFromObject,
getSendMaxModeState,
getSendTo,
getSendToAccounts,
getTokenBalance,
getTokenExchangeRate,
getUnapprovedTxs,
isSendFormInError,
// transactionsSelector,
} = selectors
import mockState from './send-selectors-test-data'
describe('send selectors', () => {
describe('accountsWithSendEtherInfoSelector()', () => {
it('should return an array of account objects with name info from identities', () => {
assert.deepEqual(
accountsWithSendEtherInfoSelector(mockState),
[
{
'code': '0x',
'balance': '0x47c9d71831c76efe',
'nonce': '0x1b',
'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
'name': 'Send Account 1',
},
{
'code': '0x',
'balance': '0x37452b1315889f80',
'nonce': '0xa',
'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'name': 'Send Account 2',
},
{
'code': '0x',
'balance': '0x30c9d71831c76efe',
'nonce': '0x1c',
'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
'name': 'Send Account 3',
},
{
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
},
]
)
})
})
// describe('autoAddToBetaUI()', () => {
// it('should', () => {
// assert.deepEqual(
// autoAddToBetaUI(mockState),
// )
// })
// })
describe('getAddressBook()', () => {
it('should return the address book', () => {
assert.deepEqual(
getAddressBook(mockState),
[
{
'address': '0x06195827297c7a80a443b6894d3bdb8824b43896',
'name': 'Address Book Account 1',
},
],
)
})
})
describe('getAmountConversionRate()', () => {
it('should return the token conversion rate if a token is selected', () => {
assert.equal(
getAmountConversionRate(mockState),
2401.76400654
)
})
it('should return the eth conversion rate if no token is selected', () => {
const editedMockState = {
metamask: Object.assign({}, mockState.metamask, { selectedTokenAddress: null }),
}
assert.equal(
getAmountConversionRate(editedMockState),
1200.88200327
)
})
})
describe('getConversionRate()', () => {
it('should return the eth conversion rate', () => {
assert.deepEqual(
getConversionRate(mockState),
1200.88200327
)
})
})
describe('getConvertedCurrency()', () => {
it('should return the currently selected currency', () => {
assert.equal(
getConvertedCurrency(mockState),
'USD'
)
})
})
describe('getCurrentAccountWithSendEtherInfo()', () => {
it('should return the currently selected account with identity info', () => {
assert.deepEqual(
getCurrentAccountWithSendEtherInfo(mockState),
{
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
}
)
})
})
describe('getCurrentCurrency()', () => {
it('should return the currently selected currency', () => {
assert.equal(
getCurrentCurrency(mockState),
'USD'
)
})
})
describe('getCurrentNetwork()', () => {
it('should return the id of the currently selected network', () => {
assert.equal(
getCurrentNetwork(mockState),
'3'
)
})
})
describe('getCurrentViewContext()', () => {
it('should return the context of the current view', () => {
assert.equal(
getCurrentViewContext(mockState),
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
)
})
})
describe('getForceGasMin()', () => {
it('should get the send.forceGasMin property', () => {
assert.equal(
getForceGasMin(mockState),
true
)
})
})
describe('getGasLimit()', () => {
it('should return the send.gasLimit', () => {
assert.equal(
getGasLimit(mockState),
'0xFFFF'
)
})
})
describe('getGasPrice()', () => {
it('should return the send.gasPrice', () => {
assert.equal(
getGasPrice(mockState),
'0xaa'
)
})
})
describe('getGasTotal()', () => {
it('should return the send.gasTotal', () => {
assert.equal(
getGasTotal(mockState),
'0xb451dc41b578'
)
})
})
describe('getPrimaryCurrency()', () => {
it('should return the symbol of the selected token', () => {
assert.equal(
getPrimaryCurrency(mockState),
'DEF'
)
})
})
describe('getSelectedAccount()', () => {
it('should return the currently selected account', () => {
assert.deepEqual(
getSelectedAccount(mockState),
{
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
}
)
})
})
describe('getSelectedAddress()', () => {
it('should', () => {
assert.equal(
getSelectedAddress(mockState),
'0xd85a4b6a394794842887b8284293d69163007bbb'
)
})
})
describe('getSelectedIdentity()', () => {
it('should return the identity object of the currently selected address', () => {
assert.deepEqual(
getSelectedIdentity(mockState),
{
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
}
)
})
})
describe('getSelectedToken()', () => {
it('should return the currently selected token if selected', () => {
assert.deepEqual(
getSelectedToken(mockState),
{
'address': '0x8d6b81208414189a58339873ab429b6c47ab92d3',
'decimals': 4,
'symbol': 'DEF',
}
)
})
it('should return the send token if none is currently selected, but a send token exists', () => {
const mockSendToken = {
'address': '0x123456708414189a58339873ab429b6c47ab92d3',
'decimals': 4,
'symbol': 'JKL',
}
const editedMockState = {
metamask: Object.assign({}, mockState.metamask, {
selectedTokenAddress: null,
send: {
token: mockSendToken,
},
}),
}
assert.deepEqual(
getSelectedToken(editedMockState),
Object.assign({}, mockSendToken)
)
})
})
// TODO
// describe('getSelectedTokenContract()', () => {
// it('should', () => {
// assert.deepEqual(
// getSelectedTokenContract(mockState),
// )
// })
// })
describe('getSelectedTokenExchangeRate()', () => {
it('should return the exchange rate for the selected token', () => {
assert.equal(
getSelectedTokenExchangeRate(mockState),
2.0
)
})
})
describe('getSelectedTokenToFiatRate()', () => {
it('should return rate for converting the selected token to fiat', () => {
assert.equal(
getSelectedTokenToFiatRate(mockState),
2401.76400654
)
})
})
describe('getSendAmount()', () => {
it('should return the send.amount', () => {
assert.equal(
getSendAmount(mockState),
'0x080'
)
})
})
describe('getSendEditingTransactionId()', () => {
it('should return the send.editingTransactionId', () => {
assert.equal(
getSendEditingTransactionId(mockState),
97531
)
})
})
describe('getSendErrors()', () => {
it('should return the send.errors', () => {
assert.deepEqual(
getSendErrors(mockState),
{ 'someError': null }
)
})
})
describe('getSendFrom()', () => {
it('should return the send.from', () => {
assert.deepEqual(
getSendFrom(mockState),
{
'address': '0xabcdefg',
'balance': '0x5f4e3d2c1',
}
)
})
})
describe('getSendFromBalance()', () => {
it('should get the send.from balance if it exists', () => {
assert.equal(
getSendFromBalance(mockState),
'0x5f4e3d2c1'
)
})
it('should get the selected account balance if the send.from does not exist', () => {
const editedMockState = {
metamask: Object.assign({}, mockState.metamask, {
send: {
from: null,
},
}),
}
assert.equal(
getSendFromBalance(editedMockState),
'0x0'
)
})
})
describe('getSendFromObject()', () => {
it('should return send.from if it exists', () => {
assert.deepEqual(
getSendFromObject(mockState),
{
'address': '0xabcdefg',
'balance': '0x5f4e3d2c1',
}
)
})
it('should return the current account with send ether info if send.from does not exist', () => {
const editedMockState = {
metamask: Object.assign({}, mockState.metamask, {
send: {
from: null,
},
}),
}
assert.deepEqual(
getSendFromObject(editedMockState),
{
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
}
)
})
})
describe('getSendMaxModeState()', () => {
it('should return send.maxModeOn', () => {
assert.equal(
getSendMaxModeState(mockState),
false
)
})
})
describe('getSendTo()', () => {
it('should return send.to', () => {
assert.equal(
getSendTo(mockState),
'0x987fedabc'
)
})
})
describe('getSendToAccounts()', () => {
it('should return an array including all the users accounts and the address book', () => {
assert.deepEqual(
getSendToAccounts(mockState),
[
{
'code': '0x',
'balance': '0x47c9d71831c76efe',
'nonce': '0x1b',
'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',
'name': 'Send Account 1',
},
{
'code': '0x',
'balance': '0x37452b1315889f80',
'nonce': '0xa',
'address': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'name': 'Send Account 2',
},
{
'code': '0x',
'balance': '0x30c9d71831c76efe',
'nonce': '0x1c',
'address': '0x2f8d4a878cfa04a6e60d46362f5644deab66572d',
'name': 'Send Account 3',
},
{
'code': '0x',
'balance': '0x0',
'nonce': '0x0',
'address': '0xd85a4b6a394794842887b8284293d69163007bbb',
'name': 'Send Account 4',
},
{
'address': '0x06195827297c7a80a443b6894d3bdb8824b43896',
'name': 'Address Book Account 1',
},
]
)
})
})
describe('getTokenBalance()', () => {
it('should', () => {
assert.equal(
getTokenBalance(mockState),
3434
)
})
})
describe('getTokenExchangeRate()', () => {
it('should return the passed tokens exchange rates', () => {
assert.equal(
getTokenExchangeRate(mockState, 'GHI'),
31.01
)
})
})
describe('getUnapprovedTxs()', () => {
it('should return the unapproved txs', () => {
assert.deepEqual(
getUnapprovedTxs(mockState),
{
'4768706228115573': {
'id': 4768706228115573,
'time': 1487363153561,
'status': 'unapproved',
'gasMultiplier': 1,
'metamaskNetworkId': '3',
'txParams': {
'from': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'to': '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761',
'value': '0xde0b6b3a7640000',
'metamaskId': 4768706228115573,
'metamaskNetworkId': '3',
'gas': '0x5209',
},
'gasLimitSpecified': false,
'estimatedGas': '0x5209',
'txFee': '17e0186e60800',
'txValue': 'de0b6b3a7640000',
'maxCost': 'de234b52e4a0800',
'gasPrice': '4a817c800',
},
}
)
})
})
describe('isSendFormInError()', () => {
it('should return true if amount or to errors are truthy', () => {
const editedMockState1 = {
send: Object.assign({}, mockState.send, {
errors: { amount: true },
}),
}
assert.deepEqual(
isSendFormInError(editedMockState1),
true
)
const editedMockState2 = {
send: Object.assign({}, mockState.send, {
errors: { to: true },
}),
}
assert.deepEqual(
isSendFormInError(editedMockState2),
true
)
})
it('should return false if amount is falsy and to is null', () => {
const editedMockState = {
send: Object.assign({}, mockState.send, { errors: { amount: false, to: null } }),
}
assert.deepEqual(
isSendFormInError(editedMockState),
false
)
})
})
// TODO
// describe('transactionsSelector()', () => {
// it('should', () => {
// assert.deepEqual(
// transactionsSelector(mockState),
// )
// })
// })
})

View File

@ -0,0 +1,264 @@
import assert from 'assert'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
const {
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
} = require('../send.constants')
const stubs = {
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => a * b),
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
rawEncode: sinon.stub().returns([16, 1100]),
}
const sendUtils = proxyquire('../send.utils.js', {
'../../conversion-util': {
addCurrencies: stubs.addCurrencies,
conversionUtil: stubs.conversionUtil,
conversionGTE: stubs.conversionGTE,
multiplyCurrencies: stubs.multiplyCurrencies,
},
'../../token-util': { calcTokenAmount: stubs.calcTokenAmount },
'ethereumjs-abi': {
rawEncode: stubs.rawEncode,
},
})
const {
calcGasTotal,
doesAmountErrorRequireUpdate,
generateTokenTransferData,
getAmountErrorObject,
getParamsForGasEstimate,
calcTokenBalance,
isBalanceSufficient,
isTokenBalanceSufficient,
} = sendUtils
describe('send utils', () => {
describe('calcGasTotal()', () => {
it('should call multiplyCurrencies with the correct params and return the multiplyCurrencies return', () => {
const result = calcGasTotal(12, 15)
assert.equal(result, 180)
const call_ = stubs.multiplyCurrencies.getCall(0).args
assert.deepEqual(
call_,
[12, 15, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
} ]
)
})
})
describe('doesAmountErrorRequireUpdate()', () => {
const config = {
'should return true if balances are different': {
balance: 0,
prevBalance: 1,
expectedResult: true,
},
'should return true if gasTotals are different': {
gasTotal: 0,
prevGasTotal: 1,
expectedResult: true,
},
'should return true if token balances are different': {
tokenBalance: 0,
prevTokenBalance: 1,
selectedToken: 'someToken',
expectedResult: true,
},
'should return false if they are all the same': {
balance: 1,
prevBalance: 1,
gasTotal: 1,
prevGasTotal: 1,
tokenBalance: 1,
prevTokenBalance: 1,
selectedToken: 'someToken',
expectedResult: false,
},
}
Object.entries(config).map(([description, obj]) => {
it(description, () => {
assert.equal(doesAmountErrorRequireUpdate(obj), obj.expectedResult)
})
})
})
describe('generateTokenTransferData()', () => {
it('should return undefined if not passed a selected token', () => {
assert.equal(generateTokenTransferData('mockAddress', false), undefined)
})
it('should return encoded token transfer data', () => {
assert.equal(generateTokenTransferData('mockAddress', true), '104c')
})
})
describe('getAmountErrorObject()', () => {
const config = {
'should return insufficientFunds error if isBalanceSufficient returns false': {
amount: 15,
amountConversionRate: 2,
balance: 1,
conversionRate: 3,
gasTotal: 17,
primaryCurrency: 'ABC',
expectedResult: { amount: INSUFFICIENT_FUNDS_ERROR },
},
'should return insufficientTokens error if token is selected and isTokenBalanceSufficient returns false': {
amount: '0x10',
amountConversionRate: 2,
balance: 100,
conversionRate: 3,
decimals: 10,
gasTotal: 17,
primaryCurrency: 'ABC',
selectedToken: 'someToken',
tokenBalance: 123,
expectedResult: { amount: INSUFFICIENT_TOKENS_ERROR },
},
}
Object.entries(config).map(([description, obj]) => {
it(description, () => {
assert.deepEqual(getAmountErrorObject(obj), obj.expectedResult)
})
})
})
describe('getParamsForGasEstimate()', () => {
it('should return from and gas properties if no symbol or data', () => {
assert.deepEqual(
getParamsForGasEstimate('mockAddress'),
{
from: 'mockAddress',
gas: '746a528800',
}
)
})
it('should return value property if symbol provided', () => {
assert.deepEqual(
getParamsForGasEstimate('mockAddress', 'ABC'),
{
from: 'mockAddress',
gas: '746a528800',
value: '0x0',
}
)
})
it('should return data property if data provided', () => {
assert.deepEqual(
getParamsForGasEstimate('mockAddress', 'ABC', 'somedata'),
{
from: 'mockAddress',
gas: '746a528800',
value: '0x0',
data: 'somedata',
}
)
})
})
describe('calcTokenBalance()', () => {
it('should return the calculated token blance', () => {
assert.equal(calcTokenBalance({
selectedToken: {
decimals: 11,
},
usersToken: {
balance: 20,
},
}), 'calc:2011')
})
})
describe('isBalanceSufficient()', () => {
it('should correctly call addCurrencies and return the result of calling conversionGTE', () => {
stubs.conversionGTE.resetHistory()
const result = isBalanceSufficient({
amount: 15,
amountConversionRate: 2,
balance: 100,
conversionRate: 3,
gasTotal: 17,
primaryCurrency: 'ABC',
})
assert.deepEqual(
stubs.addCurrencies.getCall(0).args,
[
15, 17, {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',
},
]
)
assert.deepEqual(
stubs.conversionGTE.getCall(0).args,
[
{
value: 100,
fromNumericBase: 'hex',
fromCurrency: 'ABC',
conversionRate: 3,
},
{
value: 32,
fromNumericBase: 'hex',
conversionRate: 2,
fromCurrency: 'ABC',
},
]
)
assert.equal(result, true)
})
})
describe('isTokenBalanceSufficient()', () => {
it('should correctly call conversionUtil and return the result of calling conversionGTE', () => {
stubs.conversionGTE.resetHistory()
const result = isTokenBalanceSufficient({
amount: '0x10',
tokenBalance: 123,
decimals: 10,
})
assert.deepEqual(
stubs.conversionUtil.getCall(0).args,
[
'0x10', {
fromNumericBase: 'hex',
},
]
)
assert.deepEqual(
stubs.conversionGTE.getCall(0).args,
[
{
value: 123,
fromNumericBase: 'dec',
},
{
value: 'calc:1610',
fromNumericBase: 'dec',
},
]
)
assert.equal(result, false)
})
})
})

View File

@ -203,8 +203,8 @@ TxListItem.prototype.showRetryButton = function () {
const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1]
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce
&& lastSubmittedTxWithCurrentNonce.id === transactionId
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
lastSubmittedTxWithCurrentNonce.id === transactionId
return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000
}

View File

@ -6,14 +6,14 @@ const { compose } = require('recompose')
const t = require('../i18n-helper').getMessage
class I18nProvider extends Component {
getChildContext() {
getChildContext () {
const { localeMessages } = this.props
return {
t: t.bind(null, localeMessages),
}
}
render() {
render () {
return this.props.children
}
}

View File

@ -20,7 +20,7 @@ MainContainer.prototype.render = function () {
// - pass resulting h() to MainContainer
// - error checking in separate func
// - router in separate func
let contents = {
const contents = {
component: AccountAndTransactionDetails,
key: 'account-detail',
style: {},

769
yarn.lock

File diff suppressed because it is too large Load Diff