add a test for adding custom token

This commit is contained in:
Mikhail Mikheev 2019-06-05 15:52:15 +04:00
parent 4b0cac5eb0
commit 4625ed1aa5
9 changed files with 108 additions and 69 deletions

View File

@ -29,6 +29,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
text, text,
inputAdornment, inputAdornment,
classes, classes,
testId,
...rest ...rest
} = this.props } = this.props
const helperText = value ? text : undefined const helperText = value ? text : undefined
@ -36,7 +37,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
const underline = meta.active || (meta.visited && !meta.valid) const underline = meta.active || (meta.visited && !meta.valid)
const inputRoot = helperText ? classes.root : undefined const inputRoot = helperText ? classes.root : undefined
const inputProps = { ...restInput, autoComplete: 'off' } const inputProps = { ...restInput, autoComplete: 'off', 'data-testid': testId }
const inputRootProps = { ...inputAdornment, disableUnderline: !underline, className: inputRoot } const inputRootProps = { ...inputAdornment, disableUnderline: !underline, className: inputRoot }
return ( return (
@ -51,6 +52,7 @@ class TextField extends React.PureComponent<TextFieldProps> {
inputProps={inputProps} inputProps={inputProps}
onChange={onChange} onChange={onChange}
value={value} value={value}
// data-testid={testId}
/> />
) )
} }

View File

