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

warn user when sending from different account (#8601)

This commit is contained in:
Brad Decker 2020-05-21 15:21:34 -05:00 committed by GitHub
parent dd41870f1d
commit 389df9dd37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 497 additions and 332 deletions

View File

@ -1030,6 +1030,9 @@
"noThanks": {
"message": "No Thanks"
},
"notCurrentAccount": {
"message": "Is this the correct account? It's different from the currently selected account in your wallet"
},
"notEnoughGas": {
"message": "Not Enough Gas"
},

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {
ENVIRONMENT_TYPE_POPUP,
@ -8,31 +8,27 @@ import { getEnvironmentType } from '../../../../../../app/scripts/lib/util'
import NetworkDisplay from '../../network-display'
import Identicon from '../../../ui/identicon'
import { shortenAddress } from '../../../../helpers/utils/util'
import AccountMismatchWarning from '../../../ui/account-mismatch-warning/account-mismatch-warning.component'
import { useI18nContext } from '../../../../hooks/useI18nContext'
export default class ConfirmPageContainerHeader extends Component {
static contextTypes = {
t: PropTypes.func,
export default function ConfirmPageContainerHeader ({
onEdit,
showEdit,
accountAddress,
showAccountInHeader,
children,
}) {
const t = useI18nContext()
const windowType = getEnvironmentType()
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
if (!showEdit && isFullScreen) {
return null
}
static propTypes = {
accountAddress: PropTypes.string,
showAccountInHeader: PropTypes.bool,
showEdit: PropTypes.bool,
onEdit: PropTypes.func,
children: PropTypes.node,
}
renderTop () {
const { onEdit, showEdit, accountAddress, showAccountInHeader } = this.props
const windowType = getEnvironmentType()
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
if (!showEdit && isFullScreen) {
return null
}
return (
return (
<div className="confirm-page-container-header">
<div className="confirm-page-container-header__row">
{ !showAccountInHeader
? (
@ -49,7 +45,7 @@ export default class ConfirmPageContainerHeader extends Component {
className="confirm-page-container-header__back-button"
onClick={() => onEdit()}
>
{ this.context.t('edit') }
{ t('edit') }
</span>
</div>
)
@ -67,23 +63,22 @@ export default class ConfirmPageContainerHeader extends Component {
<div className="confirm-page-container-header__address">
{ shortenAddress(accountAddress) }
</div>
<AccountMismatchWarning address={accountAddress} />
</div>
)
: null
}
{ !isFullScreen && <NetworkDisplay /> }
</div>
)
}
render () {
const { children } = this.props
return (
<div className="confirm-page-container-header">
{ this.renderTop() }
{ children }
</div>
)
}
{ children }
</div>
)
}
ConfirmPageContainerHeader.propTypes = {
accountAddress: PropTypes.string,
showAccountInHeader: PropTypes.bool,
showEdit: PropTypes.bool,
onEdit: PropTypes.func,
children: PropTypes.node,
}

View File

@ -94,6 +94,8 @@
@import '../ui/icon-with-fallback/index';
@import '../ui/icon/index';
@import '../ui/circle-icon/index';
@import '../ui/alert-circle-icon/index';
@ -111,3 +113,5 @@
@import 'permissions-connect-footer/index';
@import 'wallet-overview/index';
@import '../ui/account-mismatch-warning/index';

View File

@ -13,13 +13,20 @@
display: flex;
align-items: center;
.account-list-item__account-name {
font-size: 12px;
font-weight: 500;
}
.account-list-item {
&__top-row {
display: flex;
align-items: center;
}
.account-list-item__top-row {
margin: 0px;
&__account-name {
font-size: 12px;
font-weight: 500;
}
&__top-row {
margin: 0px;
}
}
}
}
}

View File

@ -14,6 +14,16 @@ describe('Signature Request', function () {
provider: {
type: 'test',
},
accounts: {
'0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': {
address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
balance: '0x03',
},
},
cachedBalances: {
},
selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
},
}
const store = configureMockStore()(mockStore)

View File

@ -0,0 +1,30 @@
import React from 'react'
import Tooltip from '../tooltip-v2'
import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { getSelectedAccount } from '../../../selectors'
import InfoIcon from '../icon/info-icon.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
export default function AccountMismatchWarning ({ address }) {
const selectedAccount = useSelector(getSelectedAccount)
const t = useI18nContext()
if (selectedAccount.address === address) {
return null
}
return (
<Tooltip
position="bottom"
html={<p>{t('notCurrentAccount')}</p>}
wrapperClassName="account-mismatch-warning__tooltip-wrapper"
containerClassName="account-mismatch-warning__tooltip-container"
>
<div className="account-mismatch-warning__tooltip-container-icon"><InfoIcon severity="warning" /></div>
</Tooltip>
)
}
AccountMismatchWarning.propTypes = {
address: PropTypes.string.isRequired,
}

View File

@ -0,0 +1,8 @@
.account-mismatch-warning {
&__tooltip-container {
&-icon {
display: flex;
align-items: center;
}
}
}

View File

@ -0,0 +1,32 @@
import React from 'react'
import * as reactRedux from 'react-redux'
import assert from 'assert'
import sinon from 'sinon'
import { shallow } from 'enzyme'
import InfoIcon from '../../icon/info-icon.component'
import AccountMismatchWarning from '../account-mismatch-warning.component'
import { getSelectedAccount } from '../../../../selectors'
describe('AccountMismatchWarning', function () {
before(function () {
sinon.stub(reactRedux, 'useSelector').callsFake((selector) => {
if (selector === getSelectedAccount) {
return { address: 'mockedAddress' }
}
throw new Error(
`${selector.name} is not cared for in the AccountMismatchWarning test useSelector stub`
)
})
})
it('renders nothing when the addresses match', function () {
const wrapper = shallow(<AccountMismatchWarning address="mockedAddress" />)
assert.equal(wrapper.find(InfoIcon).length, 0)
})
it('renders a warning info icon when addresses do not match', function () {
const wrapper = shallow(<AccountMismatchWarning address="mockedAddress2" />)
assert.equal(wrapper.find(InfoIcon).length, 1)
})
after(function () {
sinon.restore()
})
})

View File

@ -0,0 +1,19 @@
.info-icon {
margin: 4px;
&--success {
fill: $success-green;
}
&--info {
fill: $info-blue;
}
&--warning {
fill: $warning-yellow;
}
&--danger {
fill: $danger-red;
}
}

View File

@ -0,0 +1,21 @@
import React from 'react'
import classnames from 'classnames'
import PropTypes from 'prop-types'
export default function InfoIcon ({ severity }) {
const className = classnames('info-icon', {
'info-icon--success': severity === 'success',
'info-icon--warning': severity === 'warning',
'info-icon--danger': severity === 'danger',
'info-icon--info': severity === 'info',
})
return (
<svg className={className} width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M7.2 5.6H8.8V4H7.2V5.6ZM8 14.4C4.472 14.4 1.6 11.528 1.6 8C1.6 4.472 4.472 1.6 8 1.6C11.528 1.6 14.4 4.472 14.4 8C14.4 11.528 11.528 14.4 8 14.4ZM8 0C6.94943 0 5.90914 0.206926 4.93853 0.608964C3.96793 1.011 3.08601 1.60028 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.08601 14.3997 3.96793 14.989 4.93853 15.391C5.90914 15.7931 6.94943 16 8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 6.94943 15.7931 5.90914 15.391 4.93853C14.989 3.96793 14.3997 3.08601 13.6569 2.34315C12.914 1.60028 12.0321 1.011 11.0615 0.608964C10.0909 0.206926 9.05058 0 8 0ZM7.2 12H8.8V7.2H7.2V12Z" />
</svg>
)
}
InfoIcon.propTypes = {
severity: PropTypes.oneOf(['success', 'info', 'warning', 'danger']),
}

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react'
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Identicon from '../identicon'
@ -6,6 +6,9 @@ import Tooltip from '../tooltip-v2'
import copyToClipboard from 'copy-to-clipboard'
import { DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT } from './sender-to-recipient.constants'
import { checksumAddress, shortenAddress } from '../../../helpers/utils/util'
import AccountMismatchWarning from '../account-mismatch-warning/account-mismatch-warning.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
const variantHash = {
[DEFAULT_VARIANT]: 'sender-to-recipient--default',
@ -13,67 +16,51 @@ const variantHash = {
[FLAT_VARIANT]: 'sender-to-recipient--flat',
}
export default class SenderToRecipient extends PureComponent {
static propTypes = {
senderName: PropTypes.string,
senderAddress: PropTypes.string,
recipientName: PropTypes.string,
recipientEns: PropTypes.string,
recipientAddress: PropTypes.string,
recipientNickname: PropTypes.string,
variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
addressOnly: PropTypes.bool,
assetImage: PropTypes.string,
onRecipientClick: PropTypes.func,
onSenderClick: PropTypes.func,
function SenderAddress ({
addressOnly,
checksummedSenderAddress,
senderName,
onSenderClick,
senderAddress,
}) {
const t = useI18nContext()
const [addressCopied, setAddressCopied] = useState(false)
let tooltipHtml = <p>{t('copiedExclamation')}</p>
if (!addressCopied) {
tooltipHtml = addressOnly
? <p>{t('copyAddress')}</p>
: (
<p>
{shortenAddress(checksummedSenderAddress)}<br />
{t('copyAddress')}
</p>
)
}
static defaultProps = {
variant: DEFAULT_VARIANT,
}
static contextTypes = {
t: PropTypes.func,
}
state = {
senderAddressCopied: false,
}
renderSenderIdenticon () {
return !this.props.addressOnly && (
<div className="sender-to-recipient__sender-icon">
<Identicon
address={checksumAddress(this.props.senderAddress)}
diameter={24}
/>
</div>
)
}
renderSenderAddress () {
const { t } = this.context
const { senderName, senderAddress, addressOnly } = this.props
const checksummedSenderAddress = checksumAddress(senderAddress)
return (
return (
<div
className={classnames('sender-to-recipient__party sender-to-recipient__party--sender')}
onClick={() => {
setAddressCopied(true)
copyToClipboard(checksummedSenderAddress)
if (onSenderClick) {
onSenderClick()
}
}}
>
{!addressOnly && (
<div className="sender-to-recipient__sender-icon">
<Identicon
address={checksumAddress(senderAddress)}
diameter={24}
/>
</div>
)}
<Tooltip
position="bottom"
html={
this.state.senderAddressCopied
? <p>{t('copiedExclamation')}</p>
: addressOnly
? <p>{t('copyAddress')}</p>
: (
<p>
{shortenAddress(checksummedSenderAddress)}<br />
{t('copyAddress')}
</p>
)
}
html={tooltipHtml}
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => this.setState({ senderAddressCopied: false })}
onHidden={() => setAddressCopied(false)}
>
<div className="sender-to-recipient__name">
{
@ -83,129 +70,184 @@ export default class SenderToRecipient extends PureComponent {
}
</div>
</Tooltip>
)
<AccountMismatchWarning address={senderAddress} />
</div>
)
}
SenderAddress.propTypes = {
senderName: PropTypes.string,
checksummedSenderAddress: PropTypes.string,
addressOnly: PropTypes.bool,
senderAddress: PropTypes.string,
onSenderClick: PropTypes.func,
}
function RecipientWithAddress ({
checksummedRecipientAddress,
assetImage,
onRecipientClick,
addressOnly,
recipientNickname,
recipientEns,
recipientName,
}) {
const t = useI18nContext()
const [addressCopied, setAddressCopied] = useState(false)
let tooltipHtml = <p>{t('copiedExclamation')}</p>
if (!addressCopied) {
if (addressOnly && !recipientNickname && !recipientEns) {
tooltipHtml = <p>{t('copyAddress')}</p>
} else {
tooltipHtml = (
<p>
{shortenAddress(checksummedRecipientAddress)}<br />
{t('copyAddress')}
</p>
)
}
}
return (
<div
className="sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address"
onClick={() => {
setAddressCopied(true)
copyToClipboard(checksummedRecipientAddress)
if (onRecipientClick) {
onRecipientClick()
}
}}
>
{!addressOnly && (
<div className="sender-to-recipient__sender-icon">
<Identicon
address={checksummedRecipientAddress}
diameter={24}
image={assetImage}
/>
</div>
)}
<Tooltip
position="bottom"
html={tooltipHtml}
offset={-10}
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => setAddressCopied(false)}
>
<div className="sender-to-recipient__name">
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
{
addressOnly
? (recipientNickname || recipientEns || checksummedRecipientAddress)
: (recipientNickname || recipientEns || recipientName || t('newContract'))
}
</div>
</Tooltip>
</div>
)
}
renderRecipientIdenticon () {
const { recipientAddress, assetImage } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
RecipientWithAddress.propTypes = {
checksummedRecipientAddress: PropTypes.string,
recipientName: PropTypes.string,
recipientEns: PropTypes.string,
recipientNickname: PropTypes.string,
addressOnly: PropTypes.bool,
assetImage: PropTypes.string,
onRecipientClick: PropTypes.func,
}
return !this.props.addressOnly && (
<div className="sender-to-recipient__sender-icon">
<Identicon
address={checksummedRecipientAddress}
diameter={24}
image={assetImage}
function Arrow ({ variant }) {
return variant === DEFAULT_VARIANT
? (
<div className="sender-to-recipient__arrow-container">
<div className="sender-to-recipient__arrow-circle">
<img
height={15}
width={15}
src="./images/arrow-right.svg"
/>
</div>
</div>
) : (
<div className="sender-to-recipient__arrow-container">
<img
height={20}
src="./images/caret-right.svg"
/>
</div>
)
}
renderRecipientWithAddress () {
const { t } = this.context
const { recipientEns, recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return (
<div
className="sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address"
onClick={() => {
copyToClipboard(checksummedRecipientAddress)
if (onRecipientClick) {
onRecipientClick()
}
}}
>
{ this.renderRecipientIdenticon() }
<Tooltip
position="bottom"
html={
this.state.senderAddressCopied
? <p>{t('copiedExclamation')}</p>
: (addressOnly && !recipientNickname && !recipientEns)
? <p>{t('copyAddress')}</p>
: (
<p>
{shortenAddress(checksummedRecipientAddress)}<br />
{t('copyAddress')}
</p>
)
}
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
>
<div className="sender-to-recipient__name">
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
{
addressOnly
? (recipientNickname || recipientEns || checksummedRecipientAddress)
: (recipientNickname || recipientEns || recipientName || this.context.t('newContract'))
}
</div>
</Tooltip>
</div>
)
}
renderRecipientWithoutAddress () {
return (
<div className="sender-to-recipient__party sender-to-recipient__party--recipient">
{ !this.props.addressOnly && <i className="fa fa-file-text-o" /> }
<div className="sender-to-recipient__name">
{ this.context.t('newContract') }
</div>
</div>
)
}
renderArrow () {
return this.props.variant === DEFAULT_VARIANT
? (
<div className="sender-to-recipient__arrow-container">
<div className="sender-to-recipient__arrow-circle">
<img
height={15}
width={15}
src="./images/arrow-right.svg"
/>
</div>
</div>
) : (
<div className="sender-to-recipient__arrow-container">
<img
height={20}
src="./images/caret-right.svg"
/>
</div>
)
}
render () {
const { senderAddress, recipientAddress, variant, onSenderClick } = this.props
const checksummedSenderAddress = checksumAddress(senderAddress)
return (
<div className={classnames('sender-to-recipient', variantHash[variant])}>
<div
className={classnames('sender-to-recipient__party sender-to-recipient__party--sender')}
onClick={() => {
this.setState({ senderAddressCopied: true })
copyToClipboard(checksummedSenderAddress)
if (onSenderClick) {
onSenderClick()
}
}}
>
{ this.renderSenderIdenticon() }
{ this.renderSenderAddress() }
</div>
{ this.renderArrow() }
{
recipientAddress
? this.renderRecipientWithAddress()
: this.renderRecipientWithoutAddress()
}
</div>
)
}
}
Arrow.propTypes = {
variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
}
export default function SenderToRecipient ({
senderAddress,
addressOnly,
assetImage,
senderName,
recipientNickname,
recipientName,
recipientEns,
onRecipientClick,
onSenderClick,
recipientAddress,
variant = DEFAULT_VARIANT,
}) {
const t = useI18nContext()
const checksummedSenderAddress = checksumAddress(senderAddress)
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return (
<div className={classnames('sender-to-recipient', variantHash[variant])}>
<SenderAddress
checksummedSenderAddress={checksummedSenderAddress}
addressOnly={addressOnly}
senderName={senderName}
onSenderClick={onSenderClick}
senderAddress={senderAddress}
/>
<Arrow variant={variant} />
{recipientAddress
? (
<RecipientWithAddress
assetImage={assetImage}
checksummedRecipientAddress={checksummedRecipientAddress}
onRecipientClick={onRecipientClick}
addressOnly={addressOnly}
recipientNickname={recipientNickname}
recipientEns={recipientEns}
recipientName={recipientName}
/>
)
: (
<div className="sender-to-recipient__party sender-to-recipient__party--recipient">
{ !addressOnly && <i className="fa fa-file-text-o" /> }
<div className="sender-to-recipient__name">
{t('newContract') }
</div>
</div>
)
}
</div>
)
}
SenderToRecipient.propTypes = {
senderName: PropTypes.string,
senderAddress: PropTypes.string,
recipientName: PropTypes.string,
recipientEns: PropTypes.string,
recipientAddress: PropTypes.string,
recipientNickname: PropTypes.string,
variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
addressOnly: PropTypes.bool,
assetImage: PropTypes.string,
onRecipientClick: PropTypes.func,
onSenderClick: PropTypes.func,
}

View File

@ -11,6 +11,7 @@ export default class Tooltip extends PureComponent {
interactive: undefined,
onHidden: null,
position: 'left',
offset: 0,
size: 'small',
title: null,
trigger: 'mouseenter',
@ -24,6 +25,7 @@ export default class Tooltip extends PureComponent {
disabled: PropTypes.bool,
html: PropTypes.node,
interactive: PropTypes.bool,
offset: PropTypes.number,
onHidden: PropTypes.func,
position: PropTypes.oneOf([
'top',
@ -53,6 +55,7 @@ export default class Tooltip extends PureComponent {
title,
trigger,
onHidden,
offset,
wrapperClassName,
style,
} = this.props
@ -77,6 +80,7 @@ export default class Tooltip extends PureComponent {
onHidden={onHidden}
position={position}
size={size}
offset={offset}
style={style}
title={title}
trigger={trigger}

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { checksumAddress } from '../../../helpers/utils/util'
@ -6,105 +6,96 @@ import Identicon from '../../../components/ui/identicon'
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
import Tooltip from '../../../components/ui/tooltip-v2'
import AccountMismatchWarning from '../../../components/ui/account-mismatch-warning/account-mismatch-warning.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
export default class AccountListItem extends Component {
export default function AccountListItem ({
account,
className,
displayAddress = false,
displayBalance = true,
handleClick,
icon = null,
balanceIsCached,
showFiat = true,
}) {
const t = useI18nContext()
const { name, address, balance } = account || {}
static propTypes = {
account: PropTypes.object,
className: PropTypes.string,
displayAddress: PropTypes.bool,
displayBalance: PropTypes.bool,
handleClick: PropTypes.func,
icon: PropTypes.node,
balanceIsCached: PropTypes.bool,
showFiat: PropTypes.bool,
}
return (
<div
className={`account-list-item ${className}`}
onClick={() => handleClick && handleClick({ name, address, balance })}
>
static defaultProps = {
showFiat: true,
}
<div className="account-list-item__top-row">
<Identicon
address={address}
className="account-list-item__identicon"
diameter={18}
/>
static contextTypes = {
t: PropTypes.func,
}
<div className="account-list-item__account-name">{ name || address }</div>
render () {
const {
account,
className,
displayAddress = false,
displayBalance = true,
handleClick,
icon = null,
balanceIsCached,
showFiat,
} = this.props
const { name, address, balance } = account || {}
return (
<div
className={`account-list-item ${className}`}
onClick={() => handleClick && handleClick({ name, address, balance })}
>
<div className="account-list-item__top-row">
<Identicon
address={address}
className="account-list-item__identicon"
diameter={18}
/>
<div className="account-list-item__account-name">{ name || address }</div>
{icon && <div className="account-list-item__icon">{ icon }</div>}
</div>
{displayAddress && name && (
<div className="account-list-item__account-address">
{ checksumAddress(address) }
</div>
)}
{displayBalance && (
<Tooltip
position="left"
title={this.context.t('balanceOutdated')}
disabled={!balanceIsCached}
style={{
left: '-20px !important',
}}
>
<div
className={classnames('account-list-item__account-balances', {
'account-list-item__cached-balances': balanceIsCached,
})}
>
<div className="account-list-item__primary-cached-container">
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={balance}
hideTitle
/>
{
balanceIsCached
? <span className="account-list-item__cached-star">*</span>
: null
}
</div>
{showFiat && (
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={balance}
hideTitle
/>
)}
</div>
</Tooltip>
)}
{icon && <div className="account-list-item__icon">{ icon }</div>}
<AccountMismatchWarning address={address} />
</div>
)
}
{displayAddress && name && (
<div className="account-list-item__account-address">
{ checksumAddress(address) }
</div>
)}
{displayBalance && (
<Tooltip
position="left"
title={t('balanceOutdated')}
disabled={!balanceIsCached}
style={{
left: '-20px !important',
}}
>
<div
className={classnames('account-list-item__account-balances', {
'account-list-item__cached-balances': balanceIsCached,
})}
>
<div className="account-list-item__primary-cached-container">
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={balance}
hideTitle
/>
{
balanceIsCached
? <span className="account-list-item__cached-star">*</span>
: null
}
</div>
{showFiat && (
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={balance}
hideTitle
/>
)}
</div>
</Tooltip>
)}
</div>
)
}
AccountListItem.propTypes = {
account: PropTypes.object,
className: PropTypes.string,
displayAddress: PropTypes.bool,
displayBalance: PropTypes.bool,
handleClick: PropTypes.func,
icon: PropTypes.node,
balanceIsCached: PropTypes.bool,
showFiat: PropTypes.bool,
}

View File

@ -2,27 +2,21 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
import * as utils from '../../../../helpers/utils/util'
import Identicon from '../../../../components/ui/identicon'
import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
}
const AccountListItem = proxyquire('../account-list-item.component.js', {
'../../../helpers/utils/util': utilsMethodStubs,
}).default
const propsMethodSpies = {
handleClick: sinon.spy(),
}
import AccountListItem from '../account-list-item.component'
describe('AccountListItem Component', function () {
let wrapper
let wrapper, propsMethodSpies, checksumAddressStub
describe('render', function () {
before(function () {
checksumAddressStub = sinon.stub(utils, 'checksumAddress').returns('mockCheckSumAddress')
propsMethodSpies = {
handleClick: sinon.spy(),
}
})
beforeEach(function () {
wrapper = shallow((
<AccountListItem
@ -41,6 +35,11 @@ describe('AccountListItem Component', function () {
afterEach(function () {
propsMethodSpies.handleClick.resetHistory()
checksumAddressStub.resetHistory()
})
after(function () {
sinon.restore()
})
it('should render a div with the passed className', function () {
@ -100,7 +99,7 @@ describe('AccountListItem Component', function () {
assert.equal(wrapper.find('.account-list-item__account-address').length, 1)
assert.equal(wrapper.find('.account-list-item__account-address').text(), 'mockCheckSumAddress')
assert.deepEqual(
utilsMethodStubs.checksumAddress.getCall(0).args,
checksumAddressStub.getCall(0).args,
['mockAddress']
)
})