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

Use AdvancedGasInputs in AdvancedTabContent (#7186)

* Use `AdvancedGasInputs` in `AdvancedTabContent`

The `AdvancedGasInputs` component was originally extracted from the
`AdvancedTabContent` component, duplicating much of the rendering
logic. They have since evolved separately, with bugs being fixed in one
place but not the other.

The inputs and outputs expected weren't exactly the same, as the
`AdvancedGasInputs` component converts the input custom gas price and
limit, and it converts them both in the setter methods as well.
The `GasModalPageContainer` had to be adjusted to avoid converting
these values multiple times.

Both components dealt with input debouncing separately, both in less
than ideal ways. `AdvancedTabContent` didn't debounce either field, but
it did debounce the check for whether the gas limit field was below the
minimum value. So if a less-than-minimum value was set, it would be
propogated upwards and could be saved if the user clicked 'Save'
quickly enough. After a second delay it would snap back to the minimum
value. The `AdvancedGasInputs` component debounced both fields, but
it would replace any gas limit below the minimum with the minimum
value. This led to a problem where a brief pause during typing would
reset the field to 21000.

The `AdvancedGasInputs` approach was chosen, except that it was
updated to no longer change the gas limit if it was below the minimum.
Instead it displays an error. Parent components were checked to ensure
they would detect the error case of the gas limit being set too low,
and prevent the form submission in those cases. Of the three parents,
one had already dealt with it correctly, one needed to convert the
gas limit from hex first, and another needed the gas limit check added.

Closes #6872

* Cleanup send components

Empty README files have been removed, and a mistake in the index file
for the send page has been corrected. The Gas Slider component class
name was updated as well; it looks like it was originally created from
`AdvancedTabContent`, so it still had that class name.
This commit is contained in:
Mark Stacey 2019-10-23 09:23:15 -03:00 committed by GitHub
parent 3acd1db0be
commit ba25d52670
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 314 additions and 839 deletions

View File

@ -494,8 +494,6 @@ describe('MetaMask', function () {
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
@ -504,9 +502,9 @@ describe('MetaMask', function () {
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasLimitInput.sendKeys('25000')
await delay(largeDelayMs * 2)
await delay(1000)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
await confirmButton.click()
@ -792,14 +790,12 @@ describe('MetaMask', function () {
await modalTabs[1].click()
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
const gasLimitValue = await gasLimitInput.getAttribute('value')
assert(Number(gasLimitValue) < 100000, 'Gas Limit too high')
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
@ -808,18 +804,9 @@ describe('MetaMask', function () {
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys('60001')
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
await delay(50)
await delay(1000)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
@ -914,7 +901,7 @@ describe('MetaMask', function () {
await advancedTabButton.click()
await delay(tinyDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
assert(gasPriceInput.getAttribute('value'), 20)
assert(gasLimitInput.getAttribute('value'), 4700000)
@ -1103,12 +1090,10 @@ describe('MetaMask', function () {
await modalTabs[1].click()
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
@ -1117,18 +1102,9 @@ describe('MetaMask', function () {
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys('60000')
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
await delay(50)
await delay(1000)
const save = await findElement(driver, By.css('.page-container__footer-button'))
await save.click()
@ -1246,12 +1222,10 @@ describe('MetaMask', function () {
await modalTabs[1].click()
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
@ -1260,18 +1234,9 @@ describe('MetaMask', function () {
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys('60001')
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
await delay(50)
await delay(1000)
const save = await findElement(driver, By.css('.page-container__footer-button'))
await save.click()

View File

@ -113,7 +113,7 @@ describe('Using MetaMask with an existing account', function () {
const gasModal = await driver.findElement(By.css('span .modal'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
@ -131,6 +131,8 @@ describe('Using MetaMask with an existing account', function () {
await gasLimitInput.sendKeys('25000')
await delay(1000)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
await driver.wait(until.stalenessOf(gasModal))
@ -170,7 +172,7 @@ describe('Using MetaMask with an existing account', function () {
const gasModal = await driver.findElement(By.css('span .modal'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-gas-inputs__gas-edit-row__input'))
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
@ -187,6 +189,8 @@ describe('Using MetaMask with an existing account', function () {
await gasLimitInput.sendKeys('100000')
await delay(1000)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
await driver.wait(until.stalenessOf(gasModal))

View File

@ -3,22 +3,11 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'
import debounce from 'lodash.debounce'
export default class AdvancedTabContent extends Component {
export default class AdvancedGasInputs extends Component {
static contextTypes = {
t: PropTypes.func,
}
constructor (props) {
super(props)
this.state = {
gasPrice: this.props.customGasPrice,
gasLimit: this.props.customGasLimit,
}
this.changeGasPrice = debounce(this.changeGasPrice, 500)
this.changeGasLimit = debounce(this.changeGasLimit, 500)
}
static propTypes = {
updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func,
@ -31,6 +20,16 @@ export default class AdvancedTabContent extends Component {
showGasLimitInfoModal: PropTypes.func,
}
constructor (props) {
super(props)
this.state = {
gasPrice: this.props.customGasPrice,
gasLimit: this.props.customGasLimit,
}
this.changeGasPrice = debounce(this.changeGasPrice, 500)
this.changeGasLimit = debounce(this.changeGasLimit, 500)
}
componentDidUpdate (prevProps) {
const { customGasPrice: prevCustomGasPrice, customGasLimit: prevCustomGasLimit } = prevProps
const { customGasPrice, customGasLimit } = this.props
@ -50,13 +49,8 @@ export default class AdvancedTabContent extends Component {
}
changeGasLimit = (e) => {
if (e.target.value < 21000) {
this.setState({ gasLimit: 21000 })
this.props.updateCustomGasLimit(21000)
} else {
this.props.updateCustomGasLimit(Number(e.target.value))
}
}
onChangeGasPrice = (e) => {
this.setState({ gasPrice: e.target.value })
@ -67,54 +61,67 @@ export default class AdvancedTabContent extends Component {
this.props.updateCustomGasPrice(Number(e.target.value))
}
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
gasPriceError ({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) {
const { t } = this.context
let errorText
let errorType
let isInError = true
if (insufficientBalance) {
errorText = t('insufficientBalance')
errorType = 'error'
} else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
errorText = t('zeroGasPriceOnSpeedUpError')
errorType = 'error'
} else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
errorText = t('gasPriceExtremelyLow')
errorType = 'warning'
} else {
isInError = false
}
return {
isInError,
errorText,
errorType,
errorText: t('insufficientBalance'),
errorType: 'error',
}
} else if (isSpeedUp && gasPrice === 0) {
return {
errorText: t('zeroGasPriceOnSpeedUpError'),
errorType: 'error',
}
} else if (!customPriceIsSafe) {
return {
errorText: t('gasPriceExtremelyLow'),
errorType: 'warning',
}
}
gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
errorType,
} = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
return {}
}
gasLimitError ({ insufficientBalance, gasLimit }) {
const { t } = this.context
if (insufficientBalance) {
return {
errorText: t('insufficientBalance'),
errorType: 'error',
}
} else if (gasLimit < 21000) {
return {
errorText: t('gasLimitTooLow'),
errorType: 'error',
}
}
return {}
}
renderGasInput ({ value, onChange, errorComponent, errorType, infoOnClick, label }) {
return (
<div className="advanced-gas-inputs__gas-edit-row">
<div className="advanced-gas-inputs__gas-edit-row__label">
{ label }
<i className="fa fa-info-circle" onClick={infoOnClick} />
</div>
<div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
<input
className={classnames('advanced-gas-inputs__gas-edit-row__input', {
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
'advanced-gas-inputs__gas-edit-row__input--error': errorType === 'error',
'advanced-gas-inputs__gas-edit-row__input--warning': errorType === 'warning',
})}
type="number"
value={value}
onChange={onChange}
/>
<div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
'advanced-gas-inputs__gas-edit-row__input--error': errorType === 'error',
'advanced-gas-inputs__gas-edit-row__input--warning': errorType === 'warning',
})}>
<div
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
@ -129,27 +136,8 @@ export default class AdvancedTabContent extends Component {
<i className="fa fa-sm fa-angle-down" />
</div>
</div>
{ isInError
? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}>
{ errorText }
{ errorComponent }
</div>
: null }
</div>
)
}
infoButton (onClick) {
return <i className="fa fa-info-circle" onClick={onClick} />
}
renderGasEditRow (gasInputArgs) {
return (
<div className="advanced-gas-inputs__gas-edit-row">
<div className="advanced-gas-inputs__gas-edit-row__label">
{ this.context.t(gasInputArgs.labelKey) }
{ this.infoButton(() => gasInputArgs.infoOnClick()) }
</div>
{ this.gasInput(gasInputArgs) }
</div>
)
}
@ -162,25 +150,47 @@ export default class AdvancedTabContent extends Component {
showGasPriceInfoModal,
showGasLimitInfoModal,
} = this.props
const {
gasPrice,
gasLimit,
} = this.state
const {
errorText: gasPriceErrorText,
errorType: gasPriceErrorType,
} = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice })
const gasPriceErrorComponent = gasPriceErrorType ?
<div className={`advanced-gas-inputs__gas-edit-row__${gasPriceErrorType}-text`}>
{ gasPriceErrorText }
</div> :
null
const {
errorText: gasLimitErrorText,
errorType: gasLimitErrorType,
} = this.gasLimitError({ insufficientBalance, gasLimit })
const gasLimitErrorComponent = gasLimitErrorType ?
<div className={`advanced-gas-inputs__gas-edit-row__${gasLimitErrorType}-text`}>
{ gasLimitErrorText }
</div> :
null
return (
<div className="advanced-gas-inputs__gas-edit-rows">
{ this.renderGasEditRow({
labelKey: 'gasPrice',
{ this.renderGasInput({
label: this.context.t('gasPrice'),
value: this.state.gasPrice,
onChange: this.onChangeGasPrice,
insufficientBalance,
customPriceIsSafe,
showGWEI: true,
isSpeedUp,
errorComponent: gasPriceErrorComponent,
errorType: gasPriceErrorType,
infoOnClick: showGasPriceInfoModal,
}) }
{ this.renderGasEditRow({
labelKey: 'gasLimit',
{ this.renderGasInput({
label: this.context.t('gasLimit'),
value: this.state.gasLimit,
onChange: this.onChangeGasLimit,
insufficientBalance,
customPriceIsSafe,
errorComponent: gasLimitErrorComponent,
errorType: gasLimitErrorType,
infoOnClick: showGasLimitInfoModal,
}) }
</div>

View File

@ -25,9 +25,9 @@ const mapDispatchToProps = dispatch => {
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const {customGasPrice, customGasLimit, updateCustomGasPrice, updateCustomGasLimit} = ownProps
return {
...ownProps,
...stateProps,
...dispatchProps,
...ownProps,
customGasPrice: convertGasPriceForInputs(customGasPrice),
customGasLimit: convertGasLimitForInputs(customGasLimit),
updateCustomGasPrice: (price) => updateCustomGasPrice(decGWEIToHexWEI(price)),

View File

@ -1,9 +1,8 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Loading from '../../../../ui/loading-screen'
import GasPriceChart from '../../gas-price-chart'
import debounce from 'lodash.debounce'
import AdvancedGasInputs from '../../advanced-gas-inputs'
export default class AdvancedTabContent extends Component {
static contextTypes = {
@ -13,8 +12,8 @@ export default class AdvancedTabContent extends Component {
static propTypes = {
updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func,
customGasPrice: PropTypes.number,
customGasLimit: PropTypes.number,
customModalGasPriceInHex: PropTypes.string,
customModalGasLimitInHex: PropTypes.string,
gasEstimatesLoading: PropTypes.bool,
millisecondsRemaining: PropTypes.number,
transactionFee: PropTypes.string,
@ -26,95 +25,6 @@ export default class AdvancedTabContent extends Component {
isEthereumNetwork: PropTypes.bool,
}
constructor (props) {
super(props)
this.debouncedGasLimitReset = debounce((dVal) => {
if (dVal < 21000) {
props.updateCustomGasLimit(21000)
}
}, 1000, { trailing: true })
this.onChangeGasLimit = (val) => {
props.updateCustomGasLimit(val)
this.debouncedGasLimitReset(val)
}
}
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
const { t } = this.context
let errorText
let errorType
let isInError = true
if (insufficientBalance) {
errorText = t('insufficientBalance')
errorType = 'error'
} else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
errorText = t('zeroGasPriceOnSpeedUpError')
errorType = 'error'
} else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
errorText = t('gasPriceExtremelyLow')
errorType = 'warning'
} else {
isInError = false
}
return {
isInError,
errorText,
errorType,
}
}
gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
errorType,
} = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
return (
<div className="advanced-tab__gas-edit-row__input-wrapper">
<input
className={classnames('advanced-tab__gas-edit-row__input', {
'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
})}
type="number"
value={value}
onChange={event => onChange(Number(event.target.value))}
/>
<div className={classnames('advanced-tab__gas-edit-row__input-arrows', {
'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
})}>
<div
className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
onClick={() => onChange(value + 1)}
>
<i className="fa fa-sm fa-angle-up" />
</div>
<div
className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
onClick={() => onChange(Math.max(value - 1, 0))}
>
<i className="fa fa-sm fa-angle-down" />
</div>
</div>
{ isInError
? <div className={`advanced-tab__gas-edit-row__${errorType}-text`}>
{ errorText }
</div>
: null }
</div>
)
}
infoButton (onClick) {
return <i className="fa fa-info-circle" onClick={onClick} />
}
renderDataSummary (transactionFee, timeRemaining) {
return (
<div className="advanced-tab__transaction-data-summary">
@ -132,56 +42,14 @@ export default class AdvancedTabContent extends Component {
)
}
renderGasEditRow (gasInputArgs) {
return (
<div className="advanced-tab__gas-edit-row">
<div className="advanced-tab__gas-edit-row__label">
{ this.context.t(gasInputArgs.labelKey) }
{ this.infoButton(() => {}) }
</div>
{ this.gasInput(gasInputArgs) }
</div>
)
}
renderGasEditRows ({
customGasPrice,
updateCustomGasPrice,
customGasLimit,
insufficientBalance,
customPriceIsSafe,
isSpeedUp,
}) {
return (
<div className="advanced-tab__gas-edit-rows">
{ this.renderGasEditRow({
labelKey: 'gasPrice',
value: customGasPrice,
onChange: updateCustomGasPrice,
insufficientBalance,
customPriceIsSafe,
showGWEI: true,
isSpeedUp,
}) }
{ this.renderGasEditRow({
labelKey: 'gasLimit',
value: customGasLimit,
onChange: this.onChangeGasLimit,
insufficientBalance,
customPriceIsSafe,
}) }
</div>
)
}
render () {
const { t } = this.context
const {
updateCustomGasPrice,
updateCustomGasLimit,
timeRemaining,
customGasPrice,
customGasLimit,
customModalGasPriceInHex,
customModalGasLimitInHex,
insufficientBalance,
gasChartProps,
gasEstimatesLoading,
@ -195,15 +63,17 @@ export default class AdvancedTabContent extends Component {
<div className="advanced-tab">
{ this.renderDataSummary(transactionFee, timeRemaining) }
<div className="advanced-tab__fee-chart">
{ this.renderGasEditRows({
customGasPrice,
updateCustomGasPrice,
customGasLimit,
updateCustomGasLimit,
insufficientBalance,
customPriceIsSafe,
isSpeedUp,
}) }
<div className="advanced-tab__gas-inputs">
<AdvancedGasInputs
updateCustomGasPrice={updateCustomGasPrice}
updateCustomGasLimit={updateCustomGasLimit}
customGasPrice={customModalGasPriceInHex}
customGasLimit={customModalGasLimitInHex}
insufficientBalance={insufficientBalance}
customPriceIsSafe={customPriceIsSafe}
isSpeedUp={isSpeedUp}
/>
</div>
{ isEthereumNetwork
? <div>
<div className="advanced-tab__fee-chart__title">{ t('liveGasPricePredictions') }</div>

View File

@ -92,137 +92,13 @@
padding-right: 27px;
}
&__gas-edit-rows {
height: 73px;
&__gas-inputs {
display: flex;
flex-flow: row;
justify-content: space-between;
margin-left: 20px;
margin-right: 10px;
margin-top: 9px;
}
&__gas-edit-row {
display: flex;
flex-flow: column;
&__label {
color: #313B5E;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
.fa-info-circle {
color: $silver;
margin-left: 10px;
cursor: pointer;
}
.fa-info-circle:hover {
color: $mid-gray;
}
}
&__error-text {
font-size: 12px;
color: red;
}
&__warning-text {
font-size: 12px;
color: orange;
}
&__input-wrapper {
position: relative;
}
&__input {
/*rtl:ignore*/
direction: ltr;
border: 1px solid $dusty-gray;
border-radius: 4px;
color: $mid-gray;
font-size: 16px;
height: 24px;
width: 155px;
padding-left: 8px;
padding-top: 2px;
margin-top: 7px;
}
&__input--error {
border: 1px solid $red;
}
&__input--warning {
border: 1px solid $orange;
}
&__input-arrows {
position: absolute;
top: 7px;
/*rtl:ignore*/
right: 0px;
width: 17px;
height: 24px;
border: 1px solid #dadada;
border-top-right-radius: 4px;
display: flex;
flex-direction: column;
color: #9b9b9b;
font-size: .8em;
border-bottom-right-radius: 4px;
cursor: pointer;
&__i-wrap {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
cursor: pointer;
}
&__i-wrap:hover {
background: #4EADE7;
color: $white;
}
i:hover {
background: #4EADE7;
}
i {
font-size: 10px;
}
}
&__input-arrows--error {
border: 1px solid $red;
}
&__input-arrows--warning {
border: 1px solid $orange;
}
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
input[type="number"]:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
&__gwei-symbol {
position: absolute;
top: 8px;
right: 10px;
color: $dusty-gray;
}
margin-top: 10px;
margin-bottom: 20px
}
}

View File

@ -12,11 +12,7 @@ const propsMethodSpies = {
updateCustomGasLimit: sinon.spy(),
}
sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRow')
sinon.spy(AdvancedTabContent.prototype, 'gasInput')
sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRows')
sinon.spy(AdvancedTabContent.prototype, 'renderDataSummary')
sinon.spy(AdvancedTabContent.prototype, 'gasInputError')
describe('AdvancedTabContent Component', function () {
let wrapper
@ -25,23 +21,20 @@ describe('AdvancedTabContent Component', function () {
wrapper = shallow(<AdvancedTabContent
updateCustomGasPrice={propsMethodSpies.updateCustomGasPrice}
updateCustomGasLimit={propsMethodSpies.updateCustomGasLimit}
customGasPrice={11}
customGasLimit={23456}
timeRemaining={21500}
customModalGasPriceInHex={'11'}
customModalGasLimitInHex={'23456'}
timeRemaining={'21500'}
transactionFee={'$0.25'}
insufficientBalance={false}
customPriceIsSafe={true}
isSpeedUp={false}
isEthereumNetwork={true}
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
/>)
})
afterEach(() => {
propsMethodSpies.updateCustomGasPrice.resetHistory()
propsMethodSpies.updateCustomGasLimit.resetHistory()
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
AdvancedTabContent.prototype.gasInput.resetHistory()
AdvancedTabContent.prototype.renderGasEditRows.resetHistory()
AdvancedTabContent.prototype.renderDataSummary.resetHistory()
})
@ -59,7 +52,6 @@ describe('AdvancedTabContent Component', function () {
const feeChartDiv = advancedTabChildren.at(1)
assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
assert(feeChartDiv.childAt(1).childAt(1).is(GasPriceChart))
assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
@ -75,31 +67,15 @@ describe('AdvancedTabContent Component', function () {
const feeChartDiv = advancedTabChildren.at(1)
assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
assert(feeChartDiv.childAt(1).childAt(1).is(Loading))
assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
})
it('should call renderDataSummary with the expected params', () => {
assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args
assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500])
})
it('should call renderGasEditRows with the expected params', () => {
assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
const renderGasEditRowArgs = AdvancedTabContent.prototype.renderGasEditRows.getCall(0).args
assert.deepEqual(renderGasEditRowArgs, [{
customGasPrice: 11,
customGasLimit: 23456,
insufficientBalance: false,
customPriceIsSafe: true,
updateCustomGasPrice: propsMethodSpies.updateCustomGasPrice,
updateCustomGasLimit: propsMethodSpies.updateCustomGasLimit,
isSpeedUp: false,
}])
})
})
describe('renderDataSummary()', () => {
@ -129,237 +105,4 @@ describe('AdvancedTabContent Component', function () {
})
})
describe('renderGasEditRow()', () => {
let gasEditRow
beforeEach(() => {
AdvancedTabContent.prototype.gasInput.resetHistory()
gasEditRow = shallow(wrapper.instance().renderGasEditRow({
labelKey: 'mockLabelKey',
someArg: 'argA',
}))
})
it('should render the gas-edit-row root node', () => {
assert(gasEditRow.hasClass('advanced-tab__gas-edit-row'))
})
it('should render a label and an input', () => {
const gasEditRowChildren = gasEditRow.children()
assert.equal(gasEditRowChildren.length, 2)
assert(gasEditRowChildren.at(0).hasClass('advanced-tab__gas-edit-row__label'))
assert(gasEditRowChildren.at(1).hasClass('advanced-tab__gas-edit-row__input-wrapper'))
})
it('should render the label key and info button', () => {
const gasRowLabelChildren = gasEditRow.children().at(0).children()
assert.equal(gasRowLabelChildren.length, 2)
assert(gasRowLabelChildren.at(0), 'mockLabelKey')
assert(gasRowLabelChildren.at(1).hasClass('fa-info-circle'))
})
it('should call this.gasInput with the correct args', () => {
const gasInputSpyArgs = AdvancedTabContent.prototype.gasInput.args
assert.deepEqual(gasInputSpyArgs[0], [ { labelKey: 'mockLabelKey', someArg: 'argA' } ])
})
})
describe('renderGasEditRows()', () => {
let gasEditRows
let tempOnChangeGasLimit
beforeEach(() => {
tempOnChangeGasLimit = wrapper.instance().onChangeGasLimit
wrapper.instance().onChangeGasLimit = () => 'mockOnChangeGasLimit'
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
gasEditRows = shallow(wrapper.instance().renderGasEditRows(
'mockGasPrice',
() => 'mockUpdateCustomGasPriceReturn',
'mockGasLimit',
() => 'mockUpdateCustomGasLimitReturn',
false
))
})
afterEach(() => {
wrapper.instance().onChangeGasLimit = tempOnChangeGasLimit
})
it('should render the gas-edit-rows root node', () => {
assert(gasEditRows.hasClass('advanced-tab__gas-edit-rows'))
})
it('should render two rows', () => {
const gasEditRowsChildren = gasEditRows.children()
assert.equal(gasEditRowsChildren.length, 2)
assert(gasEditRowsChildren.at(0).hasClass('advanced-tab__gas-edit-row'))
assert(gasEditRowsChildren.at(1).hasClass('advanced-tab__gas-edit-row'))
})
it('should call this.renderGasEditRow twice, with the expected args', () => {
const renderGasEditRowSpyArgs = AdvancedTabContent.prototype.renderGasEditRow.args
assert.equal(renderGasEditRowSpyArgs.length, 2)
assert.deepEqual(renderGasEditRowSpyArgs[0].map(String), [{
labelKey: 'gasPrice',
value: 'mockGasLimit',
onChange: () => 'mockOnChangeGasLimit',
insufficientBalance: false,
customPriceIsSafe: true,
showGWEI: true,
}].map(String))
assert.deepEqual(renderGasEditRowSpyArgs[1].map(String), [{
labelKey: 'gasPrice',
value: 'mockGasPrice',
onChange: () => 'mockUpdateCustomGasPriceReturn',
insufficientBalance: false,
customPriceIsSafe: true,
showGWEI: true,
}].map(String))
})
})
describe('infoButton()', () => {
let infoButton
beforeEach(() => {
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
infoButton = shallow(wrapper.instance().infoButton(() => 'mockOnClickReturn'))
})
it('should render the i element', () => {
assert(infoButton.hasClass('fa-info-circle'))
})
it('should pass the onClick argument to the i tag onClick prop', () => {
assert(infoButton.props().onClick(), 'mockOnClickReturn')
})
})
describe('gasInput()', () => {
let gasInput
beforeEach(() => {
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
AdvancedTabContent.prototype.gasInputError.resetHistory()
gasInput = shallow(wrapper.instance().gasInput({
labelKey: 'gasPrice',
value: 321,
onChange: value => value + 7,
insufficientBalance: false,
showGWEI: true,
customPriceIsSafe: true,
isSpeedUp: false,
}))
})
it('should render the input-wrapper root node', () => {
assert(gasInput.hasClass('advanced-tab__gas-edit-row__input-wrapper'))
})
it('should render two children, including an input', () => {
assert.equal(gasInput.children().length, 2)
assert(gasInput.children().at(0).hasClass('advanced-tab__gas-edit-row__input'))
})
it('should call the passed onChange method with the value of the input onChange event', () => {
const inputOnChange = gasInput.find('input').props().onChange
assert.equal(inputOnChange({ target: { value: 8} }), 15)
})
it('should have two input arrows', () => {
const upArrow = gasInput.find('.fa-angle-up')
assert.equal(upArrow.length, 1)
const downArrow = gasInput.find('.fa-angle-down')
assert.equal(downArrow.length, 1)
})
it('should call onChange with the value incremented decremented when its onchange method is called', () => {
const upArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(0)
assert.equal(upArrow.props().onClick(), 329)
const downArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(1)
assert.equal(downArrow.props().onClick(), 327)
})
it('should call gasInputError with the expected params', () => {
assert.equal(AdvancedTabContent.prototype.gasInputError.callCount, 1)
const gasInputErrorArgs = AdvancedTabContent.prototype.gasInputError.getCall(0).args
assert.deepEqual(gasInputErrorArgs, [{
labelKey: 'gasPrice',
insufficientBalance: false,
customPriceIsSafe: true,
value: 321,
isSpeedUp: false,
}])
})
})
describe('gasInputError()', () => {
let gasInputError
beforeEach(() => {
AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
gasInputError = wrapper.instance().gasInputError({
labelKey: '',
insufficientBalance: false,
customPriceIsSafe: true,
isSpeedUp: false,
})
})
it('should return an insufficientBalance error', () => {
const gasInputError = wrapper.instance().gasInputError({
labelKey: 'gasPrice',
insufficientBalance: true,
customPriceIsSafe: true,
isSpeedUp: false,
value: 1,
})
assert.deepEqual(gasInputError, {
isInError: true,
errorText: 'insufficientBalance',
errorType: 'error',
})
})
it('should return a zero gas on retry error', () => {
const gasInputError = wrapper.instance().gasInputError({
labelKey: 'gasPrice',
insufficientBalance: false,
customPriceIsSafe: false,
isSpeedUp: true,
value: 0,
})
assert.deepEqual(gasInputError, {
isInError: true,
errorText: 'zeroGasPriceOnSpeedUpError',
errorType: 'error',
})
})
it('should return a low gas warning', () => {
const gasInputError = wrapper.instance().gasInputError({
labelKey: 'gasPrice',
insufficientBalance: false,
customPriceIsSafe: false,
isSpeedUp: false,
value: 1,
})
assert.deepEqual(gasInputError, {
isInError: true,
errorText: 'gasPriceExtremelyLow',
errorType: 'warning',
})
})
it('should return isInError false if there is no error', () => {
gasInputError = wrapper.instance().gasInputError({
labelKey: 'gasPrice',
insufficientBalance: false,
customPriceIsSafe: true,
value: 1,
})
assert.equal(gasInputError.isInError, false)
})
})
})

View File

@ -16,11 +16,15 @@ export default class GasModalPageContainer extends Component {
hideBasic: PropTypes.bool,
updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func,
currentTimeEstimate: PropTypes.string,
customGasPrice: PropTypes.number,
customGasLimit: PropTypes.number,
insufficientBalance: PropTypes.bool,
fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func,
gasPriceButtonGroupProps: PropTypes.object,
gasChartProps: PropTypes.object,
gasEstimatesLoading: PropTypes.bool,
infoRowProps: PropTypes.shape({
originalTotalFiat: PropTypes.string,
originalTotalEth: PropTypes.string,
@ -64,30 +68,32 @@ export default class GasModalPageContainer extends Component {
)
}
renderAdvancedTabContent ({
convertThenUpdateCustomGasPrice,
convertThenUpdateCustomGasLimit,
customGasPrice,
customGasLimit,
newTotalFiat,
renderAdvancedTabContent () {
const {
updateCustomGasPrice,
updateCustomGasLimit,
customModalGasPriceInHex,
customModalGasLimitInHex,
gasChartProps,
currentTimeEstimate,
insufficientBalance,
gasEstimatesLoading,
customPriceIsSafe,
isSpeedUp,
infoRowProps: {
transactionFee,
},
isEthereumNetwork,
}) {
} = this.props
return (
<AdvancedTabContent
updateCustomGasPrice={convertThenUpdateCustomGasPrice}
updateCustomGasLimit={convertThenUpdateCustomGasLimit}
customGasPrice={customGasPrice}
customGasLimit={customGasLimit}
updateCustomGasPrice={updateCustomGasPrice}
updateCustomGasLimit={updateCustomGasLimit}
customModalGasPriceInHex={customModalGasPriceInHex}
customModalGasLimitInHex={customModalGasLimitInHex}
timeRemaining={currentTimeEstimate}
transactionFee={transactionFee}
totalFee={newTotalFiat}
gasChartProps={gasChartProps}
insufficientBalance={insufficientBalance}
gasEstimatesLoading={gasEstimatesLoading}
@ -122,20 +128,27 @@ export default class GasModalPageContainer extends Component {
)
}
renderTabs ({
renderTabs () {
const {
gasPriceButtonGroupProps,
hideBasic,
infoRowProps: {
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
},
{
gasPriceButtonGroupProps,
hideBasic,
...advancedTabProps
}) {
} = this.props
let tabsToRender = [
{ name: 'basic', content: this.renderBasicTabContent(gasPriceButtonGroupProps) },
{ name: 'advanced', content: this.renderAdvancedTabContent({ transactionFee, ...advancedTabProps }) },
{
name: this.context.t('basic'),
content: this.renderBasicTabContent(gasPriceButtonGroupProps),
},
{
name: this.context.t('advanced'),
content: this.renderAdvancedTabContent(),
},
]
if (hideBasic) {
@ -144,7 +157,7 @@ export default class GasModalPageContainer extends Component {
return (
<Tabs>
{tabsToRender.map(({ name, content }, i) => <Tab name={this.context.t(name)} key={`gas-modal-tab-${i}`}>
{tabsToRender.map(({ name, content }, i) => <Tab name={name} key={`gas-modal-tab-${i}`}>
<div className="gas-modal-content">
{ content }
{ this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) }
@ -158,13 +171,11 @@ export default class GasModalPageContainer extends Component {
render () {
const {
cancelAndClose,
infoRowProps,
onSubmit,
customModalGasPriceInHex,
customModalGasLimitInHex,
disableSave,
isSpeedUp,
...tabProps
} = this.props
return (
@ -172,7 +183,7 @@ export default class GasModalPageContainer extends Component {
<PageContainer
title={this.context.t('customGas')}
subtitle={this.context.t('customGasSubTitle')}
tabsComponent={this.renderTabs(infoRowProps, tabProps)}
tabsComponent={this.renderTabs()}
disabled={disableSave}
onCancel={() => cancelAndClose()}
onClose={() => cancelAndClose()}

View File

@ -56,7 +56,6 @@ import {
addHexWEIsToDec,
subtractHexWEIsToDec,
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
decGWEIToHexWEI,
hexWEIToDecGWEI,
} from '../../../../helpers/utils/conversions.util'
import {
@ -175,8 +174,7 @@ const mapDispatchToProps = dispatch => {
},
hideModal: () => dispatch(hideModal()),
updateCustomGasPrice,
convertThenUpdateCustomGasPrice: newPrice => updateCustomGasPrice(decGWEIToHexWEI(newPrice)),
convertThenUpdateCustomGasLimit: newLimit => dispatch(setCustomGasLimit(addHexPrefix(newLimit.toString(16)))),
updateCustomGasLimit: newLimit => dispatch(setCustomGasLimit(addHexPrefix(newLimit))),
setGasData: (newLimit, newPrice) => {
dispatch(setGasLimit(newLimit))
dispatch(setGasPrice(newPrice))
@ -219,7 +217,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
transaction,
} = stateProps
const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
hideGasButtonGroup: dispatchHideGasButtonGroup,
setGasData: dispatchSetGasData,
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
@ -267,7 +264,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
},
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
handleGasPriceSelection: dispatchUpdateCustomGasPrice,
handleGasPriceSelection: otherDispatchProps.updateCustomGasPrice,
},
cancelAndClose: () => {
dispatchCancelAndClose()

View File

@ -79,7 +79,7 @@ describe('GasModalPageContainer Component', function () {
customGasLimitInHex={'mockCustomGasLimitInHex'}
insufficientBalance={false}
disableSave={false}
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
/>)
})
afterEach(() => {
@ -158,10 +158,7 @@ describe('GasModalPageContainer Component', function () {
})
it('should render a Tabs component with "Basic" and "Advanced" tabs', () => {
const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
otherProps: 'mockAdvancedTabProps',
})
const renderTabsResult = wrapper.instance().renderTabs()
const renderedTabs = shallow(renderTabsResult)
assert.equal(renderedTabs.props().className, 'tabs')
@ -175,23 +172,10 @@ describe('GasModalPageContainer Component', function () {
assert.equal(tabs.at(1).childAt(0).props().className, 'gas-modal-content')
})
it('should call renderBasicTabContent and renderAdvancedTabContent with the expected props', () => {
assert.equal(GP.renderBasicTabContent.callCount, 0)
assert.equal(GP.renderAdvancedTabContent.callCount, 0)
wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
assert.equal(GP.renderBasicTabContent.callCount, 1)
assert.equal(GP.renderAdvancedTabContent.callCount, 1)
assert.deepEqual(GP.renderBasicTabContent.getCall(0).args[0], mockGasPriceButtonGroupProps)
assert.deepEqual(GP.renderAdvancedTabContent.getCall(0).args[0], { transactionFee: 'mockTransactionFee', otherProps: 'mockAdvancedTabProps' })
})
it('should call renderInfoRows with the expected props', () => {
assert.equal(GP.renderInfoRows.callCount, 0)
wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
wrapper.instance().renderTabs()
assert.equal(GP.renderInfoRows.callCount, 2)
@ -200,11 +184,25 @@ describe('GasModalPageContainer Component', function () {
})
it('should not render the basic tab if hideBasic is true', () => {
const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
otherProps: 'mockAdvancedTabProps',
hideBasic: true,
})
wrapper = shallow(<GasModalPageContainer
cancelAndClose={propsMethodSpies.cancelAndClose}
onSubmit={propsMethodSpies.onSubmit}
fetchBasicGasAndTimeEstimates={propsMethodSpies.fetchBasicGasAndTimeEstimates}
fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
updateCustomGasPrice={() => 'mockupdateCustomGasPrice'}
updateCustomGasLimit={() => 'mockupdateCustomGasLimit'}
customGasPrice={21}
customGasLimit={54321}
gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
infoRowProps={mockInfoRowProps}
currentTimeEstimate={'1 min 31 sec'}
customGasPriceInHex={'mockCustomGasPriceInHex'}
customGasLimitInHex={'mockCustomGasLimitInHex'}
insufficientBalance={false}
disableSave={false}
hideBasic={true}
/>)
const renderTabsResult = wrapper.instance().renderTabs()
const renderedTabs = shallow(renderTabsResult)
const tabs = renderedTabs.find(Tab)
@ -224,28 +222,6 @@ describe('GasModalPageContainer Component', function () {
})
})
describe('renderAdvancedTabContent', () => {
it('should render with the correct props', () => {
const renderAdvancedTabContentResult = wrapper.instance().renderAdvancedTabContent({
convertThenUpdateCustomGasPrice: () => 'mockConvertThenUpdateCustomGasPrice',
convertThenUpdateCustomGasLimit: () => 'mockConvertThenUpdateCustomGasLimit',
customGasPrice: 123,
customGasLimit: 456,
newTotalFiat: '$0.30',
currentTimeEstimate: '1 min 31 sec',
gasEstimatesLoading: 'mockGasEstimatesLoading',
})
const advancedTabContentProps = renderAdvancedTabContentResult.props
assert.equal(advancedTabContentProps.updateCustomGasPrice(), 'mockConvertThenUpdateCustomGasPrice')
assert.equal(advancedTabContentProps.updateCustomGasLimit(), 'mockConvertThenUpdateCustomGasLimit')
assert.equal(advancedTabContentProps.customGasPrice, 123)
assert.equal(advancedTabContentProps.customGasLimit, 456)
assert.equal(advancedTabContentProps.timeRemaining, '1 min 31 sec')
assert.equal(advancedTabContentProps.totalFee, '$0.30')
assert.equal(advancedTabContentProps.gasEstimatesLoading, 'mockGasEstimatesLoading')
})
})
describe('renderInfoRows', () => {
it('should render the info rows with the passed data', () => {
const baseClassName = 'gas-modal-content__info-row'

View File

@ -297,19 +297,19 @@ describe('gas-modal-page-container container', () => {
})
})
describe('convertThenUpdateCustomGasPrice()', () => {
it('should dispatch a setCustomGasPrice action with the arg passed to convertThenUpdateCustomGasPrice converted to WEI', () => {
mapDispatchToPropsObject.convertThenUpdateCustomGasPrice('0xffff')
describe('updateCustomGasPrice()', () => {
it('should dispatch a setCustomGasPrice action', () => {
mapDispatchToPropsObject.updateCustomGasPrice('0xffff')
assert(dispatchSpy.calledOnce)
assert(gasActionSpies.setCustomGasPrice.calledOnce)
assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0x3b9a8e653600')
assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0xffff')
})
})
describe('convertThenUpdateCustomGasLimit()', () => {
it('should dispatch a setCustomGasLimit action with the arg passed to convertThenUpdateCustomGasLimit converted to hex', () => {
mapDispatchToPropsObject.convertThenUpdateCustomGasLimit(16)
describe('updateCustomGasLimit()', () => {
it('should dispatch a setCustomGasLimit action', () => {
mapDispatchToPropsObject.updateCustomGasLimit('0x10')
assert(dispatchSpy.calledOnce)
assert(gasActionSpies.setCustomGasLimit.calledOnce)
assert.equal(gasActionSpies.setCustomGasLimit.getCall(0).args[0], '0x10')

View File

@ -2,9 +2,11 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ButtonGroup from '../../../ui/button-group'
import Button from '../../../ui/button'
import { GAS_ESTIMATE_TYPES } from '../../../../helpers/constants/common'
const GAS_OBJECT_PROPTYPES_SHAPE = {
label: PropTypes.string,
gasEstimateType: PropTypes.oneOf(Object.values(GAS_ESTIMATE_TYPES)).isRequired,
feeInPrimaryCurrency: PropTypes.string,
feeInSecondaryCurrency: PropTypes.string,
timeEstimate: PropTypes.string,
@ -27,8 +29,19 @@ export default class GasPriceButtonGroup extends Component {
showCheck: PropTypes.bool,
}
gasEstimateTypeLabel (gasEstimateType) {
if (gasEstimateType === GAS_ESTIMATE_TYPES.SLOW) {
return this.context.t('slow')
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.AVERAGE) {
return this.context.t('average')
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.FAST) {
return this.context.t('fast')
}
throw new Error(`Unrecognized gas estimate type: ${gasEstimateType}`)
}
renderButtonContent ({
labelKey,
gasEstimateType,
feeInPrimaryCurrency,
feeInSecondaryCurrency,
timeEstimate,
@ -37,7 +50,7 @@ export default class GasPriceButtonGroup extends Component {
showCheck,
}) {
return (<div>
{ labelKey && <div className={`${className}__label`}>{ this.context.t(labelKey) }</div> }
{ gasEstimateType && <div className={`${className}__label`}>{ this.gasEstimateTypeLabel(gasEstimateType) }</div> }
{ timeEstimate && <div className={`${className}__time-estimate`}>{ timeEstimate }</div> }
{ feeInPrimaryCurrency && <div className={`${className}__primary-currency`}>{ feeInPrimaryCurrency }</div> }
{ feeInSecondaryCurrency && <div className={`${className}__secondary-currency`}>{ feeInSecondaryCurrency }</div> }

View File

@ -156,15 +156,15 @@ describe('GasPriceButtonGroup Component', function () {
})
describe('renderButtonContent', () => {
it('should render a label if passed a labelKey', () => {
it('should render a label if passed a gasEstimateType', () => {
const renderButtonContentResult = wrapper.instance().renderButtonContent({
labelKey: 'mockLabelKey',
gasEstimateType: 'SLOW',
}, {
className: 'someClass',
})
const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'mockLabelKey')
assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'slow')
})
it('should render a feeInPrimaryCurrency if passed a feeInPrimaryCurrency', () => {
@ -211,7 +211,7 @@ describe('GasPriceButtonGroup Component', function () {
it('should render all elements if all args passed', () => {
const renderButtonContentResult = wrapper.instance().renderButtonContent({
labelKey: 'mockLabel',
gasEstimateType: 'SLOW',
feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency',
feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency',
timeEstimate: 'mockTimeEstimate',

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class AdvancedTabContent extends Component {
export default class GasSlider extends Component {
static propTypes = {
onChange: PropTypes.func,
lowLabel: PropTypes.string,

View File

@ -12,3 +12,9 @@ export const NETWORK_TYPES = {
ROPSTEN: 'ropsten',
GOERLI: 'goerli',
}
export const GAS_ESTIMATE_TYPES = {
SLOW: 'SLOW',
AVERAGE: 'AVERAGE',
FAST: 'FAST',
}

View File

@ -14,6 +14,7 @@ import {
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transactions'
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../helpers/constants/common'
import { hexToDecimal } from '../../helpers/utils/conversions.util'
import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs'
import TextField from '../../components/ui/text-field'
@ -171,7 +172,7 @@ export default class ConfirmTransactionBase extends Component {
}
}
if (customGas.gasLimit < 21000) {
if (hexToDecimal(customGas.gasLimit) < 21000) {
return {
valid: false,
errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,

View File

@ -12,7 +12,7 @@ import classnames from 'classnames'
// init
import FirstTimeFlow from '../first-time-flow'
// accounts
const SendTransactionScreen = require('../send/send.container')
import SendTransactionScreen from '../send'
const ConfirmTransaction = require('../confirm-transaction')
// slideout menu

View File

@ -27,7 +27,7 @@ export default class SendFooter extends Component {
unapprovedTxs: PropTypes.object,
update: PropTypes.func,
sendErrors: PropTypes.object,
gasChangedLabel: PropTypes.string,
gasEstimateType: PropTypes.string,
}
static contextTypes = {
@ -58,7 +58,7 @@ export default class SendFooter extends Component {
update,
toAccounts,
history,
gasChangedLabel,
gasEstimateType,
} = this.props
const { metricsEvent } = this.context
@ -94,7 +94,7 @@ export default class SendFooter extends Component {
name: 'Complete',
},
customVariables: {
gasChanged: gasChangedLabel,
gasChanged: gasEstimateType,
},
})
history.push(CONFIRM_TRANSACTION_ROUTE)
@ -102,9 +102,10 @@ export default class SendFooter extends Component {
}
formShouldBeDisabled () {
const { data, inError, selectedToken, tokenBalance, gasTotal, to } = this.props
const { data, inError, selectedToken, tokenBalance, gasTotal, to, gasLimit } = this.props
const missingTokenBalance = selectedToken && !tokenBalance
const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to)
const gasLimitTooLow = gasLimit < 5208 // 5208 is hex value of 21000, minimum gas limit
const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to) || gasLimitTooLow
return shouldBeDisabled
}

View File

@ -42,8 +42,8 @@ function mapStateToProps (state) {
const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
const gasPrice = getGasPrice(state)
const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
const gasChangedLabel = activeButtonIndex >= 0
? gasButtonInfo[activeButtonIndex].labelKey
const gasEstimateType = activeButtonIndex >= 0
? gasButtonInfo[activeButtonIndex].gasEstimateType
: 'custom'
return {
@ -61,7 +61,7 @@ function mapStateToProps (state) {
tokenBalance: getTokenBalance(state),
unapprovedTxs: getUnapprovedTxs(state),
sendErrors: getSendErrors(state),
gasChangedLabel,
gasEstimateType,
}
}

View File

@ -48,7 +48,7 @@ proxyquire('../send-footer.container.js', {
'./send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` },
'./send-footer.utils': utilsStubs,
'../../../selectors/custom-gas': {
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ labelKey: `mockLabel:${s}` }]),
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ gasEstimateType: `mockGasEstimateType:${s}` }]),
getDefaultActiveButtonIndex: () => 0,
},
})
@ -73,7 +73,7 @@ describe('send-footer container', () => {
tokenBalance: 'mockTokenBalance:mockState',
unapprovedTxs: 'mockUnapprovedTxs:mockState',
sendErrors: 'mockSendErrors:mockState',
gasChangedLabel: 'mockLabel:mockState',
gasEstimateType: 'mockGasEstimateType:mockState',
})
})

View File

@ -62,11 +62,6 @@ import {
SEND_ROUTE,
} from '../../helpers/constants/routes'
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SendEther)
function mapStateToProps (state) {
return {
amount: getSendAmount(state),
@ -140,3 +135,8 @@ function mapDispatchToProps (dispatch) {
},
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SendEther)

View File

@ -21,6 +21,8 @@ import {
} from '../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
import { GAS_ESTIMATE_TYPES } from '../helpers/constants/common'
const selectors = {
formatTimeEstimate,
getAveragePriceEstimateInHexWEI,
@ -250,7 +252,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
return [
{
labelKey: 'slow',
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
feeInPrimaryCurrency: getRenderableEthFee(safeLow, gasLimit),
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate)
@ -259,7 +261,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
priceInHexWei: getGasPriceInHexWei(safeLow),
},
{
labelKey: 'average',
gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE,
feeInPrimaryCurrency: getRenderableEthFee(average, gasLimit),
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate)
@ -268,7 +270,7 @@ function getRenderableBasicEstimateData (state, gasLimit) {
priceInHexWei: getGasPriceInHexWei(average),
},
{
labelKey: 'fast',
gasEstimateType: GAS_ESTIMATE_TYPES.FAST,
feeInPrimaryCurrency: getRenderableEthFee(fast, gasLimit),
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate)
@ -302,7 +304,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
return [
{
labelKey: 'slow',
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate)
: '',
@ -310,7 +312,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
priceInHexWei: getGasPriceInHexWei(safeLow, true),
},
{
labelKey: 'average',
gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE,
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate)
: '',
@ -318,7 +320,7 @@ function getRenderableEstimateDataForSmallButtonsFromGWEI (state) {
priceInHexWei: getGasPriceInHexWei(average, true),
},
{
labelKey: 'fast',
gasEstimateType: GAS_ESTIMATE_TYPES.FAST,
feeInSecondaryCurrency: showFiat
? getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate)
: '',

View File

@ -77,21 +77,21 @@ describe('custom-gas selectors', () => {
{
expectedResult: [
{
labelKey: 'slow',
gasEstimateType: 'SLOW',
feeInSecondaryCurrency: '$0.01',
feeInPrimaryCurrency: '0.0000525 ETH',
timeEstimate: '~6 min 36 sec',
priceInHexWei: '0x9502f900',
},
{
labelKey: 'average',
gasEstimateType: 'AVERAGE',
feeInPrimaryCurrency: '0.000084 ETH',
feeInSecondaryCurrency: '$0.02',
priceInHexWei: '0xee6b2800',
timeEstimate: '~5 min 18 sec',
},
{
labelKey: 'fast',
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '$0.03',
feeInPrimaryCurrency: '0.000105 ETH',
timeEstimate: '~3 min 18 sec',
@ -127,7 +127,7 @@ describe('custom-gas selectors', () => {
{
expectedResult: [
{
labelKey: 'slow',
gasEstimateType: 'SLOW',
feeInSecondaryCurrency: '$0.27',
feeInPrimaryCurrency: '0.000105 ETH',
timeEstimate: '~13 min 12 sec',
@ -136,12 +136,12 @@ describe('custom-gas selectors', () => {
{
feeInPrimaryCurrency: '0.000147 ETH',
feeInSecondaryCurrency: '$0.38',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x1a13b8600',
timeEstimate: '~10 min 6 sec',
},
{
labelKey: 'fast',
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '$0.54',
feeInPrimaryCurrency: '0.00021 ETH',
timeEstimate: '~6 min 36 sec',
@ -180,21 +180,21 @@ describe('custom-gas selectors', () => {
{
expectedResult: [
{
labelKey: 'slow',
gasEstimateType: 'SLOW',
feeInSecondaryCurrency: '',
feeInPrimaryCurrency: '0.000105 ETH',
timeEstimate: '~13 min 12 sec',
priceInHexWei: '0x12a05f200',
},
{
labelKey: 'average',
gasEstimateType: 'AVERAGE',
feeInPrimaryCurrency: '0.000147 ETH',
feeInSecondaryCurrency: '',
timeEstimate: '~10 min 6 sec',
priceInHexWei: '0x1a13b8600',
},
{
labelKey: 'fast',
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '',
feeInPrimaryCurrency: '0.00021 ETH',
timeEstimate: '~6 min 36 sec',
@ -233,21 +233,21 @@ describe('custom-gas selectors', () => {
{
expectedResult: [
{
labelKey: 'slow',
gasEstimateType: 'SLOW',
feeInSecondaryCurrency: '$0.27',
feeInPrimaryCurrency: '0.000105 ETH',
timeEstimate: '~13 min 12 sec',
priceInHexWei: '0x12a05f200',
},
{
labelKey: 'average',
gasEstimateType: 'AVERAGE',
feeInPrimaryCurrency: '0.000147 ETH',
feeInSecondaryCurrency: '$0.38',
priceInHexWei: '0x1a13b8600',
timeEstimate: '~10 min 6 sec',
},
{
labelKey: 'fast',
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '$0.54',
feeInPrimaryCurrency: '0.00021 ETH',
timeEstimate: '~6 min 36 sec',
@ -286,21 +286,21 @@ describe('custom-gas selectors', () => {
{
expectedResult: [
{
labelKey: 'slow',
gasEstimateType: 'SLOW',
feeInSecondaryCurrency: '$0.27',
feeInPrimaryCurrency: '0.000105 ETH',
timeEstimate: '~13 min 12 sec',
priceInHexWei: '0x12a05f200',
},
{
labelKey: 'average',
gasEstimateType: 'AVERAGE',
feeInPrimaryCurrency: '0.000147 ETH',
feeInSecondaryCurrency: '$0.38',
priceInHexWei: '0x1a13b8600',
timeEstimate: '~10 min 6 sec',
},
{
labelKey: 'fast',
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '$0.54',
feeInPrimaryCurrency: '0.00021 ETH',
timeEstimate: '~6 min 36 sec',
@ -355,19 +355,19 @@ describe('custom-gas selectors', () => {
{
feeInSecondaryCurrency: '$0.13',
feeInPrimaryCurrency: '0.00052 ETH',
labelKey: 'slow',
gasEstimateType: 'SLOW',
priceInHexWei: '0x5d21dba00',
},
{
feeInSecondaryCurrency: '$0.16',
feeInPrimaryCurrency: '0.00063 ETH',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x6fc23ac00',
},
{
feeInSecondaryCurrency: '$0.27',
feeInPrimaryCurrency: '0.00105 ETH',
labelKey: 'fast',
gasEstimateType: 'FAST',
priceInHexWei: '0xba43b7400',
},
],
@ -405,19 +405,19 @@ describe('custom-gas selectors', () => {
{
feeInSecondaryCurrency: '$2.68',
feeInPrimaryCurrency: '0.00105 ETH',
labelKey: 'slow',
gasEstimateType: 'SLOW',
priceInHexWei: '0xba43b7400',
},
{
feeInSecondaryCurrency: '$4.03',
feeInPrimaryCurrency: '0.00157 ETH',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x1176592e00',
},
{
feeInSecondaryCurrency: '$5.37',
feeInPrimaryCurrency: '0.0021 ETH',
labelKey: 'fast',
gasEstimateType: 'FAST',
priceInHexWei: '0x174876e800',
},
],
@ -455,19 +455,19 @@ describe('custom-gas selectors', () => {
{
feeInSecondaryCurrency: '',
feeInPrimaryCurrency: '0.00105 ETH',
labelKey: 'slow',
gasEstimateType: 'SLOW',
priceInHexWei: '0xba43b7400',
},
{
feeInSecondaryCurrency: '',
feeInPrimaryCurrency: '0.00157 ETH',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x1176592e00',
},
{
feeInSecondaryCurrency: '',
feeInPrimaryCurrency: '0.0021 ETH',
labelKey: 'fast',
gasEstimateType: 'FAST',
priceInHexWei: '0x174876e800',
},
],
@ -505,19 +505,19 @@ describe('custom-gas selectors', () => {
{
feeInSecondaryCurrency: '$2.68',
feeInPrimaryCurrency: '0.00105 ETH',
labelKey: 'slow',
gasEstimateType: 'SLOW',
priceInHexWei: '0xba43b7400',
},
{
feeInSecondaryCurrency: '$4.03',
feeInPrimaryCurrency: '0.00157 ETH',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x1176592e00',
},
{
feeInSecondaryCurrency: '$5.37',
feeInPrimaryCurrency: '0.0021 ETH',
labelKey: 'fast',
gasEstimateType: 'FAST',
priceInHexWei: '0x174876e800',
},
],
@ -555,19 +555,19 @@ describe('custom-gas selectors', () => {
{
feeInSecondaryCurrency: '$2.68',
feeInPrimaryCurrency: '0.00105 ETH',
labelKey: 'slow',
gasEstimateType: 'SLOW',
priceInHexWei: '0xba43b7400',
},
{
feeInSecondaryCurrency: '$4.03',
feeInPrimaryCurrency: '0.00157 ETH',
labelKey: 'average',
gasEstimateType: 'AVERAGE',
priceInHexWei: '0x1176592e00',
},
{
feeInSecondaryCurrency: '$5.37',
feeInPrimaryCurrency: '0.0021 ETH',
labelKey: 'fast',
gasEstimateType: 'FAST',
priceInHexWei: '0x174876e800',
},
],