1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Extract selected token from token input (#8692)

The `TokenInput` component now takes the token as a prop, instead of
using the `selectedTokenAddress` state. The `UserPreferencedTokenInput`
component that wrapped `TokenInput` has also been updated to take the
token as a prop.
This commit is contained in:
Mark Stacey 2020-05-28 19:08:11 -03:00 committed by GitHub
parent 91a26bae89
commit 7ff3b4c928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 342 deletions

View File

@ -8,7 +8,7 @@ describe('UserPreferencedCurrencyInput Component', function () {
describe('rendering', function () { describe('rendering', function () {
it('should render properly', function () { it('should render properly', function () {
const wrapper = shallow( const wrapper = shallow(
<UserPreferencedTokenInput /> <UserPreferencedTokenInput token={{ address: '0x0' }} />
) )
assert.ok(wrapper) assert.ok(wrapper)
@ -18,6 +18,7 @@ describe('UserPreferencedCurrencyInput Component', function () {
it('should render showFiat for TokenInput based on preferences.useNativeCurrencyAsPrimaryCurrency', function () { it('should render showFiat for TokenInput based on preferences.useNativeCurrencyAsPrimaryCurrency', function () {
const wrapper = shallow( const wrapper = shallow(
<UserPreferencedTokenInput <UserPreferencedTokenInput
token={{ address: '0x0' }}
useNativeCurrencyAsPrimaryCurrency useNativeCurrencyAsPrimaryCurrency
/> />
) )

View File

@ -4,6 +4,11 @@ import TokenInput from '../../ui/token-input'
export default class UserPreferencedTokenInput extends PureComponent { export default class UserPreferencedTokenInput extends PureComponent {
static propTypes = { static propTypes = {
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
symbol: PropTypes.string,
}).isRequired,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
} }

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import UserPreferencedTokenInput from './user-preferenced-token-input.component' import UserPreferencedTokenInput from './user-preferenced-token-input.component'
import { getPreferences } from '../../../selectors' import { getPreferences } from '../../../selectors'
@ -10,4 +11,14 @@ const mapStateToProps = (state) => {
} }
} }
export default connect(mapStateToProps)(UserPreferencedTokenInput) const UserPreferencedTokenInputContainer = connect(mapStateToProps)(UserPreferencedTokenInput)
UserPreferencedTokenInputContainer.propTypes = {
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
symbol: PropTypes.string,
}).isRequired,
}
export default UserPreferencedTokenInputContainer

View File

@ -13,17 +13,7 @@ describe('TokenInput Component', function () {
const t = (key) => `translate ${key}` const t = (key) => `translate ${key}`
describe('rendering', function () { describe('rendering', function () {
it('should render properly without a token', function () { it('should render properly', function () {
const wrapper = shallow(
<TokenInput />,
{ context: { t } }
)
assert.ok(wrapper)
assert.equal(wrapper.find(UnitInput).length, 1)
})
it('should render properly with a token', function () {
const mockStore = { const mockStore = {
metamask: { metamask: {
currentCurrency: 'usd', currentCurrency: 'usd',
@ -35,12 +25,11 @@ describe('TokenInput Component', function () {
const wrapper = mount( const wrapper = mount(
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC"
/> />
</Provider>, </Provider>,
{ context: { t }, { context: { t },
@ -57,7 +46,7 @@ describe('TokenInput Component', function () {
assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable') assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable')
}) })
it('should render properly with a token and selectedTokenExchangeRate', function () { it('should render properly with tokenExchangeRates', function () {
const mockStore = { const mockStore = {
metamask: { metamask: {
currentCurrency: 'usd', currentCurrency: 'usd',
@ -69,13 +58,12 @@ describe('TokenInput Component', function () {
const wrapper = mount( const wrapper = mount(
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
/> />
</Provider>, </Provider>,
{ context: { t }, { context: { t },
@ -104,13 +92,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
value="2710" value="2710"
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
/> />
</Provider> </Provider>
) )
@ -138,13 +125,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
value="2710" value="2710"
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
showFiat showFiat
/> />
</Provider> </Provider>
@ -173,13 +159,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
value="2710" value="2710"
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
showFiat showFiat
hideConversion hideConversion
/> />
@ -224,13 +209,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
onChange={handleChangeSpy} onChange={handleChangeSpy}
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
/> />
</Provider> </Provider>
) )
@ -266,13 +250,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
onChange={handleChangeSpy} onChange={handleChangeSpy}
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
showFiat showFiat
/> />
</Provider> </Provider>
@ -309,13 +292,12 @@ describe('TokenInput Component', function () {
<Provider store={store}> <Provider store={store}>
<TokenInput <TokenInput
onChange={handleChangeSpy} onChange={handleChangeSpy}
selectedToken={{ token={{
address: '0x1', address: '0x1',
decimals: '4', decimals: 4,
symbol: 'ABC', symbol: 'ABC',
}} }}
suffix="ABC" tokenExchangeRates={{ '0x1': 2 }}
selectedTokenExchangeRate={2}
showFiat showFiat
/> />
</Provider> </Provider>

View File

@ -1,255 +0,0 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../token-input.container.js', {
'react-redux': {
connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('TokenInput container', function () {
describe('mapStateToProps()', function () {
it('should return the correct props when send is empty', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {},
send: {},
preferences: {
showFiatInTestnets: false,
},
provider: {
type: 'mainnet',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 0,
hideConversion: false,
})
})
it('should return the correct props when selectedTokenAddress is not found and send is populated', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x2',
contractExchangeRates: {},
send: {
token: { address: 'test' },
},
preferences: {
showFiatInTestnets: false,
},
provider: {
type: 'mainnet',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: 'test',
},
selectedTokenExchangeRate: 0,
hideConversion: false,
})
})
it('should return the correct props when contractExchangeRates is populated', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {
'0x1': 5,
},
send: {},
preferences: {
showFiatInTestnets: false,
},
provider: {
type: 'mainnet',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
hideConversion: false,
})
})
it('should return the correct props when not in mainnet and showFiatInTestnets is false', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {},
send: {},
preferences: {
showFiatInTestnets: false,
},
provider: {
type: 'rinkeby',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 0,
hideConversion: true,
})
})
it('should return the correct props when not in mainnet and showFiatInTestnets is true', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {},
send: {},
preferences: {
showFiatInTestnets: true,
},
provider: {
type: 'rinkeby',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 0,
hideConversion: false,
})
})
it('should return the correct props when in mainnet and showFiatInTestnets is true', function () {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {},
send: {},
preferences: {
showFiatInTestnets: true,
},
provider: {
type: 'mainnet',
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 0,
hideConversion: false,
})
})
})
describe('mergeProps()', function () {
it('should return the correct props', function () {
const mockStateProps = {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
}
assert.deepEqual(mergeProps(mockStateProps, {}, {}), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
suffix: 'ABC',
})
})
})
})

View File

@ -21,11 +21,14 @@ export default class TokenInput extends PureComponent {
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
value: PropTypes.string, value: PropTypes.string,
suffix: PropTypes.string,
showFiat: PropTypes.bool, showFiat: PropTypes.bool,
hideConversion: PropTypes.bool, hideConversion: PropTypes.bool,
selectedToken: PropTypes.object, token: PropTypes.shape({
selectedTokenExchangeRate: PropTypes.number, address: PropTypes.string.isRequired,
decimals: PropTypes.number,
symbol: PropTypes.string,
}).isRequired,
tokenExchangeRates: PropTypes.object,
} }
constructor (props) { constructor (props) {
@ -52,7 +55,7 @@ export default class TokenInput extends PureComponent {
} }
getValue (props) { getValue (props) {
const { value: hexValue, selectedToken: { decimals, symbol } = {} } = props const { value: hexValue, token: { decimals, symbol } = {} } = props
const multiplier = Math.pow(10, Number(decimals || 0)) const multiplier = Math.pow(10, Number(decimals || 0))
const decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), { const decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), {
@ -67,7 +70,7 @@ export default class TokenInput extends PureComponent {
} }
handleChange = (decimalValue) => { handleChange = (decimalValue) => {
const { selectedToken: { decimals } = {}, onChange } = this.props const { token: { decimals } = {}, onChange } = this.props
const multiplier = Math.pow(10, Number(decimals || 0)) const multiplier = Math.pow(10, Number(decimals || 0))
const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' }) const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' })
@ -77,8 +80,10 @@ export default class TokenInput extends PureComponent {
} }
renderConversionComponent () { renderConversionComponent () {
const { selectedTokenExchangeRate, showFiat, currentCurrency, hideConversion } = this.props const { tokenExchangeRates, showFiat, currentCurrency, hideConversion, token } = this.props
const { decimalValue } = this.state const { decimalValue } = this.state
const tokenExchangeRate = tokenExchangeRates?.[token.address] || 0
let currency, numberOfDecimals let currency, numberOfDecimals
if (hideConversion) { if (hideConversion) {
@ -99,14 +104,14 @@ export default class TokenInput extends PureComponent {
numberOfDecimals = 6 numberOfDecimals = 6
} }
const decimalEthValue = (decimalValue * selectedTokenExchangeRate) || 0 const decimalEthValue = (decimalValue * tokenExchangeRate) || 0
const hexWeiValue = getWeiHexFromDecimalValue({ const hexWeiValue = getWeiHexFromDecimalValue({
value: decimalEthValue, value: decimalEthValue,
fromCurrency: ETH, fromCurrency: ETH,
fromDenomination: ETH, fromDenomination: ETH,
}) })
return selectedTokenExchangeRate return tokenExchangeRate
? ( ? (
<CurrencyDisplay <CurrencyDisplay
className="currency-input__conversion-component" className="currency-input__conversion-component"
@ -122,13 +127,13 @@ export default class TokenInput extends PureComponent {
} }
render () { render () {
const { suffix, ...restProps } = this.props const { token, ...restProps } = this.props
const { decimalValue } = this.state const { decimalValue } = this.state
return ( return (
<UnitInput <UnitInput
{...restProps} {...restProps}
suffix={suffix} suffix={token.symbol}
onChange={this.handleChange} onChange={this.handleChange}
value={decimalValue} value={decimalValue}
> >

View File

@ -1,9 +1,9 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import TokenInput from './token-input.component' import TokenInput from './token-input.component'
import { import {
getIsMainnet, getIsMainnet,
getSelectedToken, getTokenExchangeRates,
getSelectedTokenExchangeRate,
getPreferences, getPreferences,
} from '../../../selectors' } from '../../../selectors'
@ -14,22 +14,19 @@ const mapStateToProps = (state) => {
return { return {
currentCurrency, currentCurrency,
selectedToken: getSelectedToken(state), tokenExchangeRates: getTokenExchangeRates(state),
selectedTokenExchangeRate: getSelectedTokenExchangeRate(state),
hideConversion: (!isMainnet && !showFiatInTestnets), hideConversion: (!isMainnet && !showFiatInTestnets),
} }
} }
const mergeProps = (stateProps, dispatchProps, ownProps) => { const TokenInputContainer = connect(mapStateToProps)(TokenInput)
const { selectedToken } = stateProps
const suffix = selectedToken && selectedToken.symbol
return { TokenInputContainer.propTypes = {
...stateProps, token: PropTypes.shape({
...dispatchProps, address: PropTypes.string.isRequired,
...ownProps, decimals: PropTypes.number,
suffix, symbol: PropTypes.string,
} }).isRequired,
} }
export default connect(mapStateToProps, null, mergeProps)(TokenInput) export default TokenInputContainer

View File

@ -101,15 +101,23 @@ export default class SendAmountRow extends Component {
renderInput () { renderInput () {
const { amount, inError, selectedToken } = this.props const { amount, inError, selectedToken } = this.props
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
return ( return selectedToken ?
<Component (
onChange={this.handleChange} <UserPreferencedTokenInput
error={inError} error={inError}
value={amount} onChange={this.handleChange}
/> token={selectedToken}
) value={amount}
/>
)
: (
<UserPreferencedCurrencyInput
error={inError}
onChange={this.handleChange}
value={amount}
/>
)
} }
render () { render () {

View File

@ -156,12 +156,7 @@ export function getTargetAccount (state, targetAddress) {
return accounts[targetAddress] return accounts[targetAddress]
} }
export function getSelectedTokenExchangeRate (state) { export const getTokenExchangeRates = (state) => state.metamask.contractExchangeRates
const contractExchangeRates = state.metamask.contractExchangeRates
const selectedToken = getSelectedToken(state) || {}
const { address } = selectedToken
return contractExchangeRates[address] || 0
}
export function getAssetImages (state) { export function getAssetImages (state) {
const assetImages = state.metamask.assetImages || {} const assetImages = state.metamask.assetImages || {}

View File

@ -32,10 +32,13 @@ describe('Selectors', function () {
assert.equal(account.address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') assert.equal(account.address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')
}) })
describe('#getSelectedTokenExchangeRate', function () { describe('#getTokenExchangeRates', function () {
it('returns token exchange rate for first token', function () { it('returns token exchange rates', function () {
const tokenRate = selectors.getSelectedTokenExchangeRate(mockState) const tokenExchangeRates = selectors.getTokenExchangeRates(mockState)
assert.equal(tokenRate, '0.00039345803819379796') assert.deepEqual(tokenExchangeRates, {
'0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d': 0.00039345803819379796,
'0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': 0.00008189274407698049,
})
}) })
}) })