diff --git a/test/helper.js b/test/helper.js
index 7d0df174518d..a00be9a29f62 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -51,6 +51,9 @@ require('jsdom-global')()
// localStorage
window.localStorage = {}
+// override metamask-logo
+window.requestAnimationFrame = () => {}
+
// crypto.getRandomValues
if (!window.crypto) {
window.crypto = {}
diff --git a/test/lib/render-helpers.js b/test/lib/render-helpers.js
index ea0c5cf6b237..45bcb1656257 100644
--- a/test/lib/render-helpers.js
+++ b/test/lib/render-helpers.js
@@ -1,14 +1,7 @@
-import { shallow, mount } from 'enzyme'
import React from 'react'
-import { BrowserRouter } from 'react-router-dom'
-import { shape } from 'prop-types'
-
-export function shallowWithStore (component, store) {
- const context = {
- store,
- }
- return shallow(component, { context })
-}
+import { mount } from 'enzyme'
+import { MemoryRouter } from 'react-router-dom'
+import PropTypes from 'prop-types'
export function mountWithStore (component, store) {
const context = {
@@ -17,26 +10,40 @@ export function mountWithStore (component, store) {
return mount(component, { context })
}
-export function mountWithRouter (node) {
+export function mountWithRouter (component, store = {}, pathname = '/') {
// Instantiate router context
const router = {
- history: new BrowserRouter().history,
+ history: new MemoryRouter().history,
route: {
- location: {},
+ location: {
+ pathname: pathname,
+ },
match: {},
},
}
const createContext = () => ({
- context: { router, t: () => {} },
- childContextTypes: { router: shape({}), t: () => {} },
+ context: {
+ router,
+ t: str => str,
+ tOrKey: str => str,
+ metricsEvent: () => {},
+ store,
+ },
+ childContextTypes: {
+ router: PropTypes.object,
+ t: PropTypes.func,
+ tOrKey: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ store: PropTypes.object,
+ },
})
const Wrapper = () => (
-
- {node}
-
+
+ {component}
+
)
return mount(, createContext())
diff --git a/ui/app/components/app/account-menu/account-menu.component.js b/ui/app/components/app/account-menu/account-menu.component.js
index 523aaea9de71..b249ee179b80 100644
--- a/ui/app/components/app/account-menu/account-menu.component.js
+++ b/ui/app/components/app/account-menu/account-menu.component.js
@@ -193,6 +193,12 @@ export default class AccountMenu extends Component {
renderRemoveAccount (keyring, identity) {
const { t } = this.context
+
+ // Sometimes keyrings aren't loaded yet
+ if (!keyring) {
+ return null
+ }
+
// Any account that's not from the HD wallet Keyring can be removed
const { type } = keyring
const isRemovable = type !== 'HD Key Tree'
diff --git a/ui/app/components/app/account-menu/tests/account-menu.test.js b/ui/app/components/app/account-menu/tests/account-menu.test.js
new file mode 100644
index 000000000000..b1ebcf61c902
--- /dev/null
+++ b/ui/app/components/app/account-menu/tests/account-menu.test.js
@@ -0,0 +1,209 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureMockStore from 'redux-mock-store'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import AccountMenu from '../index'
+import { Provider } from 'react-redux'
+
+describe('Account Menu', async () => {
+
+ let wrapper
+
+ const mockStore = {
+ metamask: {
+ provider: {
+ type: 'test',
+ },
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+ }
+
+ const store = configureMockStore()(mockStore)
+
+ const props = {
+ isAccountMenuOpen: true,
+ addressConnectedDomainMap: {},
+ accounts: [
+ {
+ address: '0xAddress',
+ name: 'Account 1',
+ balance: '0x0',
+ },
+ {
+ address: '0xImportedAddress',
+ name: 'Imported Account 1',
+ balance: '0x0',
+ },
+ ],
+ keyrings: [
+ {
+ type: 'HD Key Tree',
+ accounts: [
+ '0xAdress',
+ ],
+ },
+ {
+ type: 'Simple Key Pair',
+ accounts: [
+ '0xImportedAddress',
+ ],
+ },
+ ],
+ prevIsAccountMenuOpen: false,
+ lockMetamask: sinon.spy(),
+ showAccountDetail: sinon.spy(),
+ showRemoveAccountConfirmationModal: sinon.spy(),
+ toggleAccountMenu: sinon.spy(),
+ history: {
+ push: sinon.spy(),
+ },
+
+ }
+
+ before(() => {
+ wrapper = mountWithRouter(
+
+
+ , store
+ )
+ })
+
+ afterEach(() => {
+ props.toggleAccountMenu.resetHistory()
+ props.history.push.resetHistory()
+ })
+
+ describe('Render Content', () => {
+ it('returns account name from identities', () => {
+ const accountName = wrapper.find('.account-menu__name')
+ assert.equal(accountName.length, 2)
+ })
+
+ it('renders user preference currency display balance from account balance', () => {
+ const accountBalance = wrapper.find('.currency-display-component.account-menu__balance')
+ assert.equal(accountBalance.length, 2)
+ })
+
+ it('simulate click', () => {
+ const click = wrapper.find('.account-menu__account.menu__item--clickable')
+ click.first().simulate('click')
+
+ assert(props.showAccountDetail.calledOnce)
+ assert.equal(props.showAccountDetail.getCall(0).args[0], '0xAddress')
+ })
+
+ it('render imported account label', () => {
+ const importedAccount = wrapper.find('.keyring-label.allcaps')
+ assert.equal(importedAccount.text(), 'imported')
+ })
+
+ it('remove account', () => {
+ const removeAccount = wrapper.find('.remove-account-icon')
+ removeAccount.simulate('click', {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ })
+
+ assert(props.showRemoveAccountConfirmationModal.calledOnce)
+ assert.deepEqual(props.showRemoveAccountConfirmationModal.getCall(0).args[0],
+ { address: '0xImportedAddress', balance: '0x0', name: 'Imported Account 1' }
+ )
+ })
+ })
+
+ describe('Log Out', () => {
+ let logout
+
+ it('logout', () => {
+ logout = wrapper.find('.account-menu__lock-button')
+ assert.equal(logout.length, 1)
+ })
+
+ it('simulate click', () => {
+ logout.simulate('click')
+ assert(props.lockMetamask.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/')
+ })
+ })
+
+ describe('Create Account', () => {
+ let createAccount
+
+ it('renders create account item', () => {
+ createAccount = wrapper.find({ text: 'createAccount' })
+ assert.equal(createAccount.length, 1)
+ })
+
+ it('calls toggle menu and push new-account route to history', () => {
+ createAccount.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/new-account')
+ })
+ })
+
+ describe('Import Account', () => {
+ let importAccount
+
+ it('renders import account item', () => {
+ importAccount = wrapper.find({ text: 'importAccount' })
+ assert.equal(importAccount.length, 1)
+ })
+
+ it('calls toggle menu and push /new-account/import route to history', () => {
+ importAccount.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ assert(props.history.push.getCall(0).args[0], '/new-account/import')
+ })
+ })
+
+ describe('Connect Hardware Wallet', () => {
+
+ let connectHardwareWallet
+
+ it('renders import account item', () => {
+ connectHardwareWallet = wrapper.find({ text: 'connectHardwareWallet' })
+ assert.equal(connectHardwareWallet.length, 1)
+ })
+
+ it('calls toggle menu and push /new-account/connect route to history', () => {
+ connectHardwareWallet.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/new-account/connect')
+ })
+ })
+
+ describe('Info & Help', () => {
+
+ let infoHelp
+
+ it('renders import account item', () => {
+ infoHelp = wrapper.find({ text: 'infoHelp' })
+ assert.equal(infoHelp.length, 1)
+ })
+
+ it('calls toggle menu and push /new-account/connect route to history', () => {
+ infoHelp.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/settings/about-us')
+ })
+ })
+
+ describe('Settings', () => {
+
+ let settings
+
+ it('renders import account item', () => {
+ settings = wrapper.find({ text: 'settings' })
+ assert.equal(settings.length, 1)
+ })
+
+ it('calls toggle menu and push /new-account/connect route to history', () => {
+ settings.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/settings')
+ })
+ })
+})
diff --git a/ui/app/components/app/app-header/tests/app-header.test.js b/ui/app/components/app/app-header/tests/app-header.test.js
new file mode 100644
index 000000000000..37d1c32c43ed
--- /dev/null
+++ b/ui/app/components/app/app-header/tests/app-header.test.js
@@ -0,0 +1,99 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import MetaFoxLogo from '../../../ui/metafox-logo'
+import AppHeader from '../index'
+
+describe('App Header', () => {
+ let wrapper
+
+ const props = {
+ hideNetworkDropdown: sinon.spy(),
+ showNetworkDropdown: sinon.spy(),
+ toggleAccountMenu: sinon.spy(),
+ history: {
+ push: sinon.spy(),
+ },
+ network: 'test',
+ provider: {},
+ selectedAddress: '0xAddress',
+ disabled: false,
+ hideNetworkIndicator: false,
+ networkDropdownOpen: false,
+ isAccountMenuOpen: false,
+ isUnlocked: true,
+ }
+
+ beforeEach(() => {
+ wrapper = shallow(
+ , {
+ context: {
+ t: str => str,
+ metricsEvent: () => {},
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.toggleAccountMenu.resetHistory()
+ })
+
+ describe('App Header Logo', () => {
+ it('routes to default route when logo is clicked', () => {
+ const appLogo = wrapper.find(MetaFoxLogo)
+ appLogo.simulate('click')
+ assert(props.history.push.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/')
+ })
+ })
+
+ describe('Network', () => {
+ it('shows network dropdown when networkDropdownOpen is false', () => {
+ const network = wrapper.find({ network: 'test' })
+
+ network.simulate('click', {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ })
+
+ assert(props.showNetworkDropdown.calledOnce)
+ })
+
+ it('hides network dropdown when networkDropdownOpen is true', () => {
+ wrapper.setProps({ networkDropdownOpen: true })
+ const network = wrapper.find({ network: 'test' })
+
+ network.simulate('click', {
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ })
+
+ assert(props.hideNetworkDropdown.calledOnce)
+ })
+
+ it('hides network indicator', () => {
+ wrapper.setProps({ hideNetworkIndicator: true })
+ const network = wrapper.find({ network: 'test' })
+ assert.equal(network.length, 0)
+ })
+ })
+
+ describe('Account Menu', () => {
+
+ it('toggles account menu', () => {
+ const accountMenu = wrapper.find('.account-menu__icon')
+ accountMenu.simulate('click')
+ assert(props.toggleAccountMenu.calledOnce)
+ })
+
+ it('does not toggle account menu when disabled', () => {
+ wrapper.setProps({ disabled: true })
+ const accountMenu = wrapper.find('.account-menu__icon')
+ accountMenu.simulate('click')
+ assert(props.toggleAccountMenu.notCalled)
+ })
+ })
+
+})
diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js
index bfd1df7770ba..a9d5f0a5a344 100644
--- a/ui/app/components/app/dropdowns/account-details-dropdown.js
+++ b/ui/app/components/app/dropdowns/account-details-dropdown.js
@@ -40,7 +40,7 @@ class AccountDetailsDropdown extends Component {
static propTypes = {
selectedIdentity: PropTypes.object.isRequired,
- network: PropTypes.number.isRequired,
+ network: PropTypes.string.isRequired,
keyrings: PropTypes.array.isRequired,
showAccountDetailModal: PropTypes.func.isRequired,
viewOnEtherscan: PropTypes.func.isRequired,
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js
new file mode 100644
index 000000000000..e45740f5b302
--- /dev/null
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js
@@ -0,0 +1,104 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import AdvancedTabContent from '../index'
+
+describe('Advanced Gas Inputs', () => {
+ let wrapper, clock
+
+ const props = {
+ updateCustomGasPrice: sinon.spy(),
+ updateCustomGasLimit: sinon.spy(),
+ showGasPriceInfoModal: sinon.spy(),
+ showGasLimitInfoModal: sinon.spy(),
+ customGasPrice: 0,
+ customGasLimit: 0,
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ isSpeedUp: false,
+ }
+
+ beforeEach(() => {
+ clock = sinon.useFakeTimers()
+
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ })
+ })
+
+ afterEach(() => {
+ clock.restore()
+ })
+
+ it('wont update gasPrice in props before debounce', () => {
+ const event = { target: { value: 1 } }
+
+ wrapper.find('input').at(0).simulate('change', event)
+ clock.tick(499)
+
+ assert.equal(props.updateCustomGasPrice.callCount, 0)
+ })
+
+ it('simulates onChange on gas price after debounce', () => {
+ const event = { target: { value: 1 } }
+
+ wrapper.find('input').at(0).simulate('change', event)
+ clock.tick(500)
+
+ assert.equal(props.updateCustomGasPrice.calledOnce, true)
+ assert.equal(props.updateCustomGasPrice.calledWith(1), true)
+ })
+
+ it('wont update gasLimit in props before debounce', () => {
+ const event = { target: { value: 21000 } }
+
+ wrapper.find('input').at(1).simulate('change', event)
+ clock.tick(499)
+
+ assert.equal(props.updateCustomGasLimit.callCount, 0)
+ })
+
+ it('simulates onChange on gas limit after debounce', () => {
+ const event = { target: { value: 21000 } }
+
+ wrapper.find('input').at(1).simulate('change', event)
+ clock.tick(500)
+
+ assert.equal(props.updateCustomGasLimit.calledOnce, true)
+ assert.equal(props.updateCustomGasLimit.calledWith(21000), true)
+ })
+
+ it('errors when insuffientBalance under gas price and gas limit', () => {
+ wrapper.setProps({ insufficientBalance: true })
+ const renderError = wrapper.find('.advanced-gas-inputs__gas-edit-row__error-text')
+ assert.equal(renderError.length, 2)
+
+ assert.equal(renderError.at(0).text(), 'insufficientBalance')
+ assert.equal(renderError.at(1).text(), 'insufficientBalance')
+ })
+
+ it('errors zero gas price / speed up', () => {
+ wrapper.setProps({ isSpeedUp: true })
+
+ const renderError = wrapper.find('.advanced-gas-inputs__gas-edit-row__error-text')
+ assert.equal(renderError.length, 2)
+
+ assert.equal(renderError.at(0).text(), 'zeroGasPriceOnSpeedUpError')
+ assert.equal(renderError.at(1).text(), 'gasLimitTooLow')
+ })
+
+ it('warns when custom gas price is too low', () => {
+ wrapper.setProps({ customPriceIsSafe: false })
+
+ const renderWarning = wrapper.find('.advanced-gas-inputs__gas-edit-row__warning-text')
+ assert.equal(renderWarning.length, 1)
+
+ assert.equal(renderWarning.text(), 'gasPriceExtremelyLow')
+ })
+})
diff --git a/ui/app/components/app/info-box/tests/info-box.test.js b/ui/app/components/app/info-box/tests/info-box.test.js
new file mode 100644
index 000000000000..904e868baa0d
--- /dev/null
+++ b/ui/app/components/app/info-box/tests/info-box.test.js
@@ -0,0 +1,37 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+
+import InfoBox from '../index'
+
+describe('InfoBox', () => {
+
+ let wrapper
+
+ const props = {
+ title: 'Title',
+ description: 'Description',
+ onClose: sinon.spy(),
+ }
+
+ beforeEach(() => {
+ wrapper = shallow()
+ })
+
+ it('renders title from props', () => {
+ const title = wrapper.find('.info-box__title')
+ assert.equal(title.text(), props.title)
+ })
+
+ it('renders description from props', () => {
+ const description = wrapper.find('.info-box__description')
+ assert.equal(description.text(), props.description)
+ })
+
+ it('closes info box', () => {
+ const close = wrapper.find('.info-box__close')
+ close.simulate('click')
+ assert(props.onClose.calledOnce)
+ })
+})
diff --git a/ui/app/components/app/menu-bar/tests/menu-bar.test.js b/ui/app/components/app/menu-bar/tests/menu-bar.test.js
new file mode 100644
index 000000000000..a43b994baa61
--- /dev/null
+++ b/ui/app/components/app/menu-bar/tests/menu-bar.test.js
@@ -0,0 +1,94 @@
+import React from 'react'
+import configureStore from 'redux-mock-store'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import MenuBar from '../index'
+import { Provider } from 'react-redux'
+
+describe('MenuBar', () => {
+ let wrapper
+
+ const mockStore = {
+ metamask: {
+ network: '1',
+ selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ identities: {
+ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
+ address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ name: 'Account 1',
+ },
+ },
+ keyrings: [
+ {
+ type: 'HD Key Tree',
+ accounts: [
+ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ ],
+ },
+ ],
+ frequentRpcListDetail: [],
+ },
+ appState: {
+ sidebar: {
+ isOpen: false,
+ },
+ },
+ }
+
+ const store = configureStore()(mockStore)
+
+ afterEach(() => {
+ sinon.restore()
+ })
+
+ it('shows side bar when sidbarOpen is set to false', () => {
+ const props = {
+ showSidebar: sinon.spy(),
+ }
+
+ wrapper = mountWithRouter(
+
+
+ , store
+ )
+
+ const sidebarButton = wrapper.find('.menu-bar__sidebar-button')
+ sidebarButton.simulate('click')
+ assert(props.showSidebar.calledOnce)
+ })
+
+ it('hides side when sidebarOpen is set to true', () => {
+ const props = {
+ showSidebar: sinon.spy(),
+ hideSidebar: sinon.spy(),
+ sidebarOpen: true,
+ }
+
+ wrapper = mountWithRouter(
+
+
+ , store
+ )
+
+ const sidebarButton = wrapper.find('.menu-bar__sidebar-button')
+ sidebarButton.prop('onClick')()
+ assert(props.hideSidebar.calledOnce)
+ })
+
+ it('opens account detail menu when account options is clicked', () => {
+ const accountOptions = wrapper.find('.menu-bar__open-in-browser')
+ accountOptions.simulate('click')
+ assert.equal(wrapper.find('MenuBar').instance().state.accountDetailsMenuOpen, true)
+ })
+
+ it('sets accountDetailsMenuOpen to false when closed', () => {
+ wrapper.find('MenuBar').instance().setState({ accountDetailsMenuOpen: true })
+ wrapper.update()
+
+ const accountDetailsMenu = wrapper.find('AccountDetailsDropdown')
+ accountDetailsMenu.prop('onClose')()
+
+ assert.equal(wrapper.find('MenuBar').instance().state.accountDetailsMenuOpen, false)
+ })
+})
diff --git a/ui/app/components/app/modals/confirm-delete-network/tests/confirm-delete-network.test.js b/ui/app/components/app/modals/confirm-delete-network/tests/confirm-delete-network.test.js
new file mode 100644
index 000000000000..ce0d49013257
--- /dev/null
+++ b/ui/app/components/app/modals/confirm-delete-network/tests/confirm-delete-network.test.js
@@ -0,0 +1,58 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import ConfirmDeleteNetwork from '../index'
+
+describe('Confirm Delete Network', () => {
+ let wrapper
+
+ const props = {
+ hideModal: sinon.spy(),
+ delRpcTarget: sinon.stub().resolves(),
+ onConfirm: sinon.spy(),
+ target: '',
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.hideModal.resetHistory()
+ props.delRpcTarget.resetHistory()
+ props.onConfirm.resetHistory()
+ })
+
+ it('renders delete network modal title', () => {
+ const modalTitle = wrapper.find('.modal-content__title')
+ assert.equal(modalTitle.text(), 'deleteNetwork')
+ })
+
+ it('clicks cancel to hide modal', () => {
+ const cancelButton = wrapper.find('.button.btn-default.modal-container__footer-button')
+ cancelButton.simulate('click')
+
+ assert(props.hideModal.calledOnce)
+
+ })
+
+ it('clicks delete to delete the target and hides modal', () => {
+ const deleteButton = wrapper.find('.button.btn-danger.modal-container__footer-button')
+
+ deleteButton.simulate('click')
+
+ setImmediate(() => {
+ assert(props.delRpcTarget.calledOnce)
+ assert(props.hideModal.calledOnce)
+ assert(props.onConfirm.calledOnce)
+ })
+ })
+
+})
diff --git a/ui/app/components/app/modals/confirm-remove-account/tests/confirm-remove-account.test.js b/ui/app/components/app/modals/confirm-remove-account/tests/confirm-remove-account.test.js
new file mode 100644
index 000000000000..0a6eb828d023
--- /dev/null
+++ b/ui/app/components/app/modals/confirm-remove-account/tests/confirm-remove-account.test.js
@@ -0,0 +1,82 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Provider } from 'react-redux'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureStore from 'redux-mock-store'
+import { mount } from 'enzyme'
+import ConfirmRemoveAccount from '../index'
+
+describe('Confirm Remove Account', () => {
+ let wrapper
+
+ const state = {
+ metamask: {
+
+ },
+ }
+
+ const props = {
+ hideModal: sinon.spy(),
+ removeAccount: sinon.stub().resolves(),
+ network: '101',
+ identity: {
+ address: '0xAddress',
+ name: 'Account 1',
+ },
+ }
+
+
+ const mockStore = configureStore()
+ const store = mockStore(state)
+
+ beforeEach(() => {
+
+ wrapper = mount(
+
+
+ , {
+ context: {
+ t: str => str,
+ store,
+ },
+ childContextTypes: {
+ t: PropTypes.func,
+ store: PropTypes.object,
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.hideModal.resetHistory()
+ })
+
+ it('nevermind', () => {
+ const nevermind = wrapper.find({ type: 'default' })
+ nevermind.simulate('click')
+
+ assert(props.hideModal.calledOnce)
+ })
+
+ it('remove', (done) => {
+ const remove = wrapper.find({ type: 'secondary' })
+ remove.simulate('click')
+
+ assert(props.removeAccount.calledOnce)
+ assert.equal(props.removeAccount.getCall(0).args[0], props.identity.address)
+
+ setImmediate(() => {
+ assert(props.hideModal.calledOnce)
+ done()
+ })
+
+ })
+
+ it('closes', () => {
+ const close = wrapper.find('.modal-container__header-close')
+ close.simulate('click')
+
+ assert(props.hideModal.calledOnce)
+ })
+})
diff --git a/ui/app/components/app/modals/confirm-reset-account/tests/confirm-reset-account.test.js b/ui/app/components/app/modals/confirm-reset-account/tests/confirm-reset-account.test.js
new file mode 100644
index 000000000000..175a38158ca8
--- /dev/null
+++ b/ui/app/components/app/modals/confirm-reset-account/tests/confirm-reset-account.test.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import ConfirmResetAccount from '../index'
+
+describe('Confirm Reset Account', () => {
+ let wrapper
+
+ const props = {
+ hideModal: sinon.spy(),
+ resetAccount: sinon.stub().resolves(),
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.hideModal.resetHistory()
+ })
+
+ it('hides modal when nevermind button is clicked', () => {
+ const nevermind = wrapper.find('.btn-default.modal-container__footer-button')
+ nevermind.simulate('click')
+
+ assert(props.hideModal.calledOnce)
+ })
+
+ it('resets account and hidels modal when reset button is clicked', (done) => {
+ const reset = wrapper.find('.btn-danger.modal-container__footer-button')
+ reset.simulate('click')
+
+ setImmediate(() => {
+ assert(props.resetAccount.calledOnce)
+ assert(props.hideModal.calledOnce)
+ done()
+ })
+ })
+})
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js b/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js
new file mode 100644
index 000000000000..2bae8f9b1972
--- /dev/null
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/tests/metametrics-opt-in-modal.test.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import MetaMetricsOptIn from '../index'
+
+describe('MetaMetrics Opt In', () => {
+ let wrapper
+
+ const props = {
+ setParticipateInMetaMetrics: sinon.stub().resolves(),
+ hideModal: sinon.spy(),
+ participateInMetaMetrics: null,
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ metricsEvent: () => {},
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.setParticipateInMetaMetrics.resetHistory()
+ props.hideModal.resetHistory()
+ })
+
+ it('passes false to setParticipateInMetaMetrics and hides modal', (done) => {
+ const noThanks = wrapper.find('.btn-default.page-container__footer-button')
+ noThanks.simulate('click')
+
+ setImmediate(() => {
+ assert(props.setParticipateInMetaMetrics.calledOnce)
+ assert.equal(props.setParticipateInMetaMetrics.getCall(0).args[0], false)
+ assert(props.hideModal.calledOnce)
+ done()
+ })
+ })
+
+ it('passes true to setParticipateInMetaMetrics and hides modal', (done) => {
+ const iAgree = wrapper.find('.btn-primary.page-container__footer-button')
+ iAgree.simulate('click')
+
+ setImmediate(() => {
+ assert(props.setParticipateInMetaMetrics.calledOnce)
+ assert.equal(props.setParticipateInMetaMetrics.getCall(0).args[0], true)
+ assert(props.hideModal.calledOnce)
+ done()
+ })
+ })
+
+})
diff --git a/ui/app/components/app/modals/reject-transactions/tests/reject-transactions.test.js b/ui/app/components/app/modals/reject-transactions/tests/reject-transactions.test.js
new file mode 100644
index 000000000000..505e9a05ec54
--- /dev/null
+++ b/ui/app/components/app/modals/reject-transactions/tests/reject-transactions.test.js
@@ -0,0 +1,48 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import RejectTransactionsModal from '../index'
+
+describe('Reject Transactions Model', () => {
+ let wrapper
+
+ const props = {
+ onSubmit: sinon.spy(),
+ hideModal: sinon.spy(),
+ unapprovedTxCount: 2,
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ afterEach(() => {
+ props.hideModal.resetHistory()
+ })
+
+ it('hides modal when cancel button is clicked', () => {
+ const cancelButton = wrapper.find('.btn-default.modal-container__footer-button')
+ cancelButton.simulate('click')
+
+ assert(props.hideModal.calledOnce)
+ })
+
+ it('onSubmit is called and hides modal when reject all clicked', (done) => {
+ const rejectAllButton = wrapper.find('.btn-secondary.modal-container__footer-button')
+ rejectAllButton.simulate('click')
+
+ setImmediate(() => {
+ assert(props.onSubmit.calledOnce)
+ assert(props.hideModal.calledOnce)
+ done()
+ })
+
+ })
+})
diff --git a/ui/app/components/app/modals/tests/account-details-modal.test.js b/ui/app/components/app/modals/tests/account-details-modal.test.js
new file mode 100644
index 000000000000..24fe0eee40bd
--- /dev/null
+++ b/ui/app/components/app/modals/tests/account-details-modal.test.js
@@ -0,0 +1,82 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import AccountDetailsModal from '../account-details-modal'
+
+describe('Account Details Modal', () => {
+ let wrapper
+
+ global.platform = { openWindow: sinon.spy() }
+
+ const props = {
+ hideModal: sinon.spy(),
+ setAccountLabel: sinon.spy(),
+ showExportPrivateKeyModal: sinon.spy(),
+ showQrView: sinon.spy(),
+ network: 'test',
+ rpcPrefs: {},
+ selectedIdentity: {
+ address: '0xAddress',
+ name: 'Account 1',
+ },
+ keyrings: [
+ {
+ type: 'HD Key Tree',
+ accounts: [
+ '0xAddress',
+ ],
+ },
+ ],
+ identities: {
+ '0xAddress': {
+ address: '0xAddress',
+ name: 'Account 1',
+ },
+ },
+ }
+
+ beforeEach(() => {
+ wrapper = shallow(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ it('sets account label when changing default account label', () => {
+ const accountLabel = wrapper.find('.account-modal__name').first()
+ accountLabel.simulate('submit', 'New Label')
+
+ assert(props.setAccountLabel.calledOnce)
+ assert.equal(props.setAccountLabel.getCall(0).args[1], 'New Label')
+ })
+
+ it('opens new window when view block explorer is clicked', () => {
+ const modalButton = wrapper.find('.account-modal__button')
+ const etherscanLink = modalButton.first()
+
+ etherscanLink.simulate('click')
+ assert(global.platform.openWindow.calledOnce)
+ })
+
+ it('shows export private key modal when clicked', () => {
+ const modalButton = wrapper.find('.account-modal__button')
+ const etherscanLink = modalButton.last()
+
+ etherscanLink.simulate('click')
+ assert(props.showExportPrivateKeyModal.calledOnce)
+ })
+
+ it('sets blockexplorerview text when block explorer url in rpcPrefs exists', () => {
+ const blockExplorerUrl = 'https://block.explorer'
+ wrapper.setProps({ rpcPrefs: { blockExplorerUrl } })
+
+ const modalButton = wrapper.find('.account-modal__button')
+ const blockExplorerLink = modalButton.first()
+
+ assert.equal(blockExplorerLink.html(), '')
+ })
+})
diff --git a/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js b/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js
new file mode 100644
index 000000000000..7d4debfd7738
--- /dev/null
+++ b/ui/app/components/app/modals/transaction-confirmed/tests/transaction-confirmed.test.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import TransactionConfirmed from '../index'
+
+describe('Transaction Confirmed', () => {
+ let wrapper
+
+ const props = {
+ onSubmit: sinon.spy(),
+ hideModal: sinon.spy(),
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ it('clicks ok to submit and hide modal', () => {
+ const submit = wrapper.find('.btn-secondary.modal-container__footer-button')
+ submit.simulate('click')
+
+ assert(props.onSubmit.calledOnce)
+ assert(props.hideModal.calledOnce)
+ })
+})
diff --git a/ui/app/components/app/tests/signature-request.test.js b/ui/app/components/app/tests/signature-request.test.js
new file mode 100644
index 000000000000..eda054c168ac
--- /dev/null
+++ b/ui/app/components/app/tests/signature-request.test.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import { Provider } from 'react-redux'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureMockStore from 'redux-mock-store'
+import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import SignatureRequest from '../signature-request'
+
+describe('Signature Request', () => {
+ let wrapper
+
+ const mockStore = {
+ metamask: {
+ provider: {
+ type: 'test',
+ },
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const props = {
+ selectedAccount: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
+ history: {
+ push: sinon.spy(),
+ },
+ clearConfirmTransaction: sinon.spy(),
+ cancelMessage: sinon.spy(),
+ cancel: sinon.stub().resolves(),
+ sign: sinon.stub().resolves(),
+ txData: {
+ msgParams: {
+ id: 1,
+ data: '{"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 Mail","version":"1","chainId":"4","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}',
+ from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
+ origin: 'test.domain',
+ },
+ status: 'unapproved',
+ time: 1,
+ type: 'eth_sign',
+ },
+ }
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+
+
+ , store
+ )
+ })
+
+ afterEach(() => {
+ props.clearConfirmTransaction.resetHistory()
+ })
+
+ it('cancel', () => {
+ const cancelButton = wrapper.find('button.btn-default')
+ cancelButton.simulate('click')
+
+ assert(props.cancel.calledOnce)
+ })
+
+ it('sign', () => {
+ const signButton = wrapper.find('button.btn-primary')
+ signButton.simulate('click')
+
+ assert(props.sign.calledOnce)
+ })
+
+})
diff --git a/ui/app/components/app/tests/token-cell.spec.js b/ui/app/components/app/tests/token-cell.spec.js
new file mode 100644
index 000000000000..877713e4ce55
--- /dev/null
+++ b/ui/app/components/app/tests/token-cell.spec.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import assert from 'assert'
+import thunk from 'redux-thunk'
+import { Provider } from 'react-redux'
+import configureMockStore from 'redux-mock-store'
+import { mount } from 'enzyme'
+
+import TokenCell from '../token-cell'
+import Identicon from '../../ui/identicon'
+
+describe('Token Cell', () => {
+ let wrapper
+
+ const state = {
+ metamask: {
+ network: 'test',
+ currentCurrency: 'usd',
+ selectedTokenAddress: '0xToken',
+ selectedAddress: '0xAddress',
+ contractExchangeRates: {
+ '0xAnotherToken': 0.015,
+ },
+ conversionRate: 7.00,
+ },
+ appState: {
+ sidebar: {
+ isOpen: true,
+ },
+ },
+ }
+
+ const middlewares = [thunk]
+ const mockStore = configureMockStore(middlewares)
+ const store = mockStore(state)
+
+ beforeEach(() => {
+ wrapper = mount(
+
+
+
+ )
+ })
+
+ it('renders Identicon with props from token cell', () => {
+ assert.equal(wrapper.find(Identicon).prop('address'), '0xAnotherToken')
+ assert.equal(wrapper.find(Identicon).prop('network'), 'test')
+ assert.equal(wrapper.find(Identicon).prop('image'), './test-image')
+ })
+
+ it('renders token balance', () => {
+ assert.equal(wrapper.find('.token-list-item__token-balance').text(), '5.000')
+ })
+
+ it('renders token symbol', () => {
+ assert.equal(wrapper.find('.token-list-item__token-symbol').text(), 'TEST')
+ })
+
+ it('renders converted fiat amount', () => {
+ assert.equal(wrapper.find('.token-list-item__fiat-amount').text(), '0.52 USD')
+ })
+
+})
diff --git a/ui/app/components/ui/alert/tests/alert.test.js b/ui/app/components/ui/alert/tests/alert.test.js
new file mode 100644
index 000000000000..d3de51bfb0a6
--- /dev/null
+++ b/ui/app/components/ui/alert/tests/alert.test.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import Alert from '../index'
+
+describe('Alert', () => {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(
+
+ )
+ })
+
+ it('renders nothing with no visible boolean in state', () => {
+ const alert = wrapper.find('.global-alert')
+ assert.equal(alert.length, 0)
+ })
+
+ it('renders when visible in state is true, and message', () => {
+ const errorMessage = 'Error Message'
+
+ wrapper.setState({ visible: true, msg: errorMessage })
+
+ const alert = wrapper.find('.global-alert')
+ assert.equal(alert.length, 1)
+
+ const errorText = wrapper.find('.msg')
+ assert.equal(errorText.text(), errorMessage)
+ })
+
+ it('calls component method when componentWillReceiveProps is called', () => {
+ const animateInSpy = sinon.stub(wrapper.instance(), 'animateIn')
+ const animateOutSpy = sinon.stub(wrapper.instance(), 'animateOut')
+
+ wrapper.setProps({ visible: true })
+ assert(animateInSpy.calledOnce)
+
+ wrapper.setProps({ visible: false })
+ assert(animateOutSpy.calledOnce)
+ })
+})
diff --git a/ui/app/helpers/higher-order-components/with-token-tracker/tests/with-token-tracker.component.test.js b/ui/app/helpers/higher-order-components/with-token-tracker/tests/with-token-tracker.component.test.js
new file mode 100644
index 000000000000..c7d22242dc49
--- /dev/null
+++ b/ui/app/helpers/higher-order-components/with-token-tracker/tests/with-token-tracker.component.test.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import withTokenTracker from '../with-token-tracker.component'
+import TokenBalance from '../../../../components/ui/token-balance/token-balance.component'
+// import sinon from 'sinon'
+import TokenTracker from 'eth-token-tracker'
+
+const { createTestProviderTools } = require('../../../../../../test/stub/provider')
+
+const provider = createTestProviderTools({ scaffold: {} }).provider
+
+describe('WithTokenTracker HOC', () => {
+ let wrapper
+
+ beforeEach(() => {
+ const TokenTracker = withTokenTracker(TokenBalance)
+ wrapper = shallow(
+
+ )
+ })
+
+ it('#setError', () => {
+ wrapper.instance().setError('test')
+ assert.equal(wrapper.props().error, 'test')
+ })
+
+ it('#updateBalance', () => {
+ wrapper.instance().tracker = new TokenTracker({
+ provider,
+ })
+ wrapper.instance().updateBalance([{ string: 'test string', symbol: 'test symbol' }])
+ assert.equal(wrapper.props().string, 'test string')
+ assert.equal(wrapper.props().symbol, 'test symbol')
+ })
+
+})
diff --git a/ui/app/pages/add-token/tests/add-token.test.js b/ui/app/pages/add-token/tests/add-token.test.js
new file mode 100644
index 000000000000..67465752bc37
--- /dev/null
+++ b/ui/app/pages/add-token/tests/add-token.test.js
@@ -0,0 +1,100 @@
+import React from 'react'
+import { Provider } from 'react-redux'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureMockStore from 'redux-mock-store'
+import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import AddToken from '../index'
+
+describe('Add Token', () => {
+ let wrapper
+
+ const state = {
+ metamask: {
+ tokens: [],
+ },
+ }
+
+ const mockStore = configureMockStore()
+ const store = mockStore(state)
+
+ const props = {
+ history: {
+ push: sinon.stub().callsFake(() => {}),
+ },
+ setPendingTokens: sinon.spy(),
+ clearPendingTokens: sinon.spy(),
+ tokens: [],
+ identities: {},
+ }
+
+ before(() => {
+ wrapper = mountWithRouter(
+
+
+ , store
+ )
+
+ wrapper.find({ name: 'customToken' }).simulate('click')
+ })
+
+ afterEach(() => {
+ props.history.push.reset()
+ })
+
+ describe('Add Token', () => {
+
+ it('next button is disabled when no fields are populated', () => {
+ const nextButton = wrapper.find('.button.btn-secondary.page-container__footer-button')
+
+ assert.equal(nextButton.props().disabled, true)
+ })
+
+ it('edits token address', () => {
+ const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4'
+ const event = { target: { value: tokenAddress } }
+ const customAddress = wrapper.find('input#custom-address')
+
+ customAddress.simulate('change', event)
+ assert.equal(wrapper.find('AddToken').instance().state.customAddress, tokenAddress)
+ })
+
+
+ it('edits token symbol', () => {
+ const tokenSymbol = 'META'
+ const event = { target: { value: tokenSymbol } }
+ const customAddress = wrapper.find('#custom-symbol')
+ customAddress.last().simulate('change', event)
+
+ assert.equal(wrapper.find('AddToken').instance().state.customSymbol, tokenSymbol)
+ })
+
+ it('edits token decimal precision', () => {
+ const tokenPrecision = '2'
+ const event = { target: { value: tokenPrecision } }
+ const customAddress = wrapper.find('#custom-decimals')
+ customAddress.last().simulate('change', event)
+
+ assert.equal(wrapper.find('AddToken').instance().state.customDecimals, tokenPrecision)
+
+ })
+
+ it('next', () => {
+ const nextButton = wrapper.find('.button.btn-secondary.page-container__footer-button')
+ nextButton.simulate('click')
+
+ assert(props.setPendingTokens.calledOnce)
+ assert(props.history.push.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/confirm-add-token')
+ })
+
+ it('cancels', () => {
+ const cancelButton = wrapper.find('button.btn-default.page-container__footer-button')
+ cancelButton.simulate('click')
+
+ assert(props.clearPendingTokens.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/')
+ })
+ })
+
+})
diff --git a/ui/app/pages/create-account/tests/create-account.test.js b/ui/app/pages/create-account/tests/create-account.test.js
new file mode 100644
index 000000000000..b68a2241e2cf
--- /dev/null
+++ b/ui/app/pages/create-account/tests/create-account.test.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import CreateAccountPage from '../index'
+
+describe('Create Account Page', () => {
+ let wrapper
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ location: {
+ pathname: '/new-account',
+ },
+ }
+
+ before(() => {
+ wrapper = mountWithRouter(
+
+ )
+ })
+
+ afterEach(() => {
+ props.history.push.resetHistory()
+ })
+
+ it('clicks create account and routes to new-account path', () => {
+ const createAccount = wrapper.find('.new-account__tabs__tab').at(0)
+ createAccount.simulate('click')
+ assert.equal(props.history.push.getCall(0).args[0], '/new-account')
+ })
+
+ it('clicks import account and routes to import new account path', () => {
+ const importAccount = wrapper.find('.new-account__tabs__tab').at(1)
+ importAccount.simulate('click')
+ assert.equal(props.history.push.getCall(0).args[0], '/new-account/import')
+ })
+
+ it('clicks connect HD Wallet and routes to connect new account path', () => {
+ const connectHdWallet = wrapper.find('.new-account__tabs__tab').at(2)
+ connectHdWallet.simulate('click')
+ assert.equal(props.history.push.getCall(0).args[0], '/new-account/connect')
+ })
+})
diff --git a/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js b/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js
new file mode 100644
index 000000000000..23cebc68c1f2
--- /dev/null
+++ b/ui/app/pages/first-time-flow/end-of-flow/tests/end-of-flow.test.js
@@ -0,0 +1,39 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
+import EndOfFlowScreen from '../index'
+
+describe('End of Flow Screen', () => {
+ let wrapper
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ completeOnboarding: sinon.spy(),
+ }
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+
+ )
+ })
+
+ it('renders', () => {
+ assert.equal(wrapper.length, 1)
+ })
+
+ it('', (done) => {
+ const endOfFlowButton = wrapper.find('.btn-primary.first-time-flow__button')
+ endOfFlowButton.simulate('click')
+
+ setImmediate(() => {
+ assert(props.completeOnboarding.calledOnce)
+ assert(props.history.push.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], DEFAULT_ROUTE)
+ done()
+ })
+ })
+})
diff --git a/ui/app/pages/first-time-flow/first-time-flow-switch/tests/first-time-flow-switch.test.js b/ui/app/pages/first-time-flow/first-time-flow-switch/tests/first-time-flow-switch.test.js
new file mode 100644
index 000000000000..4c2b60727048
--- /dev/null
+++ b/ui/app/pages/first-time-flow/first-time-flow-switch/tests/first-time-flow-switch.test.js
@@ -0,0 +1,73 @@
+import React from 'react'
+import assert from 'assert'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import {
+ DEFAULT_ROUTE,
+ LOCK_ROUTE,
+ INITIALIZE_WELCOME_ROUTE,
+ INITIALIZE_UNLOCK_ROUTE,
+} from '../../../../helpers/constants/routes'
+import FirstTimeFlowSwitch from '../index'
+
+describe('FirstTimeFlowSwitch', () => {
+
+ it('redirects to /welcome route with no props', () => {
+ const wrapper = mountWithRouter(
+
+ )
+ assert.equal(wrapper.find('Lifecycle').find({ to: { pathname: INITIALIZE_WELCOME_ROUTE } }).length, 1)
+ })
+
+ it('redirects to / route when completedOnboarding is true', () => {
+ const props = {
+ completedOnboarding: true,
+ }
+ const wrapper = mountWithRouter(
+
+ )
+
+ assert.equal(wrapper.find('Lifecycle').find({ to: { pathname: DEFAULT_ROUTE } }).length, 1)
+ })
+
+ it('redirects to /lock route when isUnlocked is true ', () => {
+ const props = {
+ completedOnboarding: false,
+ isUnlocked: true,
+ }
+
+ const wrapper = mountWithRouter(
+
+ )
+
+ assert.equal(wrapper.find('Lifecycle').find({ to: { pathname: LOCK_ROUTE } }).length, 1)
+ })
+
+ it('redirects to /welcome route when isInitialized is false', () => {
+ const props = {
+ completedOnboarding: false,
+ isUnlocked: false,
+ isInitialized: false,
+ }
+
+ const wrapper = mountWithRouter(
+
+ )
+
+ assert.equal(wrapper.find('Lifecycle').find({ to: { pathname: INITIALIZE_WELCOME_ROUTE } }).length, 1)
+ })
+
+ it('redirects to /unlock route when isInitialized is true', () => {
+ const props = {
+ completedOnboarding: false,
+ isUnlocked: false,
+ isInitialized: true,
+ }
+
+ const wrapper = mountWithRouter(
+
+ )
+
+ assert.equal(wrapper.find('Lifecycle').find({ to: { pathname: INITIALIZE_UNLOCK_ROUTE } }).length, 1)
+ })
+
+})
diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/tests/metametrics-opt-in.test.js b/ui/app/pages/first-time-flow/metametrics-opt-in/tests/metametrics-opt-in.test.js
new file mode 100644
index 000000000000..3d70debdaedd
--- /dev/null
+++ b/ui/app/pages/first-time-flow/metametrics-opt-in/tests/metametrics-opt-in.test.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureMockStore from 'redux-mock-store'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import MetaMetricsOptIn from '../index'
+
+describe('MetaMetricsOptIn', () => {
+ let wrapper
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ setParticipateInMetaMetrics: sinon.stub().resolves(),
+ participateInMetaMetrics: false,
+ }
+
+ const mockStore = {
+ metamask: {},
+ }
+
+ const store = configureMockStore()(mockStore)
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+ , store
+ )
+ })
+
+ afterEach(() => {
+ props.setParticipateInMetaMetrics.resetHistory()
+ })
+
+ it('opt out of metametrics', () => {
+ const noThanksButton = wrapper.find('.btn-default.page-container__footer-button')
+ noThanksButton.simulate('click')
+
+ assert(props.setParticipateInMetaMetrics.calledOnce)
+ assert.equal(props.setParticipateInMetaMetrics.getCall(0).args[0], false)
+ })
+
+})
diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js
new file mode 100644
index 000000000000..41d170b5d1b0
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/tests/reveal-seed-phrase.test.js
@@ -0,0 +1,48 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import RevealSeedPhrase from '../index'
+
+describe('Reveal Seed Phrase', () => {
+ let wrapper
+
+ const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ seedPhrase: TEST_SEED,
+ setSeedPhraseBackedUp: sinon.spy(),
+ setCompletedOnboarding: sinon.spy(),
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ metricsEvent: () => {},
+ },
+ }
+ )
+ })
+
+ it('seed phrase', () => {
+ const seedPhrase = wrapper.find('.reveal-seed-phrase__secret-words--hidden')
+ assert.equal(seedPhrase.length, 1)
+ assert.equal(seedPhrase.text(), TEST_SEED)
+ })
+
+ it('clicks to reveal', () => {
+ const reveal = wrapper.find('.reveal-seed-phrase__secret-blocker')
+
+ assert.equal(wrapper.state().isShowingSeedPhrase, false)
+ reveal.simulate('click')
+ assert.equal(wrapper.state().isShowingSeedPhrase, true)
+
+ const showSeed = wrapper.find('.reveal-seed-phrase__secret-words')
+ assert.equal(showSeed.length, 1)
+ })
+})
diff --git a/ui/app/pages/first-time-flow/select-action/tests/select-action.test.js b/ui/app/pages/first-time-flow/select-action/tests/select-action.test.js
new file mode 100644
index 000000000000..fe88c9a0bab7
--- /dev/null
+++ b/ui/app/pages/first-time-flow/select-action/tests/select-action.test.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import SelectAction from '../index'
+
+describe('Selection Action', () => {
+ let wrapper
+
+ const props = {
+ isInitialized: false,
+ setFirstTimeFlowType: sinon.spy(),
+ history: {
+ push: sinon.spy(),
+ },
+ }
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+
+ )
+ })
+
+ afterEach(() => {
+ props.setFirstTimeFlowType.resetHistory()
+ props.history.push.resetHistory()
+ })
+
+ it('clicks import wallet to route to import FTF', () => {
+ const importWalletButton = wrapper.find('.btn-primary.first-time-flow__button').at(0)
+ importWalletButton.simulate('click')
+
+ assert(props.setFirstTimeFlowType.calledOnce)
+ assert.equal(props.setFirstTimeFlowType.getCall(0).args[0], 'import')
+ assert(props.history.push.calledOnce)
+ })
+
+ it('clicks create wallet to route to create FTF ', () => {
+ const createWalletButton = wrapper.find('.btn-primary.first-time-flow__button').at(1)
+ createWalletButton.simulate('click')
+
+ assert(props.setFirstTimeFlowType.calledOnce)
+ assert.equal(props.setFirstTimeFlowType.getCall(0).args[0], 'create')
+ assert(props.history.push.calledOnce)
+ })
+})
diff --git a/ui/app/pages/first-time-flow/welcome/tests/welcome.test.js b/ui/app/pages/first-time-flow/welcome/tests/welcome.test.js
new file mode 100644
index 000000000000..3649cef11b55
--- /dev/null
+++ b/ui/app/pages/first-time-flow/welcome/tests/welcome.test.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import configureMockStore from 'redux-mock-store'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
+import Welcome from '../index'
+
+describe('Welcome', () => {
+ const mockStore = {
+ metamask: {},
+ }
+
+ const store = configureMockStore()(mockStore)
+
+ after(() => {
+ sinon.restore()
+ })
+
+ it('routes to select action when participateInMetaMetrics is not initialized', () => {
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ }
+
+ const wrapper = mountWithRouter(
+ , store
+ )
+
+ const getStartedButton = wrapper.find('.btn-primary.first-time-flow__button')
+ getStartedButton.simulate('click')
+ assert.equal(props.history.push.getCall(0).args[0], '/initialize/select-action')
+
+ })
+
+ it('routes to correct password when participateInMetaMetrics is initialized', () => {
+
+ const props = {
+ welcomeScreenSeen: true,
+ participateInMetaMetrics: false,
+ history: {
+ push: sinon.spy(),
+ },
+ }
+
+ const wrapper = mountWithRouter(
+ , store
+ )
+
+ const getStartedButton = wrapper.find('.btn-primary.first-time-flow__button')
+ getStartedButton.simulate('click')
+ assert.equal(props.history.push.getCall(0).args[0], '/initialize/create-password')
+ })
+})
diff --git a/ui/app/pages/keychains/tests/reveal-seed.test.js b/ui/app/pages/keychains/tests/reveal-seed.test.js
new file mode 100644
index 000000000000..f63c5ae03d6f
--- /dev/null
+++ b/ui/app/pages/keychains/tests/reveal-seed.test.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import RevealSeedPage from '../reveal-seed'
+
+describe('Reveal Seed Page', () => {
+ let wrapper
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ requestRevealSeedWords: sinon.stub().resolves(),
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ it('form submit', () => {
+ wrapper.find('form').simulate('submit')
+ assert(props.requestRevealSeedWords.calledOnce)
+ })
+})
diff --git a/ui/app/pages/lock/tests/lock.test.js b/ui/app/pages/lock/tests/lock.test.js
new file mode 100644
index 000000000000..ba412ebf8a56
--- /dev/null
+++ b/ui/app/pages/lock/tests/lock.test.js
@@ -0,0 +1,48 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import Lock from '../index'
+
+describe('Lock', () => {
+
+ it('replaces history with default route when isUnlocked false', () => {
+
+ const props = {
+ isUnlocked: false,
+ history: {
+ replace: sinon.spy(),
+ },
+ }
+
+ mountWithRouter(
+
+ )
+
+ assert.equal(props.history.replace.getCall(0).args[0], '/')
+
+ })
+
+ it('locks and pushes history with default route when isUnlocked true', (done) => {
+
+ const props = {
+ isUnlocked: true,
+ lockMetamask: sinon.stub(),
+ history: {
+ push: sinon.spy(),
+ },
+ }
+
+ props.lockMetamask.resolves()
+
+ mountWithRouter(
+
+ )
+
+ assert(props.lockMetamask.calledOnce)
+ setImmediate(() => {
+ assert.equal(props.history.push.getCall(0).args[0], '/')
+ done()
+ })
+ })
+})
diff --git a/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/tests/gas-fee-display.component.test.js
similarity index 100%
rename from ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
rename to ui/app/pages/send/send-content/send-gas-row/gas-fee-display/tests/gas-fee-display.component.test.js
diff --git a/ui/app/pages/settings/security-tab/tests/security-tab.test.js b/ui/app/pages/settings/security-tab/tests/security-tab.test.js
new file mode 100644
index 000000000000..51080cb54ea8
--- /dev/null
+++ b/ui/app/pages/settings/security-tab/tests/security-tab.test.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import SecurityTab from '../index'
+
+describe('Security Tab', () => {
+ let wrapper
+
+ const props = {
+ revealSeedConfirmation: sinon.spy(),
+ showClearApprovalModal: sinon.spy(),
+ setParticipateInMetaMetrics: sinon.spy(),
+ displayWarning: sinon.spy(),
+ setShowIncomingTransactionsFeatureFlag: sinon.spy(),
+ history: {
+ push: sinon.spy(),
+ },
+ privacyMode: true,
+ warning: '',
+ participateInMetaMetrics: false,
+ }
+
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ metricsEvent: () => {},
+ },
+ }
+ )
+ })
+
+ it('navigates to reveal seed words page', () => {
+ const seedWords = wrapper.find('.button.btn-danger.btn--large')
+
+ seedWords.simulate('click')
+ assert(props.history.push.calledOnce)
+ assert.equal(props.history.push.getCall(0).args[0], '/seed')
+ })
+
+ it('toggles incoming txs', () => {
+ const incomingTxs = wrapper.find({ type: 'checkbox' }).at(0)
+ incomingTxs.simulate('click')
+ assert(props.setShowIncomingTransactionsFeatureFlag.calledOnce)
+ })
+
+ it('toggles metaMetrics', () => {
+ const metaMetrics = wrapper.find({ type: 'checkbox' }).at(1)
+
+ metaMetrics.simulate('click')
+ assert(props.setParticipateInMetaMetrics.calledOnce)
+ })
+})
diff --git a/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js b/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js
new file mode 100644
index 000000000000..6481b908dc9f
--- /dev/null
+++ b/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js
@@ -0,0 +1,61 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import SettingsTab from '../index'
+
+describe('Settings Tab', () => {
+ let wrapper
+
+ const props = {
+ setCurrentCurrency: sinon.spy(),
+ displayWarning: sinon.spy(),
+ setUseBlockie: sinon.spy(),
+ updateCurrentLocale: sinon.spy(),
+ setUseNativeCurrencyAsPrimaryCurrencyPreference: sinon.spy(),
+ warning: '',
+ currentLocale: 'en',
+ useBlockie: false,
+ currentCurrency: 'usd',
+ conversionDate: 1,
+ nativeCurrency: 'eth',
+ useNativeCurrencyAsPrimaryCurrency: true,
+ }
+ beforeEach(() => {
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+ })
+
+ it('selects currency', async () => {
+ const selectCurrency = wrapper.find({ placeholder: 'selectCurrency' })
+
+ selectCurrency.props().onSelect('eur')
+ assert(props.setCurrentCurrency.calledOnce)
+ })
+
+ it('selects locale', async () => {
+ const selectLocale = wrapper.find({ placeholder: 'selectLocale' })
+
+ await selectLocale.props().onSelect('ja')
+ assert(props.updateCurrentLocale.calledOnce)
+ })
+
+ it('sets fiat primary currency', () => {
+ const selectFiat = wrapper.find('#fiat-primary-currency')
+
+ selectFiat.simulate('change')
+ assert(props.setUseNativeCurrencyAsPrimaryCurrencyPreference.calledOnce)
+ })
+
+ it('toggles blockies', () => {
+ const toggleBlockies = wrapper.find({ type: 'checkbox' })
+
+ toggleBlockies.simulate('click')
+ assert(props.setUseBlockie.calledOnce)
+ })
+})
diff --git a/ui/app/pages/unlock-page/tests/unlock-page.test.js b/ui/app/pages/unlock-page/tests/unlock-page.test.js
new file mode 100644
index 000000000000..8b1e05729f02
--- /dev/null
+++ b/ui/app/pages/unlock-page/tests/unlock-page.test.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { mount } from 'enzyme'
+import UnlockPage from '../index'
+
+describe('Unlock Page', () => {
+ let wrapper
+
+ const props = {
+ history: {
+ push: sinon.spy(),
+ },
+ isUnlocked: false,
+ onImport: sinon.spy(),
+ onRestore: sinon.spy(),
+ onSubmit: sinon.spy(),
+ forceUpdateMetamaskState: sinon.spy(),
+ showOptInModal: sinon.spy(),
+ }
+
+
+ beforeEach(() => {
+
+ wrapper = mount(
+ , {
+ context: {
+ t: str => str,
+ },
+ }
+ )
+
+ })
+
+ after(() => {
+ sinon.restore()
+ })
+
+ it('renders', () => {
+ assert.equal(wrapper.length, 1)
+ })
+
+ it('changes password and submits', () => {
+ const passwordField = wrapper.find({ type: 'password', id: 'password' })
+ const loginButton = wrapper.find({ type: 'submit' }).last()
+
+ const event = { target: { value: 'password' } }
+ assert.equal(wrapper.instance().state.password, '')
+ passwordField.last().simulate('change', event)
+ assert.equal(wrapper.instance().state.password, 'password')
+
+ loginButton.simulate('click')
+ assert(props.onSubmit.calledOnce)
+ })
+
+ it('clicks imports seed button', () => {
+ const importSeedButton = wrapper.find('.unlock-page__link--import')
+
+ importSeedButton.simulate('click')
+ assert(props.onImport.calledOnce)
+
+ })
+
+ it('clicks restore', () => {
+ const restoreFromSeedButton = wrapper.find('.unlock-page__link').at(0)
+ restoreFromSeedButton.simulate('click')
+ assert(props.onRestore.calledOnce)
+ })
+})
diff --git a/ui/app/pages/unlock-page/unlock-page.component.js b/ui/app/pages/unlock-page/unlock-page.component.js
index 346257a3a91e..2b6916bd7ad2 100644
--- a/ui/app/pages/unlock-page/unlock-page.component.js
+++ b/ui/app/pages/unlock-page/unlock-page.component.js
@@ -94,13 +94,15 @@ export default class UnlockPage extends Component {
this.setState({ password: target.value, error: null })
// tell mascot to look at page action
- const element = target
- const boundingRect = element.getBoundingClientRect()
- const coordinates = getCaretCoordinates(element, element.selectionEnd)
- this.animationEventEmitter.emit('point', {
- x: boundingRect.left + coordinates.left - element.scrollLeft,
- y: boundingRect.top + coordinates.top - element.scrollTop,
- })
+ if (target.getBoundingClientRect) {
+ const element = target
+ const boundingRect = element.getBoundingClientRect()
+ const coordinates = getCaretCoordinates(element, element.selectionEnd)
+ this.animationEventEmitter.emit('point', {
+ x: boundingRect.left + coordinates.left - element.scrollLeft,
+ y: boundingRect.top + coordinates.top - element.scrollTop,
+ })
+ }
}
renderSubmitButton () {
diff --git a/ui/app/selectors/tests/confirm-transaction.test.js b/ui/app/selectors/tests/confirm-transaction.test.js
new file mode 100644
index 000000000000..db8772868002
--- /dev/null
+++ b/ui/app/selectors/tests/confirm-transaction.test.js
@@ -0,0 +1,159 @@
+import assert from 'assert'
+import {
+ unconfirmedTransactionsCountSelector,
+ tokenAmountAndToAddressSelector,
+ approveTokenAmountAndToAddressSelector,
+ sendTokenTokenAmountAndToAddressSelector,
+ contractExchangeRateSelector,
+} from '../confirm-transaction'
+
+describe('Confirm Transaction Selector', () => {
+
+ describe('unconfirmedTransactionsCountSelector', () => {
+
+ const state = {
+ metamask: {
+ unapprovedTxs: {
+ 1: {
+ metamaskNetworkId: 'test',
+ },
+ 2: {
+ metmaskNetworkId: 'other network',
+ },
+ },
+ unapprovedMsgCount: 1,
+ unapprovedPersonalMsgCount: 1,
+ unapprovedTypedMessagesCount: 1,
+ network: 'test',
+ },
+ }
+
+ it('returns number of txs in unapprovedTxs state with the same network plus unapproved signing method counts', () => {
+ assert.equal(unconfirmedTransactionsCountSelector(state), 4)
+ })
+
+ })
+
+ describe('tokenAmountAndToAddressSelector', () => {
+
+ const state = {
+ confirmTransaction: {
+ tokenData: {
+ name: 'transfer',
+ params: [
+ {
+ name: '_to',
+ value: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ type: 'address',
+ },
+ {
+ name: '_value',
+ value: '1',
+ type: 'uint256',
+ },
+ ],
+ },
+ tokenProps: {
+ tokenDecimals: '2',
+ tokenSymbol: 'META',
+ },
+ },
+ }
+
+ it('returns calulcated token amount based on token value and token decimals and recipient address', () => {
+ assert.deepEqual(tokenAmountAndToAddressSelector(state),
+ { toAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', tokenAmount: 0.01 })
+ })
+
+ })
+
+ describe('approveTokenAmountAndToAddressSelector', () => {
+
+ const state = {
+ confirmTransaction: {
+ tokenData: {
+ name: 'approve',
+ params: [
+ {
+ name: '_spender',
+ value: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ type: 'address',
+ },
+ {
+ name: '_value',
+ value: '1',
+ type: 'uint256',
+ },
+ ],
+ },
+ tokenProps: {
+ tokenDecimals: '2',
+ tokenSymbol: 'META',
+ },
+ },
+ }
+
+ it('returns token amount and recipient for approve token allocation spending', () => {
+ assert.deepEqual(approveTokenAmountAndToAddressSelector(state),
+ { toAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', tokenAmount: 0.01 })
+ })
+
+ })
+
+ describe('sendTokenTokenAmountAndToAddressSelector', () => {
+
+ const state = {
+ confirmTransaction: {
+ tokenData: {
+ name: 'transfer',
+ params: [
+ {
+ name: '_to',
+ value: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ type: 'address',
+ },
+ {
+ name: '_value',
+ value: '1',
+ type: 'uint256',
+ },
+ ],
+ },
+ tokenProps: {
+ tokenDecimals: '2',
+ tokenSymbol: 'META',
+ },
+ },
+ }
+
+ it('returns token address and calculated token amount', () => {
+ assert.deepEqual(sendTokenTokenAmountAndToAddressSelector(state),
+ { toAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', tokenAmount: 0.01 })
+ })
+
+ })
+
+ describe('contractExchangeRateSelector', () => {
+
+ const state = {
+ metamask: {
+ contractExchangeRates: {
+ '0xTokenAddress': '10',
+ },
+ },
+ confirmTransaction: {
+ txData: {
+ txParams: {
+ to: '0xTokenAddress',
+ },
+ },
+ },
+ }
+
+ it('returns contract exchange rate in metamask state based on confirm transaction txParams token recipient', () => {
+ assert.equal(contractExchangeRateSelector(state), 10)
+ })
+
+ })
+})
+
diff --git a/ui/app/selectors/tests/tokens.test.js b/ui/app/selectors/tests/tokens.test.js
new file mode 100644
index 000000000000..d058f4a05148
--- /dev/null
+++ b/ui/app/selectors/tests/tokens.test.js
@@ -0,0 +1,28 @@
+import assert from 'assert'
+import { selectedTokenSelector } from '../tokens'
+
+const metaToken = {
+ 'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4',
+ 'symbol': 'META',
+ 'decimals': 18,
+}
+
+const state = {
+ metamask: {
+ selectedTokenAddress: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4',
+ tokens: [
+ {
+ 'address': '0x06012c8cf97bead5deae237070f9587f8e7a266d',
+ 'symbol': 'CK',
+ 'decimals': 0,
+ },
+ metaToken,
+ ],
+ },
+}
+describe('Selected Token Selector', () => {
+ it('selects token info from tokens based on selectedTokenAddress in state', () => {
+ const tokenInfo = selectedTokenSelector(state)
+ assert.equal(tokenInfo, metaToken)
+ })
+})
diff --git a/ui/app/selectors/tests/transactions.test.js b/ui/app/selectors/tests/transactions.test.js
new file mode 100644
index 000000000000..3ab1be47c59c
--- /dev/null
+++ b/ui/app/selectors/tests/transactions.test.js
@@ -0,0 +1,356 @@
+import assert from 'assert'
+import {
+ unapprovedMessagesSelector,
+ transactionsSelector,
+ nonceSortedTransactionsSelector,
+ nonceSortedPendingTransactionsSelector,
+ nonceSortedCompletedTransactionsSelector,
+ submittedPendingTransactionsSelector,
+} from '../transactions'
+
+describe('Transaction Selectors', () => {
+
+ describe('unapprovedMessagesSelector', () => {
+ it('returns eth sign msg from unapprovedMsgs', () => {
+
+ const msg = {
+ id: 1,
+ msgParams: {
+ from: '0xAddress',
+ data: '0xData',
+ origin: 'origin',
+ },
+ time: 1,
+ status: 'unapproved',
+ type: 'eth_sign',
+ }
+
+ const state = {
+ metamask: {
+ unapprovedMsgs: {
+ 1: msg,
+ },
+ },
+ }
+
+ const msgSelector = unapprovedMessagesSelector(state)
+
+ assert(Array.isArray(msgSelector))
+ assert.deepEqual(msgSelector, [msg])
+ })
+
+ it('returns personal sign from unapprovedPersonalMsgsSelector', () => {
+
+ const msg = {
+ id: 1,
+ msgParams: {
+ from: '0xAddress',
+ data: '0xData',
+ origin: 'origin',
+ },
+ time: 1,
+ status: 'unapproved',
+ type: 'personal_sign',
+ }
+
+ const state = {
+ metamask: {
+ unapprovedPersonalMsgs: {
+ 1: msg,
+ },
+ },
+ }
+
+ const msgSelector = unapprovedMessagesSelector(state)
+
+ assert(Array.isArray(msgSelector))
+ assert.deepEqual(msgSelector, [msg])
+ })
+
+ it('returns typed message from unapprovedTypedMessagesSelector', () => {
+
+ const msg = {
+ id: 1,
+ msgParams: {
+ data: '0xData',
+ from: '0xAddress',
+ version: 'V3',
+ origin: 'origin',
+ },
+ time: 1,
+ status: 'unapproved',
+ type: 'eth_signTypedData',
+ }
+
+ const state = {
+ metamask: {
+ unapprovedTypedMessages: {
+ 1: msg,
+ },
+ },
+ }
+
+ const msgSelector = unapprovedMessagesSelector(state)
+
+ assert(Array.isArray(msgSelector))
+ assert.deepEqual(msgSelector, [msg])
+
+ })
+ })
+
+ describe('transactionsSelector', () => {
+
+ it('selectedAddressTxList', () => {
+
+ const state = {
+ metamask: {
+ featureFlags: {
+ showIncomingTransactions: false,
+ },
+ selectedAddressTxList: [
+ {
+ id: 0,
+ time: 0,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ },
+ },
+ {
+ id: 1,
+ time: 1,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ },
+ },
+ ],
+ },
+ }
+
+ const orderedTxlist = state.metamask.selectedAddressTxList.sort((a, b) => b.time - a.time)
+
+ const txSelector = transactionsSelector(state)
+
+ assert(Array.isArray(txSelector))
+ assert.deepEqual(txSelector, orderedTxlist)
+ })
+
+ it('returns token tx from selectedAddressTxList when selectedTokenAddress is valid', () => {
+
+ const state = {
+ metamask: {
+ featureFlags: {
+ showIncomingTransactions: false,
+ },
+ selectedTokenAddress: '0xToken',
+ selectedAddressTxList: [
+ {
+ id: 0,
+ time: 0,
+ txParams: {
+ from: '0xAddress',
+ to: '0xToken',
+ },
+ },
+ {
+ id: 1,
+ time: 1,
+ txParams: {
+ from: '0xAddress',
+ to: '0xToken',
+ },
+ },
+ ],
+ },
+
+ }
+
+ const orderedTxlist = state.metamask.selectedAddressTxList.sort((a, b) => b.time - a.time)
+
+ const txSelector = transactionsSelector(state)
+
+ assert(Array.isArray(txSelector))
+ assert.deepEqual(txSelector, orderedTxlist)
+
+ })
+
+ })
+
+ describe('nonceSortedTransactionsSelector', () => {
+
+ it('returns transaction group nonce sorted tx from from selectedTxList wit', () => {
+
+ const tx1 = {
+ id: 0,
+ time: 0,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x0',
+ },
+ }
+
+ const tx2 = {
+ id: 1,
+ time: 1,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x1',
+ },
+ }
+
+ const state = {
+ metamask: {
+ featureFlags: {
+ showIncomingTransactions: false,
+ },
+ selectedAddressTxList: [
+ tx1,
+ tx2,
+ ],
+ },
+ }
+
+ const expectedResult = [
+ {
+ nonce: '0x0',
+ transactions: [ tx1 ],
+ initialTransaction: tx1,
+ primaryTransaction: tx1,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ {
+ nonce: '0x1',
+ transactions: [ tx2 ],
+ initialTransaction: tx2,
+ primaryTransaction: tx2,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ ]
+
+ assert.deepEqual(nonceSortedTransactionsSelector(state), expectedResult)
+ })
+ })
+
+ describe('Sorting Transactions Selectors', () => {
+
+ const submittedTx = {
+ id: 0,
+ time: 0,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x0',
+ },
+ status: 'submitted',
+ }
+
+ const unapprovedTx = {
+ id: 1,
+ time: 1,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x1',
+ },
+ status: 'unapproved',
+ }
+
+ const approvedTx = {
+ id: 2,
+ time: 2,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x2',
+ },
+ status: 'approved',
+ }
+
+ const confirmedTx = {
+ id: 3,
+ time: 3,
+ txParams: {
+ from: '0xAddress',
+ to: '0xRecipient',
+ nonce: '0x3',
+ },
+ status: 'confirmed',
+ }
+
+ const state = {
+ metamask: {
+ featureFlags: {
+ showIncomingTransactions: false,
+ },
+ selectedAddressTxList: [
+ submittedTx,
+ unapprovedTx,
+ approvedTx,
+ confirmedTx,
+ ],
+ },
+ }
+
+ it('nonceSortedPendingTransactionsSelector', () => {
+
+ const expectedResult = [
+ {
+ nonce: submittedTx.txParams.nonce,
+ transactions: [ submittedTx ],
+ initialTransaction: submittedTx,
+ primaryTransaction: submittedTx,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ {
+ nonce: unapprovedTx.txParams.nonce,
+ transactions: [ unapprovedTx ],
+ initialTransaction: unapprovedTx,
+ primaryTransaction: unapprovedTx,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ {
+ nonce: approvedTx.txParams.nonce,
+ transactions: [ approvedTx ],
+ initialTransaction: approvedTx,
+ primaryTransaction: approvedTx,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ ]
+
+ assert.deepEqual(nonceSortedPendingTransactionsSelector(state), expectedResult)
+ })
+
+ it('nonceSortedCompletedTransactionsSelector', () => {
+
+ const expectedResult = [
+ {
+ nonce: confirmedTx.txParams.nonce,
+ transactions: [ confirmedTx ],
+ initialTransaction: confirmedTx,
+ primaryTransaction: confirmedTx,
+ hasRetried: false,
+ hasCancelled: false,
+ },
+ ]
+
+ assert.deepEqual(nonceSortedCompletedTransactionsSelector(state), expectedResult)
+ })
+
+ it('submittedPendingTransactionsSelector', () => {
+
+ const expectedResult = [ submittedTx ]
+ assert.deepEqual(submittedPendingTransactionsSelector(state), expectedResult)
+
+ })
+
+ })
+
+})