diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js
index c5e7aac6170..49d3a41434c 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,14 @@ export default class AddContract extends Component {
this.props.onClose();
}
}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ 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
};
diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js
index 8b5e14939c8..a232492ea7e 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,20 @@ 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 mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ 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;
});
}
}
diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js
index 0f7a08f279d..9bd6c08df0e 100644
--- a/js/src/modals/PasswordManager/passwordManager.js
+++ b/js/src/modals/PasswordManager/passwordManager.js
@@ -19,8 +19,10 @@ 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 { 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';
@@ -42,13 +44,15 @@ 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,
+ openSnackbar: PropTypes.func.isRequired,
+ newError: PropTypes.func.isRequired,
onClose: PropTypes.func
}
@@ -336,7 +340,7 @@ export default class PasswordManager extends Component {
.changePassword()
.then((result) => {
if (result) {
- showSnackbar(
+ this.props.openSnackbar(
{
- newError(error);
+ this.props.newError(error);
});
}
@@ -355,7 +359,19 @@ export default class PasswordManager extends Component {
return this.store
.testPassword()
.catch((error) => {
- newError(error);
+ this.props.newError(error);
});
}
}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ openSnackbar,
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(PasswordManager);
diff --git a/js/src/modals/PasswordManager/passwordManager.spec.js b/js/src/modals/PasswordManager/passwordManager.spec.js
index 744d0ffa37a..bdb8828dd14 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,53 @@ 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('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');
+
+ 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
};
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