From 3fa7a10a16950fff3ae818baf16a434534df2a5f Mon Sep 17 00:00:00 2001 From: apanizo Date: Thu, 12 Jul 2018 18:31:31 +0200 Subject: [PATCH] WA-232 Improvement: Only fetch balance of activated tokens. Included Tests --- src/routes/index.js | 4 +- src/routes/routes.js | 1 + src/routes/tokens/component/Token/index.jsx | 3 +- .../tokens/store/actions/fetchTokens.js | 10 +- src/test/builder/safe.dom.utils.js | 20 ++- src/test/safe.dom.tokens.test.js | 6 +- src/test/tokens.dom.enabling.test.js | 134 ++++++++++++++++++ src/test/utils/tokenMovements.js | 7 +- 8 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 src/test/tokens.dom.enabling.test.js diff --git a/src/routes/index.js b/src/routes/index.js index 109198db..42b6f74c 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,7 +4,7 @@ import Loadable from 'react-loadable' import { Switch, Redirect, Route } from 'react-router-dom' import Loader from '~/components/Loader' 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({ loader: () => import('./safe/container'), @@ -27,7 +27,7 @@ const Open = Loadable({ }) 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` diff --git a/src/routes/routes.js b/src/routes/routes.js index ed521627..4ab227a3 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -3,3 +3,4 @@ export const SAFE_PARAM_ADDRESS = 'address' export const SAFELIST_ADDRESS = '/safes' export const OPEN_ADDRESS = '/open' export const WELCOME_ADDRESS = '/welcome' +export const SETTINS_ADDRESS = '/settings' diff --git a/src/routes/tokens/component/Token/index.jsx b/src/routes/tokens/component/Token/index.jsx index 5df9da20..b40a789d 100644 --- a/src/routes/tokens/component/Token/index.jsx +++ b/src/routes/tokens/component/Token/index.jsx @@ -51,6 +51,7 @@ class TokenComponent extends React.Component { handleChange = (e: SyntheticInputEvent) => { const { checked } = e.target + const callback = checked ? this.props.onEnableToken : this.props.onDisableToken this.setState(() => ({ checked }), () => callback(this.props.token)) } @@ -69,7 +70,7 @@ class TokenComponent extends React.Component { diff --git a/src/routes/tokens/store/actions/fetchTokens.js b/src/routes/tokens/store/actions/fetchTokens.js index 64a03d05..3ddc9b09 100644 --- a/src/routes/tokens/store/actions/fetchTokens.js +++ b/src/routes/tokens/store/actions/fetchTokens.js @@ -30,14 +30,18 @@ export const calculateBalanceOf = async (tokenAddress: string, address: string, .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) => async (dispatch: ReduxDispatch) => { const tokens: List = getTokens(safeAddress) const ethBalance = await getSafeEthToken(safeAddress) - const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/' - const errMsg = 'Error querying safe balances' - const json = await enhancedFetch(url, errMsg) + const json = await exports.fetchTokensData() try { const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => { diff --git a/src/test/builder/safe.dom.utils.js b/src/test/builder/safe.dom.utils.js index c11d7571..3b50c0d6 100644 --- a/src/test/builder/safe.dom.utils.js +++ b/src/test/builder/safe.dom.utils.js @@ -8,7 +8,7 @@ import { sleep } from '~/utils/timer' import { Provider } from 'react-redux' import { ConnectedRouter } from 'react-router-redux' 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 { EMPTY_DATA } from '~/wallets/ethTransactions' @@ -93,15 +93,25 @@ export const refreshTransactions = async (store: Store) => { await sleep(1500) } -export const travelToSafe = (store: Store, address: string): React$Component<{}> => { - history.push(`${SAFELIST_ADDRESS}/${address}`) - const SafeDom = TestUtils.renderIntoDocument(( +const createDom = (store: Store): React$Component<{}> => ( + TestUtils.renderIntoDocument(( )) +) - 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) } diff --git a/src/test/safe.dom.tokens.test.js b/src/test/safe.dom.tokens.test.js index 668ac43f..858524ea 100644 --- a/src/test/safe.dom.tokens.test.js +++ b/src/test/safe.dom.tokens.test.js @@ -4,7 +4,7 @@ import TestUtils from 'react-dom/test-utils' import * as fetchBalancesAction from '~/routes/tokens/store/actions/fetchTokens' import { aNewStore } from '~/store' 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 { promisify } from '~/utils/promisify' import { getWeb3 } from '~/wallets/getWeb3' @@ -47,14 +47,14 @@ describe('DOM > Feature > SAFE ERC20 TOKENS', () => { const receiverFunds = await fetchBalancesAction.calculateBalanceOf(tokenAddress, receiver, 18) 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) expect(Number(nativeSafeFunds.valueOf())).toEqual(80 * (10 ** 18)) }) it('disables send token button when balance is 0', async () => { // GIVEN - const token = await getTokenContract(getWeb3(), accounts[0]) + const token = await getFirstTokenContract(getWeb3(), accounts[0]) await dispatchTknBalance(store, token.address, safeAddress) // WHEN diff --git a/src/test/tokens.dom.enabling.test.js b/src/test/tokens.dom.enabling.test.js new file mode 100644 index 00000000..afef8a8c --- /dev/null +++ b/src/test/tokens.dom.enabling.test.js @@ -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') + }) +}) diff --git a/src/test/utils/tokenMovements.js b/src/test/utils/tokenMovements.js index 4353f65d..1b8ff359 100644 --- a/src/test/utils/tokenMovements.js +++ b/src/test/utils/tokenMovements.js @@ -39,13 +39,14 @@ const createTokenContract = async (web3: any, executor: string) => { 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 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) await myToken.transfer(safe, nativeValue.valueOf(), { from: accounts[0], gas: '5000000' })