diff --git a/js/src/modals/CreateAccount/ChangeVault/changeVault.js b/js/src/modals/CreateAccount/ChangeVault/changeVault.js
new file mode 100644
index 000000000..566fa402c
--- /dev/null
+++ b/js/src/modals/CreateAccount/ChangeVault/changeVault.js
@@ -0,0 +1,51 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { observer } from 'mobx-react';
+import React, { Component, PropTypes } from 'react';
+
+import { VaultSelect } from '~/ui';
+
+@observer
+export default class ChangeVault extends Component {
+ static propTypes = {
+ store: PropTypes.object.isRequired,
+ vaultStore: PropTypes.object
+ }
+
+ render () {
+ const { store, vaultStore } = this.props;
+ const { vaultName } = store;
+
+ if (!vaultStore || vaultStore.vaultsOpened.length === 0) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+
+ onSelect = (vaultName) => {
+ const { store } = this.props;
+
+ store.setVaultName(vaultName);
+ }
+}
diff --git a/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js b/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js
new file mode 100644
index 000000000..a2fcb834b
--- /dev/null
+++ b/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js
@@ -0,0 +1,100 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import ChangeVault from './';
+
+let component;
+let instance;
+let store;
+let vaultStore;
+
+function createStore () {
+ store = {
+ setVaultName: sinon.stub(),
+ vaultName: 'testing'
+ };
+
+ return store;
+}
+
+function createVaultStore () {
+ vaultStore = {
+ vaultsOpened: ['testing']
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ component = shallow(
+
+ );
+ instance = component.instance();
+
+ return component;
+}
+
+describe('modals/CreateAccount/ChangeVault', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('components', () => {
+ describe('VaultSelect', () => {
+ let select;
+
+ beforeEach(() => {
+ select = component.find('VaultSelect');
+ });
+
+ it('renders', () => {
+ expect(select.get(0)).to.be.ok;
+ });
+
+ it('passes onSelect as instance method', () => {
+ expect(select.props().onSelect).to.equal(instance.onSelect);
+ });
+
+ it('passes the value', () => {
+ expect(select.props().value).to.equal('testing');
+ });
+
+ it('passes the vaultStore', () => {
+ expect(select.props().vaultStore).to.equal(vaultStore);
+ });
+ });
+ });
+
+ describe('instance methods', () => {
+ describe('onSelect', () => {
+ it('calls into store setVaultName', () => {
+ instance.onSelect('newName');
+ expect(store.setVaultName).to.have.been.calledWith('newName');
+ });
+ });
+ });
+});
diff --git a/js/src/modals/CreateAccount/ChangeVault/index.js b/js/src/modals/CreateAccount/ChangeVault/index.js
new file mode 100644
index 000000000..5eac8b21d
--- /dev/null
+++ b/js/src/modals/CreateAccount/ChangeVault/index.js
@@ -0,0 +1,17 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+export default from './changeVault';
diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js
index 3bf34b27e..e0a2f8ba2 100644
--- a/js/src/modals/CreateAccount/NewAccount/newAccount.js
+++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js
@@ -24,13 +24,15 @@ import { Form, Input, IdentityIcon } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength';
import { RefreshIcon } from '~/ui/Icons';
+import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css';
@observer
export default class CreateAccount extends Component {
static propTypes = {
newError: PropTypes.func.isRequired,
- store: PropTypes.object.isRequired
+ store: PropTypes.object.isRequired,
+ vaultStore: PropTypes.object
}
state = {
@@ -123,6 +125,10 @@ export default class CreateAccount extends Component {
+
{ this.renderIdentitySelector() }
{ this.renderIdentities() }
diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js
index 121f0be57..e3d888c3f 100644
--- a/js/src/modals/CreateAccount/NewImport/newImport.js
+++ b/js/src/modals/CreateAccount/NewImport/newImport.js
@@ -20,12 +20,15 @@ import { FormattedMessage } from 'react-intl';
import { Form, FileSelect, Input } from '~/ui';
+import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css';
@observer
export default class NewImport extends Component {
static propTypes = {
- store: PropTypes.object.isRequired
+ store: PropTypes.object.isRequired,
+ vaultStore: PropTypes.object
+
}
render () {
@@ -88,6 +91,10 @@ export default class NewImport extends Component {
/>
+
{ this.renderFileSelector() }
);
diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js
index ad96064bd..7f31cb066 100644
--- a/js/src/modals/CreateAccount/RawKey/rawKey.js
+++ b/js/src/modals/CreateAccount/RawKey/rawKey.js
@@ -21,6 +21,7 @@ import { FormattedMessage } from 'react-intl';
import { Form, Input } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength';
+import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css';
@observer
@@ -30,7 +31,8 @@ export default class RawKey extends Component {
}
static propTypes = {
- store: PropTypes.object.isRequired
+ store: PropTypes.object.isRequired,
+ vaultStore: PropTypes.object
}
render () {
@@ -131,6 +133,10 @@ export default class RawKey extends Component {
+
);
}
diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
index 894daa767..1e49f821f 100644
--- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
+++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js
@@ -22,12 +22,14 @@ import { Checkbox } from 'material-ui';
import { Form, Input } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength';
+import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css';
@observer
export default class RecoveryPhrase extends Component {
static propTypes = {
- store: PropTypes.object.isRequired
+ store: PropTypes.object.isRequired,
+ vaultStore: PropTypes.object
}
render () {
@@ -127,6 +129,10 @@ export default class RecoveryPhrase extends Component {
+
);
}
@@ -132,18 +141,27 @@ class CreateAccount extends Component {
if (createType === 'fromPhrase') {
return (
-
+
);
}
if (createType === 'fromRaw') {
return (
-
+
);
}
return (
-
+
);
case STAGE_INFO:
@@ -266,7 +284,7 @@ class CreateAccount extends Component {
this.store.setBusy(true);
return this.store
- .createAccount()
+ .createAccount(this.vaultStore)
.then(() => {
this.store.setBusy(false);
this.store.nextStage();
diff --git a/js/src/modals/CreateAccount/createAccount.test.js b/js/src/modals/CreateAccount/createAccount.test.js
index d66729aec..d4ba5a0a4 100644
--- a/js/src/modals/CreateAccount/createAccount.test.js
+++ b/js/src/modals/CreateAccount/createAccount.test.js
@@ -42,7 +42,9 @@ function createApi () {
newAccountFromWallet: sinon.stub().resolves(ADDRESS),
phraseToAddress: () => Promise.resolve(`${++counter}`),
setAccountMeta: sinon.stub().resolves(),
- setAccountName: sinon.stub().resolves()
+ setAccountName: sinon.stub().resolves(),
+ listVaults: sinon.stub().resolves([]),
+ listOpenedVaults: sinon.stub().resolves([])
}
};
}
diff --git a/js/src/modals/CreateAccount/store.js b/js/src/modals/CreateAccount/store.js
index 7371e8df3..c76102d5b 100644
--- a/js/src/modals/CreateAccount/store.js
+++ b/js/src/modals/CreateAccount/store.js
@@ -44,6 +44,7 @@ export default class Store {
@observable rawKey = '';
@observable rawKeyError = ERRORS.nokey;
@observable stage = STAGE_SELECT_TYPE;
+ @observable vaultName = '';
@observable walletFile = '';
@observable walletFileError = ERRORS.noFile;
@observable walletJson = '';
@@ -95,6 +96,7 @@ export default class Store {
this.nameError = null;
this.rawKey = '';
this.rawKeyError = null;
+ this.vaultName = '';
this.walletFile = '';
this.walletFileError = null;
this.walletJson = '';
@@ -134,6 +136,10 @@ export default class Store {
this.gethImported = gethImported;
}
+ @action setVaultName = (vaultName) => {
+ this.vaultName = vaultName;
+ }
+
@action setWindowsPhrase = (isWindowsPhrase = false) => {
this.isWindowsPhrase = isWindowsPhrase;
}
@@ -220,7 +226,28 @@ export default class Store {
this.stage--;
}
- createAccount = () => {
+ createAccount = (vaultStore) => {
+ this.setBusy(true);
+
+ return this
+ ._createAccount()
+ .then(() => {
+ if (vaultStore && this.vaultName && this.vaultName.length) {
+ return vaultStore.moveAccount(this.vaultName, this.address);
+ }
+
+ return true;
+ })
+ .then(() => {
+ this.setBusy(false);
+ })
+ .catch((error) => {
+ this.setBusy(false);
+ throw error;
+ });
+ }
+
+ _createAccount = () => {
switch (this.createType) {
case 'fromGeth':
return this.createAccountFromGeth();
diff --git a/js/src/modals/CreateAccount/store.spec.js b/js/src/modals/CreateAccount/store.spec.js
index 67303fa21..833cb7ef5 100644
--- a/js/src/modals/CreateAccount/store.spec.js
+++ b/js/src/modals/CreateAccount/store.spec.js
@@ -22,8 +22,20 @@ import { ACCOUNTS, ADDRESS, GETH_ADDRESSES, createApi } from './createAccount.te
let api;
let store;
+let vaultStore;
+
+function createVaultStore () {
+ vaultStore = {
+ moveAccount: sinon.stub().resolves(),
+ listVaults: sinon.stub().resolves()
+ };
+
+ return vaultStore;
+}
function createStore (loadGeth) {
+ createVaultStore();
+
api = createApi();
store = new Store(api, ACCOUNTS, loadGeth);
@@ -65,8 +77,9 @@ describe('modals/CreateAccount/Store', () => {
describe('@action', () => {
describe('clearErrors', () => {
beforeEach(() => {
- store.setName('');
- store.setPassword('123');
+ store.setName('testing');
+ store.setPassword('testing');
+ store.setVaultName('testing');
store.setRawKey('test');
store.setWalletFile('test');
store.setWalletJson('test');
@@ -75,10 +88,13 @@ describe('modals/CreateAccount/Store', () => {
it('clears all errors', () => {
store.clearErrors();
+ expect(store.name).to.equal('');
expect(store.nameError).to.be.null;
+ expect(store.password).to.equal('');
expect(store.passwordRepeatError).to.be.null;
expect(store.rawKey).to.equal('');
expect(store.rawKeyError).to.be.null;
+ expect(store.vaultName).to.equal('');
expect(store.walletFile).to.equal('');
expect(store.walletFileError).to.be.null;
expect(store.walletJson).to.equal('');
@@ -198,6 +214,13 @@ describe('modals/CreateAccount/Store', () => {
});
});
+ describe('setVaultName', () => {
+ it('sets the vault name', () => {
+ store.setVaultName('testVault');
+ expect(store.vaultName).to.equal('testVault');
+ });
+ });
+
describe('setWalletFile', () => {
it('sets the filepath', () => {
store.setWalletFile('testing');
@@ -384,12 +407,22 @@ describe('modals/CreateAccount/Store', () => {
let createAccountFromWalletSpy;
let createAccountFromPhraseSpy;
let createAccountFromRawSpy;
+ let busySpy;
beforeEach(() => {
createAccountFromGethSpy = sinon.spy(store, 'createAccountFromGeth');
createAccountFromWalletSpy = sinon.spy(store, 'createAccountFromWallet');
createAccountFromPhraseSpy = sinon.spy(store, 'createAccountFromPhrase');
createAccountFromRawSpy = sinon.spy(store, 'createAccountFromRaw');
+ busySpy = sinon.spy(store, 'setBusy');
+ });
+
+ afterEach(() => {
+ store.createAccountFromGeth.restore();
+ store.createAccountFromWallet.restore();
+ store.createAccountFromPhrase.restore();
+ store.createAccountFromRaw.restore();
+ store.setBusy.restore();
});
it('throws error on invalid createType', () => {
@@ -399,38 +432,68 @@ describe('modals/CreateAccount/Store', () => {
it('calls createAccountFromGeth on createType === fromGeth', () => {
store.setCreateType('fromGeth');
- store.createAccount();
- expect(createAccountFromGethSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromGethSpy).to.have.been.called;
+ });
});
it('calls createAccountFromWallet on createType === fromJSON', () => {
store.setCreateType('fromJSON');
- store.createAccount();
- expect(createAccountFromWalletSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromWalletSpy).to.have.been.called;
+ });
});
it('calls createAccountFromPhrase on createType === fromNew', () => {
store.setCreateType('fromNew');
- store.createAccount();
- expect(createAccountFromPhraseSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromPhraseSpy).to.have.been.called;
+ });
});
it('calls createAccountFromPhrase on createType === fromPhrase', () => {
store.setCreateType('fromPhrase');
- store.createAccount();
- expect(createAccountFromPhraseSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromPhraseSpy).to.have.been.called;
+ });
});
it('calls createAccountFromWallet on createType === fromPresale', () => {
store.setCreateType('fromPresale');
- store.createAccount();
- expect(createAccountFromWalletSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromWalletSpy).to.have.been.called;
+ });
});
it('calls createAccountFromRaw on createType === fromRaw', () => {
store.setCreateType('fromRaw');
- store.createAccount();
- expect(createAccountFromRawSpy).to.have.been.called;
+
+ return store.createAccount().then(() => {
+ expect(createAccountFromRawSpy).to.have.been.called;
+ });
+ });
+
+ it('moves account to vault when vaultName set', () => {
+ store.setCreateType('fromNew');
+ store.setVaultName('testing');
+
+ return store.createAccount(vaultStore).then(() => {
+ expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ADDRESS);
+ });
+ });
+
+ it('sets and rests the busy flag', () => {
+ store.setCreateType('fromNew');
+
+ return store.createAccount().then(() => {
+ expect(busySpy).to.have.been.calledWith(true);
+ expect(busySpy).to.have.been.calledWith(false);
+ });
});
describe('createAccountFromGeth', () => {
diff --git a/js/src/modals/DeleteAccount/deleteAccount.js b/js/src/modals/DeleteAccount/deleteAccount.js
index 8f47777c5..cd69d6bd9 100644
--- a/js/src/modals/DeleteAccount/deleteAccount.js
+++ b/js/src/modals/DeleteAccount/deleteAccount.js
@@ -37,25 +37,27 @@ class DeleteAccount extends Component {
}
state = {
+ isBusy: false,
password: ''
}
render () {
const { account } = this.props;
- const { password } = this.state;
+ const { isBusy, password } = this.state;
return (
}
- visible
>
{
+ this.setState({ isBusy: true });
+
if (result === true) {
router.push('/accounts');
this.closeDeleteDialog();
@@ -128,6 +134,7 @@ class DeleteAccount extends Component {
}
})
.catch((error) => {
+ this.setState({ isBusy: false });
console.error('onDeleteConfirmed', error);
newError(new Error(`Deletion failed: ${error.message}`));
});
diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js
index 71e222ca6..5d0c91dbe 100644
--- a/js/src/modals/EditMeta/editMeta.js
+++ b/js/src/modals/EditMeta/editMeta.js
@@ -21,11 +21,10 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { newError } from '~/redux/actions';
-import { Button, Form, Input, InputAddress, InputChip, Portal } from '~/ui';
+import { Button, Form, Input, InputChip, Portal, VaultSelect } from '~/ui';
import { CancelIcon, SaveIcon } from '~/ui/Icons';
import VaultStore from '~/views/Vaults/store';
-import VaultSelector from '../VaultSelector';
import Store from './store';
@observer
@@ -48,11 +47,12 @@ class EditMeta extends Component {
}
render () {
- const { description, name, nameError, tags } = this.store;
+ const { description, isBusy, name, nameError, tags } = this.store;
return (
}
>
- { this.renderVaultSelector() }
);
@@ -163,7 +162,7 @@ class EditMeta extends Component {
);
}
- renderVault () {
+ renderVaultSelector () {
const { isAccount, vaultName } = this.store;
if (!isAccount) {
@@ -171,40 +170,9 @@ class EditMeta extends Component {
}
return (
-
- }
- label={
-
- }
- onClick={ this.toggleVaultSelector }
- value={ vaultName }
- />
- );
- }
-
- renderVaultSelector () {
- const { isAccount, isVaultSelectorOpen, vaultName } = this.store;
-
- if (!isAccount || !isVaultSelectorOpen) {
- return null;
- }
-
- return (
-
);
@@ -215,21 +183,12 @@ class EditMeta extends Component {
}
onSave = () => {
- const { address, isAccount, meta, vaultName } = this.store;
-
if (this.store.hasError) {
return;
}
return this.store
- .save()
- .then(() => {
- if (isAccount && (meta.vault !== vaultName)) {
- return this.vaultStore.moveAccount(vaultName, address);
- }
-
- return true;
- })
+ .save(this.vaultStore)
.then(this.onClose)
.catch((error) => {
this.props.newError(error);
@@ -238,11 +197,6 @@ class EditMeta extends Component {
setVaultName = (vaultName) => {
this.store.setVaultName(vaultName);
- this.toggleVaultSelector();
- }
-
- toggleVaultSelector = () => {
- this.store.toggleVaultSelector();
}
}
diff --git a/js/src/modals/EditMeta/store.js b/js/src/modals/EditMeta/store.js
index 46951f095..da3d88cd7 100644
--- a/js/src/modals/EditMeta/store.js
+++ b/js/src/modals/EditMeta/store.js
@@ -21,7 +21,7 @@ import { validateName } from '~/util/validation';
export default class Store {
@observable address = null;
@observable isAccount = false;
- @observable isVaultSelectorOpen = false;
+ @observable isBusy = false;
@observable description = null;
@observable meta = null;
@observable name = null;
@@ -73,6 +73,10 @@ export default class Store {
this.passwordHint = passwordHint;
}
+ @action setBusy = (isBusy) => {
+ this.isBusy = isBusy;
+ }
+
@action setTags = (tags) => {
this.tags = tags.slice();
}
@@ -81,11 +85,9 @@ export default class Store {
this.vaultName = vaultName;
}
- @action setVaultSelectorOpen = (isOpen) => {
- this.isVaultSelectorOpen = isOpen;
- }
+ save (vaultStore) {
+ this.setBusy(true);
- save () {
const meta = {
description: this.description,
tags: this.tags.peek()
@@ -100,13 +102,20 @@ export default class Store {
this._api.parity.setAccountName(this.address, this.name),
this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta))
])
+ .then(() => {
+ if (vaultStore && this.isAccount && (this.meta.vault !== this.vaultName)) {
+ return vaultStore.moveAccount(this.vaultName, this.address);
+ }
+
+ return true;
+ })
+ .then(() => {
+ this.setBusy(false);
+ })
.catch((error) => {
console.error('onSave', error);
+ this.setBusy(false);
throw error;
});
}
-
- toggleVaultSelector () {
- this.setVaultSelectorOpen(!this.isVaultSelectorOpen);
- }
}
diff --git a/js/src/modals/EditMeta/store.spec.js b/js/src/modals/EditMeta/store.spec.js
index 4ff775718..a38da055f 100644
--- a/js/src/modals/EditMeta/store.spec.js
+++ b/js/src/modals/EditMeta/store.spec.js
@@ -14,14 +14,24 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import sinon from 'sinon';
+
import Store from './store';
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
let api;
let store;
+let vaultStore;
+
+function createVaultStore () {
+ return {
+ moveAccount: sinon.stub().resolves(true)
+ };
+}
function createStore (account) {
api = createApi();
+ vaultStore = createVaultStore();
store = new Store(api, account);
@@ -108,6 +118,13 @@ describe('modals/EditMeta/Store', () => {
createStore(ADDRESS);
});
+ describe('setBusy', () => {
+ it('sets the isBusy flag', () => {
+ store.setBusy('testing');
+ expect(store.isBusy).to.equal('testing');
+ });
+ });
+
describe('setDescription', () => {
it('sets the description', () => {
store.setDescription('description');
@@ -149,26 +166,56 @@ describe('modals/EditMeta/Store', () => {
expect(store.vaultName).to.equal('testing');
});
});
-
- describe('setVaultSelectorOpen', () => {
- it('sets the state', () => {
- store.setVaultSelectorOpen('testing');
- expect(store.isVaultSelectorOpen).to.equal('testing');
- });
- });
});
describe('operations', () => {
describe('save', () => {
beforeEach(() => {
createStore(ACCOUNT);
+ sinon.spy(store, 'setBusy');
+ });
+
+ afterEach(() => {
+ store.setBusy.restore();
+ });
+
+ it('sets the busy flag, clearing it when done', () => {
+ return store.save().then(() => {
+ expect(store.setBusy).to.have.been.calledWith(true);
+ expect(store.setBusy).to.have.been.calledWith(false);
+ });
});
it('calls parity.setAccountName with the set value', () => {
store.setName('test name');
- store.save();
- expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
+ return store.save().then(() => {
+ expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
+ });
+ });
+
+ it('calls parity.setAccountMeta with the adjusted values', () => {
+ store.setDescription('some new description');
+ store.setPasswordHint('some new passwordhint');
+ store.setTags(['taga']);
+
+ return store.save().then(() => {
+ expect(api.parity.setAccountMeta).to.have.been.calledWith(
+ ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
+ description: 'some new description',
+ passwordHint: 'some new passwordhint',
+ tags: ['taga']
+ })
+ );
+ });
+ });
+
+ it('moves vault account when applicable', () => {
+ store.setVaultName('testing');
+
+ return store.save(vaultStore).then(() => {
+ expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ACCOUNT.address);
+ });
});
it('calls parity.setAccountMeta with the adjusted values', () => {
@@ -185,11 +232,4 @@ describe('modals/EditMeta/Store', () => {
});
});
});
-
- describe('toggleVaultSelector', () => {
- it('inverts the selector state', () => {
- store.toggleVaultSelector();
- expect(store.isVaultSelectorOpen).to.be.true;
- });
- });
});
diff --git a/js/src/modals/VaultSelector/vaultSelector.js b/js/src/modals/VaultSelector/vaultSelector.js
index a2ae48294..4fceb5cb0 100644
--- a/js/src/modals/VaultSelector/vaultSelector.js
+++ b/js/src/modals/VaultSelector/vaultSelector.js
@@ -18,7 +18,9 @@ import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
-import { Portal, SelectionList, VaultCard } from '~/ui';
+import Portal from '~/ui/Portal';
+import SelectionList from '~/ui/SelectionList';
+import VaultCard from '~/ui/VaultCard';
@observer
export default class VaultSelector extends Component {
@@ -48,10 +50,9 @@ export default class VaultSelector extends Component {
}
renderList () {
- const { vaults } = this.props.vaultStore;
- const openVaults = vaults.filter((vault) => vault.isOpen);
+ const { vaultsOpened } = this.props.vaultStore;
- if (openVaults.length === 0) {
+ if (vaultsOpened.length === 0) {
return (
.
+
+export default from './vaultSelect';
diff --git a/js/src/ui/Form/VaultSelect/vaultSelect.js b/js/src/ui/Form/VaultSelect/vaultSelect.js
new file mode 100644
index 000000000..c93e530e1
--- /dev/null
+++ b/js/src/ui/Form/VaultSelect/vaultSelect.js
@@ -0,0 +1,109 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import React, { Component, PropTypes } from 'react';
+import { FormattedMessage } from 'react-intl';
+
+import VaultSelector from '~/modals/VaultSelector';
+import VaultStore from '~/views/Vaults/store';
+
+import InputAddress from '../InputAddress';
+
+export default class VaultSelect extends Component {
+ static contextTypes = {
+ api: PropTypes.object.isRequired
+ };
+
+ static propTypes = {
+ onSelect: PropTypes.func.isRequired,
+ value: PropTypes.string,
+ vaultStore: PropTypes.object
+ };
+
+ state = {
+ isOpen: false
+ };
+
+ vaultStore = this.props.vaultStore || VaultStore.get(this.context.api);
+
+ componentWillMount () {
+ return this.vaultStore.loadVaults();
+ }
+
+ render () {
+ const { value } = this.props;
+
+ return (
+
+ { this.renderSelector() }
+
+ }
+ label={
+
+ }
+ onClick={ this.openSelector }
+ value={ (value || '').toUpperCase() }
+ />
+
+ );
+ }
+
+ renderSelector () {
+ const { value } = this.props;
+ const { isOpen } = this.state;
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+
+ openSelector = () => {
+ this.setState({
+ isOpen: true
+ });
+ }
+
+ closeSelector = () => {
+ this.setState({
+ isOpen: false
+ });
+ }
+
+ onSelect = (vaultName) => {
+ this.props.onSelect(vaultName);
+ this.closeSelector();
+ }
+}
diff --git a/js/src/ui/Form/VaultSelect/vaultSelect.spec.js b/js/src/ui/Form/VaultSelect/vaultSelect.spec.js
new file mode 100644
index 000000000..a0d5ed583
--- /dev/null
+++ b/js/src/ui/Form/VaultSelect/vaultSelect.spec.js
@@ -0,0 +1,90 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import VaultSelect from './';
+
+let component;
+let instance;
+let onSelect;
+let vaultStore;
+
+function createVaultStore () {
+ vaultStore = {
+ loadVaults: sinon.stub().resolves(true)
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ onSelect = sinon.stub();
+
+ component = shallow(
+
+ );
+ instance = component.instance();
+
+ return component;
+}
+
+describe('ui/Form/VaultSelect', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('components', () => {
+ describe('InputAddress', () => {
+ let input;
+
+ beforeEach(() => {
+ input = component.find('Connect(InputAddress)');
+ });
+
+ it('renders', () => {
+ expect(input.get(0)).to.be.ok;
+ });
+
+ it('passes value from props', () => {
+ expect(input.props().value).to.equal('INITIALVALUE');
+ });
+
+ it('passes instance openSelector to onClick', () => {
+ expect(input.props().onClick).to.equal(instance.openSelector);
+ });
+ });
+ });
+
+ describe('instance methods', () => {
+ describe('onSelect', () => {
+ it('calls into props', () => {
+ instance.onSelect('testing');
+ expect(onSelect).to.have.been.calledWith('testing');
+ });
+ });
+ });
+});
diff --git a/js/src/ui/Form/index.js b/js/src/ui/Form/index.js
index bb5516c89..8c8c7e1f2 100644
--- a/js/src/ui/Form/index.js
+++ b/js/src/ui/Form/index.js
@@ -29,5 +29,6 @@ export Label from './Label';
export RadioButtons from './RadioButtons';
export Select from './Select';
export TypedInput from './TypedInput';
+export VaultSelect from './VaultSelect';
export default from './form';
diff --git a/js/src/ui/SelectionList/selectionList.css b/js/src/ui/SelectionList/selectionList.css
index 44f5a39b3..6a1a37eaf 100644
--- a/js/src/ui/SelectionList/selectionList.css
+++ b/js/src/ui/SelectionList/selectionList.css
@@ -17,6 +17,7 @@
.item {
border: 2px solid transparent;
+ cursor: pointer;
display: flex;
flex: 1;
height: 100%;
diff --git a/js/src/ui/VaultTag/index.js b/js/src/ui/VaultTag/index.js
new file mode 100644
index 000000000..af0419c99
--- /dev/null
+++ b/js/src/ui/VaultTag/index.js
@@ -0,0 +1,17 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+export default from './vaultTag';
diff --git a/js/src/ui/VaultTag/vaultTag.css b/js/src/ui/VaultTag/vaultTag.css
new file mode 100644
index 000000000..acb139c6e
--- /dev/null
+++ b/js/src/ui/VaultTag/vaultTag.css
@@ -0,0 +1,48 @@
+/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
+/* This file is part of Parity.
+/*
+/* Parity is free software: you can redistribute it and/or modify
+/* it under the terms of the GNU General Public License as published by
+/* the Free Software Foundation, either version 3 of the License, or
+/* (at your option) any later version.
+/*
+/* Parity is distributed in the hope that it will be useful,
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/* GNU General Public License for more details.
+/*
+/* You should have received a copy of the GNU General Public License
+/* along with Parity. If not, see .
+*/
+
+/* TODO: These tag styles are shared with Balances & Certifications - should be made into
+/* a component that can take a list of tags and render them in the correct format
+*/
+.vault {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0.75em 0 0;
+ vertical-align: top;
+
+ .vaultBody {
+ margin: 0.75em 0.5em 0 0;
+ background: rgba(255, 255, 255, 0.07);
+ border-radius: 16px;
+ max-height: 24px;
+ max-width: 100%;
+ display: flex;
+ align-items: center;
+ }
+
+ img {
+ height: 32px !important;
+ margin: -4px 1em 0 0;
+ width: 32px !important;
+ }
+
+ .text {
+ margin: 0 0.5em 0 0;
+ text-transform: uppercase;
+ white-space: nowrap;
+ }
+}
diff --git a/js/src/ui/VaultTag/vaultTag.js b/js/src/ui/VaultTag/vaultTag.js
new file mode 100644
index 000000000..303aaca61
--- /dev/null
+++ b/js/src/ui/VaultTag/vaultTag.js
@@ -0,0 +1,45 @@
+// Copyright 2015-2017 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import React, { Component, PropTypes } from 'react';
+
+import IdentityIcon from '~/ui/IdentityIcon';
+
+import styles from './vaultTag.css';
+
+export default class VaultTag extends Component {
+ static propTypes = {
+ vault: PropTypes.string.isRequired
+ };
+
+ render () {
+ const { vault } = this.props;
+
+ return (
+
+ );
+ }
+}
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index ae1ce8451..d076986be 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -30,7 +30,7 @@ export DappCard from './DappCard';
export DappIcon from './DappIcon';
export Errors from './Errors';
export Features, { FEATURES, FeaturesStore } from './Features';
-export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
+export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput, VaultSelect } from './Form';
export GasPriceEditor from './GasPriceEditor';
export GasPriceSelector from './GasPriceSelector';
export Icons from './Icons';
@@ -56,4 +56,5 @@ export Tooltips, { Tooltip } from './Tooltips';
export TxHash from './TxHash';
export TxList from './TxList';
export VaultCard from './VaultCard';
+export VaultTag from './VaultTag';
export Warning from './Warning';
diff --git a/js/src/views/Account/Header/header.css b/js/src/views/Account/Header/header.css
index 62f072574..f894b7c49 100644
--- a/js/src/views/Account/Header/header.css
+++ b/js/src/views/Account/Header/header.css
@@ -66,6 +66,7 @@
.text {
display: inline-block;
opacity: 0.25;
+ text-transform: uppercase;
}
}
diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js
index dc367d136..d3c4a9c69 100644
--- a/js/src/views/Account/Header/header.js
+++ b/js/src/views/Account/Header/header.js
@@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
-import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags } from '~/ui';
+import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags, VaultTag } from '~/ui';
import styles from './header.css';
@@ -69,7 +69,6 @@ export default class Header extends Component {
{ address }
- { this.renderVault() }
{ this.renderUuid() }
{ meta.description }
@@ -81,6 +80,7 @@ export default class Header extends Component {
balance={ balance }
/>
+ { this.renderVault() }
@@ -169,15 +169,7 @@ export default class Header extends Component {
}
return (
-
+
);
}
}
diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js
index 3a4b282fa..78f4dd5c4 100644
--- a/js/src/views/Accounts/Summary/summary.js
+++ b/js/src/views/Accounts/Summary/summary.js
@@ -22,7 +22,7 @@ import { isEqual } from 'lodash';
import ReactTooltip from 'react-tooltip';
import { FormattedMessage } from 'react-intl';
-import { Balance, Container, ContainerTitle, CopyToClipboard, IdentityIcon, IdentityName, Tags } from '~/ui';
+import { Balance, Container, ContainerTitle, CopyToClipboard, IdentityIcon, IdentityName, Tags, VaultTag } from '~/ui';
import Certifications from '~/ui/Certifications';
import { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes';
@@ -117,6 +117,7 @@ class Summary extends Component {
{ this.renderDescription(account.meta) }
{ this.renderOwners() }
{ this.renderCertifications() }
+ { this.renderVault(account.meta) }
}
link={ this.getLink() }
@@ -287,6 +288,16 @@ class Summary extends Component {
/>
);
}
+
+ renderVault (meta) {
+ if (!meta || !meta.vault) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
}
function mapStateToProps (state) {
diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css
index de6ce3ab5..296ae6714 100644
--- a/js/src/views/Accounts/accounts.css
+++ b/js/src/views/Accounts/accounts.css
@@ -56,6 +56,10 @@
}
}
+ .overlay {
+ margin-top: -3.25em;
+ }
+
.owners {
display: flex;
justify-content: center;
@@ -68,10 +72,6 @@
}
}
- .overlay {
- margin-top: -3.25em;
- }
-
&:not(:hover) {
.tags {
display: none;
diff --git a/js/src/views/Vaults/store.js b/js/src/views/Vaults/store.js
index 75a52954d..2d4f4c2df 100644
--- a/js/src/views/Vaults/store.js
+++ b/js/src/views/Vaults/store.js
@@ -37,6 +37,7 @@ export default class Store {
@observable selectedAccounts = {};
@observable vault = null;
@observable vaults = [];
+ @observable vaultsOpened = [];
@observable vaultNames = [];
@observable vaultName = '';
@observable vaultNameError = ERRORS.noName;
@@ -143,6 +144,7 @@ export default class Store {
isOpen: openedVaults.includes(name)
};
});
+ this.vaultsOpened = this.vaults.filter((vault) => vault.isOpen);
});
}
diff --git a/js/src/views/Vaults/store.spec.js b/js/src/views/Vaults/store.spec.js
index 9f971d383..863b853da 100644
--- a/js/src/views/Vaults/store.spec.js
+++ b/js/src/views/Vaults/store.spec.js
@@ -180,6 +180,12 @@ describe('modals/Vaults/Store', () => {
{ name: 'some', meta: 'metaSome', isOpen: false }
]);
});
+
+ it('sets the opened vaults', () => {
+ expect(store.vaultsOpened.peek()).to.deep.equal([
+ { name: 'TEST', meta: 'metaTest', isOpen: true }
+ ]);
+ });
});
describe('setVaultDescription', () => {
@@ -553,6 +559,36 @@ describe('modals/Vaults/Store', () => {
});
});
+ describe('editVaultMeta', () => {
+ beforeEach(() => {
+ sinon.spy(store, 'setBusyMeta');
+
+ store.setVaultDescription('testDescription');
+ store.setVaultName('testCreateName');
+ store.setVaultPasswordHint('testCreateHint');
+ store.setVaultTags('testTags');
+
+ return store.editVaultMeta();
+ });
+
+ afterEach(() => {
+ store.setBusyMeta.restore();
+ });
+
+ it('sets and resets the busy flag', () => {
+ expect(store.setBusyMeta).to.have.been.calledWith(true);
+ expect(store.isBusyMeta).to.be.false;
+ });
+
+ it('calls into parity_setVaultMeta', () => {
+ expect(api.parity.setVaultMeta).to.have.been.calledWith('testCreateName', {
+ description: 'testDescription',
+ passwordHint: 'testCreateHint',
+ tags: 'testTags'
+ });
+ });
+ });
+
describe('editVaultPassword', () => {
beforeEach(() => {
sinon.spy(store, 'setBusyMeta');