1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +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 () {
it('should render properly', function () {
const wrapper = shallow(
<UserPreferencedTokenInput />
<UserPreferencedTokenInput token={{ address: '0x0' }} />
)
assert.ok(wrapper)
@ -18,6 +18,7 @@ describe('UserPreferencedCurrencyInput Component', function () {
it('should render showFiat for TokenInput based on preferences.useNativeCurrencyAsPrimaryCurrency', function () {
const wrapper = shallow(
<UserPreferencedTokenInput
token={{ address: '0x0' }}
useNativeCurrencyAsPrimaryCurrency
/>
)

View File

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

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import UserPreferencedTokenInput from './user-preferenced-token-input.component'
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}`
describe('rendering', function () {
it('should render properly without a token', 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 () {
it('should render properly', function () {
const mockStore = {
metamask: {
currentCurrency: 'usd',
@ -35,12 +25,11 @@ describe('TokenInput Component', function () {
const wrapper = mount(
<Provider store={store}>
<TokenInput
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
/>
</Provider>,
{ context: { t },
@ -57,7 +46,7 @@ describe('TokenInput Component', function () {
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 = {
metamask: {
currentCurrency: 'usd',
@ -69,13 +58,12 @@ describe('TokenInput Component', function () {
const wrapper = mount(
<Provider store={store}>
<TokenInput
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
/>
</Provider>,
{ context: { t },
@ -104,13 +92,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
value="2710"
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
/>
</Provider>
)
@ -138,13 +125,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
value="2710"
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
/>
</Provider>
@ -173,13 +159,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
value="2710"
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
hideConversion
/>
@ -224,13 +209,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
/>
</Provider>
)
@ -266,13 +250,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
/>
</Provider>
@ -309,13 +292,12 @@ describe('TokenInput Component', function () {
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
selectedToken={{
token={{
address: '0x1',
decimals: '4',
decimals: 4,
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
tokenExchangeRates={{ '0x1': 2 }}
showFiat
/>
</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,
onChange: PropTypes.func,
value: PropTypes.string,
suffix: PropTypes.string,
showFiat: PropTypes.bool,
hideConversion: PropTypes.bool,
selectedToken: PropTypes.object,
selectedTokenExchangeRate: PropTypes.number,
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
symbol: PropTypes.string,
}).isRequired,
tokenExchangeRates: PropTypes.object,
}
constructor (props) {
@ -52,7 +55,7 @@ export default class TokenInput extends PureComponent {
}
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 decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), {
@ -67,7 +70,7 @@ export default class TokenInput extends PureComponent {
}
handleChange = (decimalValue) => {
const { selectedToken: { decimals } = {}, onChange } = this.props
const { token: { decimals } = {}, onChange } = this.props
const multiplier = Math.pow(10, Number(decimals || 0))
const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' })
@ -77,8 +80,10 @@ export default class TokenInput extends PureComponent {
}
renderConversionComponent () {
const { selectedTokenExchangeRate, showFiat, currentCurrency, hideConversion } = this.props
const { tokenExchangeRates, showFiat, currentCurrency, hideConversion, token } = this.props
const { decimalValue } = this.state
const tokenExchangeRate = tokenExchangeRates?.[token.address] || 0
let currency, numberOfDecimals
if (hideConversion) {
@ -99,14 +104,14 @@ export default class TokenInput extends PureComponent {
numberOfDecimals = 6
}
const decimalEthValue = (decimalValue * selectedTokenExchangeRate) || 0
const decimalEthValue = (decimalValue * tokenExchangeRate) || 0
const hexWeiValue = getWeiHexFromDecimalValue({
value: decimalEthValue,
fromCurrency: ETH,
fromDenomination: ETH,
})
return selectedTokenExchangeRate
return tokenExchangeRate
? (
<CurrencyDisplay
className="currency-input__conversion-component"
@ -122,13 +127,13 @@ export default class TokenInput extends PureComponent {
}
render () {
const { suffix, ...restProps } = this.props
const { token, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
suffix={token.symbol}
onChange={this.handleChange}
value={decimalValue}
>

View File

@ -1,9 +1,9 @@
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import TokenInput from './token-input.component'
import {
getIsMainnet,
getSelectedToken,
getSelectedTokenExchangeRate,
getTokenExchangeRates,
getPreferences,
} from '../../../selectors'
@ -14,22 +14,19 @@ const mapStateToProps = (state) => {
return {
currentCurrency,
selectedToken: getSelectedToken(state),
selectedTokenExchangeRate: getSelectedTokenExchangeRate(state),
tokenExchangeRates: getTokenExchangeRates(state),
hideConversion: (!isMainnet && !showFiatInTestnets),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { selectedToken } = stateProps
const suffix = selectedToken && selectedToken.symbol
const TokenInputContainer = connect(mapStateToProps)(TokenInput)
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
}
TokenInputContainer.propTypes = {
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
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 () {
const { amount, inError, selectedToken } = this.props
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
return (
<Component
onChange={this.handleChange}
error={inError}
value={amount}
/>
)
return selectedToken ?
(
<UserPreferencedTokenInput
error={inError}
onChange={this.handleChange}
token={selectedToken}
value={amount}
/>
)
: (
<UserPreferencedCurrencyInput
error={inError}
onChange={this.handleChange}
value={amount}
/>
)
}
render () {

View File

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

View File

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