@ -12,6 +12,7 @@ const styles = {
type Props = { type Props = {
minWidth?: number, minWidth?: number,
minHeight?: number, minHeight?: number,
testId: string,
} }
const calculateStyleBased = (minWidth, minHeight) => ({ const calculateStyleBased = (minWidth, minHeight) => ({
@ -19,10 +20,10 @@ const calculateStyleBased = (minWidth, minHeight) => ({
minHeight: minHeight && `${minHeight}px`, minHeight: minHeight && `${minHeight}px`,
}) })
const GnoButton = ({ minWidth, minHeight, ...props }: Props) => { const GnoButton = ({ minWidth, minHeight, testId = '', ...props }: Props) => {
const style = calculateStyleBased(minWidth, minHeight) const style = calculateStyleBased(minWidth, minHeight)
return <Button style={style} {...props} /> return <Button style={style} data-testid={testId} {...props} />
} }
export default withStyles(styles)(GnoButton) export default withStyles(styles)(GnoButton)

View File

@ -1,6 +1,5 @@
// @flow // @flow
/* eslint-disable react/button-has-type */ /* eslint-disable react/button-has-type */
/* eslint-disable react/default-props-match-prop-types */
import * as React from 'react' import * as React from 'react'
import cn from 'classnames/bind' import cn from 'classnames/bind'
import styles from './index.scss' import styles from './index.scss'
@ -12,20 +11,16 @@ type Props = {
size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl', size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl',
weight?: 'light' | 'regular' | 'bolder' | 'bold', weight?: 'light' | 'regular' | 'bolder' | 'bold',
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled', color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled',
testId?: string,
} }
const GnoButtonLink = ({ const GnoButtonLink = ({
type, size, weight, color, ...props type = 'button',
}: Props) => ( size = 'md',
<button type={type} className={cx(styles.btnLink, size, color, weight)} {...props} /> weight = 'regular',
) color = 'secondary',
testId = '',
...props
GnoButtonLink.defaultProps = { }: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight)} data-testid={testId} {...props} />
type: 'button',
size: 'md',
weight: 'regular',
color: 'secondary',
}
export default GnoButtonLink export default GnoButtonLink

View File

@ -23,6 +23,11 @@ import { addressIsTokenContract, doesntExistInTokenList } from './validators'
import { styles } from './style' import { styles } from './style'
import { getSymbolAndDecimalsFromContract } from './utils' import { getSymbolAndDecimalsFromContract } from './utils'
export const ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID = 'add-custom-token-address-input'
export const ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID = 'add-custom-token-symbols-input'
export const ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID = 'add-custom-token-decimals-input'
export const ADD_CUSTOM_TOKEN_FORM = 'add-custom-token-form'
type Props = { type Props = {
classes: Object, classes: Object,
addToken: Function, addToken: Function,
@ -115,7 +120,7 @@ const AddCustomToken = (props: Props) => {
return ( return (
<React.Fragment> <React.Fragment>
<GnoForm onSubmit={handleSubmit} initialValues={formValues}> <GnoForm onSubmit={handleSubmit} initialValues={formValues} testId={ADD_CUSTOM_TOKEN_FORM}>
{() => ( {() => (
<React.Fragment> <React.Fragment>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
@ -135,6 +140,7 @@ const AddCustomToken = (props: Props) => {
placeholder="Token contract address*" placeholder="Token contract address*"
text="Token contract address*" text="Token contract address*"
className={classes.addressInput} className={classes.addressInput}
testId={ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID}
/> />
<FormSpy <FormSpy
subscription={{ subscription={{
@ -156,6 +162,7 @@ const AddCustomToken = (props: Props) => {
placeholder="Token symbol*" placeholder="Token symbol*"
text="Token symbol" text="Token symbol"
className={classes.addressInput} className={classes.addressInput}
testId={ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID}
/> />
<Field <Field
name="decimals" name="decimals"
@ -165,6 +172,7 @@ const AddCustomToken = (props: Props) => {
placeholder="Token decimals*" placeholder="Token decimals*"
text="Token decimals*" text="Token decimals*"
className={classes.addressInput} className={classes.addressInput}
testId={ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID}
/> />
<Block align="left"> <Block align="left">
<Field name="showForAllSafes" component={Checkbox} type="checkbox" className={classes.checkbox} /> <Field name="showForAllSafes" component={Checkbox} type="checkbox" className={classes.checkbox} />

View File

@ -24,6 +24,8 @@ import { type Token } from '~/logic/tokens/store/model/token'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { styles } from './style' import { styles } from './style'
export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn'
type Props = { type Props = {
classes: Object, classes: Object,
tokens: List<Token>, tokens: List<Token>,
@ -141,6 +143,7 @@ class Tokens extends React.Component<Props, State> {
color="secondary" color="secondary"
className={classes.add} className={classes.add}
onClick={switchToAddCustomTokenScreen} onClick={switchToAddCustomTokenScreen}
testId={ADD_CUSTOM_TOKEN_BUTTON_TEST_ID}
> >
+ ADD CUSTOM TOKEN + ADD CUSTOM TOKEN
</Button> </Button>

View File

@ -26,6 +26,9 @@ import SendModal from './SendModal'
import Receive from './Receive' import Receive from './Receive'
import { styles } from './style' import { styles } from './style'
export const MANAGE_TOKENS_BUTTON_TEST_ID = 'manage-tokens-btn'
export const BALANCE_ROW_TEST_ID = 'balance-row'
type State = { type State = {
hideZero: boolean, hideZero: boolean,
showToken: boolean, showToken: boolean,
@ -128,7 +131,7 @@ class Balances extends React.Component<Props, State> {
<Paragraph className={classes.zero}>Hide zero balances</Paragraph> <Paragraph className={classes.zero}>Hide zero balances</Paragraph>
</Col> </Col>
<Col xs={6} end="sm"> <Col xs={6} end="sm">
<ButtonLink onClick={this.onShow('Token')}>Manage Tokens</ButtonLink> <ButtonLink onClick={this.onShow('Token')} testId="manage-tokens-btn">Manage Tokens</ButtonLink>
<Modal <Modal
title="Manage Tokens" title="Manage Tokens"
description="Enable and disable tokens to be listed" description="Enable and disable tokens to be listed"
@ -153,7 +156,7 @@ class Balances extends React.Component<Props, State> {
defaultFixed defaultFixed
> >
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => ( {(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid="balance-row"> <TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={BALANCE_ROW_TEST_ID}>
{autoColumns.map((column: Column) => ( {autoColumns.map((column: Column) => (
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td"> <TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
{column.id === BALANCE_TABLE_ASSET_ID ? <AssetTableCell asset={row[column.id]} /> : row[column.id]} {column.id === BALANCE_TABLE_ASSET_ID ? <AssetTableCell asset={row[column.id]} /> : row[column.id]}

View File

@ -13,6 +13,7 @@ import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalanc
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens' import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
import 'jest-dom/extend-expect' import 'jest-dom/extend-expect'
import updateSafe from '~/routes/safe/store/actions/updateSafe' import updateSafe from '~/routes/safe/store/actions/updateSafe'
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
afterEach(cleanup) afterEach(cleanup)
@ -37,7 +38,7 @@ describe('DOM > Feature > Funds', () => {
await sleep(1300) await sleep(1300)
// Open send funds modal // Open send funds modal
const balanceRows = SafeDom.getAllByTestId('balance-row') const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows[0]).toHaveTextContent(`${ethAmount} ETH`) expect(balanceRows[0]).toHaveTextContent(`${ethAmount} ETH`)
const sendButton = SafeDom.getByTestId('balance-send-btn') const sendButton = SafeDom.getByTestId('balance-send-btn')
fireEvent.click(sendButton) fireEvent.click(sendButton)
@ -91,7 +92,7 @@ describe('DOM > Feature > Funds', () => {
await sleep(1000) await sleep(1000)
// Open send funds modal // Open send funds modal
const balanceRows = SafeDom.getAllByTestId('balance-row') const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows.length).toBe(2) expect(balanceRows.length).toBe(2)
const sendButtons = SafeDom.getAllByTestId('balance-send-btn') const sendButtons = SafeDom.getAllByTestId('balance-send-btn')
expect(sendButtons.length).toBe(2) expect(sendButtons.length).toBe(2)

View File

@ -1,65 +1,83 @@
// @flow // @flow
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { type Match } from 'react-router-dom' import { fireEvent } from '@testing-library/react'
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements' import { getFirstTokenContract } from '~/test/utils/tokenMovements'
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 { travelToTokens } from '~/test/builder/safe.dom.utils' import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { sleep } from '~/utils/timer' import { sleep } from '~/utils/timer'
import { buildMatchPropsFrom } from '~/test/utils/buildReactRouterProps' import { clickOnManageTokens, clickOnAddCustomToken } from '~/test/utils/DOMNavigation'
import { tokenListSelector } from '~/logic/tokens/store/selectors'
import { testToken } from '~/test/builder/tokens.dom.utils'
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens' import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
import * as enhancedFetchModule from '~/utils/fetch' import {
ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID,
ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID,
ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID,
ADD_CUSTOM_TOKEN_FORM,
} from '~/routes/safe/components/Balances/Tokens/screens/AddCustomToken'
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances/'
import 'jest-dom/extend-expect'
describe('DOM > Feature > Add new ERC 20 Tokens', () => { // https://github.com/testing-library/@testing-library/react/issues/281
const originalError = console.error
beforeAll(() => {
console.error = (...args) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return
}
originalError.call(console, ...args)
}
})
afterAll(() => {
console.error = originalError
})
describe('DOM > Feature > Add custom ERC 20 Tokens', () => {
let web3 let web3
let accounts let accounts
let firstErc20Token let erc20Token
let secondErc20Token
beforeAll(async () => { beforeAll(async () => {
web3 = getWeb3() web3 = getWeb3()
accounts = await web3.eth.getAccounts() accounts = await web3.eth.getAccounts()
firstErc20Token = await getFirstTokenContract(web3, accounts[0]) erc20Token = await getFirstTokenContract(web3, accounts[0])
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
// $FlowFixMe
enhancedFetchModule.enhancedFetch = jest.fn()
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
results: [
{
address: firstErc20Token.address,
name: 'First Token Example',
symbol: 'FTE',
decimals: 18,
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
},
],
}))
}) })
it('adds a second erc 20 token filling the form', async () => { it('adds and displays an erc 20 token after filling the form', async () => {
// GIVEN // GIVEN
const store = aNewStore() const store = aNewStore()
const safeAddress = await aMinedSafe(store) const safeAddress = await aMinedSafe(store)
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress)) await store.dispatch(fetchTokensModule.fetchTokens())
const TokensDom = await travelToTokens(store, safeAddress) const TokensDom = renderSafeView(store, safeAddress)
await sleep(400) await sleep(400)
const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
expect(tokens.length).toBe(2)
testToken(tokens[0].props.token, 'FTE', false)
testToken(tokens[1].props.token, 'ETH', true)
// WHEN // WHEN
await clickOnAddToken(TokensDom) clickOnManageTokens(TokensDom)
await fillAddress(TokensDom, secondErc20Token) clickOnAddCustomToken(TokensDom)
await fillHumanReadableInfo(TokensDom) await sleep(200)
// THEN
const match: Match = buildMathPropsFrom(safeAddress) // Fill address
const tokenList = tokenListSelector(store.getState(), { match }) const addTokenForm = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_FORM)
expect(tokenList.count()).toBe(3) const addressInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID)
testToken(tokenList.get(0), 'FTE', false) fireEvent.change(addressInput, { target: { value: erc20Token.address } })
testToken(tokenList.get(1), 'ETH', true) await sleep(500)
testToken(tokenList.get(2), 'TKN', true)
// Check if it loaded symbol/decimals correctly
const symbolInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID)
const decimalsInput = TokensDom.getByTestId(ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID)
const tokenSymbol = await erc20Token.symbol()
const tokenDecimals = await erc20Token.decimals()
expect(symbolInput.value).toBe(tokenSymbol)
expect(decimalsInput.value).toBe(tokenDecimals.toString())
// Submit form
fireEvent.submit(addTokenForm)
await sleep(300)
// check if token is displayed
const balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
expect(balanceRows.length).toBe(2)
expect(balanceRows[1]).toHaveTextContent(tokenSymbol)
}) })
}) })

View File

@ -1,8 +1,16 @@
// @flow // @flow
import { fireEvent } from '@testing-library/reactß' import { fireEvent } from '@testing-library/react'
import { MANAGE_TOKENS_BUTTON_TEST_ID } from '~/routes/safe/components/Balances'
import { ADD_CUSTOM_TOKEN_BUTTON_TEST_ID } from '~/routes/safe/components/Balances/Tokens/screens/TokenList'
const clickOnManageTokens = (dom) => { export const clickOnManageTokens = (dom: any): void => {
const btn = dom.findByTestId() const btn = dom.getByTestId(MANAGE_TOKENS_BUTTON_TEST_ID)
fireEvent.click(btn)
} }
export const clickOnAddCustomToken = (dom: any): void => {
const btn = dom.getByTestId(ADD_CUSTOM_TOKEN_BUTTON_TEST_ID)
fireEvent.click(btn)
}