1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-23 11:46:13 +02:00
metamask-extension/test/unit/ui/app/actions.spec.js
Mark Stacey 53feb20803
Alert user upon switching to unconnected account (#8312)
An alert is now shown when the user switches from an account that is
connected to the active tab to an account that is not connected. The
alert prompts the user to dismiss the alert or connect the account
they're switching to.

The "loading" state is handled by disabling the buttons, and the error
state is handled by displaying a generic error message and disabling
the connect button.

The new reducer for this alert has been created with `createSlice` from
the Redux Toolkit. This utility is recommended by the Redux team, and
represents a new style of writing reducers that I hope we will use more
in the future (or at least something similar). `createSlice` constructs
a reducer, actions, and action creators automatically. The reducer is
constructed using their `createReducer` helper, which uses Immer to
allow directly mutating the state in the reducer but exposing these
changes as immutable.
2020-04-29 14:10:51 -03:00

1385 lines
42 KiB
JavaScript

import assert from 'assert'
import sinon from 'sinon'
import { cloneDeep } from 'lodash'
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import EthQuery from 'eth-query'
import Eth from 'ethjs'
import KeyringController from 'eth-keyring-controller'
import { createTestProviderTools } from '../../../stub/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'
const provider = createTestProviderTools({ scaffold: {} }).provider
const middleware = [thunk]
const defaultState = { metamask: {} }
const mockStore = (state = defaultState) => configureStore(middleware)(state)
describe('Actions', function () {
const noop = () => {}
const currentNetworkId = 42
let background, metamaskController
const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
const password = 'a-fake-password'
const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'
beforeEach(async function () {
metamaskController = new MetaMaskController({
provider,
keyringController: new KeyringController({}),
showUnapprovedTx: noop,
showUnconfirmedMessage: noop,
encryptor: {
encrypt: function (_, object) {
this.object = object
return Promise.resolve('mock-encrypted')
},
decrypt: function () {
return Promise.resolve(this.object)
},
},
initState: cloneDeep(firstTimeState),
})
metamaskController.threeBoxController = {
new3Box: sinon.spy(),
getThreeBoxSyncingState: sinon.spy(),
}
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
await metamaskController.importAccountWithStrategy('Private Key', [ importPrivkey ])
background = metamaskController.getApi()
actions._setBackgroundConnection(background)
global.ethQuery = new EthQuery(provider)
})
describe('#tryUnlockMetamask', function () {
let submitPasswordSpy, verifySeedPhraseSpy
afterEach(function () {
submitPasswordSpy.restore()
verifySeedPhraseSpy.restore()
})
it('calls submitPassword and verifySeedPhrase', async function () {
const store = mockStore()
submitPasswordSpy = sinon.spy(background, 'submitPassword')
verifySeedPhraseSpy = sinon.spy(background, 'verifySeedPhrase')
await store.dispatch(actions.tryUnlockMetamask())
assert(submitPasswordSpy.calledOnce)
assert(verifySeedPhraseSpy.calledOnce)
})
it('errors on submitPassword will fail', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'UNLOCK_IN_PROGRESS' },
{ type: 'UNLOCK_FAILED', value: 'error in submitPassword' },
{ type: 'HIDE_LOADING_INDICATION' },
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error in submitPassword'))
})
try {
await store.dispatch(actions.tryUnlockMetamask('test'))
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
it('displays warning error and unlock failed when verifySeed fails', async function () {
const store = mockStore()
const displayWarningError = [ { type: 'DISPLAY_WARNING', value: 'error' } ]
const unlockFailedError = [ { type: 'UNLOCK_FAILED', value: 'error' } ]
verifySeedPhraseSpy = sinon.stub(background, 'verifySeedPhrase')
verifySeedPhraseSpy.callsFake((callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.tryUnlockMetamask('test'))
assert.fail('Should have thrown error')
} catch (_) {
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)
}
})
})
describe('#createNewVaultAndRestore', function () {
let createNewVaultAndRestoreSpy
afterEach(function () {
createNewVaultAndRestoreSpy.restore()
})
it('restores new vault', async function () {
const store = mockStore()
createNewVaultAndRestoreSpy = sinon.spy(background, 'createNewVaultAndRestore')
try {
await store.dispatch(actions.createNewVaultAndRestore())
assert.fail('Should have thrown error')
} catch (_) {
assert(createNewVaultAndRestoreSpy.calledOnce)
}
})
it('errors when callback in createNewVaultAndRestore throws', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'HIDE_LOADING_INDICATION' },
]
createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore')
createNewVaultAndRestoreSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.createNewVaultAndRestore())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#requestRevealSeedWords', function () {
let submitPasswordSpy
afterEach(function () {
submitPasswordSpy.restore()
})
it('calls submitPassword in background', async function () {
const store = mockStore()
submitPasswordSpy = sinon.spy(background, 'verifySeedPhrase')
await store.dispatch(actions.requestRevealSeedWords())
assert(submitPasswordSpy.calledOnce)
})
it('displays warning error message then callback in background errors', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
submitPasswordSpy = sinon.stub(background, 'verifySeedPhrase')
submitPasswordSpy.callsFake((callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.requestRevealSeedWords())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#removeAccount', function () {
let removeAccountSpy
afterEach(function () {
removeAccountSpy.restore()
})
it('calls removeAccount in background and expect actions to show account', async function () {
const store = mockStore()
const expectedActions = [
'SHOW_LOADING_INDICATION',
'SELECTED_ADDRESS_CHANGED',
'UPDATE_METAMASK_STATE',
'HIDE_LOADING_INDICATION',
'SHOW_ACCOUNTS_PAGE',
]
removeAccountSpy = sinon.spy(background, 'removeAccount')
await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'))
assert(removeAccountSpy.calledOnce)
const actionTypes = store
.getActions()
.map((action) => action.type)
assert.deepEqual(actionTypes, expectedActions)
})
it('displays warning error message when removeAccount callback errors', async function () {
const store = mockStore()
const expectedActions = [
'SHOW_LOADING_INDICATION',
'DISPLAY_WARNING',
'HIDE_LOADING_INDICATION',
]
removeAccountSpy = sinon.stub(background, 'removeAccount')
removeAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc'))
assert.fail('Should have thrown error')
} catch (_) {
const actionTypes = store
.getActions()
.map((action) => action.type)
assert.deepEqual(actionTypes, expectedActions)
}
})
})
describe('#resetAccount', function () {
let resetAccountSpy
afterEach(function () {
resetAccountSpy.restore()
})
it('resets account', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'SHOW_ACCOUNTS_PAGE' },
]
resetAccountSpy = sinon.spy(background, 'resetAccount')
await store.dispatch(actions.resetAccount())
assert(resetAccountSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
})
it('throws if resetAccount throws', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
resetAccountSpy = sinon.stub(background, 'resetAccount')
resetAccountSpy.callsFake((callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.resetAccount())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#importNewAccount', function () {
let importAccountWithStrategySpy
afterEach(function () {
importAccountWithStrategySpy.restore()
})
it('calls importAccountWithStrategies in background', async function () {
const store = mockStore()
importAccountWithStrategySpy = sinon.spy(background, 'importAccountWithStrategy')
const importPrivkey = 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3'
await store.dispatch(actions.importNewAccount('Private Key', [ importPrivkey ]))
assert(importAccountWithStrategySpy.calledOnce)
})
it('displays warning error message when importAccount in background callback errors', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: 'This may take a while, please be patient.' },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy')
importAccountWithStrategySpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.importNewAccount())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#addNewAccount', function () {
it('Adds a new account', async function () {
const store = mockStore({ metamask: { identities: {} } })
const addNewAccountSpy = sinon.spy(background, 'addNewAccount')
await store.dispatch(actions.addNewAccount())
assert(addNewAccountSpy.calledOnce)
})
})
describe('#checkHardwareStatus', function () {
let checkHardwareStatusSpy
afterEach(function () {
checkHardwareStatusSpy.restore()
})
it('calls checkHardwareStatus in background', async function () {
checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus')
.callsArgWith(2, null)
const store = mockStore()
await store.dispatch(actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`))
assert.equal(checkHardwareStatusSpy.calledOnce, true)
})
it('shows loading indicator and displays error', async function () {
checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus')
.callsArgWith(2, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.checkHardwareStatus())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#forgetDevice', function () {
let forgetDeviceSpy
afterEach(function () {
forgetDeviceSpy.restore()
})
it('calls forgetDevice in background', async function () {
forgetDeviceSpy = sinon.stub(background, 'forgetDevice')
.callsArgWith(1, null)
const store = mockStore()
await store.dispatch(actions.forgetDevice('ledger'))
assert.equal(forgetDeviceSpy.calledOnce, true)
})
it('shows loading indicator and displays error', async function () {
forgetDeviceSpy = sinon.stub(background, 'forgetDevice')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.forgetDevice())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#connectHardware', function () {
let connectHardwareSpy
afterEach(function () {
connectHardwareSpy.restore()
})
it('calls connectHardware in background', async function () {
connectHardwareSpy = sinon.stub(background, 'connectHardware')
.callsArgWith(3, null)
const store = mockStore()
await store.dispatch(actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`))
assert.equal(connectHardwareSpy.calledOnce, true)
})
it('shows loading indicator and displays error', async function () {
connectHardwareSpy = sinon.stub(background, 'connectHardware')
.callsArgWith(3, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.connectHardware())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#unlockHardwareWalletAccount', function () {
let unlockHardwareWalletAccountSpy
afterEach(function () {
unlockHardwareWalletAccountSpy.restore()
})
it('calls unlockHardwareWalletAccount in background', async function () {
unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount')
.callsArgWith(3, null)
const store = mockStore()
await store.dispatch(actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`))
assert.equal(unlockHardwareWalletAccountSpy.calledOnce, true)
})
it('shows loading indicator and displays error', async function () {
unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount')
.callsArgWith(3, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.unlockHardwareWalletAccount())
assert.fail('Should have thrown error')
} catch (error) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#setCurrentCurrency', function () {
let setCurrentCurrencySpy
afterEach(function () {
setCurrentCurrencySpy.restore()
})
it('calls setCurrentCurrency', async function () {
setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency')
.callsArgWith(1, null, {})
const store = mockStore()
await store.dispatch(actions.setCurrentCurrency('jpy'))
assert(setCurrentCurrencySpy.calledOnce)
})
it('throws if setCurrentCurrency throws', async function () {
setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
await store.dispatch(actions.setCurrentCurrency())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#signMsg', function () {
let signMessageSpy, metamaskMsgs, msgId, messages
const msgParams = {
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0',
}
beforeEach(function () {
metamaskController.newUnsignedMessage(msgParams, noop)
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
messages = metamaskController.messageManager.messages
msgId = Object.keys(metamaskMsgs)[0]
messages[0].msgParams.metamaskId = parseInt(msgId)
})
afterEach(function () {
signMessageSpy.restore()
})
it('calls signMsg in background', async function () {
const store = mockStore()
signMessageSpy = sinon.spy(background, 'signMessage')
await store.dispatch(actions.signMsg(msgParams))
assert(signMessageSpy.calledOnce)
})
it('errors when signMessage in background throws', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
signMessageSpy = sinon.stub(background, 'signMessage')
signMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.signMsg())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#signPersonalMsg', function () {
let signPersonalMessageSpy, metamaskMsgs, msgId, personalMessages
const msgParams = {
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0',
}
beforeEach(function () {
metamaskController.newUnsignedPersonalMessage(msgParams, noop)
metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
personalMessages = metamaskController.personalMessageManager.messages
msgId = Object.keys(metamaskMsgs)[0]
personalMessages[0].msgParams.metamaskId = parseInt(msgId)
})
afterEach(function () {
signPersonalMessageSpy.restore()
})
it('calls signPersonalMessage', async function () {
const store = mockStore()
signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage')
await store.dispatch(actions.signPersonalMsg(msgParams))
assert(signPersonalMessageSpy.calledOnce)
})
it('throws if signPersonalMessage throws', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage')
signPersonalMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.signPersonalMsg(msgParams))
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#signTypedMsg', function () {
let signTypedMsgSpy, messages, typedMessages, msgId
const msgParamsV3 = {
from: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
data: JSON.stringify({
'types': {
'EIP712Domain': [
{ 'name': 'name', 'type': 'string' },
{ 'name': 'version', 'type': 'string' },
{ 'name': 'chainId', 'type': 'uint256' },
{ 'name': 'verifyingContract', 'type': 'address' },
],
'Person': [
{ 'name': 'name', 'type': 'string' },
{ 'name': 'wallet', 'type': 'address' },
],
'Mail': [
{ 'name': 'from', 'type': 'Person' },
{ 'name': 'to', 'type': 'Person' },
{ 'name': 'contents', 'type': 'string' },
],
},
'primaryType': 'Mail',
'domain': {
'name': 'Ether Mainl',
'version': '1',
'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
'message': {
'from': {
'name': 'Cow',
'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
'to': {
'name': 'Bob',
'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
'contents': 'Hello, Bob!',
},
}),
}
beforeEach(function () {
metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3')
messages = metamaskController.typedMessageManager.getUnapprovedMsgs()
typedMessages = metamaskController.typedMessageManager.messages
msgId = Object.keys(messages)[0]
typedMessages[0].msgParams.metamaskId = parseInt(msgId)
})
afterEach(function () {
signTypedMsgSpy.restore()
})
it('calls signTypedMsg in background with no error', async function () {
signTypedMsgSpy = sinon.stub(background, 'signTypedMessage')
.callsArgWith(1, null, defaultState)
const store = mockStore()
await store.dispatch(actions.signTypedMsg(msgParamsV3))
assert(signTypedMsgSpy.calledOnce)
})
it('returns expected actions with error', async function () {
signTypedMsgSpy = sinon.stub(background, 'signTypedMessage')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.signTypedMsg())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#signTx', function () {
let sendTransactionSpy
beforeEach(function () {
sendTransactionSpy = sinon.stub(global.ethQuery, 'sendTransaction')
})
afterEach(function () {
sendTransactionSpy.restore()
})
it('calls sendTransaction in global ethQuery', function () {
const store = mockStore()
store.dispatch(actions.signTx())
assert(sendTransactionSpy.calledOnce)
})
it('errors in when sendTransaction throws', function () {
const store = mockStore()
const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'SHOW_CONF_TX_PAGE', id: undefined },
]
sendTransactionSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
store.dispatch(actions.signTx())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#updatedGasData', function () {
it('errors when get code does not return', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'GAS_LOADING_STARTED' },
{ type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: 'gasLoadingError' } },
{ type: 'GAS_LOADING_FINISHED' },
]
const mockData = {
gasPrice: '0x3b9aca00', //
blockGasLimit: '0x6ad79a', // 7002010
selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
value: '0xde0b6b3a7640000', // 1000000000000000000
}
try {
await store.dispatch(actions.updateGasData(mockData))
assert.fail('Should have thrown error')
} catch (error) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
it('returns default gas limit for basic eth transaction', async function () {
const mockData = {
gasPrice: '0x3b9aca00',
blockGasLimit: '0x6ad79a', // 7002010
selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc',
to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B',
value: '0xde0b6b3a7640000', // 1000000000000000000
}
global.eth = {
getCode: sinon.stub().returns('0x'),
}
const store = mockStore()
const expectedActions = [
{ type: 'GAS_LOADING_STARTED' },
{ type: 'UPDATE_GAS_LIMIT', value: '0x5208' },
{ type: 'metamask/gas/SET_CUSTOM_GAS_LIMIT', value: '0x5208' },
{ type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: null } },
{ type: 'GAS_LOADING_FINISHED' },
]
await store.dispatch(actions.updateGasData(mockData))
assert.deepEqual(store.getActions(), expectedActions)
global.eth.getCode.reset()
})
})
describe('#signTokenTx', function () {
it('calls eth.contract', function () {
global.eth = new Eth(provider)
const tokenSpy = sinon.spy(global.eth, 'contract')
const store = mockStore()
store.dispatch(actions.signTokenTx())
assert(tokenSpy.calledOnce)
tokenSpy.restore()
})
})
describe('#updateTransaction', function () {
let updateTransactionSpy
const txParams = {
'from': '0x1',
'gas': '0x5208',
'gasPrice': '0x3b9aca00',
'to': '0x2',
'value': '0x0',
}
const txData = { id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: txParams }
beforeEach(async function () {
await metamaskController.txController.txStateManager.addTx(txData)
})
afterEach(function () {
updateTransactionSpy.restore()
})
it('updates transaction', async function () {
const store = mockStore()
updateTransactionSpy = sinon.spy(background, 'updateTransaction')
await store.dispatch(actions.updateTransaction(txData))
const resultantActions = store.getActions()
assert.ok(updateTransactionSpy.calledOnce)
assert.deepEqual(resultantActions[1], { type: 'UPDATE_TRANSACTION_PARAMS', id: txData.id, value: txParams })
})
it('rejects with error message', async function () {
const store = mockStore()
updateTransactionSpy = sinon.stub(background, 'updateTransaction')
updateTransactionSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.updateTransaction(txData))
assert.fail('Should have thrown error')
} catch (error) {
assert.equal(error.message, 'error')
}
})
})
describe('#lockMetamask', function () {
let backgroundSetLockedSpy
afterEach(function () {
backgroundSetLockedSpy.restore()
})
it('calls setLocked', async function () {
const store = mockStore()
backgroundSetLockedSpy = sinon.spy(background, 'setLocked')
await store.dispatch(actions.lockMetamask())
assert(backgroundSetLockedSpy.calledOnce)
})
it('returns display warning error with value when setLocked in background callback errors', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'LOCK_METAMASK' },
]
backgroundSetLockedSpy = sinon.stub(background, 'setLocked')
backgroundSetLockedSpy.callsFake((callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.lockMetamask())
assert.fail('Should have thrown error')
} catch (error) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#setSelectedAddress', function () {
let setSelectedAddressSpy
afterEach(function () {
setSelectedAddressSpy.restore()
})
it('calls setSelectedAddress in background', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, null)
const store = mockStore()
await store.dispatch(actions.setSelectedAddress('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
assert(setSelectedAddressSpy.calledOnce)
})
it('errors when setSelectedAddress throws', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
await store.dispatch(actions.setSelectedAddress())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#showAccountDetail', function () {
let setSelectedAddressSpy
afterEach(function () {
setSelectedAddressSpy.restore()
})
it('#showAccountDetail', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, null)
const store = mockStore({ metamask: { selectedAddress: '0x123' } })
await store.dispatch(actions.showAccountDetail())
assert(setSelectedAddressSpy.calledOnce)
})
it('displays warning if setSelectedAddress throws', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, new Error('error'))
const store = mockStore({ metamask: { selectedAddress: '0x123' } })
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
await store.dispatch(actions.showAccountDetail())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#addToken', function () {
let addTokenSpy
afterEach(function () {
addTokenSpy.restore()
})
it('calls addToken in background', async function () {
addTokenSpy = sinon.stub(background, 'addToken')
.callsArgWith(4, null)
const store = mockStore()
await store.dispatch(actions.addToken())
assert(addTokenSpy.calledOnce)
})
it('errors when addToken in background throws', async function () {
addTokenSpy = sinon.stub(background, 'addToken')
.callsArgWith(4, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.addToken())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#removeToken', function () {
let removeTokenSpy
afterEach(function () {
removeTokenSpy.restore()
})
it('calls removeToken in background', async function () {
removeTokenSpy = sinon.stub(background, 'removeToken')
.callsArgWith(1, null)
const store = mockStore()
await store.dispatch(actions.removeToken())
assert(removeTokenSpy.calledOnce)
})
it('errors when removeToken in background fails', async function () {
removeTokenSpy = sinon.stub(background, 'removeToken')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.removeToken())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#setProviderType', function () {
let setProviderTypeSpy
let store
beforeEach(function () {
store = mockStore({ metamask: { provider: {} } })
})
afterEach(function () {
setProviderTypeSpy.restore()
})
it('calls setProviderType', async function () {
setProviderTypeSpy = sinon.stub(background, 'setProviderType')
.callsArgWith(1, null)
await store.dispatch(actions.setProviderType())
assert(setProviderTypeSpy.calledOnce)
})
it('displays warning when setProviderType throws', async function () {
setProviderTypeSpy = sinon.stub(background, 'setProviderType')
.callsArgWith(1, new Error('error'))
const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
await store.dispatch(actions.setProviderType())
assert(setProviderTypeSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#setRpcTarget', function () {
let setRpcTargetSpy
afterEach(function () {
setRpcTargetSpy.restore()
})
it('calls setRpcTarget', async function () {
setRpcTargetSpy = sinon.stub(background, 'setCustomRpc')
.callsArgWith(4, null)
const store = mockStore()
await store.dispatch(actions.setRpcTarget('http://localhost:8545'))
assert(setRpcTargetSpy.calledOnce)
})
it('displays warning when setRpcTarget throws', async function () {
setRpcTargetSpy = sinon.stub(background, 'setCustomRpc')
.callsArgWith(4, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
await store.dispatch(actions.setRpcTarget())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#addToAddressBook', function () {
it('calls setAddressBook', async function () {
const addToAddressBookSpy = sinon.stub(background, 'setAddressBook')
.callsArgWith(4, null, true)
const store = mockStore()
await store.dispatch(actions.addToAddressBook('test'))
assert(addToAddressBookSpy.calledOnce)
addToAddressBookSpy.restore()
})
})
describe('#exportAccount', function () {
let submitPasswordSpy, exportAccountSpy
afterEach(function () {
submitPasswordSpy.restore()
exportAccountSpy.restore()
})
it('returns expected actions for successful action', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'SHOW_PRIVATE_KEY', value: '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505' },
]
submitPasswordSpy = sinon.spy(background, 'submitPassword')
exportAccountSpy = sinon.spy(background, 'exportAccount')
await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
assert(submitPasswordSpy.calledOnce)
assert(exportAccountSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
})
it('returns action errors when first func callback errors', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'Incorrect Password.' },
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
it('returns action errors when second func callback errors', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'Had a problem exporting the account.' },
]
exportAccountSpy = sinon.stub(background, 'exportAccount')
exportAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#setAccountLabel', function () {
it('calls setAccountLabel', async function () {
const setAccountLabelSpy = sinon.stub(background, 'setAccountLabel')
.callsArgWith(2, null)
const store = mockStore()
await store.dispatch(actions.setAccountLabel('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', 'test'))
assert(setAccountLabelSpy.calledOnce)
})
})
describe('#setFeatureFlag', function () {
let setFeatureFlagSpy
afterEach(function () {
setFeatureFlagSpy.restore()
})
it('calls setFeatureFlag in the background', async function () {
setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag')
.callsArgWith(2, null)
const store = mockStore()
await store.dispatch(actions.setFeatureFlag())
assert(setFeatureFlagSpy.calledOnce)
})
it('errors when setFeatureFlag in background throws', async function () {
setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag')
.callsArgWith(2, new Error('error'))
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
try {
await store.dispatch(actions.setFeatureFlag())
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#setCompletedOnboarding', function () {
it('completes onboarding', async function () {
const completeOnboardingSpy = sinon.stub(background, 'completeOnboarding')
completeOnboardingSpy.callsFake((cb) => cb())
const store = mockStore()
await store.dispatch(actions.setCompletedOnboarding())
assert.equal(completeOnboardingSpy.callCount, 1)
completeOnboardingSpy.restore()
})
})
describe('#setUseBlockie', function () {
let setUseBlockieSpy
beforeEach(function () {
setUseBlockieSpy = sinon.stub(background, 'setUseBlockie')
})
afterEach(function () {
setUseBlockieSpy.restore()
})
it('calls setUseBlockie in background', function () {
const store = mockStore()
store.dispatch(actions.setUseBlockie())
assert(setUseBlockieSpy.calledOnce)
})
it('errors when setUseBlockie in background throws', function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'SET_USE_BLOCKIE', value: undefined },
]
setUseBlockieSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
store.dispatch(actions.setUseBlockie())
assert.deepEqual(store.getActions(), expectedActions)
})
})
describe('#updateCurrentLocale', function () {
let setCurrentLocaleSpy
beforeEach(function () {
sinon.stub(window, 'fetch')
.resolves({
json: async () => enLocale,
})
})
afterEach(function () {
setCurrentLocaleSpy.restore()
window.fetch.restore()
})
it('calls expected actions', async function () {
const store = mockStore()
setCurrentLocaleSpy = sinon.spy(background, 'setCurrentLocale')
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'SET_CURRENT_LOCALE', value: { locale: 'en', messages: enLocale } },
{ type: 'HIDE_LOADING_INDICATION' },
]
await store.dispatch(actions.updateCurrentLocale('en'))
assert(setCurrentLocaleSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
})
it('errors when setCurrentLocale throws', async function () {
const store = mockStore()
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale')
setCurrentLocaleSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
try {
await store.dispatch(actions.updateCurrentLocale('en'))
assert.fail('Should have thrown error')
} catch (_) {
assert.deepEqual(store.getActions(), expectedActions)
}
})
})
describe('#markPasswordForgotten', function () {
it('calls markPasswordForgotten', async function () {
const store = mockStore()
const markPasswordForgottenSpy = sinon.stub(background, 'markPasswordForgotten').callsArg(0)
await store.dispatch(actions.markPasswordForgotten())
const resultantActions = store.getActions()
assert.deepEqual(resultantActions[1], { type: 'FORGOT_PASSWORD', value: true })
assert.ok(markPasswordForgottenSpy.calledOnce)
markPasswordForgottenSpy.restore()
})
})
describe('#unMarkPasswordForgotten', function () {
it('calls unMarkPasswordForgotten', async function () {
const store = mockStore()
const unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten').callsArg(0)
await store.dispatch(actions.unMarkPasswordForgotten())
const resultantActions = store.getActions()
assert.deepEqual(resultantActions[0], { type: 'FORGOT_PASSWORD', value: false })
assert.ok(unMarkPasswordForgottenSpy.calledOnce)
unMarkPasswordForgottenSpy.restore()
})
})
})