Skip to content

Commit

Permalink
Replaced authentication actions/reducers with new actions architecture (
Browse files Browse the repository at this point in the history
#586)

* Add failed screen when data can't be fetched

* Replaced module auth functions with actions

* Fixed withData to account for data not being loaded yet

* Added redirects when user logs in or out

* Added error notifications to login actions

* Fixed showing back to back error notifications

* Added wallet reload when network changes
  • Loading branch information
mhuggins authored and dvdschwrtz committed Feb 2, 2018
1 parent ac05e39 commit 0f1dd3c
Show file tree
Hide file tree
Showing 61 changed files with 710 additions and 671 deletions.
5 changes: 4 additions & 1 deletion __tests__/components/App.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'
import thunk from 'redux-thunk'
import storage from 'electron-json-storage'
import configureStore from 'redux-mock-store'
Expand Down Expand Up @@ -54,7 +55,9 @@ const setup = (state, shallowRender = true) => {
} else {
wrapper = mount(
<Provider store={store}>
<App />
<MemoryRouter>
<App />
</MemoryRouter>
</Provider>
)
}
Expand Down
63 changes: 6 additions & 57 deletions __tests__/components/LoginNep2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import { shallow, mount } from 'enzyme'
import { createMemoryHistory } from 'history'

import LoginNep2 from '../../app/containers/LoginNep2'
import { SHOW_NOTIFICATION, HIDE_NOTIFICATIONS, HIDE_NOTIFICATION, DEFAULT_POSITION } from '../../app/modules/notifications'
import { LOGIN } from '../../app/modules/account'
import { NOTIFICATION_LEVELS } from '../../app/core/constants'

jest.useFakeTimers()
jest.mock('neon-js')
Expand Down Expand Up @@ -60,6 +57,7 @@ describe('LoginNep2', () => {
expect(keyField.props.type).toEqual('password')
done()
})

test('the login button is working correctly with no passphrase or wif', (done) => {
const { wrapper, store } = setup(false)

Expand All @@ -70,35 +68,8 @@ describe('LoginNep2', () => {
done()
})
})
// test('the login button is working correctly with only a short passphrase', (done) => {
// const { wrapper, store } = setup(false)

// const passwordField = wrapper.find('input[placeholder="Enter your passphrase here"]')
// passwordField.instance().value = 'T'
// passwordField.simulate('change')

// const keyField = wrapper.find('input[placeholder="Enter your encrypted key here"]')
// keyField.instance().value = '6PYUGtvXiT5TBetgWf77QyAFidQj61V8FJeFBFtYttmsSxcbmP4vCFRCWu'
// keyField.simulate('change')

// wrapper.find('#loginButton').simulate('click')

// Promise.resolve('pause').then(() => {
// jest.runAllTimers()
// const actions = store.getActions()
// expect(actions.length).toEqual(2)
// expect(actions[0]).toEqual({
// type: SEND_TRANSACTION,
// success: false,
// message: 'Passphrase too short'
// })
// expect(actions[1]).toEqual({
// type: CLEAR_TRANSACTION
// })
// done()
// })
// })
test('the login button is working correctly with key and passphrase', (done) => {

test('the login button is working correctly with key and passphrase', () => {
const { wrapper, store } = setup(false)

const passwordField = wrapper.find('input[placeholder="Enter your passphrase here"]')
Expand All @@ -110,31 +81,9 @@ describe('LoginNep2', () => {
keyField.simulate('change')

wrapper.find('#loginButton').first().simulate('submit')
Promise.resolve('Pause').then().then()
jest.runAllTimers()

const actions = store.getActions()
expect(actions.length).toEqual(4)
expect(actions[0]).toEqual({
type: HIDE_NOTIFICATIONS,
payload: {
dismissible: true,
position: DEFAULT_POSITION
}
})
expect(actions[1]).toEqual({
type: SHOW_NOTIFICATION,
payload: expect.objectContaining({
message: 'Decrypting encoded key...',
level: NOTIFICATION_LEVELS.INFO
})
})
expect(actions[2]).toEqual({
type: HIDE_NOTIFICATION,
payload: expect.objectContaining({
id: 'notification_1'
})
})
expect(actions[3]).toHaveProperty('type', LOGIN)
done()
expect(actions.length).toEqual(1)
expect(actions[0].type).toEqual('ACCOUNT/REQ/REQUEST')
})
})
9 changes: 5 additions & 4 deletions __tests__/components/Logout.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React from 'react'
import { shallow } from 'enzyme'

import Logout from '../../app/containers/App/Header/Logout'
import Logout from '../../app/containers/App/Header/Logout/Logout'

describe('Logout', () => {
const logout = jest.fn()

test('should render without crashing', () => {
const wrapper = shallow(<Logout onClick={logout} />)
const wrapper = shallow(<Logout logout={logout} />)
expect(wrapper).toMatchSnapshot()
})

test('should dispatch logout action when clicked', () => {
const wrapper = shallow(<Logout onClick={logout} />)
const wrapper = shallow(<Logout logout={logout} />)
expect(logout.mock.calls.length).toEqual(0)
wrapper.find('.logout').simulate('click')
wrapper.find('#logout').simulate('click')
expect(logout.mock.calls.length).toEqual(1)
})
})
12 changes: 7 additions & 5 deletions __tests__/components/WalletInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ const initialState = {
state: LOADED,
data: MAIN_NETWORK_ID
},
ACCOUNT: {
batch: false,
state: LOADED,
data: {
address: 'ANqUrhv99rwCiFTL6N1An9NH5UVkPYxTuw'
}
},
SETTINGS: {
batch: false,
state: LOADED,
Expand All @@ -70,11 +77,6 @@ const initialState = {
}
}
},
account: {
address: 'ANqUrhv99rwCiFTL6N1An9NH5UVkPYxTuw'
},
metadata: {
},
wallet: {
NEO: '100001',
GAS: '1000.0001601',
Expand Down
2 changes: 1 addition & 1 deletion __tests__/components/__snapshots__/LoginNep2.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`LoginNep2 renders without crashing 1`] = `
<LoginNep2
<withActions(Connect(Connect(withError(Connect(withProgressProp(ErrorNotifier))))))
loginNep2={[Function]}
store={
Object {
Expand Down
13 changes: 4 additions & 9 deletions __tests__/components/__snapshots__/Logout.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,10 @@ exports[`Logout should render without crashing 1`] = `
id="logout"
onClick={[Function]}
>
<Link
replace={false}
to=""
<Tooltip
title="Logout"
>
<Tooltip
title="Logout"
>
<MdPowerSettingsNew />
</Tooltip>
</Link>
<MdPowerSettingsNew />
</Tooltip>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TransactionHistory renders without crashing 1`] = `
<Connect(withData(mapProps(TransactionHistory)))
address="AWy7RNBVr9vDadRMK9p7i7Z1tL7GrLAxoh"
<Connect(withData(mapProps(Connect(withData(TransactionHistory)))))
isLoadingTransactions={false}
store={
Object {
Expand Down
3 changes: 1 addition & 2 deletions __tests__/components/__snapshots__/WalletInfo.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`WalletInfo renders without crashing 1`] = `
<Connect(withData(Connect(withData(mapProps(Connect(withData(Connect(withActions(WalletInfo)))))))))
<Connect(withData(Connect(withData(mapProps(Connect(withData(Connect(withData(Connect(withActions(WalletInfo)))))))))))
GAS="1000.0001601"
NEO="100001"
address="ANqUrhv99rwCiFTL6N1An9NH5UVkPYxTuw"
loadWalletData={[Function]}
networks={
Array [
Expand Down
35 changes: 0 additions & 35 deletions __tests__/modules/account.test.js

This file was deleted.

5 changes: 0 additions & 5 deletions __tests__/modules/dashboard.test.js

This file was deleted.

5 changes: 1 addition & 4 deletions __tests__/store/reducers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ describe('root reducer', () => {
it('should combine all reducers', () => {
expect(reducer({}, { type: '@@INIT' })).toEqual({
api: expect.any(Object),
account: expect.any(Object),
addressBook: expect.any(Object),
generateWallet: expect.any(Object),
wallet: expect.any(Object),
transactions: expect.any(Object),
dashboard: expect.any(Object),
notifications: expect.any(Object),
claim: expect.any(Object),
modal: expect.any(Object),
sale: expect.any(Object)
modal: expect.any(Object)
})
})
})
69 changes: 69 additions & 0 deletions app/actions/accountActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// @flow
import { wallet } from 'neon-js'
import { noop } from 'lodash'

import createRequestActions from '../util/api/createRequestActions'
import { upgradeNEP6AddAddresses } from '../core/account'
import { validatePassphraseLength } from '../core/wallet'

type WifLoginProps = {
wif: string
}

type LedgerLoginProps = {
publicKey: string,
signingFunction: Function
}

type Nep2LoginProps = {
passphrase: string,
encryptedWIF: string
}

type AccountType = ?{
address: string,
wif?: string,
publicKey?: string,
signingFunction?: Function,
isHardwareLogin: boolean
}

export const ID = 'ACCOUNT'

export const wifLoginActions = createRequestActions(ID, ({ wif }: WifLoginProps) => (state: Object): AccountType => {
const account = new wallet.Account(wif)

return { wif, address: account.address, isHardwareLogin: false }
})

export const nep2LoginActions = createRequestActions(ID, ({ passphrase, encryptedWIF }: Nep2LoginProps) => async (state: Object): Promise<AccountType> => {
if (!validatePassphraseLength(passphrase)) {
throw new Error('Passphrase too short')
}

if (!wallet.isNEP2(encryptedWIF)) {
throw new Error('That is not a valid encrypted key')
}

const wif = wallet.decrypt(encryptedWIF, passphrase)
const account = new wallet.Account(wif)

await upgradeNEP6AddAddresses(encryptedWIF, wif)

return { wif, address: account.address, isHardwareLogin: false }
})

export const ledgerLoginActions = createRequestActions(ID, ({ publicKey, signingFunction }: LedgerLoginProps) => (state: Object): AccountType => {
const publicKeyEncoded = wallet.getPublicKeyEncoded(publicKey)
const account = new wallet.Account(publicKeyEncoded)

return { address: account.address, publicKey, signingFunction, isHardwareLogin: true }
})

export const logoutActions = createRequestActions(ID, () => (state: Object): AccountType => {
return null
})

// TODO: Better way to expose action data than to make a faux function? One idea is to change
// `withData` to accept the `ID` exported from this file instead of a generated action.
export default createRequestActions(ID, () => (state: Object) => noop)
29 changes: 29 additions & 0 deletions app/actions/accountsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @flow
import createRequestActions from '../util/api/createRequestActions'
import { getStorage, setStorage } from '../core/storage'
import { DEFAULT_WALLET } from '../core/constants'

type Props = {
networkId: string
}

const STORAGE_KEY = 'userWallet'

const getWallet = async (): Promise<Object> => {
return await getStorage(STORAGE_KEY) || DEFAULT_WALLET
}

export const ID = 'ACCOUNTS'

export const updateAccountsActions = createRequestActions(ID, (accounts) => async (state: Object): Promise<Object> => {
const userWallet = await getWallet()
const newWallet = { ...userWallet, accounts }
await setStorage(STORAGE_KEY, newWallet)

return newWallet
})

export default createRequestActions(ID, ({ networkId }: Props = {}) => async (state: Object): Promise<Object> => {
const userWallet = await getWallet()
return userWallet.accounts
})
3 changes: 2 additions & 1 deletion app/actions/appActions.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// @flow
import createBatchActions from '../util/api/createBatchActions'

import accountsActions from './accountsActions'
import blockHeightActions from './blockHeightActions'
import pricesActions from './pricesActions'
import settingsActions from './settingsActions'

export const ID = 'APP'

export default createBatchActions(ID, {
accounts: accountsActions,
blockHeight: blockHeightActions,
prices: pricesActions,
settings: settingsActions
Expand Down
Loading

0 comments on commit 0f1dd3c

Please sign in to comment.