From bc386e936a16ad06831909ce16ddb366d1a610b7 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 3 Jan 2017 12:31:52 +0100 Subject: [PATCH 1/5] AddContract properly binds newError --- js/src/modals/AddContract/addContract.js | 22 ++++++++- js/src/modals/AddContract/addContract.spec.js | 47 +++++++++++++++---- js/src/modals/AddContract/addContract.test.js | 13 ++++- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index c5e7aac6170..20e7d0d082f 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -17,6 +17,8 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { newError } from '~/redux/actions'; import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; @@ -25,13 +27,14 @@ import { AddIcon, CancelIcon, NextIcon, PrevIcon } from '~/ui/Icons'; import Store from './store'; @observer -export default class AddContract extends Component { +class AddContract extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { contracts: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, onClose: PropTypes.func }; @@ -244,7 +247,7 @@ export default class AddContract extends Component { this.onClose(); }) .catch((error) => { - newError(error); + this.props.newError(error); }); } @@ -252,3 +255,18 @@ export default class AddContract extends Component { this.props.onClose(); } } + +function mapStateToProps (state) { + return {}; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + newError + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(AddContract); diff --git a/js/src/modals/AddContract/addContract.spec.js b/js/src/modals/AddContract/addContract.spec.js index d65e06f6785..3e0bdf527c7 100644 --- a/js/src/modals/AddContract/addContract.spec.js +++ b/js/src/modals/AddContract/addContract.spec.js @@ -20,24 +20,27 @@ import sinon from 'sinon'; import AddContract from './'; -import { CONTRACTS, createApi } from './addContract.test.js'; +import { CONTRACTS, createApi, createRedux } from './addContract.test.js'; +let api; let component; +let instance; let onClose; +let reduxStore; -function renderShallow (props) { +function render (props = {}) { + api = createApi(); onClose = sinon.stub(); + reduxStore = createRedux(); + component = shallow( , - { - context: { - api: createApi() - } - } - ); + { context: { store: reduxStore } } + ).find('AddContract').shallow({ context: { api } }); + instance = component.instance(); return component; } @@ -45,11 +48,37 @@ function renderShallow (props) { describe('modals/AddContract', () => { describe('rendering', () => { beforeEach(() => { - renderShallow(); + render(); }); it('renders the defauls', () => { expect(component).to.be.ok; }); }); + + describe('onAdd', () => { + it('calls store addContract', () => { + sinon.stub(instance.store, 'addContract').resolves(true); + return instance.onAdd().then(() => { + expect(instance.store.addContract).to.have.been.called; + instance.store.addContract.restore(); + }); + }); + + it('calls closes dialog on success', () => { + sinon.stub(instance.store, 'addContract').resolves(true); + return instance.onAdd().then(() => { + expect(onClose).to.have.been.called; + instance.store.addContract.restore(); + }); + }); + + it('adds newError on failure', () => { + sinon.stub(instance.store, 'addContract').rejects('test'); + return instance.onAdd().then(() => { + expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' }); + instance.store.addContract.restore(); + }); + }); + }); }); diff --git a/js/src/modals/AddContract/addContract.test.js b/js/src/modals/AddContract/addContract.test.js index 5dc2af29308..250d9895c01 100644 --- a/js/src/modals/AddContract/addContract.test.js +++ b/js/src/modals/AddContract/addContract.test.js @@ -31,8 +31,19 @@ function createApi () { }; } +function createRedux () { + return { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return {}; + } + }; +} + export { ABI, CONTRACTS, - createApi + createApi, + createRedux }; From fe22775fa0449ea8ceac34ef1785915d92116a17 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 3 Jan 2017 12:41:45 +0100 Subject: [PATCH 2/5] EditMeta properly binds newError --- js/src/modals/EditMeta/editMeta.js | 26 ++++++++++++++++-- js/src/modals/EditMeta/editMeta.spec.js | 36 ++++++++++++++++++------- js/src/modals/EditMeta/editMeta.test.js | 13 ++++++++- js/src/modals/EditMeta/store.js | 4 +-- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index 8b5e14939c8..516882458e8 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -17,20 +17,24 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { newError } from '~/redux/actions'; import { Button, Form, Input, InputChip, Modal } from '~/ui'; import { CancelIcon, SaveIcon } from '~/ui/Icons'; import Store from './store'; @observer -export default class EditMeta extends Component { +class EditMeta extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { account: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } @@ -138,6 +142,24 @@ export default class EditMeta extends Component { return this.store .save() - .then(() => this.props.onClose()); + .then(() => this.props.onClose()) + .catch((error) => { + this.props.newError(error); + }); } } + +function mapStateToProps (state) { + return {}; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + newError + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditMeta); diff --git a/js/src/modals/EditMeta/editMeta.spec.js b/js/src/modals/EditMeta/editMeta.spec.js index 917f3207637..e6dac1f23b0 100644 --- a/js/src/modals/EditMeta/editMeta.spec.js +++ b/js/src/modals/EditMeta/editMeta.spec.js @@ -20,25 +20,27 @@ import sinon from 'sinon'; import EditMeta from './'; -import { ACCOUNT, createApi } from './editMeta.test.js'; +import { ACCOUNT, createApi, createRedux } from './editMeta.test.js'; +let api; let component; +let instance; let onClose; +let reduxStore; function render (props) { + api = createApi(); onClose = sinon.stub(); + reduxStore = createRedux(); component = shallow( , - { - context: { - api: createApi() - } - } - ); + { context: { store: reduxStore } } + ).find('EditMeta').shallow({ context: { api } }); + instance = component.instance(); return component; } @@ -56,15 +58,29 @@ describe('modals/EditMeta', () => { }); describe('onSave', () => { - it('calls store.save() & props.onClose', () => { - const instance = component.instance(); + it('calls store.save', () => { sinon.spy(instance.store, 'save'); - instance.onSave().then(() => { + return instance.onSave().then(() => { expect(instance.store.save).to.have.been.called; + instance.store.save.restore(); + }); + }); + + it('closes the dialog on success', () => { + return instance.onSave().then(() => { expect(onClose).to.have.been.called; }); }); + + it('adds newError on failure', () => { + sinon.stub(instance.store, 'save').rejects('test'); + + return instance.onSave().then(() => { + expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' }); + instance.store.save.restore(); + }); + }); }); }); }); diff --git a/js/src/modals/EditMeta/editMeta.test.js b/js/src/modals/EditMeta/editMeta.test.js index 98bd61fea1c..369debe1f9a 100644 --- a/js/src/modals/EditMeta/editMeta.test.js +++ b/js/src/modals/EditMeta/editMeta.test.js @@ -50,8 +50,19 @@ function createApi () { }; } +function createRedux () { + return { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return {}; + } + }; +} + export { ACCOUNT, ADDRESS, - createApi + createApi, + createRedux }; diff --git a/js/src/modals/EditMeta/store.js b/js/src/modals/EditMeta/store.js index b2d71c63a45..58c5998defd 100644 --- a/js/src/modals/EditMeta/store.js +++ b/js/src/modals/EditMeta/store.js @@ -16,7 +16,6 @@ import { action, computed, observable, transaction } from 'mobx'; -import { newError } from '~/redux/actions'; import { validateName } from '~/util/validation'; export default class Store { @@ -92,8 +91,7 @@ export default class Store { ]) .catch((error) => { console.error('onSave', error); - - newError(error); + throw error; }); } } From ee1950e83cde8070a8818c04c2efc3eabbfc3ebd Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 3 Jan 2017 12:49:51 +0100 Subject: [PATCH 3/5] PasswordManager properly binds newError --- .../modals/PasswordManager/passwordManager.js | 24 ++++++++-- .../PasswordManager/passwordManager.spec.js | 47 ++++++++++++++----- .../PasswordManager/passwordManager.test.js | 13 ++++- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 0f7a08f279d..0e5fb86a954 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -19,6 +19,8 @@ import { Tabs, Tab } from 'material-ui/Tabs'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { newError, showSnackbar } from '~/redux/actions'; import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; @@ -42,13 +44,14 @@ const TABS_ITEM_STYLE = { }; @observer -export default class PasswordManager extends Component { +class PasswordManager extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { account: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, onClose: PropTypes.func } @@ -347,7 +350,7 @@ export default class PasswordManager extends Component { } }) .catch((error) => { - newError(error); + this.props.newError(error); }); } @@ -355,7 +358,22 @@ export default class PasswordManager extends Component { return this.store .testPassword() .catch((error) => { - newError(error); + this.props.newError(error); }); } } + +function mapStateToProps (state) { + return {}; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + newError + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(PasswordManager); diff --git a/js/src/modals/PasswordManager/passwordManager.spec.js b/js/src/modals/PasswordManager/passwordManager.spec.js index 744d0ffa37a..66c129a4427 100644 --- a/js/src/modals/PasswordManager/passwordManager.spec.js +++ b/js/src/modals/PasswordManager/passwordManager.spec.js @@ -20,25 +20,25 @@ import sinon from 'sinon'; import PasswordManager from './'; -import { ACCOUNT, createApi } from './passwordManager.test.js'; +import { ACCOUNT, createApi, createRedux } from './passwordManager.test.js'; let component; +let instance; let onClose; +let reduxStore; function render (props) { onClose = sinon.stub(); + reduxStore = createRedux(); component = shallow( , - { - context: { - api: createApi() - } - } - ); + { context: { store: reduxStore } } + ).find('PasswordManager').shallow({ context: { api: createApi() } }); + instance = component.instance(); return component; } @@ -56,24 +56,47 @@ describe('modals/PasswordManager', () => { }); describe('changePassword', () => { - it('calls store.changePassword & props.onClose', () => { - const instance = component.instance(); + it('calls store.changePassword', () => { sinon.spy(instance.store, 'changePassword'); - instance.changePassword().then(() => { + return instance.changePassword().then(() => { expect(instance.store.changePassword).to.have.been.called; + instance.store.changePassword.restore(); + }); + }); + + it('closes the dialog on success', () => { + return instance.changePassword().then(() => { expect(onClose).to.have.been.called; }); }); + + it('adds newError on failure', () => { + sinon.stub(instance.store, 'changePassword').rejects('test'); + + return instance.changePassword().then(() => { + expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' }); + instance.store.changePassword.restore(); + }); + }); }); describe('testPassword', () => { it('calls store.testPassword', () => { - const instance = component.instance(); sinon.spy(instance.store, 'testPassword'); - instance.testPassword().then(() => { + return instance.testPassword().then(() => { expect(instance.store.testPassword).to.have.been.called; + instance.store.testPassword.restore(); + }); + }); + + it('adds newError on failure', () => { + sinon.stub(instance.store, 'testPassword').rejects('test'); + + return instance.testPassword().then(() => { + expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' }); + instance.store.testPassword.restore(); }); }); }); diff --git a/js/src/modals/PasswordManager/passwordManager.test.js b/js/src/modals/PasswordManager/passwordManager.test.js index 9a8f883ecf8..409756fde06 100644 --- a/js/src/modals/PasswordManager/passwordManager.test.js +++ b/js/src/modals/PasswordManager/passwordManager.test.js @@ -37,7 +37,18 @@ function createApi (result = true) { }; } +function createRedux () { + return { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return {}; + } + }; +} + export { ACCOUNT, - createApi + createApi, + createRedux }; From f0cc5a37745bb370897e069d629107ca6a43a255 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 3 Jan 2017 13:29:34 +0100 Subject: [PATCH 4/5] pass null instead of empty mapStateToProps --- js/src/modals/AddContract/addContract.js | 6 +----- js/src/modals/EditMeta/editMeta.js | 6 +----- js/src/modals/PasswordManager/passwordManager.js | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 20e7d0d082f..49d3a41434c 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -256,10 +256,6 @@ class AddContract extends Component { } } -function mapStateToProps (state) { - return {}; -} - function mapDispatchToProps (dispatch) { return bindActionCreators({ newError @@ -267,6 +263,6 @@ function mapDispatchToProps (dispatch) { } export default connect( - mapStateToProps, + null, mapDispatchToProps )(AddContract); diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index 516882458e8..a232492ea7e 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -149,10 +149,6 @@ class EditMeta extends Component { } } -function mapStateToProps (state) { - return {}; -} - function mapDispatchToProps (dispatch) { return bindActionCreators({ newError @@ -160,6 +156,6 @@ function mapDispatchToProps (dispatch) { } export default connect( - mapStateToProps, + null, mapDispatchToProps )(EditMeta); diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 0e5fb86a954..ac84766729b 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -363,10 +363,6 @@ class PasswordManager extends Component { } } -function mapStateToProps (state) { - return {}; -} - function mapDispatchToProps (dispatch) { return bindActionCreators({ newError @@ -374,6 +370,6 @@ function mapDispatchToProps (dispatch) { } export default connect( - mapStateToProps, + null, mapDispatchToProps )(PasswordManager); From 5c460ed4f62ce3b358b6972998da0852f99acb3b Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 3 Jan 2017 14:57:11 +0100 Subject: [PATCH 5/5] Add openSnackbar test & binded prop --- js/src/modals/PasswordManager/passwordManager.js | 6 ++++-- js/src/modals/PasswordManager/passwordManager.spec.js | 6 ++++++ js/src/redux/actions.js | 3 ++- js/src/redux/providers/snackbarActions.js | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index ac84766729b..9bd6c08df0e 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { newError, showSnackbar } from '~/redux/actions'; +import { newError, openSnackbar } from '~/redux/actions'; import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; import Form, { Input } from '~/ui/Form'; import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons'; @@ -51,6 +51,7 @@ class PasswordManager extends Component { static propTypes = { account: PropTypes.object.isRequired, + openSnackbar: PropTypes.func.isRequired, newError: PropTypes.func.isRequired, onClose: PropTypes.func } @@ -339,7 +340,7 @@ class PasswordManager extends Component { .changePassword() .then((result) => { if (result) { - showSnackbar( + this.props.openSnackbar(
{ }); }); + it('shows snackbar on success', () => { + return instance.changePassword().then(() => { + expect(reduxStore.dispatch).to.have.been.calledWithMatch({ type: 'openSnackbar' }); + }); + }); + it('adds newError on failure', () => { sinon.stub(instance.store, 'changePassword').rejects('test'); diff --git a/js/src/redux/actions.js b/js/src/redux/actions.js index 0618adfd3bb..773ade5fcbe 100644 --- a/js/src/redux/actions.js +++ b/js/src/redux/actions.js @@ -16,7 +16,7 @@ import { newError } from '~/ui/Errors/actions'; import { setAddressImage } from './providers/imagesActions'; -import { showSnackbar } from './providers/snackbarActions'; +import { openSnackbar, showSnackbar } from './providers/snackbarActions'; import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions'; import { toggleView } from '~/views/Settings/actions'; @@ -24,6 +24,7 @@ export { newError, clearStatusLogs, setAddressImage, + openSnackbar, showSnackbar, toggleStatusLogs, toggleStatusRefresh, diff --git a/js/src/redux/providers/snackbarActions.js b/js/src/redux/providers/snackbarActions.js index 0048854da3d..98dcf554f2b 100644 --- a/js/src/redux/providers/snackbarActions.js +++ b/js/src/redux/providers/snackbarActions.js @@ -20,7 +20,7 @@ export function showSnackbar (message, cooldown) { }; } -function openSnackbar (message, cooldown) { +export function openSnackbar (message, cooldown) { return { type: 'openSnackbar', message, cooldown