diff options
Diffstat (limited to 'ui/app/pages/send/send-content/send-amount-row/amount-max-button')
9 files changed, 390 insertions, 0 deletions
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js new file mode 100644 index 000000000..7901ccef6 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +export default class AmountMaxButton extends Component { + + static propTypes = { + balance: PropTypes.string, + buttonDataLoading: PropTypes.bool, + clearMaxAmount: PropTypes.func, + inError: PropTypes.bool, + gasTotal: PropTypes.string, + maxModeOn: PropTypes.bool, + selectedToken: PropTypes.object, + setAmountToMax: PropTypes.func, + setMaxModeTo: PropTypes.func, + tokenBalance: PropTypes.string, + + } + + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + } + + setMaxAmount () { + const { + balance, + gasTotal, + selectedToken, + setAmountToMax, + tokenBalance, + } = this.props + + setAmountToMax({ + balance, + gasTotal, + selectedToken, + tokenBalance, + }) + } + + onMaxClick = () => { + const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props + const { metricsEvent } = this.context + + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Clicked "Amount Max"', + }, + }) + if (!maxModeOn) { + setMaxModeTo(true) + this.setMaxAmount() + } else { + setMaxModeTo(false) + clearMaxAmount() + } + } + + render () { + const { maxModeOn, buttonDataLoading, inError } = this.props + + return ( + <div className={'send-v2__amount-max'} onClick={buttonDataLoading || inError ? null : this.onMaxClick}> + <input type="checkbox" checked={maxModeOn} /> + <div className={classnames('send-v2__amount-max__button', { 'send-v2__amount-max__button__disabled': buttonDataLoading || inError })}> + {this.context.t('max')} + </div> + </div> + ) + } +} diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js new file mode 100644 index 000000000..e444589a1 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -0,0 +1,45 @@ +import { connect } from 'react-redux' +import { + getGasTotal, + getSelectedToken, + getSendFromBalance, + getTokenBalance, +} from '../../../send.selectors.js' +import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors/custom-gas' +import { getMaxModeOn } from './amount-max-button.selectors.js' +import { calcMaxAmount } from './amount-max-button.utils.js' +import { + updateSendAmount, + setMaxModeTo, +} from '../../../../../store/actions' +import AmountMaxButton from './amount-max-button.component' +import { + updateSendErrors, +} from '../../../../../ducks/send/send.duck' + +export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) + +function mapStateToProps (state) { + + return { + balance: getSendFromBalance(state), + buttonDataLoading: getBasicGasEstimateLoadingStatus(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))) + }, + clearMaxAmount: () => { + dispatch(updateSendAmount('0')) + }, + setMaxModeTo: bool => dispatch(setMaxModeTo(bool)), + } +} diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js new file mode 100644 index 000000000..69fec1994 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js @@ -0,0 +1,9 @@ +const selectors = { + getMaxModeOn, +} + +module.exports = selectors + +function getMaxModeOn (state) { + return state.metamask.send.maxModeOn +} diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js new file mode 100644 index 000000000..a570e49b4 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js @@ -0,0 +1,29 @@ +const { + multiplyCurrencies, + subtractCurrencies, +} = require('../../../../../helpers/utils/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', + multiplicandBase: 16, + } + ) + : subtractCurrencies( + ethUtil.addHexPrefix(balance), + ethUtil.addHexPrefix(gasTotal), + { toNumericBase: 'hex' } + ) +} + +module.exports = { + calcMaxAmount, +} diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js new file mode 100644 index 000000000..ee8271494 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js @@ -0,0 +1 @@ +export { default } from './amount-max-button.container' diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js new file mode 100644 index 000000000..f986b26bb --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js @@ -0,0 +1,89 @@ +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', + metricsEvent: () => {}, + }, + }) + 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 checkbox is checked', () => { + 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 render the expected text when maxModeOn is false', () => { + wrapper.setProps({ maxModeOn: false }) + assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t') + }) + }) +}) diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js new file mode 100644 index 000000000..dcee8fda0 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js @@ -0,0 +1,93 @@ +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 }, + '../../../../../selectors/custom-gas': { getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`}, + '../../../../../store/actions': actionSpies, + '../../../../../ducks/send/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', + buttonDataLoading: 'mockButtonDataLoading: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' + ) + }) + }) + + }) + +}) diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js new file mode 100644 index 000000000..655fe1969 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js @@ -0,0 +1,22 @@ +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) + }) + }) + +}) diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js new file mode 100644 index 000000000..1ee858f67 --- /dev/null +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js @@ -0,0 +1,27 @@ +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: '64', + }), 'e8d4a51000') + }) + }) + +}) |