Use combineReducers for rootReducer (#7964)

feature/default_network_editable
Whymarrh Whitby 5 years ago committed by GitHub
parent 7e04be9b58
commit dea8f0f24d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/scripts/ui.js
  2. 13
      development/mock-dev.js
  3. 3
      test/lib/example-code.json
  4. 40
      test/lib/mock-simple-keychain.js
  5. 12
      test/lib/mock-store.js
  6. 13
      test/unit/actions/tx_test.js
  7. 36
      test/unit/ui/app/actions.spec.js
  8. 63
      test/unit/ui/app/reducers/app.spec.js
  9. 74
      test/unit/ui/app/reducers/metamask.spec.js
  10. 113
      ui/app/ducks/app/app.js
  11. 52
      ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
  12. 58
      ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
  13. 78
      ui/app/ducks/gas/gas-duck.test.js
  14. 43
      ui/app/ducks/gas/gas.duck.js
  15. 105
      ui/app/ducks/index.js
  16. 14
      ui/app/ducks/locale/locale.js
  17. 38
      ui/app/ducks/metamask/metamask.js
  18. 71
      ui/app/ducks/send/send-duck.test.js
  19. 19
      ui/app/ducks/send/send.duck.js
  20. 7
      ui/app/pages/confirm-transaction/conf-tx.js
  21. 32
      ui/app/store/actions.js
  22. 46
      ui/index.js

@ -43,7 +43,9 @@ async function start () {
// provide app state to append to error logs
function getState () {
// get app state
const state = window.getCleanAppState()
const state = window.getCleanAppState
? window.getCleanAppState()
: {}
// remove unnecessary data
delete state.localeMessages
delete state.metamask.recentBlocks

@ -13,11 +13,13 @@
import log from 'loglevel'
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import * as qs from 'qs'
import Selector from './selector'
import * as actions from '../ui/app/store/actions'
import Root from '../ui/app/pages'
import configureStore from '../ui/app/store/store'
import rootReducer from '../ui/app/ducks'
import MetamaskController from '../app/scripts/metamask-controller'
import firstTimeState from '../app/scripts/first-time-state'
import ExtensionPlatform from '../app/scripts/platforms/extension'
@ -92,7 +94,14 @@ function modifyBackgroundConnection (backgroundConnectionModifier) {
}
// parse opts
const store = configureStore(firstState)
const store = createStore(
(state, action) =>
(action.type === 'GLOBAL_FORCE_UPDATE'
? action.value
: rootReducer(state, action)),
firstState,
applyMiddleware(thunkMiddleware),
)
// start app
startApp()

@ -1,3 +0,0 @@
{
"delegateCallCode": "0x606060405260e060020a60003504637bd703e8811461003157806390b98a111461005c578063f8b2cb4f1461008e575b005b6100b4600435600073f28c53067227848f8145355c455da5cfdd20e3136396e4ee3d6100da84610095565b6100c660043560243533600160a060020a03166000908152602081905260408120548290101561011f57506000610189565b6100b46004355b600160a060020a0381166000908152602081905260409020545b919050565b60408051918252519081900360200190f35b604080519115158252519081900360200190f35b60026040518360e060020a02815260040180838152602001828152602001925050506020604051808303818660325a03f4156100025750506040515191506100af9050565b33600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b9291505056"
}

@ -1,40 +0,0 @@
const fakeWallet = {
privKey: '0x123456788890abcdef',
address: '0xfedcba0987654321',
}
const type = 'Simple Key Pair'
export default class MockSimpleKeychain {
static type () {
return type
}
constructor (opts) {
this.type = type
this.opts = opts || {}
this.wallets = []
}
serialize () {
return [ fakeWallet.privKey ]
}
deserialize (data) {
if (!Array.isArray(data)) {
throw new Error('Simple keychain deserialize requires a privKey array.')
}
this.wallets = [ fakeWallet ]
}
addAccounts (n = 1) {
for (let i = 0; i < n; i++) {
this.wallets.push(fakeWallet)
}
}
getAccounts () {
return this.wallets.map(w => w.address)
}
}

@ -1,12 +0,0 @@
import { applyMiddleware, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
const rootReducer = function () {}
export default configureStore
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore)
function configureStore (initialState) {
return createStoreWithMiddleware(rootReducer, initialState)
}

@ -10,9 +10,6 @@ describe('tx confirmation screen', function () {
const txId = 1457634084250832
const initialState = {
appState: {
currentView: {
name: 'confTx',
},
},
metamask: {
unapprovedTxs: {
@ -43,14 +40,12 @@ describe('tx confirmation screen', function () {
done()
})
it('creates COMPLETED_TX with the cancelled transaction ID', function (done) {
store.dispatch(actions.cancelTx({ id: txId }))
.then(() => {
it('creates COMPLETED_TX with the cancelled transaction ID', async function () {
await store.dispatch(actions.cancelTx({ id: txId }))
const storeActions = store.getActions()
const completedTxAction = storeActions.find(({ type }) => type === actions.actionConstants.COMPLETED_TX)
assert.equal(completedTxAction.value, txId)
done()
})
const { id } = completedTxAction.value
assert.equal(id, txId)
})
})
})

@ -1,9 +1,4 @@
/* eslint-disable */
// Used to inspect long objects
// util.inspect({JSON}, false, null))
// const util = require('util')
import assert from 'assert'
import sinon from 'sinon'
import { cloneDeep } from 'lodash'
import nock from 'nock'
@ -14,14 +9,13 @@ import EthQuery from 'eth-query'
import Eth from 'ethjs'
import KeyringController from 'eth-keyring-controller'
import { createTestProviderTools } from '../../../stub/provider'
const provider = createTestProviderTools({ scaffold: {}}).provider
import enLocale from '../../../../app/_locales/en/messages.json'
import * as actions from '../../../../ui/app/store/actions'
import MetaMaskController from '../../../../app/scripts/metamask-controller'
import firstTimeState from '../../localhostState'
import devState from '../../../data/2-state.json'
const provider = createTestProviderTools({ scaffold: {} }).provider
const middleware = [thunk]
const mockStore = configureStore(middleware)
@ -133,9 +127,9 @@ describe('Actions', () => {
await store.dispatch(actions.tryUnlockMetamask('test'))
assert.fail('Should have thrown error')
} catch (_) {
const actions = store.getActions()
const warning = actions.filter(action => action.type === 'DISPLAY_WARNING')
const unlockFailed = actions.filter(action => action.type === 'UNLOCK_FAILED')
const actions1 = store.getActions()
const warning = actions1.filter(action => action.type === 'DISPLAY_WARNING')
const unlockFailed = actions1.filter(action => action.type === 'UNLOCK_FAILED')
assert.deepEqual(warning, displayWarningError)
assert.deepEqual(unlockFailed, unlockFailedError)
}
@ -450,7 +444,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
checkHardwareStatusSpy.callsFake((deviceName, hdPath, callback) => {
checkHardwareStatusSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@ -492,7 +486,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
forgetDeviceSpy.callsFake((deviceName, callback) => {
forgetDeviceSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -534,7 +528,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
connectHardwareSpy.callsFake((deviceName, page, hdPath, callback) => {
connectHardwareSpy.callsFake((_, __, ___, callback) => {
callback(new Error('error'))
})
@ -576,7 +570,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
unlockHardwareWalletAccountSpy.callsFake((deviceName, page, hdPath, callback) => {
unlockHardwareWalletAccountSpy.callsFake((_, __, ___, callback) => {
callback(new Error('error'))
})
@ -646,7 +640,9 @@ describe('Actions', () => {
})
it('calls signMsg in background', () => {
const store = mockStore()
const store = mockStore({
metamask: {},
})
signMessageSpy = sinon.spy(background, 'signMessage')
store.dispatch(actions.signMsg(msgParams))
@ -655,7 +651,9 @@ describe('Actions', () => {
})
it('errors when signMessage in background throws', async () => {
const store = mockStore()
const store = mockStore({
metamask: {},
})
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'UPDATE_METAMASK_STATE', value: undefined },
@ -700,7 +698,9 @@ describe('Actions', () => {
})
it('calls signPersonalMessage', () => {
const store = mockStore()
const store = mockStore({
metamask: {},
})
signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage')
@ -976,7 +976,7 @@ describe('Actions', () => {
const store = mockStore()
updateTransactionSpy = sinon.stub(background, 'updateTransaction')
updateTransactionSpy.callsFake((res, callback) => {
updateTransactionSpy.callsFake((_, callback) => {
callback(new Error('error'))
})

@ -7,7 +7,6 @@ const actions = actionConstants
describe('App State', () => {
const metamaskState = {
metamask: {
selectedAddress: '0xAddress',
identities: {
'0xAddress': {
@ -15,7 +14,6 @@ describe('App State', () => {
address: '0xAddress',
},
},
},
}
it('App init state', () => {
@ -24,7 +22,7 @@ describe('App State', () => {
assert(initState)
})
it('sets networkd dropdown to true', () => {
it('sets networkDropdownOpen dropdown to true', () => {
const state = reduceApp(metamaskState, {
type: actions.NETWORK_DROPDOWN_OPEN,
})
@ -32,7 +30,7 @@ describe('App State', () => {
assert.equal(state.networkDropdownOpen, true)
})
it('sets networkd dropdown to false', () => {
it('sets networkDropdownOpen dropdown to false', () => {
const dropdown = { networkDropdowopen: true }
const state = { ...metamaskState, ...dropdown }
const newState = reduceApp(state, {
@ -129,7 +127,7 @@ describe('App State', () => {
assert.equal(newState.modal.modalState.name, null)
})
it('tansitions forwards', () => {
it('transitions forwards', () => {
const state = reduceApp(metamaskState, {
type: actions.TRANSITION_FORWARD,
})
@ -137,22 +135,11 @@ describe('App State', () => {
assert.equal(state.transForward, true)
})
it('sets forgot password', () => {
const state = reduceApp(metamaskState, {
type: actions.FORGOT_PASSWORD,
value: true,
})
assert.equal(state.currentView.name, 'restoreVault')
})
it('shows send token page', () => {
const state = reduceApp(metamaskState, {
type: actions.SHOW_SEND_TOKEN_PAGE,
})
assert.equal(state.currentView.name, 'sendToken')
assert.equal(state.currentView.context, '0xAddress')
assert.equal(state.transForward, true)
assert.equal(state.warning, null)
})
@ -173,8 +160,6 @@ describe('App State', () => {
type: actions.LOCK_METAMASK,
})
assert.equal(state.currentView.name, 'accountDetail')
assert.equal(state.currentView.context, '0xAddress')
assert.equal(state.transForward, false)
assert.equal(state.warning, null)
})
@ -184,7 +169,6 @@ describe('App State', () => {
type: actions.GO_HOME,
})
assert.equal(state.currentView.name, 'accountDetail')
assert.equal(state.accountDetail.subview, 'transactions')
assert.equal(state.accountDetail.accountExport, 'none')
assert.equal(state.accountDetail.privateKey, '')
@ -199,8 +183,6 @@ describe('App State', () => {
value: 'context address',
})
assert.equal(state.forgottenPassword, null) // default
assert.equal(state.currentView.name, 'accountDetail')
assert.equal(state.currentView.context, 'context address')
assert.equal(state.accountDetail.subview, 'transactions') // default
assert.equal(state.accountDetail.accountExport, 'none') // default
assert.equal(state.accountDetail.privateKey, '') // default
@ -213,7 +195,6 @@ describe('App State', () => {
type: actions.SHOW_ACCOUNTS_PAGE,
})
assert.equal(state.currentView.name, 'accounts')
assert.equal(state.transForward, true)
assert.equal(state.isLoading, false)
assert.equal(state.warning, null)
@ -232,17 +213,14 @@ describe('App State', () => {
},
},
}
const oldState = {
metamask: { ...metamaskState.metamask, ...txs },
}
const oldState = { ...metamaskState, ...txs }
const state = reduceApp(oldState, {
type: actions.SHOW_CONF_TX_PAGE,
id: 2,
transForward: false,
})
assert.equal(state.currentView.name, 'confTx')
assert.equal(state.currentView.context, 1)
assert.equal(state.txId, 2)
assert.equal(state.transForward, false)
assert.equal(state.warning, null)
assert.equal(state.isLoading, false)
@ -261,17 +239,16 @@ describe('App State', () => {
},
}
const oldState = {
metamask: { ...metamaskState, ...txs },
}
const oldState = { ...metamaskState, ...txs }
const state = reduceApp(oldState, {
type: actions.COMPLETED_TX,
value: 1,
value: {
id: 1,
},
})
assert.equal(state.currentView.name, 'confTx')
assert.equal(state.currentView.context, 0)
assert.equal(state.txId, null)
assert.equal(state.transForward, false)
assert.equal(state.warning, null)
})
@ -279,25 +256,17 @@ describe('App State', () => {
it('returns to account detail page when no unconf actions completed tx', () => {
const state = reduceApp(metamaskState, {
type: actions.COMPLETED_TX,
value: {
unconfirmedActionsCount: 0,
},
})
assert.equal(state.currentView.name, 'accountDetail')
assert.equal(state.currentView.context, '0xAddress')
assert.equal(state.transForward, false)
assert.equal(state.warning, null)
assert.equal(state.accountDetail.subview, 'transactions')
})
it('sets error message in confTx view', () => {
const state = reduceApp(metamaskState, {
type: actions.TRANSACTION_ERROR,
})
assert.equal(state.currentView.name, 'confTx')
assert.equal(state.currentView.errorMessage, 'There was a problem submitting this transaction.')
})
it('sets default warning when unlock fails', () => {
const state = reduceApp(metamaskState, {
type: actions.UNLOCK_FAILED,
@ -423,7 +392,6 @@ describe('App State', () => {
}
const appState = {
appState: {
buyView: {
buyAddress: '0xAddress',
amount: '12.00',
@ -431,7 +399,6 @@ describe('App State', () => {
coinOptions,
},
},
},
}
const marketinfo = {
@ -477,11 +444,9 @@ describe('App State', () => {
it('shows qr view', () => {
const appState = {
appState: {
currentView: {
context: 'accounts',
},
},
}
const oldState = { ...metamaskState, ...appState }
@ -493,8 +458,6 @@ describe('App State', () => {
},
})
assert.equal(state.currentView.name, 'qr')
assert.equal(state.currentView.context, 'accounts')
assert.equal(state.transForward, true)
assert.equal(state.Qr.message, 'message')
assert.equal(state.Qr.data, 'data')

@ -1,13 +1,11 @@
import assert from 'assert'
import reduceMetamask from '../../../../../ui/app/ducks/metamask/metamask'
import { actionConstants } from '../../../../../ui/app/store/actions'
const actions = actionConstants
import { actionConstants as actions } from '../../../../../ui/app/store/actions'
describe('MetaMask Reducers', () => {
it('init state', () => {
const initState = reduceMetamask({ metamask: {} }, {})
const initState = reduceMetamask(undefined, {})
assert(initState)
})
@ -24,11 +22,9 @@ describe('MetaMask Reducers', () => {
it('locks MetaMask', () => {
const unlockMetaMaskState = {
metamask: {
isUnlocked: true,
isInitialzed: false,
selectedAddress: 'test address',
},
}
const lockMetaMask = reduceMetamask(unlockMetaMaskState, {
type: actions.LOCK_METAMASK,
@ -55,70 +51,6 @@ describe('MetaMask Reducers', () => {
assert.equal(state.provider.type, 'provider type')
})
describe('CompletedTx', () => {
const oldState = {
metamask: {
unapprovedTxs: {
1: {
id: 1,
time: 1538495996507,
status: 'unapproved',
metamaskNetworkId: 4,
loadingDefaults: false,
txParams: {
from: '0xAddress',
to: '0xAddress2',
value: '0x16345785d8a0000',
gas: '0x5208',
gasPrice: '0x3b9aca00',
},
type: 'standard',
},
2: {
test: 'Should persist',
},
},
unapprovedMsgs: {
1: {
id: 2,
msgParams: {
from: '0xAddress',
data: '0xData',
origin: 'test origin',
},
time: 1538498521717,
status: 'unapproved',
type: 'eth_sign',
},
2: {
test: 'Should Persist',
},
},
},
}
it('removes tx from new state if completed in action.', () => {
const state = reduceMetamask(oldState, {
type: actions.COMPLETED_TX,
id: 1,
})
assert.equal(Object.keys(state.unapprovedTxs).length, 1)
assert.equal(state.unapprovedTxs[2].test, 'Should persist')
})
it('removes msg from new state if completed id in action', () => {
const state = reduceMetamask(oldState, {
type: actions.COMPLETED_TX,
id: 1,
})
assert.equal(Object.keys(state.unapprovedMsgs).length, 1)
assert.equal(state.unapprovedTxs[2].test, 'Should persist')
})
})
it('shows account detail', () => {
const state = reduceMetamask({}, {
@ -342,14 +274,12 @@ describe('MetaMask Reducers', () => {
it('updates value of tx by id', () => {
const oldState = {
metamask: {
selectedAddressTxList: [
{
id: 1,
txParams: 'foo',
},
],
},
}
const state = reduceMetamask(oldState, {

@ -1,33 +1,9 @@
import txHelper from '../../../lib/tx-helper'
import log from 'loglevel'
import { actionConstants } from '../../store/actions'
const actions = actionConstants
import { actionConstants as actions } from '../../store/actions'
// Actions
const SET_THREEBOX_LAST_UPDATED = 'metamask/app/SET_THREEBOX_LAST_UPDATED'
export default function reduceApp (state, action) {
log.debug('App Reducer got ' + action.type)
// clone and defaults
const selectedAddress = state.metamask.selectedAddress
const hasUnconfActions = checkUnconfActions(state)
let name = 'accounts'
if (selectedAddress) {
name = 'accountDetail'
}
if (hasUnconfActions) {
log.debug('pending txs detected, defaulting to conf-tx view.')
name = 'confTx'
}
const defaultView = {
name,
detailView: null,
context: selectedAddress,
}
export default function reduceApp (state = {}, action) {
// default state
const appState = Object.assign({
shouldClose: false,
@ -52,7 +28,6 @@ export default function reduceApp (state, action) {
alertMessage: null,
qrCodeData: null,
networkDropdownOpen: false,
currentView: defaultView,
accountDetail: {
subview: 'transactions',
},
@ -79,7 +54,7 @@ export default function reduceApp (state, action) {
requestAccountTabs: {},
openMetaMaskTabs: {},
currentWindowTab: {},
}, state.appState)
}, state)
switch (action.type) {
// dropdown methods
@ -157,7 +132,7 @@ export default function reduceApp (state, action) {
return {
...appState,
modal: Object.assign(
state.appState.modal,
appState.modal,
{ open: false },
{ modalState: { name: null, props: {} } },
{ previousModalState: appState.modal.modalState },
@ -172,26 +147,14 @@ export default function reduceApp (state, action) {
}
case actions.FORGOT_PASSWORD:
const newState = {
return {
...appState,
forgottenPassword: action.value,
}
if (action.value) {
newState.currentView = {
name: 'restoreVault',
}
}
return newState
case actions.SHOW_SEND_TOKEN_PAGE:
return {
...appState,
currentView: {
name: 'sendToken',
context: appState.currentView.context,
},
transForward: true,
warning: null,
}
@ -211,7 +174,6 @@ export default function reduceApp (state, action) {
case actions.LOCK_METAMASK:
return {
...appState,
currentView: defaultView,
transForward: false,
warning: null,
}
@ -221,10 +183,6 @@ export default function reduceApp (state, action) {
case actions.GO_HOME:
return {
...appState,
currentView: {
...appState.currentView,
name: 'accountDetail',
},
accountDetail: {
subview: 'transactions',
accountExport: 'none',
@ -238,10 +196,6 @@ export default function reduceApp (state, action) {
return {
...appState,
forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
currentView: {
name: 'accountDetail',
context: action.value,
},
accountDetail: {
subview: 'transactions',
accountExport: 'none',
@ -253,9 +207,6 @@ export default function reduceApp (state, action) {
case actions.SHOW_ACCOUNTS_PAGE:
return {
...appState,
currentView: {
name: 'accounts',
},
transForward: true,
isLoading: false,
warning: null,
@ -266,44 +217,28 @@ export default function reduceApp (state, action) {
case actions.SHOW_CONF_TX_PAGE:
return {
...appState,
currentView: {
name: 'confTx',
context: action.id ? indexForPending(state, action.id) : 0,
},
txId: action.id,
transForward: action.transForward,
warning: null,
isLoading: false,
}
case actions.COMPLETED_TX:
log.debug('reducing COMPLETED_TX for tx ' + action.value)
const otherUnconfActions = getUnconfActionList(state)
.filter(tx => tx.id !== action.value)
const hasOtherUnconfActions = otherUnconfActions.length > 0
if (hasOtherUnconfActions) {
log.debug('reducer detected txs - rendering confTx view')
if (action.value.unconfirmedActionsCount > 0) {
return {
...appState,
transForward: false,
currentView: {
name: 'confTx',
context: 0,
},
txId: null,
warning: null,
}
} else {
log.debug('attempting to close popup')
return {
...appState,
// indicate notification should close
shouldClose: true,
transForward: false,
warning: null,
currentView: {
name: 'accountDetail',
context: state.metamask.selectedAddress,
},
txId: null,
accountDetail: {
subview: 'transactions',
},
@ -313,10 +248,6 @@ export default function reduceApp (state, action) {
case actions.TRANSACTION_ERROR:
return {
...appState,
currentView: {
name: 'confTx',
errorMessage: 'There was a problem submitting this transaction.',
},
}
case actions.UNLOCK_FAILED:
@ -421,10 +352,6 @@ export default function reduceApp (state, action) {
case actions.SHOW_QR_VIEW:
return {
...appState,
currentView: {
name: 'qr',
context: appState.currentView.context,
},
transForward: true,
Qr: {
message: action.value.message,
@ -525,25 +452,3 @@ export function setThreeBoxLastUpdated (lastUpdated) {
value: lastUpdated,
}
}
// Helpers
function checkUnconfActions (state) {
const unconfActionList = getUnconfActionList(state)
const hasUnconfActions = unconfActionList.length > 0
return hasUnconfActions
}
function getUnconfActionList (state) {
const { unapprovedTxs, unapprovedMsgs,
unapprovedPersonalMsgs, unapprovedTypedMessages, network } = state.metamask
const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
return unconfActionList
}
function indexForPending (state, txId) {
const unconfTxList = getUnconfActionList(state)
const match = unconfTxList.find((tx) => tx.id === txId)
const index = unconfTxList.indexOf(match)
return index
}

@ -66,102 +66,102 @@ const initState = {
}
// Reducer
export default function reducer ({ confirmTransaction: confirmState = initState }, action = {}) {
export default function reducer (state = initState, action = {}) {
switch (action.type) {
case UPDATE_TX_DATA:
return {
...confirmState,
...state,
txData: {
...action.payload,
},
}
case CLEAR_TX_DATA:
return {
...confirmState,
...state,
txData: {},
}
case UPDATE_TOKEN_DATA:
return {
...confirmState,
...state,
tokenData: {
...action.payload,
},
}
case CLEAR_TOKEN_DATA:
return {
...confirmState,
...state,
tokenData: {},
}
case UPDATE_METHOD_DATA:
return {
...confirmState,
...state,
methodData: {
...action.payload,
},
}
case CLEAR_METHOD_DATA:
return {
...confirmState,
...state,
methodData: {},
}
case UPDATE_TRANSACTION_AMOUNTS:
const { fiatTransactionAmount, ethTransactionAmount, hexTransactionAmount } = action.payload
return {
...confirmState,
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
hexTransactionAmount: hexTransactionAmount || confirmState.hexTransactionAmount,
...state,
fiatTransactionAmount: fiatTransactionAmount || state.fiatTransactionAmount,
ethTransactionAmount: ethTransactionAmount || state.ethTransactionAmount,
hexTransactionAmount: hexTransactionAmount || state.hexTransactionAmount,
}
case UPDATE_TRANSACTION_FEES:
const { fiatTransactionFee, ethTransactionFee, hexTransactionFee } = action.payload
return {
...confirmState,
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
hexTransactionFee: hexTransactionFee || confirmState.hexTransactionFee,
...state,
fiatTransactionFee: fiatTransactionFee || state.fiatTransactionFee,
ethTransactionFee: ethTransactionFee || state.ethTransactionFee,
hexTransactionFee: hexTransactionFee || state.hexTransactionFee,
}
case UPDATE_TRANSACTION_TOTALS:
const { fiatTransactionTotal, ethTransactionTotal, hexTransactionTotal } = action.payload
return {
...confirmState,
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
hexTransactionTotal: hexTransactionTotal || confirmState.hexTransactionTotal,
...state,
fiatTransactionTotal: fiatTransactionTotal || state.fiatTransactionTotal,
ethTransactionTotal: ethTransactionTotal || state.ethTransactionTotal,
hexTransactionTotal: hexTransactionTotal || state.hexTransactionTotal,
}
case UPDATE_TOKEN_PROPS:
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
return {
...confirmState,
...state,
tokenProps: {
...confirmState.tokenProps,
...state.tokenProps,
tokenSymbol,
tokenDecimals,
},
}
case UPDATE_NONCE:
return {
...confirmState,
...state,
nonce: action.payload,
}
case UPDATE_TO_SMART_CONTRACT:
return {
...confirmState,
...state,
toSmartContract: action.payload,
}
case FETCH_DATA_START:
return {
...confirmState,
...state,
fetchingData: true,
}
case FETCH_DATA_END:
return {
...confirmState,
...state,
fetchingData: false,
}
case CLEAR_CONFIRM_TRANSACTION:
return initState
default:
return confirmState
return state
}
}

@ -46,7 +46,6 @@ const CLEAR_CONFIRM_TRANSACTION = 'metamask/confirm-transaction/CLEAR_CONFIRM_TR
describe('Confirm Transaction Duck', () => {
describe('State changes', () => {
const mockState = {
confirmTransaction: {
txData: {
id: 1,
},
@ -72,14 +71,10 @@ describe('Confirm Transaction Duck', () => {
nonce: '0x0',
toSmartContract: false,
fetchingData: false,
},
}
it('should initialize state', () => {
assert.deepEqual(
ConfirmTransactionReducer({}),
initialState
)
assert.deepEqual(ConfirmTransactionReducer(undefined, {}), initialState)
})
it('should return state unchanged if it does not match a dispatched actions type', () => {
@ -88,7 +83,7 @@ describe('Confirm Transaction Duck', () => {
type: 'someOtherAction',
value: 'someValue',
}),
{ ...mockState.confirmTransaction },
{ ...mockState },
)
})
@ -101,9 +96,9 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
txData: {
...mockState.confirmTransaction.txData,
...mockState.txData,
id: 2,
},
}
@ -116,7 +111,7 @@ describe('Confirm Transaction Duck', () => {
type: CLEAR_TX_DATA,
}),
{
...mockState.confirmTransaction,
...mockState,
txData: {},
}
)
@ -131,9 +126,9 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
tokenData: {
...mockState.confirmTransaction.tokenData,
...mockState.tokenData,
name: 'defToken',
},
}
@ -146,7 +141,7 @@ describe('Confirm Transaction Duck', () => {
type: CLEAR_TOKEN_DATA,
}),
{
...mockState.confirmTransaction,
...mockState,
tokenData: {},
}
)
@ -161,9 +156,9 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
methodData: {
...mockState.confirmTransaction.methodData,
...mockState.methodData,
name: 'transferFrom',
},
}
@ -176,7 +171,7 @@ describe('Confirm Transaction Duck', () => {
type: CLEAR_METHOD_DATA,
}),
{
...mockState.confirmTransaction,
...mockState,
methodData: {},
}
)
@ -193,7 +188,7 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
fiatTransactionAmount: '123.45',
ethTransactionAmount: '.5',
hexTransactionAmount: '0x1',
@ -212,7 +207,7 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
fiatTransactionFee: '123.45',
ethTransactionFee: '.5',
hexTransactionFee: '0x1',
@ -231,7 +226,7 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
fiatTransactionTotal: '123.45',
ethTransactionTotal: '.5',
hexTransactionTotal: '0x1',
@ -249,7 +244,7 @@ describe('Confirm Transaction Duck', () => {
},
}),
{
...mockState.confirmTransaction,
...mockState,
tokenProps: {
tokenSymbol: 'DEF',
tokenDecimals: '1',
@ -265,7 +260,7 @@ describe('Confirm Transaction Duck', () => {
payload: '0x1',
}),
{
...mockState.confirmTransaction,
...mockState,
nonce: '0x1',
}
)
@ -278,7 +273,7 @@ describe('Confirm Transaction Duck', () => {
payload: true,
}),
{
...mockState.confirmTransaction,
...mockState,
toSmartContract: true,
}
)
@ -290,7 +285,7 @@ describe('Confirm Transaction Duck', () => {
type: FETCH_DATA_START,
}),
{
...mockState.confirmTransaction,
...mockState,
fetchingData: true,
}
)
@ -298,24 +293,13 @@ describe('Confirm Transaction Duck', () => {
it('should set fetchingData to false when receiving a FETCH_DATA_END action', () => {
assert.deepEqual(
ConfirmTransactionReducer({ confirmTransaction: { fetchingData: true } }, {
type: FETCH_DATA_END,
}),
{
fetchingData: false,
}
ConfirmTransactionReducer({ fetchingData: true }, { type: FETCH_DATA_END }),
{ fetchingData: false },
)
})
it('should clear confirmTransaction when receiving a FETCH_DATA_END action', () => {
assert.deepEqual(
ConfirmTransactionReducer(mockState, {
type: CLEAR_CONFIRM_TRANSACTION,
}),
{
...initialState,
}
)
assert.deepEqual(ConfirmTransactionReducer(mockState, { type: CLEAR_CONFIRM_TRANSACTION }), initialState)
})
})

@ -93,9 +93,7 @@ describe('Gas Duck', () => {
})
const mockState = {
gas: {
mockProp: 123,
},
}
const initState = {
customData: {
@ -140,10 +138,7 @@ describe('Gas Duck', () => {
describe('GasReducer()', () => {
it('should initialize state', () => {
assert.deepEqual(
GasReducer({}),
initState
)
assert.deepEqual(GasReducer(undefined, {}), initState)
})
it('should return state unchanged if it does not match a dispatched actions type', () => {
@ -152,58 +147,45 @@ describe('Gas Duck', () => {
type: 'someOtherAction',
value: 'someValue',
}),
Object.assign({}, mockState.gas)
mockState,
)
})
it('should set basicEstimateIsLoading to true when receiving a BASIC_GAS_ESTIMATE_LOADING_STARTED action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: BASIC_GAS_ESTIMATE_LOADING_STARTED,
}),
Object.assign({ basicEstimateIsLoading: true }, mockState.gas)
GasReducer(mockState, { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }),
{ basicEstimateIsLoading: true, ...mockState },
)
})
it('should set basicEstimateIsLoading to false when receiving a BASIC_GAS_ESTIMATE_LOADING_FINISHED action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: BASIC_GAS_ESTIMATE_LOADING_FINISHED,
}),
Object.assign({ basicEstimateIsLoading: false }, mockState.gas)
GasReducer(mockState, { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }),
{ basicEstimateIsLoading: false, ...mockState },
)
})
it('should set gasEstimatesLoading to true when receiving a GAS_ESTIMATE_LOADING_STARTED action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: GAS_ESTIMATE_LOADING_STARTED,
}),
Object.assign({ gasEstimatesLoading: true }, mockState.gas)
GasReducer(mockState, { type: GAS_ESTIMATE_LOADING_STARTED }),
{ gasEstimatesLoading: true, ...mockState }
)
})
it('should set gasEstimatesLoading to false when receiving a GAS_ESTIMATE_LOADING_FINISHED action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: GAS_ESTIMATE_LOADING_FINISHED,
}),
Object.assign({ gasEstimatesLoading: false }, mockState.gas)
GasReducer(mockState, { type: GAS_ESTIMATE_LOADING_FINISHED }),
{ gasEstimatesLoading: false, ...mockState },
)
})
it('should return a new object (and not just modify the existing state object)', () => {
assert.deepEqual(GasReducer(mockState), mockState.gas)
assert.notEqual(GasReducer(mockState), mockState.gas)
})
it('should set basicEstimates when receiving a SET_BASIC_GAS_ESTIMATE_DATA action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: SET_BASIC_GAS_ESTIMATE_DATA,
value: { someProp: 'someData123' },
}),
Object.assign({ basicEstimates: { someProp: 'someData123' } }, mockState.gas)
{ basicEstimates: { someProp: 'someData123' }, ...mockState },
)
})
@ -213,7 +195,7 @@ describe('Gas Duck', () => {
type: SET_PRICE_AND_TIME_ESTIMATES,
value: { someProp: 'someData123' },
}),
Object.assign({ priceAndTimeEstimates: { someProp: 'someData123' } }, mockState.gas)
{ priceAndTimeEstimates: { someProp: 'someData123' }, ...mockState },
)
})
@ -223,7 +205,7 @@ describe('Gas Duck', () => {
type: SET_CUSTOM_GAS_PRICE,
value: 4321,
}),
Object.assign({ customData: { price: 4321 } }, mockState.gas)
{ customData: { price: 4321 }, ...mockState },
)
})
@ -233,7 +215,7 @@ describe('Gas Duck', () => {
type: SET_CUSTOM_GAS_LIMIT,
value: 9876,
}),
Object.assign({ customData: { limit: 9876 } }, mockState.gas)
{ customData: { limit: 9876 }, ...mockState },
)
})
@ -243,7 +225,7 @@ describe('Gas Duck', () => {
type: SET_CUSTOM_GAS_TOTAL,
value: 10000,
}),
Object.assign({ customData: { total: 10000 } }, mockState.gas)
{ customData: { total: 10000 }, ...mockState },
)
})
@ -253,7 +235,7 @@ describe('Gas Duck', () => {
type: SET_API_ESTIMATES_LAST_RETRIEVED,
value: 1500000000000,
}),
Object.assign({ priceAndTimeEstimatesLastRetrieved: 1500000000000 }, mockState.gas)
{ priceAndTimeEstimatesLastRetrieved: 1500000000000, ...mockState },
)
})
@ -263,7 +245,7 @@ describe('Gas Duck', () => {
type: SET_BASIC_API_ESTIMATES_LAST_RETRIEVED,
value: 1700000000000,
}),
Object.assign({ basicPriceAndTimeEstimatesLastRetrieved: 1700000000000 }, mockState.gas)
{ basicPriceAndTimeEstimatesLastRetrieved: 1700000000000, ...mockState },
)
})
@ -273,35 +255,27 @@ describe('Gas Duck', () => {
type: SET_CUSTOM_GAS_ERRORS,
value: { someError: 'error_error' },
}),
Object.assign({ errors: { someError: 'error_error' } }, mockState.gas)
{ errors: { someError: 'error_error' }, ...mockState },
)
})
it('should return the initial state in response to a RESET_CUSTOM_GAS_STATE action', () => {
assert.deepEqual(
GasReducer(mockState, {
type: RESET_CUSTOM_GAS_STATE,
}),
Object.assign({}, initState)
GasReducer(mockState, { type: RESET_CUSTOM_GAS_STATE }),
initState,
)
})
})
describe('basicGasEstimatesLoadingStarted', () => {
it('should create the correct action', () => {
assert.deepEqual(
basicGasEstimatesLoadingStarted(),
{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED }
)
assert.deepEqual(basicGasEstimatesLoadingStarted(), { type: BASIC_GAS_ESTIMATE_LOADING_STARTED })
})
})
describe('basicGasEstimatesLoadingFinished', () => {
it('should create the correct action', () => {
assert.deepEqual(
basicGasEstimatesLoadingFinished(),
{ type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }
)
assert.deepEqual(basicGasEstimatesLoadingFinished(), { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED })
})
})
@ -309,11 +283,9 @@ describe('Gas Duck', () => {
it('should call fetch with the expected params', async () => {
const mockDistpatch = sinon.spy()
await fetchBasicGasEstimates()(mockDistpatch, () => ({ gas: Object.assign(
{},
initState,
{ basicPriceAEstimatesLastRetrieved: 1000000 }
) }))
await fetchBasicGasEstimates()(mockDistpatch, () => ({
gas: { ...initState, basicPriceAEstimatesLastRetrieved: 1000000 },
}))
assert.deepEqual(
mockDistpatch.getCall(0).args,
[{ type: BASIC_GAS_ESTIMATE_LOADING_STARTED } ]

@ -28,7 +28,6 @@ const SET_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_API_ESTIMATES_LAST_RE
const SET_BASIC_API_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_API_ESTIMATES_LAST_RETRIEVED'
const SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED = 'metamask/gas/SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
customData: {
price: null,
@ -57,96 +56,94 @@ const initState = {
}
// Reducer
export default function reducer ({ gas: gasState = initState }, action = {}) {
const newState = clone(gasState)
export default function reducer (state = initState, action) {
switch (action.type) {
case BASIC_GAS_ESTIMATE_LOADING_STARTED:
return {
...newState,
...state,
basicEstimateIsLoading: true,
}
case BASIC_GAS_ESTIMATE_LOADING_FINISHED:
return {
...newState,
...state,
basicEstimateIsLoading: false,
}
case GAS_ESTIMATE_LOADING_STARTED:
return {
...newState,
...state,
gasEstimatesLoading: true,
}
case GAS_ESTIMATE_LOADING_FINISHED:
return {
...newState,
...state,
gasEstimatesLoading: false,
}
case SET_BASIC_GAS_ESTIMATE_DATA:
return {
...newState,
...state,
basicEstimates: action.value,
}
case SET_CUSTOM_GAS_PRICE:
return {
...newState,
...state,
customData: {
...newState.customData,
...state.customData,
price: action.value,
},
}
case SET_CUSTOM_GAS_LIMIT:
return {
...newState,
...state,
customData: {
...newState.customData,
...state.customData,
limit: action.value,
},
}
case SET_CUSTOM_GAS_TOTAL:
return {
...newState,
...state,
customData: {
...newState.customData,
...state.customData,
total: action.value,
},
}
case SET_PRICE_AND_TIME_ESTIMATES:
return {
...newState,
...state,
priceAndTimeEstimates: action.value,
}
case SET_CUSTOM_GAS_ERRORS:
return {
...newState,
...state,
errors: {
...newState.errors,
...state.errors,
...action.value,
},
}
case SET_API_ESTIMATES_LAST_RETRIEVED:
return {
...newState,
...state,
priceAndTimeEstimatesLastRetrieved: action.value,
}
case SET_BASIC_API_ESTIMATES_LAST_RETRIEVED:
return {
...newState,
...state,
basicPriceAndTimeEstimatesLastRetrieved: action.value,
}
case SET_BASIC_PRICE_ESTIMATES_LAST_RETRIEVED:
return {
...newState,
...state,
basicPriceEstimatesLastRetrieved: action.value,
}
case RESET_CUSTOM_DATA:
return {
...newState,
...state,
customData: clone(initState.customData),
}
case RESET_CUSTOM_GAS_STATE:
return clone(initState)
default:
return newState
return state
}
}

@ -1,90 +1,17 @@
import { cloneDeep } from 'lodash'
import copyToClipboard from 'copy-to-clipboard'
//
// Sub-Reducers take in the complete state and return their sub-state
//
import reduceMetamask from './metamask/metamask'
import reduceLocale from './locale/locale'
import reduceSend from './send/send.duck'
import reduceApp from './app/app'
import reduceConfirmTransaction from './confirm-transaction/confirm-transaction.duck'
import reduceGas from './gas/gas.duck'
window.METAMASK_CACHED_LOG_STATE = null
export default rootReducer
function rootReducer (state, action) {
// clone
state = { ...state }
if (action.type === 'GLOBAL_FORCE_UPDATE') {
return action.value
}
//
// MetaMask
//
state.metamask = reduceMetamask(state, action)
//
// AppState
//
state.appState = reduceApp(state, action)
//
// LocaleMessages
//
state.localeMessages = reduceLocale(state, action)
//
// Send
//
state.send = reduceSend(state, action)
state.confirmTransaction = reduceConfirmTransaction(state, action)
state.gas = reduceGas(state, action)
window.METAMASK_CACHED_LOG_STATE = state
return state
}
window.getCleanAppState = function () {
const state = cloneDeep(window.METAMASK_CACHED_LOG_STATE)
// append additional information
state.version = global.platform.getVersion()
state.browser = window.navigator.userAgent
return state
}
window.logStateString = function (cb) {
const state = window.getCleanAppState()
global.platform.getPlatformInfo((err, platform) => {
if (err) {
return cb(err)
}
state.platform = platform
const stateString = JSON.stringify(state, null, 2)
cb(null, stateString)
})
}
window.logState = function (toClipboard) {
return window.logStateString((err, result) => {
if (err) {
console.error(err.message)
} else if (toClipboard) {
copyToClipboard(result)
console.log('State log copied')
} else {
console.log(result)
}
import { combineReducers } from 'redux'
import metamaskReducer from './metamask/metamask'
import localeMessagesReducer from './locale/locale'
import sendReducer from './send/send.duck'
import appStateReducer from './app/app'
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
import gasReducer from './gas/gas.duck'
export default combineReducers({
activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer,
appState: appStateReducer,
send: sendReducer,
confirmTransaction: confirmTransactionReducer,
gas: gasReducer,
localeMessages: localeMessagesReducer,
})
}

@ -1,17 +1,13 @@
import { actionConstants } from '../../store/actions'
export default reduceMetamask
function reduceMetamask (state, action) {
const localeMessagesState = { ...state.localeMessages }
switch (action.type) {
export default function reduceLocaleMessages (state = {}, { type, value }) {
switch (type) {
case actionConstants.SET_CURRENT_LOCALE:
return {
...localeMessagesState,
current: action.value.messages,
...state,
current: value.messages,
}
default:
return localeMessagesState
return state
}
}

@ -1,15 +1,8 @@
import { actionConstants } from '../../store/actions'
import { actionConstants as actions } from '../../store/actions'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
const actions = actionConstants
export default reduceMetamask
function reduceMetamask (state, action) {
let newState
// clone + defaults
export default function reduceMetamask (state = {}, action) {
const metamaskState = Object.assign({
isInitialized: false,
isUnlocked: false,
@ -59,7 +52,7 @@ function reduceMetamask (state, action) {
participateInMetaMetrics: null,
metaMetricsSendCount: 0,
nextNonce: null,
}, state.metamask)
}, state)
switch (action.type) {
@ -97,25 +90,6 @@ function reduceMetamask (state, action) {
},
}
case actions.COMPLETED_TX:
const stringId = String(action.id)
newState = {
...metamaskState,
unapprovedTxs: {},
unapprovedMsgs: {},
}
for (const id in metamaskState.unapprovedTxs) {
if (id !== stringId) {
newState.unapprovedTxs[id] = metamaskState.unapprovedTxs[id]
}
}
for (const id in metamaskState.unapprovedMsgs) {
if (id !== stringId) {
newState.unapprovedMsgs[id] = metamaskState.unapprovedMsgs[id]
}
}
return newState
case actions.SHOW_ACCOUNT_DETAIL:
return {
...metamaskState,
@ -124,8 +98,8 @@ function reduceMetamask (state, action) {
selectedAddress: action.value,
}
case actions.SET_SELECTED_TOKEN:
newState = {
case actions.SET_SELECTED_TOKEN: {
const newState = {
...metamaskState,
selectedTokenAddress: action.value,
}
@ -148,6 +122,7 @@ function reduceMetamask (state, action) {
newState.send = newSend
return newState
}
case actions.SET_ACCOUNT_LABEL:
const account = action.value.account
@ -411,6 +386,5 @@ function reduceMetamask (state, action) {
default:
return metamaskState
}
}

@ -10,9 +10,7 @@ import SendReducer, {
describe('Send Duck', () => {
const mockState = {
send: {
mockProp: 123,
},
}
const initState = {
toDropdownOpen: false,
@ -28,10 +26,7 @@ describe('Send Duck', () => {
describe('SendReducer()', () => {
it('should initialize state', () => {
assert.deepEqual(
SendReducer({}),
initState
)
assert.deepEqual(SendReducer(undefined, {}), initState)
})
it('should return state unchanged if it does not match a dispatched actions type', () => {
@ -40,22 +35,16 @@ describe('Send Duck', () => {
type: 'someOtherAction',
value: 'someValue',
}),
Object.assign({}, mockState.send)
mockState,
)
})
it('should return a new object (and not just modify the existing state object)', () => {
assert.deepEqual(SendReducer(mockState), mockState.send)
assert.notEqual(SendReducer(mockState), mockState.send)
})
it('should set toDropdownOpen to true when receiving a OPEN_TO_DROPDOWN action', () => {
assert.deepEqual(
SendReducer(mockState, {
type: OPEN_TO_DROPDOWN,
}),
Object.assign({ toDropdownOpen: true }, mockState.send)
{ toDropdownOpen: true, ...mockState },
)
})
@ -64,47 +53,43 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: CLOSE_TO_DROPDOWN,
}),
Object.assign({ toDropdownOpen: false }, mockState.send)
{ toDropdownOpen: false, ...mockState },
)
})
it('should set gasButtonGroupShown to true when receiving a SHOW_GAS_BUTTON_GROUP action', () => {
assert.deepEqual(
SendReducer(Object.assign({}, mockState, { gasButtonGroupShown: false }), {
type: SHOW_GAS_BUTTON_GROUP,
}),
Object.assign({ gasButtonGroupShown: true }, mockState.send)
SendReducer({ ...mockState, gasButtonGroupShown: false }, { type: SHOW_GAS_BUTTON_GROUP }),
{ gasButtonGroupShown: true, ...mockState },
)
})
it('should set gasButtonGroupShown to false when receiving a HIDE_GAS_BUTTON_GROUP action', () => {
assert.deepEqual(
SendReducer(mockState, {
type: HIDE_GAS_BUTTON_GROUP,
}),
Object.assign({ gasButtonGroupShown: false }, mockState.send)
SendReducer(mockState, { type: HIDE_GAS_BUTTON_GROUP }),
{ gasButtonGroupShown: false, ...mockState },
)
})
it('should extend send.errors with the value of a UPDATE_SEND_ERRORS action', () => {
const modifiedMockState = Object.assign({}, mockState, {
send: {
const modifiedMockState = {
...mockState,
errors: {
someError: false,
},
},
})
}
assert.deepEqual(
SendReducer(modifiedMockState, {
type: UPDATE_SEND_ERRORS,
value: { someOtherError: true },
}),
Object.assign({}, modifiedMockState.send, {
{
...modifiedMockState,
errors: {
someError: false,
someOtherError: true,
},
})
},
)
})
@ -113,44 +98,28 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: RESET_SEND_STATE,
}),
Object.assign({}, initState)
initState,
)
})
})
describe('openToDropdown', () => {
assert.deepEqual(
openToDropdown(),
{ type: OPEN_TO_DROPDOWN }
)
assert.deepEqual(openToDropdown(), { type: OPEN_TO_DROPDOWN })
})
describe('closeToDropdown', () => {
assert.deepEqual(
closeToDropdown(),
{ type: CLOSE_TO_DROPDOWN }
)
assert.deepEqual(closeToDropdown(), { type: CLOSE_TO_DROPDOWN })
})
describe('showGasButtonGroup', () => {
assert.deepEqual(
showGasButtonGroup(),
{ type: SHOW_GAS_BUTTON_GROUP }
)
assert.deepEqual(showGasButtonGroup(), { type: SHOW_GAS_BUTTON_GROUP })
})
describe('hideGasButtonGroup', () => {
assert.deepEqual(
hideGasButtonGroup(),
{ type: HIDE_GAS_BUTTON_GROUP }
)
assert.deepEqual(hideGasButtonGroup(), { type: HIDE_GAS_BUTTON_GROUP })
})
describe('updateSendErrors', () => {
assert.deepEqual(
updateSendErrors('mockErrorObject'),
{ type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' }
)
assert.deepEqual(updateSendErrors('mockErrorObject'), { type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' })
})
})

@ -6,7 +6,6 @@ const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP'
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
toDropdownOpen: false,
gasButtonGroupShown: true,
@ -14,42 +13,40 @@ const initState = {
}
// Reducer
export default function reducer ({ send: sendState = initState }, action = {}) {
const newState = { ...sendState }
export default function reducer (state = initState, action) {
switch (action.type) {
case OPEN_TO_DROPDOWN:
return {
...newState,
...state,
toDropdownOpen: true,
}
case CLOSE_TO_DROPDOWN:
return {
...newState,
...state,
toDropdownOpen: false,
}
case UPDATE_SEND_ERRORS:
return {
...newState,
...state,
errors: {
...newState.errors,
...state.errors,
...action.value,
},
}
case SHOW_GAS_BUTTON_GROUP:
return {
...newState,
...state,
gasButtonGroupShown: true,
}
case HIDE_GAS_BUTTON_GROUP:
return {
...newState,
...state,
gasButtonGroupShown: false,
}
case RESET_SEND_STATE:
return { ...initState }
default:
return newState
return state
}
}

@ -13,12 +13,15 @@ import Loading from '../../components/ui/loading-screen'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
function mapStateToProps (state) {
const { metamask } = state
const { metamask, appState } = state
const {
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
const {
txId,
} = appState
return {
identities: state.metamask.identities,
@ -26,7 +29,7 @@ function mapStateToProps (state) {
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
index: state.appState.currentView.context,
index: txId,
warning: state.appState.warning,
network: state.metamask.network,
provider: state.metamask.provider,

@ -13,6 +13,7 @@ import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../app/scripts/lib/enums'
import { hasUnconfirmedTransactions } from '../helpers/utils/confirm-tx.util'
import { setCustomGasLimit } from '../ducks/gas/gas.duck'
import WebcamUtils from '../../lib/webcam-utils'
import txHelper from '../../lib/tx-helper'
export const actionConstants = {
GO_HOME: 'GO_HOME',
@ -678,7 +679,7 @@ export function signTx (txData) {
return dispatch(displayWarning(err.message))
}
})
dispatch(showConfTxPage({}))
dispatch(showConfTxPage())
}
}
@ -858,7 +859,7 @@ export function signTokenTx (tokenAddress, toAddress, amount, txData) {
dispatch(hideLoadingIndication())
dispatch(displayWarning(err.message))
})
dispatch(showConfTxPage({}))
dispatch(showConfTxPage())
}
}
@ -877,9 +878,7 @@ const updateMetamaskStateFromBackground = () => {
}
export function updateTransaction (txData) {
log.info('actions: updateTx: ' + JSON.stringify(txData))
return dispatch => {
log.debug(`actions calling background.updateTx`)
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
@ -906,9 +905,7 @@ export function updateTransaction (txData) {
}
export function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
@ -944,9 +941,24 @@ export function updateAndApproveTx (txData) {
}
export function completedTx (id) {
return {
return (dispatch, getState) => {
const state = getState()
const {
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
network,
} = state.metamask
const unconfirmedActions = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
const otherUnconfirmedActions = unconfirmedActions.filter(tx => tx.id !== id)
dispatch({
type: actionConstants.COMPLETED_TX,
value: id,
value: {
id,
unconfirmedActionsCount: otherUnconfirmedActions.length,
},
})
}
}
@ -969,7 +981,6 @@ export function cancelMsg (msgData) {
return (dispatch) => {
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`background.cancelMessage`)
background.cancelMessage(msgData.id, (err, newState) => {
dispatch(updateMetamaskState(newState))
dispatch(hideLoadingIndication())
@ -1033,7 +1044,6 @@ export function cancelTypedMsg (msgData) {
export function cancelTx (txData) {
return (dispatch) => {
log.debug(`background.cancelTransaction`)
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, err => {
@ -1252,7 +1262,7 @@ export function showAccountsPage () {
}
}
export function showConfTxPage ({ transForward = true, id }) {
export function showConfTxPage ({ transForward = true, id } = {}) {
return {
type: actionConstants.SHOW_CONF_TX_PAGE,
transForward,

@ -1,3 +1,6 @@
import copyToClipboard from 'copy-to-clipboard'
import log from 'loglevel'
import { clone } from 'lodash'
import React from 'react'
import { render } from 'react-dom'
import Root from './app/pages'
@ -6,13 +9,10 @@ import configureStore from './app/store/store'
import txHelper from './lib/tx-helper'
import { fetchLocale } from './app/helpers/utils/i18n-helper'
import switchDirection from './app/helpers/utils/switch-direction'
import log from 'loglevel'
export default launchMetamaskUi
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
function launchMetamaskUi (opts, cb) {
export default function launchMetamaskUi (opts, cb) {
const { backgroundConnection } = opts
actions._setBackgroundConnection(backgroundConnection)
// check if we are unlocked first
@ -22,6 +22,7 @@ function launchMetamaskUi (opts, cb) {
}
startApp(metamaskState, backgroundConnection, opts)
.then((store) => {
setupDebuggingHelpers(store)
cb(null, store)
})
})
@ -55,9 +56,6 @@ async function startApp (metamaskState, backgroundConnection, opts) {
current: currentLocaleMessages,
en: enLocaleMessages,
},
// Which blockchain we are using:
networkVersion: opts.networkVersion,
})
// if unconfirmed txs, start on txConf page
@ -104,3 +102,37 @@ async function startApp (metamaskState, backgroundConnection, opts) {
return store
}
function setupDebuggingHelpers (store) {
window.getCleanAppState = function () {
const state = clone(store.getState())
state.version = global.platform.getVersion()
state.browser = window.navigator.userAgent
return state
}
}
window.logStateString = function (cb) {
const state = window.getCleanAppState()
global.platform.getPlatformInfo((err, platform) => {
if (err) {
return cb(err)
}
state.platform = platform
const stateString = JSON.stringify(state, null, 2)
cb(null, stateString)
})
}
window.logState = function (toClipboard) {
return window.logStateString((err, result) => {
if (err) {
console.error(err.message)
} else if (toClipboard) {
copyToClipboard(result)
console.log('State log copied')
} else {
console.log(result)
}
})
}

Loading…
Cancel
Save