WA-232 Improvement: Only fetch balance of activated tokens. Included Tests
This commit is contained in:
parent
7de871ba81
commit
3fa7a10a16
|
@ -4,7 +4,7 @@ import Loadable from 'react-loadable'
|
||||||
import { Switch, Redirect, Route } from 'react-router-dom'
|
import { Switch, Redirect, Route } from 'react-router-dom'
|
||||||
import Loader from '~/components/Loader'
|
import Loader from '~/components/Loader'
|
||||||
import Welcome from './welcome/container'
|
import Welcome from './welcome/container'
|
||||||
import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS } from './routes'
|
import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS, SETTINS_ADDRESS } from './routes'
|
||||||
|
|
||||||
const Safe = Loadable({
|
const Safe = Loadable({
|
||||||
loader: () => import('./safe/container'),
|
loader: () => import('./safe/container'),
|
||||||
|
@ -27,7 +27,7 @@ const Open = Loadable({
|
||||||
})
|
})
|
||||||
|
|
||||||
const SAFE_ADDRESS = `${SAFELIST_ADDRESS}/:${SAFE_PARAM_ADDRESS}`
|
const SAFE_ADDRESS = `${SAFELIST_ADDRESS}/:${SAFE_PARAM_ADDRESS}`
|
||||||
const SAFE_SETTINGS = `${SAFE_ADDRESS}/settings`
|
const SAFE_SETTINGS = `${SAFE_ADDRESS}${SETTINS_ADDRESS}`
|
||||||
|
|
||||||
export const settingsUrlFrom = (safeAddress: string) => `${SAFELIST_ADDRESS}/${safeAddress}/settings`
|
export const settingsUrlFrom = (safeAddress: string) => `${SAFELIST_ADDRESS}/${safeAddress}/settings`
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,4 @@ export const SAFE_PARAM_ADDRESS = 'address'
|
||||||
export const SAFELIST_ADDRESS = '/safes'
|
export const SAFELIST_ADDRESS = '/safes'
|
||||||
export const OPEN_ADDRESS = '/open'
|
export const OPEN_ADDRESS = '/open'
|
||||||
export const WELCOME_ADDRESS = '/welcome'
|
export const WELCOME_ADDRESS = '/welcome'
|
||||||
|
export const SETTINS_ADDRESS = '/settings'
|
||||||
|
|
|
@ -51,6 +51,7 @@ class TokenComponent extends React.Component<Props, State> {
|
||||||
|
|
||||||
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
|
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
const { checked } = e.target
|
const { checked } = e.target
|
||||||
|
|
||||||
const callback = checked ? this.props.onEnableToken : this.props.onDisableToken
|
const callback = checked ? this.props.onEnableToken : this.props.onDisableToken
|
||||||
this.setState(() => ({ checked }), () => callback(this.props.token))
|
this.setState(() => ({ checked }), () => callback(this.props.token))
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,7 @@ class TokenComponent extends React.Component<Props, State> {
|
||||||
<Typography variant="subheading" color="textSecondary">
|
<Typography variant="subheading" color="textSecondary">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
checked={this.state.checked}
|
checked={!!this.state.checked}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -30,14 +30,18 @@ export const calculateBalanceOf = async (tokenAddress: string, address: string,
|
||||||
.catch(() => '0')
|
.catch(() => '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchTokensData = async () => {
|
||||||
|
const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/'
|
||||||
|
const errMsg = 'Error querying safe balances'
|
||||||
|
return enhancedFetch(url, errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
export const fetchTokens = (safeAddress: string) =>
|
export const fetchTokens = (safeAddress: string) =>
|
||||||
async (dispatch: ReduxDispatch<GlobalState>) => {
|
async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
const tokens: List<string> = getTokens(safeAddress)
|
const tokens: List<string> = getTokens(safeAddress)
|
||||||
const ethBalance = await getSafeEthToken(safeAddress)
|
const ethBalance = await getSafeEthToken(safeAddress)
|
||||||
|
|
||||||
const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/'
|
const json = await exports.fetchTokensData()
|
||||||
const errMsg = 'Error querying safe balances'
|
|
||||||
const json = await enhancedFetch(url, errMsg)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
|
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { sleep } from '~/utils/timer'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { ConnectedRouter } from 'react-router-redux'
|
import { ConnectedRouter } from 'react-router-redux'
|
||||||
import AppRoutes from '~/routes'
|
import AppRoutes from '~/routes'
|
||||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
import { SAFELIST_ADDRESS, SETTINS_ADDRESS } from '~/routes/routes'
|
||||||
import { history, type GlobalState } from '~/store'
|
import { history, type GlobalState } from '~/store'
|
||||||
import { EMPTY_DATA } from '~/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/wallets/ethTransactions'
|
||||||
|
|
||||||
|
@ -93,15 +93,25 @@ export const refreshTransactions = async (store: Store<GlobalState>) => {
|
||||||
await sleep(1500)
|
await sleep(1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const travelToSafe = (store: Store, address: string): React$Component<{}> => {
|
const createDom = (store: Store): React$Component<{}> => (
|
||||||
history.push(`${SAFELIST_ADDRESS}/${address}`)
|
TestUtils.renderIntoDocument((
|
||||||
const SafeDom = TestUtils.renderIntoDocument((
|
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
))
|
))
|
||||||
|
)
|
||||||
|
|
||||||
return SafeDom
|
export const travelToSafe = (store: Store, address: string): React$Component<{}> => {
|
||||||
|
history.push(`${SAFELIST_ADDRESS}/${address}`)
|
||||||
|
|
||||||
|
return createDom(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const travelToTokens = (store: Store, address: string): React$Component<{}> => {
|
||||||
|
const url = `${SAFELIST_ADDRESS}/${address}${SETTINS_ADDRESS}`
|
||||||
|
history.push(url)
|
||||||
|
|
||||||
|
return createDom(store)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import TestUtils from 'react-dom/test-utils'
|
||||||
import * as fetchBalancesAction from '~/routes/tokens/store/actions/fetchTokens'
|
import * as fetchBalancesAction from '~/routes/tokens/store/actions/fetchTokens'
|
||||||
import { aNewStore } from '~/store'
|
import { aNewStore } from '~/store'
|
||||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||||
import { addTknTo, getTokenContract } from '~/test/utils/tokenMovements'
|
import { addTknTo, getFirstTokenContract } from '~/test/utils/tokenMovements'
|
||||||
import { EXPAND_BALANCE_INDEX, travelToSafe } from '~/test/builder/safe.dom.utils'
|
import { EXPAND_BALANCE_INDEX, travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||||
import { promisify } from '~/utils/promisify'
|
import { promisify } from '~/utils/promisify'
|
||||||
import { getWeb3 } from '~/wallets/getWeb3'
|
import { getWeb3 } from '~/wallets/getWeb3'
|
||||||
|
@ -47,14 +47,14 @@ describe('DOM > Feature > SAFE ERC20 TOKENS', () => {
|
||||||
const receiverFunds = await fetchBalancesAction.calculateBalanceOf(tokenAddress, receiver, 18)
|
const receiverFunds = await fetchBalancesAction.calculateBalanceOf(tokenAddress, receiver, 18)
|
||||||
expect(Number(receiverFunds)).toBe(20)
|
expect(Number(receiverFunds)).toBe(20)
|
||||||
|
|
||||||
const token = await getTokenContract(getWeb3(), accounts[0])
|
const token = await getFirstTokenContract(getWeb3(), accounts[0])
|
||||||
const nativeSafeFunds = await token.balanceOf(safeAddress)
|
const nativeSafeFunds = await token.balanceOf(safeAddress)
|
||||||
expect(Number(nativeSafeFunds.valueOf())).toEqual(80 * (10 ** 18))
|
expect(Number(nativeSafeFunds.valueOf())).toEqual(80 * (10 ** 18))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('disables send token button when balance is 0', async () => {
|
it('disables send token button when balance is 0', async () => {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
const token = await getTokenContract(getWeb3(), accounts[0])
|
const token = await getFirstTokenContract(getWeb3(), accounts[0])
|
||||||
await dispatchTknBalance(store, token.address, safeAddress)
|
await dispatchTknBalance(store, token.address, safeAddress)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
// @flow
|
||||||
|
import * as TestUtils from 'react-dom/test-utils'
|
||||||
|
import { getWeb3 } from '~/wallets/getWeb3'
|
||||||
|
import { type Match } from 'react-router-dom'
|
||||||
|
import { promisify } from '~/utils/promisify'
|
||||||
|
import TokenComponent from '~/routes/tokens/component/Token'
|
||||||
|
import Checkbox from '@material-ui/core/Checkbox'
|
||||||
|
import { getFirstTokenContract, getSecondTokenContract, addTknTo } from '~/test/utils/tokenMovements'
|
||||||
|
import { aNewStore } from '~/store'
|
||||||
|
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||||
|
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||||
|
import { sleep } from '~/utils/timer'
|
||||||
|
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||||
|
import { tokenListSelector, activeTokensSelector } from '~/routes/tokens/store/selectors'
|
||||||
|
import { type Token } from '~/routes/tokens/store/model/token'
|
||||||
|
|
||||||
|
const fetchTokensModule = require('../routes/tokens/store/actions/fetchTokens')
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
fetchTokensModule.fetchTokensData = jest.fn()
|
||||||
|
|
||||||
|
describe('DOM > Feature > Enable and disable default tokens', () => {
|
||||||
|
let web3
|
||||||
|
let accounts
|
||||||
|
let firstErc20Token
|
||||||
|
let secondErc20Token
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
web3 = getWeb3()
|
||||||
|
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||||
|
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||||
|
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||||
|
// $FlowFixMe
|
||||||
|
fetchTokensModule.fetchTokensData.mockImplementation(() => Promise.resolve([
|
||||||
|
{
|
||||||
|
address: firstErc20Token.address,
|
||||||
|
name: 'First Token Example',
|
||||||
|
symbol: 'FTE',
|
||||||
|
decimals: 18,
|
||||||
|
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: secondErc20Token.address,
|
||||||
|
name: 'Second Token Example',
|
||||||
|
symbol: 'STE',
|
||||||
|
decimals: 18,
|
||||||
|
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||||
|
},
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('retrieves only ether as active token', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const store = aNewStore()
|
||||||
|
const safeAddress = await aMinedSafe(store)
|
||||||
|
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const TokensDom = await travelToTokens(store, safeAddress)
|
||||||
|
await sleep(400)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
|
||||||
|
expect(tokens.length).toBe(3)
|
||||||
|
|
||||||
|
const firstToken = tokens[0]
|
||||||
|
expect(firstToken.props.token.get('symbol')).toBe('FTE')
|
||||||
|
expect(firstToken.props.token.get('status')).toBe(false)
|
||||||
|
|
||||||
|
const secontToken = tokens[1]
|
||||||
|
expect(secontToken.props.token.get('symbol')).toBe('STE')
|
||||||
|
expect(secontToken.props.token.get('status')).toBe(false)
|
||||||
|
|
||||||
|
const etherToken = tokens[2]
|
||||||
|
expect(etherToken.props.token.get('symbol')).toBe('ETH')
|
||||||
|
expect(etherToken.props.token.get('status')).toBe(true)
|
||||||
|
|
||||||
|
const ethCheckbox = TestUtils.findRenderedComponentWithType(etherToken, Checkbox)
|
||||||
|
if (!ethCheckbox) throw new Error()
|
||||||
|
expect(ethCheckbox.props.disabled).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
const testToken = (token: Token | typeof undefined, symbol: string, status: boolean, funds?: string) => {
|
||||||
|
if (!token) throw new Error()
|
||||||
|
expect(token.get('symbol')).toBe(symbol)
|
||||||
|
expect(token.get('status')).toBe(status)
|
||||||
|
if (funds) {
|
||||||
|
expect(token.get('funds')).toBe(funds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('fetch balances of only enabled tokens', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const store = aNewStore()
|
||||||
|
const safeAddress = await aMinedSafe(store)
|
||||||
|
await addTknTo(safeAddress, 50, firstErc20Token)
|
||||||
|
await addTknTo(safeAddress, 50, secondErc20Token)
|
||||||
|
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||||
|
const TokensDom = await travelToTokens(store, safeAddress)
|
||||||
|
await sleep(400)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(TokensDom, 'input')
|
||||||
|
|
||||||
|
const ethTokenInput = inputs[2]
|
||||||
|
expect(ethTokenInput.hasAttribute('disabled')).toBe(true)
|
||||||
|
const firstTokenInput = inputs[0]
|
||||||
|
expect(firstTokenInput.hasAttribute('disabled')).toBe(false)
|
||||||
|
TestUtils.Simulate.change(firstTokenInput, { target: { checked: 'true' } })
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const match: Match = buildMathPropsFrom(safeAddress)
|
||||||
|
const tokenList = tokenListSelector(store.getState(), { match })
|
||||||
|
|
||||||
|
testToken(tokenList.get(0), 'FTE', true)
|
||||||
|
testToken(tokenList.get(1), 'STE', false)
|
||||||
|
testToken(tokenList.get(2), 'ETH', true)
|
||||||
|
|
||||||
|
const activeTokenList = activeTokensSelector(store.getState(), { match })
|
||||||
|
expect(activeTokenList.count()).toBe(2)
|
||||||
|
|
||||||
|
testToken(activeTokenList.get(0), 'FTE', true)
|
||||||
|
testToken(activeTokenList.get(1), 'ETH', true)
|
||||||
|
|
||||||
|
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||||
|
|
||||||
|
const fundedTokenList = tokenListSelector(store.getState(), { match })
|
||||||
|
expect(fundedTokenList.count()).toBe(3)
|
||||||
|
|
||||||
|
testToken(fundedTokenList.get(0), 'FTE', true, '50')
|
||||||
|
testToken(fundedTokenList.get(1), 'STE', false, '0')
|
||||||
|
testToken(fundedTokenList.get(2), 'ETH', true, '0')
|
||||||
|
})
|
||||||
|
})
|
|
@ -39,13 +39,14 @@ const createTokenContract = async (web3: any, executor: string) => {
|
||||||
return token.new({ from: executor, gas: '5000000' })
|
return token.new({ from: executor, gas: '5000000' })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTokenContract = ensureOnce(createTokenContract)
|
export const getFirstTokenContract = ensureOnce(createTokenContract)
|
||||||
|
export const getSecondTokenContract = ensureOnce(createTokenContract)
|
||||||
|
|
||||||
export const addTknTo = async (safe: string, value: number) => {
|
export const addTknTo = async (safe: string, value: number, tokenContract?: any) => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||||
|
|
||||||
const myToken = await getTokenContract(web3, accounts[0])
|
const myToken = tokenContract || await getFirstTokenContract(web3, accounts[0])
|
||||||
const nativeValue = await toNative(value, 18)
|
const nativeValue = await toNative(value, 18)
|
||||||
await myToken.transfer(safe, nativeValue.valueOf(), { from: accounts[0], gas: '5000000' })
|
await myToken.transfer(safe, nativeValue.valueOf(), { from: accounts[0], gas: '5000000' })
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue