import assert from 'assert' import React from 'react' import proxyquire from 'proxyquire' import { shallow } from 'enzyme' import sinon from 'sinon' import timeout from '../../../../lib/test-timeout' import AddRecipient from '../send-content/add-recipient/add-recipient.container' import SendHeader from '../send-header/send-header.container' import SendContent from '../send-content/send-content.container' import SendFooter from '../send-footer/send-footer.container' describe('Send Component', function () { let wrapper const mockBasicGasEstimates = { blockTime: 'mockBlockTime', } const propsMethodSpies = { updateAndSetGasLimit: sinon.spy(), updateSendErrors: sinon.spy(), updateSendTokenBalance: sinon.spy(), resetSendState: sinon.spy(), fetchBasicGasEstimates: sinon .stub() .returns(Promise.resolve(mockBasicGasEstimates)), fetchGasEstimates: sinon.spy(), updateToNicknameIfNecessary: sinon.spy(), } const utilsMethodStubs = { getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }), getGasFeeErrorObject: sinon.stub().returns({ gasFee: 'mockGasFeeError' }), doesAmountErrorRequireUpdate: sinon .stub() .callsFake((obj) => obj.balance !== obj.prevBalance), } const SendTransactionScreen = proxyquire('../send.component.js', { './send.utils': utilsMethodStubs, }).default before(function () { sinon.spy(SendTransactionScreen.prototype, 'componentDidMount') sinon.spy(SendTransactionScreen.prototype, 'updateGas') }) beforeEach(function () { wrapper = shallow( undefined} scanQrCode={() => undefined} updateSendEnsResolution={() => undefined} updateSendEnsResolutionError={() => undefined} updateSendErrors={propsMethodSpies.updateSendErrors} updateSendTo={() => undefined} updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance} resetSendState={propsMethodSpies.resetSendState} updateToNicknameIfNecessary={ propsMethodSpies.updateToNicknameIfNecessary } />, ) }) afterEach(function () { SendTransactionScreen.prototype.componentDidMount.resetHistory() SendTransactionScreen.prototype.updateGas.resetHistory() utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory() utilsMethodStubs.getAmountErrorObject.resetHistory() utilsMethodStubs.getGasFeeErrorObject.resetHistory() propsMethodSpies.fetchBasicGasEstimates.resetHistory() propsMethodSpies.updateAndSetGasLimit.resetHistory() propsMethodSpies.updateSendErrors.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() propsMethodSpies.updateToNicknameIfNecessary.resetHistory() }) after(function () { sinon.restore() }) it('should call componentDidMount', function () { assert(SendTransactionScreen.prototype.componentDidMount.calledOnce) }) describe('componentDidMount', function () { it('should call props.fetchBasicGasAndTimeEstimates', function () { propsMethodSpies.fetchBasicGasEstimates.resetHistory() assert.strictEqual(propsMethodSpies.fetchBasicGasEstimates.callCount, 0) wrapper.instance().componentDidMount() assert.strictEqual(propsMethodSpies.fetchBasicGasEstimates.callCount, 1) }) it('should call this.updateGas', async function () { SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateSendErrors.resetHistory() assert.strictEqual(SendTransactionScreen.prototype.updateGas.callCount, 0) wrapper.instance().componentDidMount() await timeout(250) assert.strictEqual(SendTransactionScreen.prototype.updateGas.callCount, 1) }) }) describe('componentWillUnmount', function () { it('should call this.props.resetSendState', function () { propsMethodSpies.resetSendState.resetHistory() assert.strictEqual(propsMethodSpies.resetSendState.callCount, 0) wrapper.instance().componentWillUnmount() assert.strictEqual(propsMethodSpies.resetSendState.callCount, 1) }) }) describe('componentDidUpdate', function () { it('should call doesAmountErrorRequireUpdate with the expected params', function () { utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: '', }, }) assert(utilsMethodStubs.doesAmountErrorRequireUpdate.calledTwice) assert.deepStrictEqual( utilsMethodStubs.doesAmountErrorRequireUpdate.getCall(0).args[0], { balance: 'mockBalance', gasTotal: 'mockGasTotal', prevBalance: '', prevGasTotal: undefined, prevTokenBalance: undefined, sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST', }, tokenBalance: 'mockTokenBalance', }, ) }) it('should not call getAmountErrorObject if doesAmountErrorRequireUpdate returns false', function () { utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'mockBalance', }, }) assert.strictEqual(utilsMethodStubs.getAmountErrorObject.callCount, 0) }) it('should call getAmountErrorObject if doesAmountErrorRequireUpdate returns true', function () { utilsMethodStubs.getAmountErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, }) assert.strictEqual(utilsMethodStubs.getAmountErrorObject.callCount, 1) assert.deepStrictEqual( utilsMethodStubs.getAmountErrorObject.getCall(0).args[0], { amount: 'mockAmount', balance: 'mockBalance', conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST', }, tokenBalance: 'mockTokenBalance', }, ) }) it('should call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true and sendToken is truthy', function () { utilsMethodStubs.getGasFeeErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, }) assert.strictEqual(utilsMethodStubs.getGasFeeErrorObject.callCount, 1) assert.deepStrictEqual( utilsMethodStubs.getGasFeeErrorObject.getCall(0).args[0], { balance: 'mockBalance', conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST', }, }, ) }) it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns false', function () { utilsMethodStubs.getGasFeeErrorObject.resetHistory() wrapper.instance().componentDidUpdate({ from: { address: 'mockAddress', balance: 'mockBalance' }, }) assert.strictEqual(utilsMethodStubs.getGasFeeErrorObject.callCount, 0) }) it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true but sendToken is falsy', function () { utilsMethodStubs.getGasFeeErrorObject.resetHistory() wrapper.setProps({ sendToken: null }) wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, }) assert.strictEqual(utilsMethodStubs.getGasFeeErrorObject.callCount, 0) }) it('should call updateSendErrors with the expected params if sendToken is falsy', function () { propsMethodSpies.updateSendErrors.resetHistory() wrapper.setProps({ sendToken: null }) wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, }) assert.strictEqual(propsMethodSpies.updateSendErrors.callCount, 1) assert.deepStrictEqual( propsMethodSpies.updateSendErrors.getCall(0).args[0], { amount: 'mockAmountError', gasFee: null, }, ) }) it('should call updateSendErrors with the expected params if sendToken is truthy', function () { propsMethodSpies.updateSendErrors.resetHistory() wrapper.setProps({ sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, }) wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, }) assert.strictEqual(propsMethodSpies.updateSendErrors.callCount, 1) assert.deepStrictEqual( propsMethodSpies.updateSendErrors.getCall(0).args[0], { amount: 'mockAmountError', gasFee: 'mockGasFeeError', }, ) }) it('should not call updateSendTokenBalance or this.updateGas if network === prevNetwork', function () { SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, network: '3', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.strictEqual(SendTransactionScreen.prototype.updateGas.callCount, 0) }) it('should not call updateSendTokenBalance or this.updateGas if network === loading', function () { wrapper.setProps({ network: 'loading' }) SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, network: '3', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.strictEqual(SendTransactionScreen.prototype.updateGas.callCount, 0) }) it('should call updateSendTokenBalance and this.updateGas with the correct params', function () { SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', }, network: '2', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.strictEqual(propsMethodSpies.updateSendTokenBalance.callCount, 1) assert.deepStrictEqual( propsMethodSpies.updateSendTokenBalance.getCall(0).args[0], { sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST', }, // Make sure not to hit updateGas when changing asset tokenContract: { method: 'mockTokenMethod' }, address: 'mockAddress', }, ) assert.strictEqual(SendTransactionScreen.prototype.updateGas.callCount, 1) assert.deepStrictEqual( SendTransactionScreen.prototype.updateGas.getCall(0).args, [], ) }) it('should call updateGas when sendToken.address is changed', function () { SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateAndSetGasLimit.resetHistory() wrapper.instance().componentDidUpdate({ from: { balance: 'balancedChanged', }, network: '3', // Make sure not to hit updateGas when changing network sendToken: { address: 'newSelectedToken' }, }) assert.strictEqual( propsMethodSpies.updateToNicknameIfNecessary.callCount, 0, ) // Network did not change assert.strictEqual(propsMethodSpies.updateAndSetGasLimit.callCount, 1) }) }) describe('updateGas', function () { it('should call updateAndSetGasLimit with the correct params if no to prop is passed', function () { propsMethodSpies.updateAndSetGasLimit.resetHistory() wrapper.instance().updateGas() assert.strictEqual(propsMethodSpies.updateAndSetGasLimit.callCount, 1) assert.deepStrictEqual( propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0], { blockGasLimit: 'mockBlockGasLimit', editingTransactionId: 'mockEditingTransactionId', gasLimit: 'mockGasLimit', gasPrice: 'mockGasPrice', selectedAddress: 'mockSelectedAddress', sendToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST', }, to: '', value: 'mockAmount', data: undefined, }, ) }) it('should call updateAndSetGasLimit with the correct params if a to prop is passed', function () { propsMethodSpies.updateAndSetGasLimit.resetHistory() wrapper.setProps({ to: 'someAddress' }) wrapper.instance().updateGas() assert.strictEqual( propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0].to, 'someaddress', ) }) it('should call updateAndSetGasLimit with to set to lowercase if passed', function () { propsMethodSpies.updateAndSetGasLimit.resetHistory() wrapper.instance().updateGas({ to: '0xABC' }) assert.strictEqual( propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0].to, '0xabc', ) }) }) describe('render', function () { it('should render a page-container class', function () { assert.strictEqual(wrapper.find('.page-container').length, 1) }) it('should render SendHeader and AddRecipient', function () { assert.strictEqual(wrapper.find(SendHeader).length, 1) assert.strictEqual(wrapper.find(AddRecipient).length, 1) }) it('should pass the history prop to SendHeader and SendFooter', function () { wrapper.setProps({ to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', }) assert.strictEqual(wrapper.find(SendHeader).length, 1) assert.strictEqual(wrapper.find(SendContent).length, 1) assert.strictEqual(wrapper.find(SendFooter).length, 1) assert.deepStrictEqual(wrapper.find(SendFooter).props(), { history: { mockProp: 'history-abc' }, }) }) it('should pass showHexData to SendContent', function () { wrapper.setProps({ to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', }) assert.strictEqual(wrapper.find(SendContent).props().showHexData, true) }) }) describe('validate when input change', function () { let clock beforeEach(function () { clock = sinon.useFakeTimers() }) afterEach(function () { clock.restore() }) it('should validate when input changes', function () { const instance = wrapper.instance() instance.onRecipientInputChange( '0x80F061544cC398520615B5d3e7A3BedD70cd4510', ) assert.deepStrictEqual(instance.state, { internalSearch: false, query: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', toError: null, toWarning: null, }) }) it('should validate when input changes and has error', function () { const instance = wrapper.instance() instance.onRecipientInputChange( '0x80F061544cC398520615B5d3e7a3BedD70cd4510', ) clock.tick(1001) assert.deepStrictEqual(instance.state, { internalSearch: false, query: '0x80F061544cC398520615B5d3e7a3BedD70cd4510', toError: 'invalidAddressRecipient', toWarning: null, }) }) it('should validate when input changes and has error on a bad network', function () { wrapper.setProps({ network: 'bad' }) const instance = wrapper.instance() instance.onRecipientInputChange( '0x80F061544cC398520615B5d3e7a3BedD70cd4510', ) clock.tick(1001) assert.deepStrictEqual(instance.state, { internalSearch: false, query: '0x80F061544cC398520615B5d3e7a3BedD70cd4510', toError: 'invalidAddressRecipientNotEthNetwork', toWarning: null, }) }) it('should synchronously validate when input changes to ""', function () { wrapper.setProps({ network: 'bad' }) const instance = wrapper.instance() instance.onRecipientInputChange( '0x80F061544cC398520615B5d3e7a3BedD70cd4510', ) clock.tick(1001) assert.deepStrictEqual(instance.state, { internalSearch: false, query: '0x80F061544cC398520615B5d3e7a3BedD70cd4510', toError: 'invalidAddressRecipientNotEthNetwork', toWarning: null, }) instance.onRecipientInputChange('') assert.deepStrictEqual(instance.state, { internalSearch: false, query: '', toError: '', toWarning: '', }) }) it('should warn when send to a known token contract address', function () { wrapper.setProps({ address: '0x888', decimals: 18, symbol: '888' }) const instance = wrapper.instance() instance.onRecipientInputChange( '0x13cb85823f78Cff38f0B0E90D3e975b8CB3AAd64', ) clock.tick(1001) assert.deepStrictEqual(instance.state, { internalSearch: false, query: '0x13cb85823f78Cff38f0B0E90D3e975b8CB3AAd64', toError: null, toWarning: 'knownAddressRecipient', }) }) }) })