Final cleanup
This commit is contained in:
parent
799baff067
commit
4bce1e4678
|
@ -46,14 +46,8 @@ async function start () {
|
|||
|
||||
// Code commented out until we begin auto adding users to NewUI
|
||||
|
||||
let css = OldMetaMaskUiCss()
|
||||
let deleteInjectedCss = injectCss(css)
|
||||
|
||||
store.subscribe(() => {
|
||||
deleteInjectedCss()
|
||||
css = OldMetaMaskUiCss()
|
||||
deleteInjectedCss = injectCss(css)
|
||||
})
|
||||
const css = OldMetaMaskUiCss()
|
||||
injectCss(css)
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default class ButtonGroup extends PureComponent {
|
||||
static propTypes = {
|
||||
defaultActiveButtonIndex: PropTypes.number,
|
||||
disabled: PropTypes.bool,
|
||||
children: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
className: 'button-group',
|
||||
}
|
||||
|
||||
state = {
|
||||
activeButtonIndex: this.props.defaultActiveButtonIndex || 0,
|
||||
}
|
||||
|
||||
handleButtonClick (activeButtonIndex) {
|
||||
this.setState({ activeButtonIndex })
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
const { children, disabled } = this.props
|
||||
|
||||
return React.Children.map(children, (child, index) => {
|
||||
return child && (
|
||||
<button
|
||||
className={classnames(
|
||||
'button-group__button',
|
||||
{ 'button-group__button--active': index === this.state.activeButtonIndex },
|
||||
)}
|
||||
onClick={() => {
|
||||
this.handleButtonClick(index)
|
||||
child.props.onClick && child.props.onClick()
|
||||
}}
|
||||
disabled={disabled || child.props.disabled}
|
||||
key={index}
|
||||
>
|
||||
{ child.props.children }
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, style } = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{ this.renderButtons() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import React from 'react'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import ButtonGroup from './'
|
||||
import Button from '../button'
|
||||
import { text, boolean } from '@storybook/addon-knobs/react'
|
||||
|
||||
storiesOf('ButtonGroup', module)
|
||||
.add('with Buttons', () =>
|
||||
<ButtonGroup
|
||||
style={{ width: '300px' }}
|
||||
disabled={boolean('Disabled', false)}
|
||||
defaultActiveButtonIndex={1}
|
||||
>
|
||||
<Button
|
||||
onClick={action('cheap')}
|
||||
>
|
||||
{text('Button1', 'Cheap')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('average')}
|
||||
>
|
||||
{text('Button2', 'Average')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('fast')}
|
||||
>
|
||||
{text('Button3', 'Fast')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
||||
.add('with a disabled Button', () =>
|
||||
<ButtonGroup
|
||||
style={{ width: '300px' }}
|
||||
disabled={boolean('Disabled', false)}
|
||||
>
|
||||
<Button
|
||||
onClick={action('enabled')}
|
||||
>
|
||||
{text('Button1', 'Enabled')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('disabled')}
|
||||
disabled
|
||||
>
|
||||
{text('Button2', 'Disabled')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './button-group.component'
|
|
@ -1,38 +0,0 @@
|
|||
.button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__button {
|
||||
font-family: Roboto;
|
||||
font-size: 1rem;
|
||||
color: $tundora;
|
||||
border-style: solid;
|
||||
border-color: $alto;
|
||||
border-width: 1px 1px 1px;
|
||||
border-left: 0;
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid $alto;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: $dodger-blue;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import ButtonGroup from '../button-group.component.js'
|
||||
|
||||
const childButtonSpies = {
|
||||
onClick: sinon.spy(),
|
||||
}
|
||||
|
||||
sinon.spy(ButtonGroup.prototype, 'handleButtonClick')
|
||||
sinon.spy(ButtonGroup.prototype, 'renderButtons')
|
||||
|
||||
const mockButtons = [
|
||||
<button onClick={childButtonSpies.onClick} key={'a'}><div className="mockClass" /></button>,
|
||||
<button onClick={childButtonSpies.onClick} key={'b'}></button>,
|
||||
<button onClick={childButtonSpies.onClick} key={'c'}></button>,
|
||||
]
|
||||
|
||||
describe('ButtonGroup Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ButtonGroup
|
||||
defaultActiveButtonIndex={1}
|
||||
disabled={false}
|
||||
className="someClassName"
|
||||
style={ { color: 'red' } }
|
||||
>{mockButtons}</ButtonGroup>)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
childButtonSpies.onClick.resetHistory()
|
||||
ButtonGroup.prototype.handleButtonClick.resetHistory()
|
||||
ButtonGroup.prototype.renderButtons.resetHistory()
|
||||
})
|
||||
|
||||
describe('handleButtonClick', () => {
|
||||
it('should set the activeButtonIndex', () => {
|
||||
assert.equal(wrapper.state('activeButtonIndex'), 1)
|
||||
wrapper.instance().handleButtonClick(2)
|
||||
assert.equal(wrapper.state('activeButtonIndex'), 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderButtons', () => {
|
||||
it('should render a button for each child', () => {
|
||||
const childButtons = wrapper.find('.button-group__button')
|
||||
assert.equal(childButtons.length, 3)
|
||||
})
|
||||
|
||||
it('should render the correct button with an active state', () => {
|
||||
const childButtons = wrapper.find('.button-group__button')
|
||||
const activeChildButton = wrapper.find('.button-group__button--active')
|
||||
assert.deepEqual(childButtons.get(1), activeChildButton.get(0))
|
||||
})
|
||||
|
||||
it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', () => {
|
||||
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0)
|
||||
assert.equal(childButtonSpies.onClick.callCount, 0)
|
||||
const childButtons = wrapper.find('.button-group__button')
|
||||
childButtons.at(0).props().onClick()
|
||||
childButtons.at(1).props().onClick()
|
||||
childButtons.at(2).props().onClick()
|
||||
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3)
|
||||
assert.equal(childButtonSpies.onClick.callCount, 3)
|
||||
})
|
||||
|
||||
it('should render all child buttons as disabled if props.disabled is true', () => {
|
||||
const childButtons = wrapper.find('.button-group__button')
|
||||
childButtons.forEach(button => {
|
||||
assert.equal(button.props().disabled, undefined)
|
||||
})
|
||||
wrapper.setProps({ disabled: true })
|
||||
const disabledChildButtons = wrapper.find('[disabled=true]')
|
||||
assert.equal(disabledChildButtons.length, 3)
|
||||
})
|
||||
|
||||
it('should render the children of the button', () => {
|
||||
const mockClass = wrapper.find('.mockClass')
|
||||
assert.equal(mockClass.length, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a div with the expected class and style', () => {
|
||||
assert.equal(wrapper.find('div').at(0).props().className, 'someClassName')
|
||||
assert.deepEqual(wrapper.find('div').at(0).props().style, { color: 'red' })
|
||||
})
|
||||
|
||||
it('should call renderButtons when rendering', () => {
|
||||
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1)
|
||||
wrapper.instance().render()
|
||||
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,25 +0,0 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default class Card extends PureComponent {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
overrideClassName: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, overrideClassName, title } = this.props
|
||||
|
||||
return (
|
||||
<div className={classnames({ 'card': !overrideClassName }, className)}>
|
||||
<div className="card__title">
|
||||
{ title }
|
||||
</div>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './card.component'
|
|
@ -1,11 +0,0 @@
|
|||
.card {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
|
||||
padding: 8px;
|
||||
|
||||
&__title {
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
padding-bottom: 4px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import Card from '../card.component'
|
||||
|
||||
describe('Card Component', () => {
|
||||
it('should render a card with a title and child element', () => {
|
||||
const wrapper = shallow(
|
||||
<Card
|
||||
title="Test"
|
||||
className="card-test-class"
|
||||
>
|
||||
<div className="child-test-class">Child</div>
|
||||
</Card>
|
||||
)
|
||||
|
||||
assert.ok(wrapper.hasClass('card-test-class'))
|
||||
const title = wrapper.find('.card__title')
|
||||
assert.ok(title)
|
||||
assert.equal(title.text(), 'Test')
|
||||
const child = wrapper.find('.child-test-class')
|
||||
assert.ok(child)
|
||||
assert.equal(child.text(), 'Child')
|
||||
})
|
||||
})
|
|
@ -1,84 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
const ConfirmDetailRow = props => {
|
||||
const {
|
||||
label,
|
||||
primaryText,
|
||||
secondaryText,
|
||||
onHeaderClick,
|
||||
primaryValueTextColor,
|
||||
headerText,
|
||||
headerTextClassName,
|
||||
value,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className="confirm-detail-row">
|
||||
<div className="confirm-detail-row__label">
|
||||
{ label }
|
||||
</div>
|
||||
<div className="confirm-detail-row__details">
|
||||
<div
|
||||
className={classnames('confirm-detail-row__header-text', headerTextClassName)}
|
||||
onClick={() => onHeaderClick && onHeaderClick()}
|
||||
>
|
||||
{ headerText }
|
||||
</div>
|
||||
{
|
||||
primaryText
|
||||
? (
|
||||
<div
|
||||
className="confirm-detail-row__primary"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
>
|
||||
{ primaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__primary"
|
||||
type={PRIMARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
ethLogoHeight="18"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
secondaryText
|
||||
? (
|
||||
<div className="confirm-detail-row__secondary">
|
||||
{ secondaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__secondary"
|
||||
type={SECONDARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmDetailRow.propTypes = {
|
||||
headerText: PropTypes.string,
|
||||
headerTextClassName: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onHeaderClick: PropTypes.func,
|
||||
primaryValueTextColor: PropTypes.string,
|
||||
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
secondaryText: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfirmDetailRow
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-detail-row.component'
|
|
@ -1,46 +0,0 @@
|
|||
.confirm-detail-row {
|
||||
padding: 14px 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
font-size: .75rem;
|
||||
font-weight: 500;
|
||||
color: $scorpion;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__details {
|
||||
flex: 1;
|
||||
text-align: end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__primary {
|
||||
font-size: 1.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__secondary {
|
||||
color: $oslo-gray;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__header-text {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
color: $scorpion;
|
||||
|
||||
&--edit {
|
||||
color: $curious-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&--total {
|
||||
font-size: .625rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import ConfirmDetailRow from '../confirm-detail-row.component.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const propsMethodSpies = {
|
||||
onHeaderClick: sinon.spy(),
|
||||
}
|
||||
|
||||
describe('Confirm Detail Row Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<ConfirmDetailRow
|
||||
errorType={'mockErrorType'}
|
||||
label={'mockLabel'}
|
||||
showError={false}
|
||||
primaryText = {'mockFiatText'}
|
||||
secondaryText = {'mockEthText'}
|
||||
primaryValueTextColor= {'mockColor'}
|
||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||
headerText = {'mockHeaderText'}
|
||||
headerTextClassName = {'mockHeaderClass'}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a div with a confirm-detail-row class', () => {
|
||||
assert.equal(wrapper.find('div.confirm-detail-row').length, 1)
|
||||
})
|
||||
|
||||
it('should render the label as a child of the confirm-detail-row__label', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row > .confirm-detail-row__label').childAt(0).text(), 'mockLabel')
|
||||
})
|
||||
|
||||
it('should render the headerText as a child of the confirm-detail-row__header-text', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
|
||||
})
|
||||
|
||||
it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
|
||||
})
|
||||
|
||||
it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
|
||||
})
|
||||
|
||||
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
|
||||
})
|
||||
|
||||
it('should assure the confirm-detail-row__header-text classname is correct', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__header-text').props().className, 'confirm-detail-row__header-text mockHeaderClass')
|
||||
})
|
||||
|
||||
it('should call onHeaderClick when headerText div gets clicked', () => {
|
||||
wrapper.find('.confirm-detail-row__header-text').props().onClick()
|
||||
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,110 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { Tabs, Tab } from '../../tabs'
|
||||
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from './'
|
||||
import ErrorMessage from '../../error-message'
|
||||
|
||||
export default class ConfirmPageContainerContent extends Component {
|
||||
static propTypes = {
|
||||
action: PropTypes.string,
|
||||
dataComponent: PropTypes.node,
|
||||
detailsComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
identiconAddress: PropTypes.string,
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
summaryComponent: PropTypes.node,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.node,
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { detailsComponent, dataComponent } = this.props
|
||||
|
||||
if (detailsComponent && dataComponent) {
|
||||
return this.renderTabs()
|
||||
} else {
|
||||
return detailsComponent || dataComponent
|
||||
}
|
||||
}
|
||||
|
||||
renderTabs () {
|
||||
const { detailsComponent, dataComponent } = this.props
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<Tab name="Details">
|
||||
{ detailsComponent }
|
||||
</Tab>
|
||||
<Tab name="Data">
|
||||
{ dataComponent }
|
||||
</Tab>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
action,
|
||||
errorKey,
|
||||
errorMessage,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
assetImage,
|
||||
summaryComponent,
|
||||
detailsComponent,
|
||||
dataComponent,
|
||||
warning,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="confirm-page-container-content">
|
||||
{
|
||||
warning && (
|
||||
<ConfirmPageContainerWarning warning={warning} />
|
||||
)
|
||||
}
|
||||
{
|
||||
summaryComponent || (
|
||||
<ConfirmPageContainerSummary
|
||||
className={classnames({
|
||||
'confirm-page-container-summary--border': !detailsComponent || !dataComponent,
|
||||
})}
|
||||
action={action}
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
identiconAddress={identiconAddress}
|
||||
nonce={nonce}
|
||||
assetImage={assetImage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ this.renderContent() }
|
||||
{
|
||||
(errorKey || errorMessage) && (
|
||||
<div className="confirm-page-container-content__error-container">
|
||||
<ErrorMessage
|
||||
errorMessage={errorMessage}
|
||||
errorKey={errorKey}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import Identicon from '../../../identicon'
|
||||
|
||||
const ConfirmPageContainerSummary = props => {
|
||||
const {
|
||||
action,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
className,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
assetImage,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className={classnames('confirm-page-container-summary', className)}>
|
||||
<div className="confirm-page-container-summary__action-row">
|
||||
<div className="confirm-page-container-summary__action">
|
||||
{ action }
|
||||
</div>
|
||||
{
|
||||
nonce && (
|
||||
<div className="confirm-page-container-summary__nonce">
|
||||
{ `#${nonce}` }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="confirm-page-container-summary__title">
|
||||
{
|
||||
identiconAddress && (
|
||||
<Identicon
|
||||
className="confirm-page-container-summary__identicon"
|
||||
diameter={36}
|
||||
address={identiconAddress}
|
||||
image={assetImage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className="confirm-page-container-summary__title-text">
|
||||
{ titleComponent || title }
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
|
||||
{ subtitleComponent || subtitle }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmPageContainerSummary.propTypes = {
|
||||
action: PropTypes.string,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.node,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
identiconAddress: PropTypes.string,
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfirmPageContainerSummary
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-page-container-summary.component'
|
|
@ -1,54 +0,0 @@
|
|||
.confirm-page-container-summary {
|
||||
padding: 16px 24px 0;
|
||||
background-color: #f9fafa;
|
||||
height: 133px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__action-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__action {
|
||||
text-transform: uppercase;
|
||||
color: $oslo-gray;
|
||||
font-size: .75rem;
|
||||
padding: 3px 8px;
|
||||
border: 1px solid $oslo-gray;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__nonce {
|
||||
color: $oslo-gray;
|
||||
}
|
||||
|
||||
&__title {
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__identicon {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&__title-text {
|
||||
font-size: 2.25rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
color: $oslo-gray;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-bottom: 1px solid $geyser;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const ConfirmPageContainerWarning = props => {
|
||||
return (
|
||||
<div className="confirm-page-container-warning">
|
||||
<img
|
||||
className="confirm-page-container-warning__icon"
|
||||
src="/images/alert.svg"
|
||||
/>
|
||||
<div className="confirm-page-container-warning__warning">
|
||||
{ props.warning }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmPageContainerWarning.propTypes = {
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfirmPageContainerWarning
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-page-container-warning.component'
|
|
@ -1,18 +0,0 @@
|
|||
.confirm-page-container-warning {
|
||||
background-color: #fffcdb;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $geyser;
|
||||
padding: 12px 24px;
|
||||
|
||||
&__icon {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&__warning {
|
||||
font-size: .75rem;
|
||||
color: #5f5922;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export { default } from './confirm-page-container-content.component'
|
||||
export { default as ConfirmPageContainerSummary } from './confirm-page-container-summary'
|
||||
export { default as ConfirmPageContainerWarning } from './confirm-page-container-warning'
|
|
@ -1,64 +0,0 @@
|
|||
@import './confirm-page-container-warning/index';
|
||||
|
||||
@import './confirm-page-container-summary/index';
|
||||
|
||||
.confirm-page-container-content {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
&__error-container {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
&__details {
|
||||
box-sizing: border-box;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
&__data {
|
||||
padding: 16px;
|
||||
color: $oslo-gray;
|
||||
}
|
||||
|
||||
&__data-box {
|
||||
background-color: #f9fafa;
|
||||
padding: 12px;
|
||||
font-size: .75rem;
|
||||
margin-bottom: 16px;
|
||||
word-wrap: break-word;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
&-label {
|
||||
text-transform: uppercase;
|
||||
padding: 8px 0 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__data-field {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&-label {
|
||||
font-weight: 500;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__gas-fee {
|
||||
border-bottom: 1px solid $geyser;
|
||||
}
|
||||
|
||||
&__function-type {
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
color: $black;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
} from '../../../../../app/scripts/lib/enums'
|
||||
import NetworkDisplay from '../../network-display'
|
||||
|
||||
export default class ConfirmPageContainer extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
showEdit: PropTypes.bool,
|
||||
onEdit: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
renderTop () {
|
||||
const { onEdit, showEdit } = this.props
|
||||
const windowType = window.METAMASK_UI_TYPE
|
||||
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
|
||||
windowType !== ENVIRONMENT_TYPE_POPUP
|
||||
|
||||
if (!showEdit && isFullScreen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="confirm-page-container-header__row">
|
||||
<div
|
||||
className="confirm-page-container-header__back-button-container"
|
||||
style={{
|
||||
visibility: showEdit ? 'initial' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/caret-left.svg"
|
||||
/>
|
||||
<span
|
||||
className="confirm-page-container-header__back-button"
|
||||
onClick={() => onEdit()}
|
||||
>
|
||||
{ this.context.t('edit') }
|
||||
</span>
|
||||
</div>
|
||||
{ !isFullScreen && <NetworkDisplay /> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props
|
||||
|
||||
return (
|
||||
<div className="confirm-page-container-header">
|
||||
{ this.renderTop() }
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-page-container-header.component'
|
|
@ -1,27 +0,0 @@
|
|||
.confirm-page-container-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid $geyser;
|
||||
padding: 13px 13px 13px 24px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__back-button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__back-button {
|
||||
color: #2f9ae0;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SenderToRecipient from '../sender-to-recipient'
|
||||
import { PageContainerFooter } from '../page-container'
|
||||
import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
|
||||
|
||||
export default class ConfirmPageContainer extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
// Header
|
||||
action: PropTypes.string,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
onEdit: PropTypes.func,
|
||||
showEdit: PropTypes.bool,
|
||||
subtitle: PropTypes.string,
|
||||
subtitleComponent: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
titleComponent: PropTypes.node,
|
||||
// Sender to Recipient
|
||||
fromAddress: PropTypes.string,
|
||||
fromName: PropTypes.string,
|
||||
toAddress: PropTypes.string,
|
||||
toName: PropTypes.string,
|
||||
// Content
|
||||
contentComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
fiatTransactionAmount: PropTypes.string,
|
||||
fiatTransactionFee: PropTypes.string,
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
ethTransactionAmount: PropTypes.string,
|
||||
ethTransactionFee: PropTypes.string,
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
onEditGas: PropTypes.func,
|
||||
dataComponent: PropTypes.node,
|
||||
detailsComponent: PropTypes.node,
|
||||
identiconAddress: PropTypes.string,
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
summaryComponent: PropTypes.node,
|
||||
warning: PropTypes.string,
|
||||
unapprovedTxCount: PropTypes.number,
|
||||
// Footer
|
||||
onCancelAll: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
showEdit,
|
||||
onEdit,
|
||||
fromName,
|
||||
fromAddress,
|
||||
toName,
|
||||
toAddress,
|
||||
disabled,
|
||||
errorKey,
|
||||
errorMessage,
|
||||
contentComponent,
|
||||
action,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
summaryComponent,
|
||||
detailsComponent,
|
||||
dataComponent,
|
||||
onCancelAll,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
unapprovedTxCount,
|
||||
assetImage,
|
||||
warning,
|
||||
} = this.props
|
||||
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<ConfirmPageContainerHeader
|
||||
showEdit={showEdit}
|
||||
onEdit={() => onEdit()}
|
||||
>
|
||||
<SenderToRecipient
|
||||
senderName={fromName}
|
||||
senderAddress={fromAddress}
|
||||
recipientName={toName}
|
||||
recipientAddress={toAddress}
|
||||
assetImage={renderAssetImage ? assetImage : undefined}
|
||||
/>
|
||||
</ConfirmPageContainerHeader>
|
||||
{
|
||||
contentComponent || (
|
||||
<ConfirmPageContainerContent
|
||||
action={action}
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
summaryComponent={summaryComponent}
|
||||
detailsComponent={detailsComponent}
|
||||
dataComponent={dataComponent}
|
||||
errorMessage={errorMessage}
|
||||
errorKey={errorKey}
|
||||
identiconAddress={identiconAddress}
|
||||
nonce={nonce}
|
||||
assetImage={assetImage}
|
||||
warning={warning}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<PageContainerFooter
|
||||
onCancel={() => onCancel()}
|
||||
cancelText={this.context.t('reject')}
|
||||
onSubmit={() => onSubmit()}
|
||||
submitText={this.context.t('confirm')}
|
||||
submitButtonType="confirm"
|
||||
disabled={disabled}
|
||||
>
|
||||
{unapprovedTxCount > 1 && (
|
||||
<a onClick={() => onCancelAll()}>
|
||||
{this.context.t('rejectTxsN', [unapprovedTxCount])}
|
||||
</a>
|
||||
)}
|
||||
</PageContainerFooter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
export { default } from './confirm-page-container.component'
|
||||
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
|
||||
export { default as ConfirmDetailRow } from './confirm-detail-row'
|
||||
export {
|
||||
default as ConfirmPageContainerContent,
|
||||
ConfirmPageContainerSummary,
|
||||
ConfirmPageContainerError,
|
||||
} from './confirm-page-container-content'
|
|
@ -1,5 +0,0 @@
|
|||
@import './confirm-page-container-content/index';
|
||||
|
||||
@import './confirm-page-container-header/index';
|
||||
|
||||
@import './confirm-detail-row/index';
|
|
@ -1,66 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
CopyButton.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect()(CopyButton)
|
||||
|
||||
|
||||
inherits(CopyButton, Component)
|
||||
function CopyButton () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
CopyButton.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
|
||||
const value = props.value
|
||||
const copied = state.copied
|
||||
|
||||
const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton')
|
||||
|
||||
return h('.copy-button', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(Tooltip, {
|
||||
title: message,
|
||||
}, [
|
||||
h('i.fa.fa-clipboard.cursor-pointer.color-orange', {
|
||||
style: {
|
||||
margin: '5px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
CopyButton.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
Copyable.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect()(Copyable)
|
||||
|
||||
|
||||
inherits(Copyable, Component)
|
||||
function Copyable () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
copied: false,
|
||||
}
|
||||
}
|
||||
|
||||
Copyable.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
const { value, children } = props
|
||||
const { copied } = state
|
||||
|
||||
return h(Tooltip, {
|
||||
title: copied ? this.context.t('copiedExclamation') : this.context.t('copy'),
|
||||
position: 'bottom',
|
||||
}, h('span', {
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}, children))
|
||||
}
|
||||
|
||||
Copyable.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { ETH, GWEI } from '../../constants/common'
|
||||
|
||||
export default class CurrencyDisplay extends PureComponent {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
displayValue: PropTypes.string,
|
||||
prefix: PropTypes.string,
|
||||
prefixComponent: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
// Used in container
|
||||
currency: PropTypes.oneOf([ETH]),
|
||||
denomination: PropTypes.oneOf([GWEI]),
|
||||
value: PropTypes.string,
|
||||
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
hideLabel: PropTypes.bool,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, displayValue, prefix, prefixComponent, style } = this.props
|
||||
const text = `${prefix || ''}${displayValue}`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('currency-display-component', className)}
|
||||
style={style}
|
||||
title={text}
|
||||
>
|
||||
{ prefixComponent}
|
||||
<span className="currency-display-component__text">{ text }</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import CurrencyDisplay from './currency-display.component'
|
||||
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { currentCurrency, conversionRate } } = state
|
||||
|
||||
return {
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
}
|
||||
}
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
|
||||
const {
|
||||
value,
|
||||
numberOfDecimals = 2,
|
||||
currency,
|
||||
denomination,
|
||||
hideLabel,
|
||||
...restOwnProps
|
||||
} = ownProps
|
||||
|
||||
const toCurrency = currency || currentCurrency
|
||||
const convertedValue = getValueFromWeiHex({
|
||||
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
})
|
||||
const formattedValue = formatCurrency(convertedValue, toCurrency)
|
||||
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
||||
|
||||
return {
|
||||
...restStateProps,
|
||||
...dispatchProps,
|
||||
...restOwnProps,
|
||||
displayValue,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './currency-display.container'
|
|
@ -1,10 +0,0 @@
|
|||
.currency-display-component {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import CurrencyDisplay from '../currency-display.component'
|
||||
|
||||
describe('CurrencyDisplay Component', () => {
|
||||
it('should render text with a className', () => {
|
||||
const wrapper = shallow(<CurrencyDisplay
|
||||
displayValue="$123.45"
|
||||
className="currency-display"
|
||||
/>)
|
||||
|
||||
assert.ok(wrapper.hasClass('currency-display'))
|
||||
assert.equal(wrapper.text(), '$123.45')
|
||||
})
|
||||
|
||||
it('should render text with a prefix', () => {
|
||||
const wrapper = shallow(<CurrencyDisplay
|
||||
displayValue="$123.45"
|
||||
className="currency-display"
|
||||
prefix="-"
|
||||
/>)
|
||||
|
||||
assert.ok(wrapper.hasClass('currency-display'))
|
||||
assert.equal(wrapper.text(), '-$123.45')
|
||||
})
|
||||
})
|
|
@ -1,120 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
|
||||
let mapStateToProps, mergeProps
|
||||
|
||||
proxyquire('../currency-display.container.js', {
|
||||
'react-redux': {
|
||||
connect: (ms, md, mp) => {
|
||||
mapStateToProps = ms
|
||||
mergeProps = mp
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
describe('CurrencyDisplay container', () => {
|
||||
describe('mapStateToProps()', () => {
|
||||
it('should return the correct props', () => {
|
||||
const mockState = {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
},
|
||||
}
|
||||
|
||||
assert.deepEqual(mapStateToProps(mockState), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeProps()', () => {
|
||||
it('should return the correct props', () => {
|
||||
const mockStateProps = {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{
|
||||
props: {
|
||||
value: '0x2386f26fc10000',
|
||||
numberOfDecimals: 2,
|
||||
currency: 'usd',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x2386f26fc10000',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1.266 ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1.266',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
denomination: 'GWEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
denomination: 'WEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1000000000',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
numberOfDecimals: 100,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1e-9',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
tests.forEach(({ props, result }) => {
|
||||
assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const ErrorMessage = (props, context) => {
|
||||
const { errorMessage, errorKey } = props
|
||||
const error = errorKey ? context.t(errorKey) : errorMessage
|
||||
|
||||
return (
|
||||
<div className="error-message">
|
||||
<img
|
||||
src="/images/alert-red.svg"
|
||||
className="error-message__icon"
|
||||
/>
|
||||
<div className="error-message__text">
|
||||
{ `ALERT: ${error}` }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ErrorMessage.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
errorKey: PropTypes.string,
|
||||
}
|
||||
|
||||
ErrorMessage.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
export default ErrorMessage
|
|
@ -1 +0,0 @@
|
|||
export { default } from './error-message.component'
|
|
@ -1,21 +0,0 @@
|
|||
.error-message {
|
||||
min-height: 32px;
|
||||
border: 1px solid $monzo;
|
||||
color: $monzo;
|
||||
background: lighten($monzo, 56%);
|
||||
border-radius: 4px;
|
||||
font-size: .75rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
|
||||
&__icon {
|
||||
margin-right: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__text {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import ErrorMessage from '../error-message.component'
|
||||
|
||||
describe('ErrorMessage Component', () => {
|
||||
const t = key => `translate ${key}`
|
||||
|
||||
it('should render a message from props.errorMessage', () => {
|
||||
const wrapper = shallow(
|
||||
<ErrorMessage
|
||||
errorMessage="This is an error."
|
||||
/>,
|
||||
{ context: { t }}
|
||||
)
|
||||
|
||||
assert.ok(wrapper)
|
||||
assert.equal(wrapper.find('.error-message').length, 1)
|
||||
assert.equal(wrapper.find('.error-message__icon').length, 1)
|
||||
assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: This is an error.')
|
||||
})
|
||||
|
||||
it('should render a message translated from props.errorKey', () => {
|
||||
const wrapper = shallow(
|
||||
<ErrorMessage
|
||||
errorKey="testKey"
|
||||
/>,
|
||||
{ context: { t }}
|
||||
)
|
||||
|
||||
assert.ok(wrapper)
|
||||
assert.equal(wrapper.find('.error-message').length, 1)
|
||||
assert.equal(wrapper.find('.error-message__icon').length, 1)
|
||||
assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: translate testKey')
|
||||
})
|
||||
})
|
|
@ -1,21 +0,0 @@
|
|||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { hexToDecimal } from '../../helpers/conversions.util'
|
||||
|
||||
export default class HexToDecimal extends PureComponent {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, value } = this.props
|
||||
const decimalValue = hexToDecimal(value)
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{ decimalValue }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './hex-to-decimal.component'
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import HexToDecimal from '../hex-to-decimal.component'
|
||||
|
||||
describe('HexToDecimal Component', () => {
|
||||
it('should render a prefixed hex as a decimal with a className', () => {
|
||||
const wrapper = shallow(<HexToDecimal
|
||||
value="0x3039"
|
||||
className="hex-to-decimal"
|
||||
/>)
|
||||
|
||||
assert.ok(wrapper.hasClass('hex-to-decimal'))
|
||||
assert.equal(wrapper.text(), '12345')
|
||||
})
|
||||
|
||||
it('should render an unprefixed hex as a decimal with a className', () => {
|
||||
const wrapper = shallow(<HexToDecimal
|
||||
value="1A85"
|
||||
className="hex-to-decimal"
|
||||
/>)
|
||||
|
||||
assert.ok(wrapper.hasClass('hex-to-decimal'))
|
||||
assert.equal(wrapper.text(), '6789')
|
||||
})
|
||||
})
|
|
@ -1,81 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const {
|
||||
addCurrencies,
|
||||
conversionGTE,
|
||||
conversionLTE,
|
||||
subtractCurrencies,
|
||||
} = require('../conversion-util')
|
||||
|
||||
module.exports = InputNumber
|
||||
|
||||
inherits(InputNumber, Component)
|
||||
function InputNumber () {
|
||||
Component.call(this)
|
||||
|
||||
this.setValue = this.setValue.bind(this)
|
||||
}
|
||||
|
||||
function isValidInput (text) {
|
||||
const re = /^([1-9]\d*|0)(\.|\.\d*)?$/
|
||||
return re.test(text)
|
||||
}
|
||||
|
||||
function removeLeadingZeroes (str) {
|
||||
return str.replace(/^0*(?=\d)/, '')
|
||||
}
|
||||
|
||||
InputNumber.prototype.setValue = function (newValue) {
|
||||
newValue = removeLeadingZeroes(newValue)
|
||||
if (newValue && !isValidInput(newValue)) return
|
||||
const { fixed, min = -1, max = Infinity, onChange } = this.props
|
||||
|
||||
newValue = fixed ? newValue.toFixed(4) : newValue
|
||||
const newValueGreaterThanMin = conversionGTE(
|
||||
{ value: newValue || '0', fromNumericBase: 'dec' },
|
||||
{ value: min, fromNumericBase: 'hex' },
|
||||
)
|
||||
|
||||
const newValueLessThanMax = conversionLTE(
|
||||
{ value: newValue || '0', fromNumericBase: 'dec' },
|
||||
{ value: max, fromNumericBase: 'hex' },
|
||||
)
|
||||
if (newValueGreaterThanMin && newValueLessThanMax) {
|
||||
onChange(newValue)
|
||||
} else if (!newValueGreaterThanMin) {
|
||||
onChange(min)
|
||||
} else if (!newValueLessThanMax) {
|
||||
onChange(max)
|
||||
}
|
||||
}
|
||||
|
||||
InputNumber.prototype.render = function () {
|
||||
const { unitLabel, step = 1, placeholder, value } = this.props
|
||||
|
||||
return h('div.customize-gas-input-wrapper', {}, [
|
||||
h('input', {
|
||||
className: 'customize-gas-input',
|
||||
value,
|
||||
placeholder,
|
||||
type: 'number',
|
||||
onChange: e => {
|
||||
this.setValue(e.target.value)
|
||||
},
|
||||
min: 0,
|
||||
}),
|
||||
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
||||
h('div.gas-tooltip-input-arrows', {}, [
|
||||
h('div.gas-tooltip-input-arrow-wrapper', {
|
||||
onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
|
||||
}, [
|
||||
h('i.fa.fa-angle-up'),
|
||||
]),
|
||||
h('div.gas-tooltip-input-arrow-wrapper', {
|
||||
onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })),
|
||||
}, [
|
||||
h('i.fa.fa-angle-down'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { checksumAddress } from '../../../util'
|
||||
import Identicon from '../../identicon'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
export default class AccountListItem extends Component {
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
displayAddress: PropTypes.bool,
|
||||
displayBalance: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
icon: PropTypes.node,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
account,
|
||||
className,
|
||||
displayAddress = false,
|
||||
displayBalance = true,
|
||||
handleClick,
|
||||
icon = null,
|
||||
} = this.props
|
||||
|
||||
const { name, address, balance } = account || {}
|
||||
|
||||
return (<div
|
||||
className={`account-list-item ${className}`}
|
||||
onClick={() => 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 && (
|
||||
<div className="account-list-item__account-balances">
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={balance}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={balance}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getConversionRate,
|
||||
getCurrentCurrency,
|
||||
} from '../send.selectors.js'
|
||||
import AccountListItem from './account-list-item.component'
|
||||
|
||||
export default connect(mapStateToProps)(AccountListItem)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: getConversionRate(state),
|
||||
currentCurrency: getCurrentCurrency(state),
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './account-list-item.container'
|
|
@ -1,132 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import proxyquire from 'proxyquire'
|
||||
import Identicon from '../../../identicon'
|
||||
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
|
||||
|
||||
const utilsMethodStubs = {
|
||||
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
|
||||
}
|
||||
|
||||
const AccountListItem = proxyquire('../account-list-item.component.js', {
|
||||
'../../../util': utilsMethodStubs,
|
||||
}).default
|
||||
|
||||
|
||||
const propsMethodSpies = {
|
||||
handleClick: sinon.spy(),
|
||||
}
|
||||
|
||||
describe('AccountListItem Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AccountListItem
|
||||
account={ { address: 'mockAddress', name: 'mockName', balance: 'mockBalance' } }
|
||||
className={'mockClassName'}
|
||||
conversionRate={4}
|
||||
currentCurrency={'mockCurrentyCurrency'}
|
||||
displayAddress={false}
|
||||
displayBalance={false}
|
||||
handleClick={propsMethodSpies.handleClick}
|
||||
icon={<i className="mockIcon" />}
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.handleClick.resetHistory()
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a div with the passed className', () => {
|
||||
assert.equal(wrapper.find('.mockClassName').length, 1)
|
||||
assert(wrapper.find('.mockClassName').is('div'))
|
||||
assert(wrapper.find('.mockClassName').hasClass('account-list-item'))
|
||||
})
|
||||
|
||||
it('should call handleClick with the expected props when the root div is clicked', () => {
|
||||
const { onClick } = wrapper.find('.mockClassName').props()
|
||||
assert.equal(propsMethodSpies.handleClick.callCount, 0)
|
||||
onClick()
|
||||
assert.equal(propsMethodSpies.handleClick.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.handleClick.getCall(0).args,
|
||||
[{ address: 'mockAddress', name: 'mockName', balance: 'mockBalance' }]
|
||||
)
|
||||
})
|
||||
|
||||
it('should have a top row div', () => {
|
||||
assert.equal(wrapper.find('.mockClassName > .account-list-item__top-row').length, 1)
|
||||
assert(wrapper.find('.mockClassName > .account-list-item__top-row').is('div'))
|
||||
})
|
||||
|
||||
it('should have an identicon, name and icon in the top row', () => {
|
||||
const topRow = wrapper.find('.mockClassName > .account-list-item__top-row')
|
||||
assert.equal(topRow.find(Identicon).length, 1)
|
||||
assert.equal(topRow.find('.account-list-item__account-name').length, 1)
|
||||
assert.equal(topRow.find('.account-list-item__icon').length, 1)
|
||||
})
|
||||
|
||||
it('should show the account name if it exists', () => {
|
||||
const topRow = wrapper.find('.mockClassName > .account-list-item__top-row')
|
||||
assert.equal(topRow.find('.account-list-item__account-name').text(), 'mockName')
|
||||
})
|
||||
|
||||
it('should show the account address if there is no name', () => {
|
||||
wrapper.setProps({ account: { address: 'addressButNoName' } })
|
||||
const topRow = wrapper.find('.mockClassName > .account-list-item__top-row')
|
||||
assert.equal(topRow.find('.account-list-item__account-name').text(), 'addressButNoName')
|
||||
})
|
||||
|
||||
it('should render the passed icon', () => {
|
||||
const topRow = wrapper.find('.mockClassName > .account-list-item__top-row')
|
||||
assert(topRow.find('.account-list-item__icon').childAt(0).is('i'))
|
||||
assert(topRow.find('.account-list-item__icon').childAt(0).hasClass('mockIcon'))
|
||||
})
|
||||
|
||||
it('should not render an icon if none is passed', () => {
|
||||
wrapper.setProps({ icon: null })
|
||||
const topRow = wrapper.find('.mockClassName > .account-list-item__top-row')
|
||||
assert.equal(topRow.find('.account-list-item__icon').length, 0)
|
||||
})
|
||||
|
||||
it('should render the account address as a checksumAddress if displayAddress is true and name is provided', () => {
|
||||
wrapper.setProps({ displayAddress: true })
|
||||
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,
|
||||
['mockAddress']
|
||||
)
|
||||
})
|
||||
|
||||
it('should not render the account address as a checksumAddress if displayAddress is false', () => {
|
||||
wrapper.setProps({ displayAddress: false })
|
||||
assert.equal(wrapper.find('.account-list-item__account-address').length, 0)
|
||||
})
|
||||
|
||||
it('should not render the account address as a checksumAddress if name is not provided', () => {
|
||||
wrapper.setProps({ account: { address: 'someAddressButNoName' } })
|
||||
assert.equal(wrapper.find('.account-list-item__account-address').length, 0)
|
||||
})
|
||||
|
||||
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
|
||||
wrapper.setProps({ displayBalance: true })
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
assert.deepEqual(
|
||||
wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
|
||||
{
|
||||
type: 'PRIMARY',
|
||||
value: 'mockBalance',
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should not render a CurrencyDisplay if displayBalance is false', () => {
|
||||
wrapper.setProps({ displayBalance: false })
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,32 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
|
||||
let mapStateToProps
|
||||
|
||||
proxyquire('../account-list-item.container.js', {
|
||||
'react-redux': {
|
||||
connect: (ms, md) => {
|
||||
mapStateToProps = ms
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
'../send.selectors.js': {
|
||||
getConversionRate: (s) => `mockConversionRate:${s}`,
|
||||
getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
|
||||
},
|
||||
})
|
||||
|
||||
describe('account-list-item container', () => {
|
||||
|
||||
describe('mapStateToProps()', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), {
|
||||
conversionRate: 'mockConversionRate:mockState',
|
||||
currentCurrency: 'mockCurrentCurrency:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send.container'
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send-content.component'
|
|
@ -1,60 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class AmountMaxButton extends Component {
|
||||
|
||||
static propTypes = {
|
||||
balance: PropTypes.string,
|
||||
gasTotal: PropTypes.string,
|
||||
maxModeOn: PropTypes.bool,
|
||||
selectedToken: PropTypes.object,
|
||||
setAmountToMax: PropTypes.func,
|
||||
setMaxModeTo: PropTypes.func,
|
||||
tokenBalance: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
setMaxAmount () {
|
||||
const {
|
||||
balance,
|
||||
gasTotal,
|
||||
selectedToken,
|
||||
setAmountToMax,
|
||||
tokenBalance,
|
||||
} = this.props
|
||||
|
||||
setAmountToMax({
|
||||
balance,
|
||||
gasTotal,
|
||||
selectedToken,
|
||||
tokenBalance,
|
||||
})
|
||||
}
|
||||
|
||||
onMaxClick = (event) => {
|
||||
const { setMaxModeTo } = this.props
|
||||
|
||||
event.preventDefault()
|
||||
setMaxModeTo(true)
|
||||
this.setMaxAmount()
|
||||
}
|
||||
|
||||
render () {
|
||||
return this.props.maxModeOn
|
||||
? null
|
||||
: (
|
||||
<div>
|
||||
<span
|
||||
className="send-v2__amount-max"
|
||||
onClick={this.onMaxClick}
|
||||
>
|
||||
{this.context.t('max')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getGasTotal,
|
||||
getSelectedToken,
|
||||
getSendFromBalance,
|
||||
getTokenBalance,
|
||||
} from '../../../send.selectors.js'
|
||||
import { getMaxModeOn } from './amount-max-button.selectors.js'
|
||||
import { calcMaxAmount } from './amount-max-button.utils.js'
|
||||
import {
|
||||
updateSendAmount,
|
||||
setMaxModeTo,
|
||||
} from '../../../../../actions'
|
||||
import AmountMaxButton from './amount-max-button.component'
|
||||
import {
|
||||
updateSendErrors,
|
||||
} from '../../../../../ducks/send.duck'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
||||
return {
|
||||
balance: getSendFromBalance(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
maxModeOn: getMaxModeOn(state),
|
||||
selectedToken: getSelectedToken(state),
|
||||
tokenBalance: getTokenBalance(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
setAmountToMax: maxAmountDataObject => {
|
||||
dispatch(updateSendErrors({ amount: null }))
|
||||
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
||||
},
|
||||
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
const selectors = {
|
||||
getMaxModeOn,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
|
||||
function getMaxModeOn (state) {
|
||||
return state.metamask.send.maxModeOn
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
const {
|
||||
multiplyCurrencies,
|
||||
subtractCurrencies,
|
||||
} = require('../../../../../conversion-util')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
|
||||
const { decimals } = selectedToken || {}
|
||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||
|
||||
return selectedToken
|
||||
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
|
||||
: subtractCurrencies(
|
||||
ethUtil.addHexPrefix(balance),
|
||||
ethUtil.addHexPrefix(gasTotal),
|
||||
{ toNumericBase: 'hex' }
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calcMaxAmount,
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './amount-max-button.container'
|
|
@ -1,89 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import AmountMaxButton from '../amount-max-button.component.js'
|
||||
|
||||
const propsMethodSpies = {
|
||||
setAmountToMax: sinon.spy(),
|
||||
setMaxModeTo: sinon.spy(),
|
||||
}
|
||||
|
||||
const MOCK_EVENT = { preventDefault: () => {} }
|
||||
|
||||
sinon.spy(AmountMaxButton.prototype, 'setMaxAmount')
|
||||
|
||||
describe('AmountMaxButton Component', function () {
|
||||
let wrapper
|
||||
let instance
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AmountMaxButton
|
||||
balance={'mockBalance'}
|
||||
gasTotal={'mockGasTotal'}
|
||||
maxModeOn={false}
|
||||
selectedToken={ { address: 'mockTokenAddress' } }
|
||||
setAmountToMax={propsMethodSpies.setAmountToMax}
|
||||
setMaxModeTo={propsMethodSpies.setMaxModeTo}
|
||||
tokenBalance={'mockTokenBalance'}
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
instance = wrapper.instance()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.setAmountToMax.resetHistory()
|
||||
propsMethodSpies.setMaxModeTo.resetHistory()
|
||||
AmountMaxButton.prototype.setMaxAmount.resetHistory()
|
||||
})
|
||||
|
||||
describe('setMaxAmount', () => {
|
||||
|
||||
it('should call setAmountToMax with the correct params', () => {
|
||||
assert.equal(propsMethodSpies.setAmountToMax.callCount, 0)
|
||||
instance.setMaxAmount()
|
||||
assert.equal(propsMethodSpies.setAmountToMax.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.setAmountToMax.getCall(0).args,
|
||||
[{
|
||||
balance: 'mockBalance',
|
||||
gasTotal: 'mockGasTotal',
|
||||
selectedToken: { address: 'mockTokenAddress' },
|
||||
tokenBalance: 'mockTokenBalance',
|
||||
}]
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render an element with a send-v2__amount-max class', () => {
|
||||
assert(wrapper.exists('.send-v2__amount-max'))
|
||||
})
|
||||
|
||||
it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
|
||||
const {
|
||||
onClick,
|
||||
} = wrapper.find('.send-v2__amount-max').props()
|
||||
|
||||
assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 0)
|
||||
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0)
|
||||
onClick(MOCK_EVENT)
|
||||
assert.equal(AmountMaxButton.prototype.setMaxAmount.callCount, 1)
|
||||
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.setMaxModeTo.getCall(0).args,
|
||||
[true]
|
||||
)
|
||||
})
|
||||
|
||||
it('should not render anything when maxModeOn is true', () => {
|
||||
wrapper.setProps({ maxModeOn: true })
|
||||
assert.ok(!wrapper.exists('.send-v2__amount-max'))
|
||||
})
|
||||
|
||||
it('should render the expected text when maxModeOn is false', () => {
|
||||
wrapper.setProps({ maxModeOn: false })
|
||||
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,91 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let mapStateToProps
|
||||
let mapDispatchToProps
|
||||
|
||||
const actionSpies = {
|
||||
setMaxModeTo: sinon.spy(),
|
||||
updateSendAmount: sinon.spy(),
|
||||
}
|
||||
const duckActionSpies = {
|
||||
updateSendErrors: sinon.spy(),
|
||||
}
|
||||
|
||||
proxyquire('../amount-max-button.container.js', {
|
||||
'react-redux': {
|
||||
connect: (ms, md) => {
|
||||
mapStateToProps = ms
|
||||
mapDispatchToProps = md
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
'../../../send.selectors.js': {
|
||||
getGasTotal: (s) => `mockGasTotal:${s}`,
|
||||
getSelectedToken: (s) => `mockSelectedToken:${s}`,
|
||||
getSendFromBalance: (s) => `mockBalance:${s}`,
|
||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||
},
|
||||
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
|
||||
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
|
||||
'../../../../../actions': actionSpies,
|
||||
'../../../../../ducks/send.duck': duckActionSpies,
|
||||
})
|
||||
|
||||
describe('amount-max-button container', () => {
|
||||
|
||||
describe('mapStateToProps()', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), {
|
||||
balance: 'mockBalance:mockState',
|
||||
gasTotal: 'mockGasTotal:mockState',
|
||||
maxModeOn: 'mockMaxModeOn:mockState',
|
||||
selectedToken: 'mockSelectedToken:mockState',
|
||||
tokenBalance: 'mockTokenBalance:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('mapDispatchToProps()', () => {
|
||||
let dispatchSpy
|
||||
let mapDispatchToPropsObject
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy()
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||
})
|
||||
|
||||
describe('setAmountToMax()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' })
|
||||
assert(dispatchSpy.calledTwice)
|
||||
assert(duckActionSpies.updateSendErrors.calledOnce)
|
||||
assert.deepEqual(
|
||||
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
||||
{ amount: null }
|
||||
)
|
||||
assert(actionSpies.updateSendAmount.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.updateSendAmount.getCall(0).args[0],
|
||||
12
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setMaxModeTo()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.setMaxModeTo('mockVal')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.setMaxModeTo.getCall(0).args[0],
|
||||
'mockVal'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -1,22 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import {
|
||||
getMaxModeOn,
|
||||
} from '../amount-max-button.selectors.js'
|
||||
|
||||
describe('amount-max-button selectors', () => {
|
||||
|
||||
describe('getMaxModeOn()', () => {
|
||||
it('should', () => {
|
||||
const state = {
|
||||
metamask: {
|
||||
send: {
|
||||
maxModeOn: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(getMaxModeOn(state), null)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,27 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import {
|
||||
calcMaxAmount,
|
||||
} from '../amount-max-button.utils.js'
|
||||
|
||||
describe('amount-max-button utils', () => {
|
||||
|
||||
describe('calcMaxAmount()', () => {
|
||||
it('should calculate the correct amount when no selectedToken defined', () => {
|
||||
assert.deepEqual(calcMaxAmount({
|
||||
balance: 'ffffff',
|
||||
gasTotal: 'ff',
|
||||
selectedToken: false,
|
||||
}), 'ffff00')
|
||||
})
|
||||
|
||||
it('should calculate the correct amount when a selectedToken is defined', () => {
|
||||
assert.deepEqual(calcMaxAmount({
|
||||
selectedToken: {
|
||||
decimals: 10,
|
||||
},
|
||||
tokenBalance: 100,
|
||||
}), 'e8d4a51000')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send-amount-row.container'
|
|
@ -1,120 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/'
|
||||
import AmountMaxButton from './amount-max-button/'
|
||||
import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
|
||||
import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
|
||||
|
||||
export default class SendAmountRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
amount: PropTypes.string,
|
||||
amountConversionRate: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
balance: PropTypes.string,
|
||||
conversionRate: PropTypes.number,
|
||||
convertedCurrency: PropTypes.string,
|
||||
gasTotal: PropTypes.string,
|
||||
inError: PropTypes.bool,
|
||||
primaryCurrency: PropTypes.string,
|
||||
selectedToken: PropTypes.object,
|
||||
setMaxModeTo: PropTypes.func,
|
||||
tokenBalance: PropTypes.string,
|
||||
updateGasFeeError: PropTypes.func,
|
||||
updateSendAmount: PropTypes.func,
|
||||
updateSendAmountError: PropTypes.func,
|
||||
updateGas: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
validateAmount (amount) {
|
||||
const {
|
||||
amountConversionRate,
|
||||
balance,
|
||||
conversionRate,
|
||||
gasTotal,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
tokenBalance,
|
||||
updateGasFeeError,
|
||||
updateSendAmountError,
|
||||
} = this.props
|
||||
|
||||
updateSendAmountError({
|
||||
amount,
|
||||
amountConversionRate,
|
||||
balance,
|
||||
conversionRate,
|
||||
gasTotal,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
tokenBalance,
|
||||
})
|
||||
|
||||
if (selectedToken) {
|
||||
updateGasFeeError({
|
||||
amount,
|
||||
amountConversionRate,
|
||||
balance,
|
||||
conversionRate,
|
||||
gasTotal,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
tokenBalance,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
updateAmount (amount) {
|
||||
const { updateSendAmount, setMaxModeTo } = this.props
|
||||
|
||||
setMaxModeTo(false)
|
||||
updateSendAmount(amount)
|
||||
}
|
||||
|
||||
updateGas (amount) {
|
||||
const { selectedToken, updateGas } = this.props
|
||||
|
||||
if (selectedToken) {
|
||||
updateGas({ amount })
|
||||
}
|
||||
}
|
||||
|
||||
renderInput () {
|
||||
const { amount, inError, selectedToken } = this.props
|
||||
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
|
||||
|
||||
return (
|
||||
<Component
|
||||
onChange={newAmount => this.validateAmount(newAmount)}
|
||||
onBlur={newAmount => {
|
||||
this.updateGas(newAmount)
|
||||
this.updateAmount(newAmount)
|
||||
}}
|
||||
error={inError}
|
||||
value={amount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { gasTotal, inError } = this.props
|
||||
|
||||
return (
|
||||
<SendRowWrapper
|
||||
label={`${this.context.t('amount')}:`}
|
||||
showError={inError}
|
||||
errorType={'amount'}
|
||||
>
|
||||
{!inError && gasTotal && <AmountMaxButton />}
|
||||
{ this.renderInput() }
|
||||
</SendRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getAmountConversionRate,
|
||||
getConversionRate,
|
||||
getCurrentCurrency,
|
||||
getGasTotal,
|
||||
getPrimaryCurrency,
|
||||
getSelectedToken,
|
||||
getSendAmount,
|
||||
getSendFromBalance,
|
||||
getTokenBalance,
|
||||
} from '../../send.selectors'
|
||||
import {
|
||||
sendAmountIsInError,
|
||||
} from './send-amount-row.selectors'
|
||||
import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
|
||||
import {
|
||||
setMaxModeTo,
|
||||
updateSendAmount,
|
||||
} from '../../../../actions'
|
||||
import {
|
||||
updateSendErrors,
|
||||
} from '../../../../ducks/send.duck'
|
||||
import SendAmountRow from './send-amount-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
amount: getSendAmount(state),
|
||||
amountConversionRate: getAmountConversionRate(state),
|
||||
balance: getSendFromBalance(state),
|
||||
conversionRate: getConversionRate(state),
|
||||
convertedCurrency: getCurrentCurrency(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
inError: sendAmountIsInError(state),
|
||||
primaryCurrency: getPrimaryCurrency(state),
|
||||
selectedToken: getSelectedToken(state),
|
||||
tokenBalance: getTokenBalance(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
|
||||
updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
|
||||
updateGasFeeError: (amountDataObject) => {
|
||||
dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
|
||||
},
|
||||
updateSendAmountError: (amountDataObject) => {
|
||||
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
const selectors = {
|
||||
sendAmountIsInError,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
|
||||
function sendAmountIsInError (state) {
|
||||
return Boolean(state.send.errors.amount)
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import SendAmountRow from '../send-amount-row.component.js'
|
||||
|
||||
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
||||
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
|
||||
import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
|
||||
|
||||
const propsMethodSpies = {
|
||||
setMaxModeTo: sinon.spy(),
|
||||
updateSendAmount: sinon.spy(),
|
||||
updateSendAmountError: sinon.spy(),
|
||||
updateGas: sinon.spy(),
|
||||
updateGasFeeError: sinon.spy(),
|
||||
}
|
||||
|
||||
sinon.spy(SendAmountRow.prototype, 'updateAmount')
|
||||
sinon.spy(SendAmountRow.prototype, 'validateAmount')
|
||||
sinon.spy(SendAmountRow.prototype, 'updateGas')
|
||||
|
||||
describe('SendAmountRow Component', function () {
|
||||
let wrapper
|
||||
let instance
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SendAmountRow
|
||||
amount={'mockAmount'}
|
||||
amountConversionRate={'mockAmountConversionRate'}
|
||||
balance={'mockBalance'}
|
||||
conversionRate={7}
|
||||
convertedCurrency={'mockConvertedCurrency'}
|
||||
gasTotal={'mockGasTotal'}
|
||||
inError={false}
|
||||
primaryCurrency={'mockPrimaryCurrency'}
|
||||
selectedToken={ { address: 'mockTokenAddress' } }
|
||||
setMaxModeTo={propsMethodSpies.setMaxModeTo}
|
||||
tokenBalance={'mockTokenBalance'}
|
||||
updateGasFeeError={propsMethodSpies.updateGasFeeError}
|
||||
updateSendAmount={propsMethodSpies.updateSendAmount}
|
||||
updateSendAmountError={propsMethodSpies.updateSendAmountError}
|
||||
updateGas={propsMethodSpies.updateGas}
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
instance = wrapper.instance()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.setMaxModeTo.resetHistory()
|
||||
propsMethodSpies.updateSendAmount.resetHistory()
|
||||
propsMethodSpies.updateSendAmountError.resetHistory()
|
||||
propsMethodSpies.updateGasFeeError.resetHistory()
|
||||
SendAmountRow.prototype.validateAmount.resetHistory()
|
||||
SendAmountRow.prototype.updateAmount.resetHistory()
|
||||
})
|
||||
|
||||
describe('validateAmount', () => {
|
||||
|
||||
it('should call updateSendAmountError with the correct params', () => {
|
||||
assert.equal(propsMethodSpies.updateSendAmountError.callCount, 0)
|
||||
instance.validateAmount('someAmount')
|
||||
assert.equal(propsMethodSpies.updateSendAmountError.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.updateSendAmountError.getCall(0).args,
|
||||
[{
|
||||
amount: 'someAmount',
|
||||
amountConversionRate: 'mockAmountConversionRate',
|
||||
balance: 'mockBalance',
|
||||
conversionRate: 7,
|
||||
gasTotal: 'mockGasTotal',
|
||||
primaryCurrency: 'mockPrimaryCurrency',
|
||||
selectedToken: { address: 'mockTokenAddress' },
|
||||
tokenBalance: 'mockTokenBalance',
|
||||
}]
|
||||
)
|
||||
})
|
||||
|
||||
it('should call updateGasFeeError if selectedToken is truthy', () => {
|
||||
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
|
||||
instance.validateAmount('someAmount')
|
||||
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.updateGasFeeError.getCall(0).args,
|
||||
[{
|
||||
amount: 'someAmount',
|
||||
amountConversionRate: 'mockAmountConversionRate',
|
||||
balance: 'mockBalance',
|
||||
conversionRate: 7,
|
||||
gasTotal: 'mockGasTotal',
|
||||
primaryCurrency: 'mockPrimaryCurrency',
|
||||
selectedToken: { address: 'mockTokenAddress' },
|
||||
tokenBalance: 'mockTokenBalance',
|
||||
}]
|
||||
)
|
||||
})
|
||||
|
||||
it('should call not updateGasFeeError if selectedToken is falsey', () => {
|
||||
wrapper.setProps({ selectedToken: null })
|
||||
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
|
||||
instance.validateAmount('someAmount')
|
||||
assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('updateAmount', () => {
|
||||
|
||||
it('should call setMaxModeTo', () => {
|
||||
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0)
|
||||
instance.updateAmount('someAmount')
|
||||
assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.setMaxModeTo.getCall(0).args,
|
||||
[false]
|
||||
)
|
||||
})
|
||||
|
||||
it('should call updateSendAmount', () => {
|
||||
assert.equal(propsMethodSpies.updateSendAmount.callCount, 0)
|
||||
instance.updateAmount('someAmount')
|
||||
assert.equal(propsMethodSpies.updateSendAmount.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.updateSendAmount.getCall(0).args,
|
||||
['someAmount']
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a SendRowWrapper component', () => {
|
||||
assert.equal(wrapper.find(SendRowWrapper).length, 1)
|
||||
})
|
||||
|
||||
it('should pass the correct props to SendRowWrapper', () => {
|
||||
const {
|
||||
errorType,
|
||||
label,
|
||||
showError,
|
||||
} = wrapper.find(SendRowWrapper).props()
|
||||
|
||||
assert.equal(errorType, 'amount')
|
||||
|
||||
assert.equal(label, 'amount_t:')
|
||||
|
||||
assert.equal(showError, false)
|
||||
})
|
||||
|
||||
it('should render an AmountMaxButton as the first child of the SendRowWrapper', () => {
|
||||
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
|
||||
})
|
||||
|
||||
it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
|
||||
console.log('HI', wrapper.find(SendRowWrapper).childAt(1))
|
||||
assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput))
|
||||
})
|
||||
|
||||
it('should render the UserPreferencedTokenInput with the correct props', () => {
|
||||
const {
|
||||
onBlur,
|
||||
onChange,
|
||||
error,
|
||||
value,
|
||||
} = wrapper.find(SendRowWrapper).childAt(1).props()
|
||||
assert.equal(error, false)
|
||||
assert.equal(value, 'mockAmount')
|
||||
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
|
||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
||||
onBlur('mockNewAmount')
|
||||
assert.equal(SendAmountRow.prototype.updateGas.callCount, 1)
|
||||
assert.deepEqual(
|
||||
SendAmountRow.prototype.updateGas.getCall(0).args,
|
||||
['mockNewAmount']
|
||||
)
|
||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1)
|
||||
assert.deepEqual(
|
||||
SendAmountRow.prototype.updateAmount.getCall(0).args,
|
||||
['mockNewAmount']
|
||||
)
|
||||
assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0)
|
||||
onChange('mockNewAmount')
|
||||
assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1)
|
||||
assert.deepEqual(
|
||||
SendAmountRow.prototype.validateAmount.getCall(0).args,
|
||||
['mockNewAmount']
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,125 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let mapStateToProps
|
||||
let mapDispatchToProps
|
||||
|
||||
const actionSpies = {
|
||||
setMaxModeTo: sinon.spy(),
|
||||
updateSendAmount: sinon.spy(),
|
||||
}
|
||||
const duckActionSpies = {
|
||||
updateSendErrors: sinon.spy(),
|
||||
}
|
||||
|
||||
proxyquire('../send-amount-row.container.js', {
|
||||
'react-redux': {
|
||||
connect: (ms, md) => {
|
||||
mapStateToProps = ms
|
||||
mapDispatchToProps = md
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
'../../send.selectors': {
|
||||
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
|
||||
getConversionRate: (s) => `mockConversionRate:${s}`,
|
||||
getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
|
||||
getGasTotal: (s) => `mockGasTotal:${s}`,
|
||||
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
|
||||
getSelectedToken: (s) => `mockSelectedToken:${s}`,
|
||||
getSendAmount: (s) => `mockAmount:${s}`,
|
||||
getSendFromBalance: (s) => `mockBalance:${s}`,
|
||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||
},
|
||||
'./send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` },
|
||||
'../../send.utils': {
|
||||
getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
|
||||
getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
|
||||
},
|
||||
'../../../../actions': actionSpies,
|
||||
'../../../../ducks/send.duck': duckActionSpies,
|
||||
})
|
||||
|
||||
describe('send-amount-row container', () => {
|
||||
|
||||
describe('mapStateToProps()', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), {
|
||||
amount: 'mockAmount:mockState',
|
||||
amountConversionRate: 'mockAmountConversionRate:mockState',
|
||||
balance: 'mockBalance:mockState',
|
||||
conversionRate: 'mockConversionRate:mockState',
|
||||
convertedCurrency: 'mockConvertedCurrency:mockState',
|
||||
gasTotal: 'mockGasTotal:mockState',
|
||||
inError: 'mockInError:mockState',
|
||||
primaryCurrency: 'mockPrimaryCurrency:mockState',
|
||||
selectedToken: 'mockSelectedToken:mockState',
|
||||
tokenBalance: 'mockTokenBalance:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('mapDispatchToProps()', () => {
|
||||
let dispatchSpy
|
||||
let mapDispatchToPropsObject
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy()
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||
duckActionSpies.updateSendErrors.resetHistory()
|
||||
})
|
||||
|
||||
describe('setMaxModeTo()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.setMaxModeTo('mockBool')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(actionSpies.setMaxModeTo.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.setMaxModeTo.getCall(0).args[0],
|
||||
'mockBool'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateSendAmount()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.updateSendAmount('mockAmount')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(actionSpies.updateSendAmount.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.updateSendAmount.getCall(0).args[0],
|
||||
'mockAmount'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateGasFeeError()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.updateGasFeeError({ some: 'data' })
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(duckActionSpies.updateSendErrors.calledOnce)
|
||||
assert.deepEqual(
|
||||
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
||||
{ some: 'data', mockGasFeeErrorChange: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateSendAmountError()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.updateSendAmountError({ some: 'data' })
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(duckActionSpies.updateSendErrors.calledOnce)
|
||||
assert.deepEqual(
|
||||
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
||||
{ some: 'data', mockChange: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -1,34 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import {
|
||||
sendAmountIsInError,
|
||||
} from '../send-amount-row.selectors.js'
|
||||
|
||||
describe('send-amount-row selectors', () => {
|
||||
|
||||
describe('sendAmountIsInError()', () => {
|
||||
it('should return true if send.errors.amount is truthy', () => {
|
||||
const state = {
|
||||
send: {
|
||||
errors: {
|
||||
amount: 'abc',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(sendAmountIsInError(state), true)
|
||||
})
|
||||
|
||||
it('should return false if send.errors.amount is falsy', () => {
|
||||
const state = {
|
||||
send: {
|
||||
errors: {
|
||||
amount: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(sendAmountIsInError(state), false)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,41 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import PageContainerContent from '../../page-container/page-container-content.component'
|
||||
import SendAmountRow from './send-amount-row/'
|
||||
import SendFromRow from './send-from-row/'
|
||||
import SendGasRow from './send-gas-row/'
|
||||
import SendHexDataRow from './send-hex-data-row'
|
||||
import SendToRow from './send-to-row/'
|
||||
|
||||
export default class SendContent extends Component {
|
||||
|
||||
static propTypes = {
|
||||
updateGas: PropTypes.func,
|
||||
scanQrCode: PropTypes.func,
|
||||
showHexData: PropTypes.bool,
|
||||
};
|
||||
|
||||
updateGas = (updateData) => this.props.updateGas(updateData)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<PageContainerContent>
|
||||
<div className="send-v2__form">
|
||||
<SendFromRow />
|
||||
<SendToRow
|
||||
updateGas={this.updateGas}
|
||||
scanQrCode={ _ => this.props.scanQrCode()}
|
||||
/>
|
||||
<SendAmountRow updateGas={this.updateGas} />
|
||||
<SendGasRow />
|
||||
{(this.props.showHexData && (
|
||||
<SendHexDataRow
|
||||
updateGas={this.updateGas}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PageContainerContent>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send-dropdown-list.component'
|
|
@ -1,52 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import AccountListItem from '../../account-list-item/'
|
||||
|
||||
export default class SendDropdownList extends Component {
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.array,
|
||||
closeDropdown: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
activeAddress: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
getListItemIcon (accountAddress, activeAddress) {
|
||||
return accountAddress === activeAddress
|
||||
? <i className={`fa fa-check fa-lg`} style={ { color: '#02c9b1' } }/>
|
||||
: null
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
accounts,
|
||||
closeDropdown,
|
||||
onSelect,
|
||||
activeAddress,
|
||||
} = this.props
|
||||
|
||||
return (<div>
|
||||
<div
|
||||
className="send-v2__from-dropdown__close-area"
|
||||
onClick={() => closeDropdown()}
|
||||
/>
|
||||
<div className="send-v2__from-dropdown__list">
|
||||
{accounts.map((account, index) => <AccountListItem
|
||||
account={account}
|
||||
className="account-list-item__dropdown"
|
||||
handleClick={() => {
|
||||
onSelect(account)
|
||||
closeDropdown()
|
||||
}}
|
||||
icon={this.getListItemIcon(account.address, activeAddress)}
|
||||
key={`send-dropdown-account-#${index}`}
|
||||
/>)}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import SendDropdownList from '../send-dropdown-list.component.js'
|
||||
|
||||
import AccountListItem from '../../../account-list-item/account-list-item.container'
|
||||
|
||||
const propsMethodSpies = {
|
||||
closeDropdown: sinon.spy(),
|
||||
onSelect: sinon.spy(),
|
||||
}
|
||||
|
||||
sinon.spy(SendDropdownList.prototype, 'getListItemIcon')
|
||||
|
||||
describe('SendDropdownList Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SendDropdownList
|
||||
accounts={[
|
||||
{ address: 'mockAccount0' },
|
||||
{ address: 'mockAccount1' },
|
||||
{ address: 'mockAccount2' },
|
||||
]}
|
||||
closeDropdown={propsMethodSpies.closeDropdown}
|
||||
onSelect={propsMethodSpies.onSelect}
|
||||
activeAddress={'mockAddress2'}
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.closeDropdown.resetHistory()
|
||||
propsMethodSpies.onSelect.resetHistory()
|
||||
SendDropdownList.prototype.getListItemIcon.resetHistory()
|
||||
})
|
||||
|
||||
describe('getListItemIcon', () => {
|
||||
it('should return check icon if the passed addresses are the same', () => {
|
||||
assert.deepEqual(
|
||||
wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount0'),
|
||||
<i className={`fa fa-check fa-lg`} style={ { color: '#02c9b1' } }/>
|
||||
)
|
||||
})
|
||||
|
||||
it('should return null if the passed addresses are different', () => {
|
||||
assert.equal(
|
||||
wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount1'),
|
||||
null
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a single div with two children', () => {
|
||||
assert(wrapper.is('div'))
|
||||
assert.equal(wrapper.children().length, 2)
|
||||
})
|
||||
|
||||
it('should render the children with the correct classes', () => {
|
||||
assert(wrapper.childAt(0).hasClass('send-v2__from-dropdown__close-area'))
|
||||
assert(wrapper.childAt(1).hasClass('send-v2__from-dropdown__list'))
|
||||
})
|
||||
|
||||
it('should call closeDropdown onClick of the send-v2__from-dropdown__close-area', () => {
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 0)
|
||||
wrapper.childAt(0).props().onClick()
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 1)
|
||||
})
|
||||
|
||||
it('should render an AccountListItem for each item in accounts', () => {
|
||||
assert.equal(wrapper.childAt(1).children().length, 3)
|
||||
assert(wrapper.childAt(1).children().every(AccountListItem))
|
||||
})
|
||||
|
||||
it('should pass the correct props to the AccountListItem', () => {
|
||||
wrapper.childAt(1).children().forEach((accountListItem, index) => {
|
||||
const {
|
||||
account,
|
||||
className,
|
||||
handleClick,
|
||||
} = accountListItem.props()
|
||||
assert.deepEqual(account, { address: 'mockAccount' + index })
|
||||
assert.equal(className, 'account-list-item__dropdown')
|
||||
assert.equal(propsMethodSpies.onSelect.callCount, 0)
|
||||
handleClick()
|
||||
assert.equal(propsMethodSpies.onSelect.callCount, 1)
|
||||
assert.deepEqual(propsMethodSpies.onSelect.getCall(0).args[0], { address: 'mockAccount' + index })
|
||||
propsMethodSpies.onSelect.resetHistory()
|
||||
propsMethodSpies.closeDropdown.resetHistory()
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 0)
|
||||
handleClick()
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 1)
|
||||
propsMethodSpies.onSelect.resetHistory()
|
||||
propsMethodSpies.closeDropdown.resetHistory()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call this.getListItemIcon for each AccountListItem', () => {
|
||||
assert.equal(SendDropdownList.prototype.getListItemIcon.callCount, 3)
|
||||
const getListItemIconCalls = SendDropdownList.prototype.getListItemIcon.getCalls()
|
||||
assert(getListItemIconCalls.every(({ args }, index) => args[0] === 'mockAccount' + index))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,46 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import AccountListItem from '../../../account-list-item/'
|
||||
import SendDropdownList from '../../send-dropdown-list/'
|
||||
|
||||
export default class FromDropdown extends Component {
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.array,
|
||||
closeDropdown: PropTypes.func,
|
||||
dropdownOpen: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
openDropdown: PropTypes.func,
|
||||
selectedAccount: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
accounts,
|
||||
closeDropdown,
|
||||
dropdownOpen,
|
||||
openDropdown,
|
||||
selectedAccount,
|
||||
onSelect,
|
||||
} = this.props
|
||||
|
||||
return <div className="send-v2__from-dropdown">
|
||||
<AccountListItem
|
||||
account={selectedAccount}
|
||||
handleClick={openDropdown}
|
||||
icon={<i className={`fa fa-caret-down fa-lg`} style={ { color: '#dedede' } }/>}
|
||||
/>
|
||||
{dropdownOpen && <SendDropdownList
|
||||
accounts={accounts}
|
||||
closeDropdown={closeDropdown}
|
||||
onSelect={onSelect}
|
||||
activeAddress={selectedAccount.address}
|
||||
/>}
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './from-dropdown.component'
|
|
@ -1,88 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import FromDropdown from '../from-dropdown.component.js'
|
||||
|
||||
import AccountListItem from '../../../../account-list-item/account-list-item.container'
|
||||
import SendDropdownList from '../../../send-dropdown-list/send-dropdown-list.component'
|
||||
|
||||
const propsMethodSpies = {
|
||||
closeDropdown: sinon.spy(),
|
||||
openDropdown: sinon.spy(),
|
||||
onSelect: sinon.spy(),
|
||||
}
|
||||
|
||||
describe('FromDropdown Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<FromDropdown
|
||||
accounts={['mockAccount']}
|
||||
closeDropdown={propsMethodSpies.closeDropdown}
|
||||
dropdownOpen={false}
|
||||
onSelect={propsMethodSpies.onSelect}
|
||||
openDropdown={propsMethodSpies.openDropdown}
|
||||
selectedAccount={ { address: 'mockAddress' } }
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.closeDropdown.resetHistory()
|
||||
propsMethodSpies.openDropdown.resetHistory()
|
||||
propsMethodSpies.onSelect.resetHistory()
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a div with a .send-v2__from-dropdown class', () => {
|
||||
assert.equal(wrapper.find('.send-v2__from-dropdown').length, 1)
|
||||
})
|
||||
|
||||
it('should render an AccountListItem as the first child of the .send-v2__from-dropdown div', () => {
|
||||
assert(wrapper.find('.send-v2__from-dropdown').childAt(0).is(AccountListItem))
|
||||
})
|
||||
|
||||
it('should pass the correct props to AccountListItem', () => {
|
||||
const {
|
||||
account,
|
||||
handleClick,
|
||||
icon,
|
||||
} = wrapper.find('.send-v2__from-dropdown').childAt(0).props()
|
||||
assert.deepEqual(account, { address: 'mockAddress' })
|
||||
assert.deepEqual(
|
||||
icon,
|
||||
<i className={`fa fa-caret-down fa-lg`} style={ { color: '#dedede' } }/>
|
||||
)
|
||||
assert.equal(propsMethodSpies.openDropdown.callCount, 0)
|
||||
handleClick()
|
||||
assert.equal(propsMethodSpies.openDropdown.callCount, 1)
|
||||
})
|
||||
|
||||
it('should not render a SendDropdownList when dropdownOpen is false', () => {
|
||||
assert.equal(wrapper.find(SendDropdownList).length, 0)
|
||||
})
|
||||
|
||||
it('should render a SendDropdownList when dropdownOpen is true', () => {
|
||||
wrapper.setProps({ dropdownOpen: true })
|
||||
assert(wrapper.find(SendDropdownList).length, 1)
|
||||
})
|
||||
|
||||
it('should pass the correct props to the SendDropdownList]', () => {
|
||||
wrapper.setProps({ dropdownOpen: true })
|
||||
const {
|
||||
accounts,
|
||||
closeDropdown,
|
||||
onSelect,
|
||||
activeAddress,
|
||||
} = wrapper.find(SendDropdownList).props()
|
||||
assert.deepEqual(accounts, ['mockAccount'])
|
||||
assert.equal(activeAddress, 'mockAddress')
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 0)
|
||||
closeDropdown()
|
||||
assert.equal(propsMethodSpies.closeDropdown.callCount, 1)
|
||||
assert.equal(propsMethodSpies.onSelect.callCount, 0)
|
||||
onSelect()
|
||||
assert.equal(propsMethodSpies.onSelect.callCount, 1)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send-from-row.container'
|
|
@ -1,63 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/'
|
||||
import FromDropdown from './from-dropdown/'
|
||||
|
||||
export default class SendFromRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
closeFromDropdown: PropTypes.func,
|
||||
conversionRate: PropTypes.number,
|
||||
from: PropTypes.object,
|
||||
fromAccounts: PropTypes.array,
|
||||
fromDropdownOpen: PropTypes.bool,
|
||||
openFromDropdown: PropTypes.func,
|
||||
tokenContract: PropTypes.object,
|
||||
updateSendFrom: PropTypes.func,
|
||||
setSendTokenBalance: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
async handleFromChange (newFrom) {
|
||||
const {
|
||||
updateSendFrom,
|
||||
tokenContract,
|
||||
setSendTokenBalance,
|
||||
} = this.props
|
||||
|
||||
if (tokenContract) {
|
||||
const usersToken = await tokenContract.balanceOf(newFrom.address)
|
||||
setSendTokenBalance(usersToken)
|
||||
}
|
||||
updateSendFrom(newFrom)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
closeFromDropdown,
|
||||
conversionRate,
|
||||
from,
|
||||
fromAccounts,
|
||||
fromDropdownOpen,
|
||||
openFromDropdown,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<SendRowWrapper label={`${this.context.t('from')}:`}>
|
||||
<FromDropdown
|
||||
accounts={fromAccounts}
|
||||
closeDropdown={() => closeFromDropdown()}
|
||||
conversionRate={conversionRate}
|
||||
dropdownOpen={fromDropdownOpen}
|
||||
onSelect={newFrom => this.handleFromChange(newFrom)}
|
||||
openDropdown={() => openFromDropdown()}
|
||||
selectedAccount={from}
|
||||
/>
|
||||
</SendRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
getConversionRate,
|
||||
getSelectedTokenContract,
|
||||
getSendFromObject,
|
||||
} from '../../send.selectors.js'
|
||||
import {
|
||||
getFromDropdownOpen,
|
||||
} from './send-from-row.selectors.js'
|
||||
import { calcTokenBalance } from '../../send.utils.js'
|
||||
import {
|
||||
updateSendFrom,
|
||||
setSendTokenBalance,
|
||||
} from '../../../../actions'
|
||||
import {
|
||||
closeFromDropdown,
|
||||
openFromDropdown,
|
||||
} from '../../../../ducks/send.duck'
|
||||
import SendFromRow from './send-from-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: getConversionRate(state),
|
||||
from: getSendFromObject(state),
|
||||
fromAccounts: accountsWithSendEtherInfoSelector(state),
|
||||
fromDropdownOpen: getFromDropdownOpen(state),
|
||||
tokenContract: getSelectedTokenContract(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
closeFromDropdown: () => dispatch(closeFromDropdown()),
|
||||
openFromDropdown: () => dispatch(openFromDropdown()),
|
||||
updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
|
||||
setSendTokenBalance: (usersToken, selectedToken) => {
|
||||
if (!usersToken) return
|
||||
|
||||
const tokenBalance = calcTokenBalance({ usersToken, selectedToken })
|
||||
dispatch(setSendTokenBalance(tokenBalance))
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
const selectors = {
|
||||
getFromDropdownOpen,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
|
||||
function getFromDropdownOpen (state) {
|
||||
return state.send.fromDropdownOpen
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import SendFromRow from '../send-from-row.component.js'
|
||||
|
||||
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
||||
import FromDropdown from '../from-dropdown/from-dropdown.component'
|
||||
|
||||
const propsMethodSpies = {
|
||||
closeFromDropdown: sinon.spy(),
|
||||
openFromDropdown: sinon.spy(),
|
||||
updateSendFrom: sinon.spy(),
|
||||
setSendTokenBalance: sinon.spy(),
|
||||
}
|
||||
|
||||
sinon.spy(SendFromRow.prototype, 'handleFromChange')
|
||||
|
||||
describe('SendFromRow Component', function () {
|
||||
let wrapper
|
||||
let instance
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SendFromRow
|
||||
closeFromDropdown={propsMethodSpies.closeFromDropdown}
|
||||
conversionRate={15}
|
||||
from={ { address: 'mockAddress' } }
|
||||
fromAccounts={['mockAccount']}
|
||||
fromDropdownOpen={false}
|
||||
openFromDropdown={propsMethodSpies.openFromDropdown}
|
||||
setSendTokenBalance={propsMethodSpies.setSendTokenBalance}
|
||||
tokenContract={null}
|
||||
updateSendFrom={propsMethodSpies.updateSendFrom}
|
||||
/>, { context: { t: str => str + '_t' } })
|
||||
instance = wrapper.instance()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.closeFromDropdown.resetHistory()
|
||||
propsMethodSpies.openFromDropdown.resetHistory()
|
||||
propsMethodSpies.updateSendFrom.resetHistory()
|
||||
propsMethodSpies.setSendTokenBalance.resetHistory()
|
||||
SendFromRow.prototype.handleFromChange.resetHistory()
|
||||
})
|
||||
|
||||
describe('handleFromChange', () => {
|
||||
|
||||
it('should call updateSendFrom', () => {
|
||||
assert.equal(propsMethodSpies.updateSendFrom.callCount, 0)
|
||||
instance.handleFromChange('mockFrom')
|
||||
assert.equal(propsMethodSpies.updateSendFrom.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.updateSendFrom.getCall(0).args,
|
||||
['mockFrom']
|
||||
)
|
||||
})
|
||||
|
||||
it('should call tokenContract.balanceOf and setSendTokenBalance if tokenContract is defined', async () => {
|
||||
wrapper.setProps({
|
||||
tokenContract: {
|
||||
balanceOf: () => new Promise((resolve) => resolve('mockUsersToken')),
|
||||
},
|
||||
})
|
||||
assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 0)
|
||||
await instance.handleFromChange('mockFrom')
|
||||
assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 1)
|
||||
assert.deepEqual(
|
||||
propsMethodSpies.setSendTokenBalance.getCall(0).args,
|
||||
['mockUsersToken']
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a SendRowWrapper component', () => {
|
||||
assert.equal(wrapper.find(SendRowWrapper).length, 1)
|
||||
})
|
||||
|
||||
it('should pass the correct props to SendRowWrapper', () => {
|
||||
const {
|
||||
label,
|
||||
} = wrapper.find(SendRowWrapper).props()
|
||||
|
||||
assert.equal(label, 'from_t:')
|
||||
})
|
||||
|
||||
it('should render an FromDropdown as a child of the SendRowWrapper', () => {
|
||||
assert(wrapper.find(SendRowWrapper).childAt(0).is(FromDropdown))
|
||||
})
|
||||
|
||||
it('should render the FromDropdown with the correct props', () => {
|
||||
const {
|
||||
accounts,
|
||||
closeDropdown,
|
||||
conversionRate,
|
||||
dropdownOpen,
|
||||
onSelect,
|
||||
openDropdown,
|
||||
selectedAccount,
|
||||
} = wrapper.find(SendRowWrapper).childAt(0).props()
|
||||
assert.deepEqual(accounts, ['mockAccount'])
|
||||
assert.equal(dropdownOpen, false)
|
||||
assert.equal(conversionRate, 15)
|
||||
assert.deepEqual(selectedAccount, { address: 'mockAddress' })
|
||||
assert.equal(propsMethodSpies.closeFromDropdown.callCount, 0)
|
||||
closeDropdown()
|
||||
assert.equal(propsMethodSpies.closeFromDropdown.callCount, 1)
|
||||
assert.equal(propsMethodSpies.openFromDropdown.callCount, 0)
|
||||
openDropdown()
|
||||
assert.equal(propsMethodSpies.openFromDropdown.callCount, 1)
|
||||
assert.equal(SendFromRow.prototype.handleFromChange.callCount, 0)
|
||||
onSelect('mockNewFrom')
|
||||
assert.equal(SendFromRow.prototype.handleFromChange.callCount, 1)
|
||||
assert.deepEqual(
|
||||
SendFromRow.prototype.handleFromChange.getCall(0).args,
|
||||
['mockNewFrom']
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,110 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let mapStateToProps
|
||||
let mapDispatchToProps
|
||||
|
||||
const actionSpies = {
|
||||
updateSendFrom: sinon.spy(),
|
||||
setSendTokenBalance: sinon.spy(),
|
||||
}
|
||||
const duckActionSpies = {
|
||||
closeFromDropdown: sinon.spy(),
|
||||
openFromDropdown: sinon.spy(),
|
||||
}
|
||||
|
||||
proxyquire('../send-from-row.container.js', {
|
||||
'react-redux': {
|
||||
connect: (ms, md) => {
|
||||
mapStateToProps = ms
|
||||
mapDispatchToProps = md
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
'../../send.selectors.js': {
|
||||
accountsWithSendEtherInfoSelector: (s) => `mockFromAccounts:${s}`,
|
||||
getConversionRate: (s) => `mockConversionRate:${s}`,
|
||||
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
|
||||
getSendFromObject: (s) => `mockFrom:${s}`,
|
||||
},
|
||||
'./send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` },
|
||||
'../../send.utils.js': { calcTokenBalance: ({ usersToken, selectedToken }) => usersToken + selectedToken },
|
||||
'../../../../actions': actionSpies,
|
||||
'../../../../ducks/send.duck': duckActionSpies,
|
||||
})
|
||||
|
||||
describe('send-from-row container', () => {
|
||||
|
||||
describe('mapStateToProps()', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), {
|
||||
conversionRate: 'mockConversionRate:mockState',
|
||||
from: 'mockFrom:mockState',
|
||||
fromAccounts: 'mockFromAccounts:mockState',
|
||||
fromDropdownOpen: 'mockFromDropdownOpen:mockState',
|
||||
tokenContract: 'mockTokenContract:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('mapDispatchToProps()', () => {
|
||||
let dispatchSpy
|
||||
let mapDispatchToPropsObject
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy()
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||
})
|
||||
|
||||
describe('closeFromDropdown()', () => {
|
||||
it('should dispatch a closeFromDropdown action', () => {
|
||||
mapDispatchToPropsObject.closeFromDropdown()
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(duckActionSpies.closeFromDropdown.calledOnce)
|
||||
assert.equal(
|
||||
duckActionSpies.closeFromDropdown.getCall(0).args[0],
|
||||
undefined
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('openFromDropdown()', () => {
|
||||
it('should dispatch a openFromDropdown action', () => {
|
||||
mapDispatchToPropsObject.openFromDropdown()
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert(duckActionSpies.openFromDropdown.calledOnce)
|
||||
assert.equal(
|
||||
duckActionSpies.openFromDropdown.getCall(0).args[0],
|
||||
undefined
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateSendFrom()', () => {
|
||||
it('should dispatch an updateSendFrom action', () => {
|
||||
mapDispatchToPropsObject.updateSendFrom('mockFrom')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.updateSendFrom.getCall(0).args[0],
|
||||
'mockFrom'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setSendTokenBalance()', () => {
|
||||
it('should dispatch an setSendTokenBalance action', () => {
|
||||
mapDispatchToPropsObject.setSendTokenBalance('mockUsersToken', 'mockSelectedToken')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert.equal(
|
||||
actionSpies.setSendTokenBalance.getCall(0).args[0],
|
||||
'mockUsersTokenmockSelectedToken'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -1,20 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import {
|
||||
getFromDropdownOpen,
|
||||
} from '../send-from-row.selectors.js'
|
||||
|
||||
describe('send-from-row selectors', () => {
|
||||
|
||||
describe('getFromDropdownOpen()', () => {
|
||||
it('should get send.fromDropdownOpen', () => {
|
||||
const state = {
|
||||
send: {
|
||||
fromDropdownOpen: null,
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(getFromDropdownOpen(state), null)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,58 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../../../constants/common'
|
||||
|
||||
export default class GasFeeDisplay extends Component {
|
||||
|
||||
static propTypes = {
|
||||
conversionRate: PropTypes.number,
|
||||
primaryCurrency: PropTypes.string,
|
||||
convertedCurrency: PropTypes.string,
|
||||
gasLoadingError: PropTypes.bool,
|
||||
gasTotal: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { gasTotal, onClick, gasLoadingError } = this.props
|
||||
|
||||
return (
|
||||
<div className="send-v2__gas-fee-display">
|
||||
{gasTotal
|
||||
? (
|
||||
<div className="currency-display">
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={gasTotal}
|
||||
type={PRIMARY}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="currency-display__converted-value"
|
||||
value={gasTotal}
|
||||
type={SECONDARY}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: gasLoadingError
|
||||
? <div className="currency-display.currency-display--message">
|
||||
{this.context.t('setGasPrice')}
|
||||
</div>
|
||||
: <div className="currency-display">
|
||||
{this.context.t('loading')}
|
||||
</div>
|
||||
}
|
||||
<button
|
||||
className="sliders-icon-container"
|
||||
onClick={onClick}
|
||||
disabled={!gasTotal && !gasLoadingError}
|
||||
>
|
||||
<i className="fa fa-sliders sliders-icon" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default } from './gas-fee-display.component'
|
|
@ -1,53 +0,0 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import {shallow} from 'enzyme'
|
||||
import GasFeeDisplay from '../gas-fee-display.component'
|
||||
import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
|
||||
import sinon from 'sinon'
|
||||
|
||||
|
||||
const propsMethodSpies = {
|
||||
showCustomizeGasModal: sinon.spy(),
|
||||
}
|
||||
|
||||
describe('SendGasRow Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<GasFeeDisplay
|
||||
conversionRate={20}
|
||||
gasTotal={'mockGasTotal'}
|
||||
onClick={propsMethodSpies.showCustomizeGasModal}
|
||||
primaryCurrency={'mockPrimaryCurrency'}
|
||||
convertedCurrency={'mockConvertedCurrency'}
|
||||
/>, {context: {t: str => str + '_t'}})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
propsMethodSpies.showCustomizeGasModal.resetHistory()
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a CurrencyDisplay component', () => {
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
})
|
||||
|
||||
it('should render the CurrencyDisplay with the correct props', () => {
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
} = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
|
||||
assert.equal(type, 'PRIMARY')
|
||||
assert.equal(value, 'mockGasTotal')
|
||||
})
|
||||
|
||||
it('should render the Button with the correct props', () => {
|
||||
const {
|
||||
onClick,
|
||||
} = wrapper.find('button').props()
|
||||
assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0)
|
||||
onClick()
|
||||
assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export { default } from './send-gas-row.container'
|
|
@ -1,48 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/'
|
||||
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
|
||||
|
||||
export default class SendGasRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
conversionRate: PropTypes.number,
|
||||
convertedCurrency: PropTypes.string,
|
||||
gasFeeError: PropTypes.bool,
|
||||
gasLoadingError: PropTypes.bool,
|
||||
gasTotal: PropTypes.string,
|
||||
showCustomizeGasModal: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
gasLoadingError,
|
||||
gasTotal,
|
||||
gasFeeError,
|
||||
showCustomizeGasModal,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<SendRowWrapper
|
||||
label={`${this.context.t('gasFee')}:`}
|
||||
showError={gasFeeError}
|
||||
errorType={'gasFee'}
|
||||
>
|
||||
<GasFeeDisplay
|
||||
conversionRate={conversionRate}
|
||||
convertedCurrency={convertedCurrency}
|
||||
gasLoadingError={gasLoadingError}
|
||||
gasTotal={gasTotal}
|
||||
onClick={() => showCustomizeGasModal()}
|
||||
/>
|
||||
</SendRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import {
|
||||
getConversionRate,
|
||||
getCurrentCurrency,
|
||||
getGasTotal,
|
||||
} from '../../send.selectors.js'
|
||||
import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
|
||||
import { showModal } from '../../../../actions'
|
||||
import SendGasRow from './send-gas-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: getConversionRate(state),
|
||||
convertedCurrency: getCurrentCurrency(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
gasFeeError: gasFeeIsInError(state),
|
||||
gasLoadingError: getGasLoadingError(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })),
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue