From cdda52d799ea9edeb135de44e02fecc9d6d8e94b Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 20 Jul 2018 10:25:45 -0700 Subject: Add ButtonGroup component --- .../button-group/button-group.component.js | 61 ++++++++++++++++++++++ .../button-group/button-group.stories.js | 49 +++++++++++++++++ ui/app/components/button-group/index.js | 1 + ui/app/components/button-group/index.scss | 38 ++++++++++++++ ui/app/components/index.scss | 2 + 5 files changed, 151 insertions(+) create mode 100644 ui/app/components/button-group/button-group.component.js create mode 100644 ui/app/components/button-group/button-group.stories.js create mode 100644 ui/app/components/button-group/index.js create mode 100644 ui/app/components/button-group/index.scss (limited to 'ui/app/components') diff --git a/ui/app/components/button-group/button-group.component.js b/ui/app/components/button-group/button-group.component.js new file mode 100644 index 000000000..f99f710ce --- /dev/null +++ b/ui/app/components/button-group/button-group.component.js @@ -0,0 +1,61 @@ +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 && ( + + ) + }) + } + + render () { + const { className, style } = this.props + + return ( +
+ { this.renderButtons() } +
+ ) + } +} diff --git a/ui/app/components/button-group/button-group.stories.js b/ui/app/components/button-group/button-group.stories.js new file mode 100644 index 000000000..14e1a7e49 --- /dev/null +++ b/ui/app/components/button-group/button-group.stories.js @@ -0,0 +1,49 @@ +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', () => + + + + + + ) + .add('with a disabled Button', () => + + + + + ) diff --git a/ui/app/components/button-group/index.js b/ui/app/components/button-group/index.js new file mode 100644 index 000000000..df470bd57 --- /dev/null +++ b/ui/app/components/button-group/index.js @@ -0,0 +1 @@ +export { default } from './button-group.component' diff --git a/ui/app/components/button-group/index.scss b/ui/app/components/button-group/index.scss new file mode 100644 index 000000000..29713c75b --- /dev/null +++ b/ui/app/components/button-group/index.scss @@ -0,0 +1,38 @@ +.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; + } + } +} \ No newline at end of file diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 32f0e90e4..b3e14ce23 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -1,3 +1,5 @@ +@import './button-group/index'; + @import './export-text-container/index'; @import './selected-account/index'; -- cgit v1.2.3 From a146e5ebbcdf3ff334d2a5fb3b7aba1dcac9ab12 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 23 Jul 2018 16:49:28 -0230 Subject: Adds tests for button group component. --- .../tests/button-group-component.test.js | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 ui/app/components/button-group/tests/button-group-component.test.js (limited to 'ui/app/components') diff --git a/ui/app/components/button-group/tests/button-group-component.test.js b/ui/app/components/button-group/tests/button-group-component.test.js new file mode 100644 index 000000000..f07bb97c8 --- /dev/null +++ b/ui/app/components/button-group/tests/button-group-component.test.js @@ -0,0 +1,97 @@ +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 = [ + , + , + , +] + +describe('ButtonGroup Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow({mockButtons}) + }) + + 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) + }) + }) +}) -- cgit v1.2.3 From 2359062b62cf65f38b36ccb6bb33fa7d15ada1ae Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 23 Jul 2018 22:20:06 -0230 Subject: UI confirm screen closes confirmation window on submit or cancel of a tx --- .../confirm-transaction-base.component.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index e1bf2210f..2811e6157 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -8,6 +8,9 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, } from '../../../constants/error-keys' +import { + ENVIRONMENT_TYPE_NOTIFICATION, +} from '../../../../../app/scripts/lib/enums' export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -250,8 +253,12 @@ export default class ConfirmTransactionBase extends Component { } else { cancelTransaction(txData) .then(() => { - clearConfirmTransaction() - history.push(DEFAULT_ROUTE) + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { + return global.platform.closeCurrentWindow() + } else { + clearConfirmTransaction() + history.push(DEFAULT_ROUTE) + } }) } } @@ -264,8 +271,12 @@ export default class ConfirmTransactionBase extends Component { } else { sendTransaction(txData) .then(() => { - clearConfirmTransaction() - history.push(DEFAULT_ROUTE) + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { + return global.platform.closeCurrentWindow() + } else { + clearConfirmTransaction() + history.push(DEFAULT_ROUTE) + } }) } } -- cgit v1.2.3 From 152246f3b0862c8c5a8b42872852d236400c0e9d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 23 Jul 2018 20:25:04 -0700 Subject: Add close window support to signature requests. Move logic to actions --- .../confirm-transaction-base.component.js | 19 ++++--------------- ui/app/components/pages/home.js | 12 ++++++------ 2 files changed, 10 insertions(+), 21 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index 2811e6157..e1bf2210f 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -8,9 +8,6 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, } from '../../../constants/error-keys' -import { - ENVIRONMENT_TYPE_NOTIFICATION, -} from '../../../../../app/scripts/lib/enums' export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -253,12 +250,8 @@ export default class ConfirmTransactionBase extends Component { } else { cancelTransaction(txData) .then(() => { - if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { - return global.platform.closeCurrentWindow() - } else { - clearConfirmTransaction() - history.push(DEFAULT_ROUTE) - } + clearConfirmTransaction() + history.push(DEFAULT_ROUTE) }) } } @@ -271,12 +264,8 @@ export default class ConfirmTransactionBase extends Component { } else { sendTransaction(txData) .then(() => { - if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { - return global.platform.closeCurrentWindow() - } else { - clearConfirmTransaction() - history.push(DEFAULT_ROUTE) - } + clearConfirmTransaction() + history.push(DEFAULT_ROUTE) }) } } diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 38aa02dae..5e3fdc9af 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -27,19 +27,17 @@ const { NOTICE_ROUTE, } = require('../../routes') +const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction') + class Home extends Component { componentDidMount () { const { history, - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, + unconfirmedTransactionsCount = 0, } = this.props // unapprovedTxs and unapproved messages - if (Object.keys(unapprovedTxs).length || - unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } } @@ -167,6 +165,7 @@ Home.propTypes = { isPopup: PropTypes.bool, isMouseUser: PropTypes.bool, t: PropTypes.func, + unconfirmedTransactionsCount: PropTypes.number, } function mapStateToProps (state) { @@ -230,6 +229,7 @@ function mapStateToProps (state) { // state needed to get account dropdown temporarily rendering from app bar selected, + unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), } } -- cgit v1.2.3 From 9ca4b66c82ab9ec46c6599778df7b661e927edfa Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Wed, 25 Jul 2018 11:57:41 -0700 Subject: Add tests for Confirm Detail Row --- .../tests/confirm-detail-row.component.test.js | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js (limited to 'ui/app/components') diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js new file mode 100644 index 000000000..334ec4497 --- /dev/null +++ b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -0,0 +1,64 @@ +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() + }) + + 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 fiatText as a child of the confirm-detail-row__fiat', () => { + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatFee') + }) + + it('should render the ethText as a child of the confirm-detail-row__eth', () => { + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), '♦ mockEthFee') + }) + + it('should set the fiatTextColor on confirm-detail-row__fiat', () => { + assert.equal(wrapper.find('.confirm-detail-row__fiat').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)) + }) + + + }) +}) -- cgit v1.2.3 From 6eadf5b22b9ffac4fe41be0e690e95f825e700ea Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Wed, 25 Jul 2018 16:26:30 -0700 Subject: Update prop names --- .../tests/confirm-detail-row.component.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js index 334ec4497..fb5cff1c1 100644 --- a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js +++ b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -8,7 +8,7 @@ const propsMethodSpies = { onHeaderClick: sinon.spy(), } -describe('Confirm Detail Row Component', function () { +describe.only('Confirm Detail Row Component', function () { let wrapper beforeEach(() => { @@ -16,16 +16,16 @@ describe('Confirm Detail Row Component', function () { errorType={'mockErrorType'} label={'mockLabel'} showError={false} - fiatFee = {'mockFiatFee'} - ethFee = {'mockEthFee'} - fiatFeeColor= {'mockColor'} + fiatText = {'mockFiatText'} + ethText = {'mockEthText'} + fiatTextColor= {'mockColor'} onHeaderClick= {propsMethodSpies.onHeaderClick} headerText = {'mockHeaderText'} headerTextClassName = {'mockHeaderClass'} />) }) - describe('render', () => { + describe.only('render', () => { it('should render a div with a confirm-detail-row class', () => { assert.equal(wrapper.find('div.confirm-detail-row').length, 1) }) @@ -39,11 +39,11 @@ describe('Confirm Detail Row Component', function () { }) it('should render the fiatText as a child of the confirm-detail-row__fiat', () => { - assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatFee') + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText') }) it('should render the ethText as a child of the confirm-detail-row__eth', () => { - assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), '♦ mockEthFee') + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText') }) it('should set the fiatTextColor on confirm-detail-row__fiat', () => { -- cgit v1.2.3 From 9934690bd976b74cadd3211ab492022d1ff00a20 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Wed, 25 Jul 2018 16:31:33 -0700 Subject: lint fix --- .../confirm-detail-row/tests/confirm-detail-row.component.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js index fb5cff1c1..6f2489071 100644 --- a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js +++ b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -8,7 +8,7 @@ const propsMethodSpies = { onHeaderClick: sinon.spy(), } -describe.only('Confirm Detail Row Component', function () { +describe('Confirm Detail Row Component', function () { let wrapper beforeEach(() => { @@ -25,7 +25,7 @@ describe.only('Confirm Detail Row Component', function () { />) }) - describe.only('render', () => { + describe('render', () => { it('should render a div with a confirm-detail-row class', () => { assert.equal(wrapper.find('div.confirm-detail-row').length, 1) }) -- cgit v1.2.3 From c8d45cb4a8a671bad20cd5a5aa1bcbbd88463bdb Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 25 Jul 2018 20:02:12 -0400 Subject: only show retry button on earliest pending tx --- ui/app/components/tx-list-item.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'ui/app/components') diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 0d693b805..92466568c 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -216,11 +216,17 @@ TxListItem.prototype.showRetryButton = function () { const currentNonce = txParams.nonce const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const currentSubmittedTxs = selectedAddressTxList.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transactionId + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.id < tx2.id) return tx1 + return tx2 + }) + const currentTxIsLatest = lastTx.id === transactionId - return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 + return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 && currentTxIsLatest } TxListItem.prototype.setSelectedToken = function (tokenAddress) { -- cgit v1.2.3 From b580f60d749b862fe08adfc7457c726905ebc623 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 25 Jul 2018 20:58:20 -0400 Subject: earliest tx by submittedTime --- ui/app/components/tx-list-item.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 92466568c..1a639d0b9 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -213,6 +213,7 @@ TxListItem.prototype.showRetryButton = function () { if (!txParams) { return false } + let currentTxIsLatest = false const currentNonce = txParams.nonce const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') @@ -220,11 +221,13 @@ TxListItem.prototype.showRetryButton = function () { const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transactionId - const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { - if (tx1.id < tx2.id) return tx1 - return tx2 - }) - const currentTxIsLatest = lastTx.id === transactionId + if (currentSubmittedTxs.length > 0) { + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.submittedTime < tx2.submittedTime) return tx1 + return tx2 + }) + currentTxIsLatest = lastTx.id === transactionId + } return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 && currentTxIsLatest } -- cgit v1.2.3