From 7e600b5a82d5eb0e655b3a2fa018fcd0e9aee933 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 28 Dec 2016 18:09:45 +0100 Subject: [PATCH 01/28] Store for EditPassword Modal (#3979) * External store (WIP) * address & meta * Add editable (WIP) * View converted (WIP) * Single API stub creation * Testing (WIP) * Simplified meta assign * Tests running * Fix duplicate exports * Fix tags not editable --- js/src/modals/EditMeta/editMeta.js | 2 +- js/src/modals/EditMeta/editMeta.spec.js | 9 +- js/src/modals/EditMeta/editMeta.test.js | 14 +- js/src/modals/EditMeta/store.js | 26 +- js/src/modals/EditMeta/store.spec.js | 16 +- .../modals/PasswordManager/passwordManager.js | 501 ++++++++---------- .../PasswordManager/passwordManager.spec.js | 81 +++ .../PasswordManager/passwordManager.test.js | 43 ++ js/src/modals/PasswordManager/store.js | 161 ++++++ js/src/modals/PasswordManager/store.spec.js | 103 ++++ js/src/redux/actions.js | 2 + js/src/ui/Icons/index.js | 2 + js/src/views/Account/Header/header.js | 2 +- 13 files changed, 658 insertions(+), 304 deletions(-) create mode 100644 js/src/modals/PasswordManager/passwordManager.spec.js create mode 100644 js/src/modals/PasswordManager/passwordManager.test.js create mode 100644 js/src/modals/PasswordManager/store.js create mode 100644 js/src/modals/PasswordManager/store.spec.js diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index aea7a7060..8b5e14939 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -85,7 +85,7 @@ export default class EditMeta extends Component { defaultMessage='(optional) tags' /> } onTokensChange={ this.store.setTags } - tokens={ tags } /> + tokens={ tags.slice() } /> ); diff --git a/js/src/modals/EditMeta/editMeta.spec.js b/js/src/modals/EditMeta/editMeta.spec.js index b490fcdaf..917f32076 100644 --- a/js/src/modals/EditMeta/editMeta.spec.js +++ b/js/src/modals/EditMeta/editMeta.spec.js @@ -20,7 +20,7 @@ import sinon from 'sinon'; import EditMeta from './'; -import { ACCOUNT } from './editMeta.test.js'; +import { ACCOUNT, createApi } from './editMeta.test.js'; let component; let onClose; @@ -35,12 +35,7 @@ function render (props) { onClose={ onClose } />, { context: { - api: { - parity: { - setAccountName: sinon.stub().resolves(), - setAccountMeta: sinon.stub().resolves() - } - } + api: createApi() } } ); diff --git a/js/src/modals/EditMeta/editMeta.test.js b/js/src/modals/EditMeta/editMeta.test.js index 7f359a405..98bd61fea 100644 --- a/js/src/modals/EditMeta/editMeta.test.js +++ b/js/src/modals/EditMeta/editMeta.test.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import sinon from 'sinon'; + const ACCOUNT = { address: '0x123456789a123456789a123456789a123456789a', meta: { @@ -39,7 +41,17 @@ const ADDRESS = { name: 'Random address' }; +function createApi () { + return { + parity: { + setAccountName: sinon.stub().resolves(), + setAccountMeta: sinon.stub().resolves() + } + }; +} + export { ACCOUNT, - ADDRESS + ADDRESS, + createApi }; diff --git a/js/src/modals/EditMeta/store.js b/js/src/modals/EditMeta/store.js index 3f0355e84..b2d71c63a 100644 --- a/js/src/modals/EditMeta/store.js +++ b/js/src/modals/EditMeta/store.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { action, computed, observable, toJS, transaction } from 'mobx'; +import { action, computed, observable, transaction } from 'mobx'; import { newError } from '~/redux/actions'; import { validateName } from '~/util/validation'; @@ -23,25 +23,27 @@ export default class Store { @observable address = null; @observable isAccount = false; @observable description = null; - @observable meta = {}; + @observable meta = null; @observable name = null; @observable nameError = null; @observable passwordHint = null; - @observable tags = []; + @observable tags = null; constructor (api, account) { const { address, name, meta, uuid } = account; this._api = api; - this.isAccount = !!uuid; - this.address = address; - this.meta = Object.assign({}, meta || {}); - this.name = name || ''; + transaction(() => { + this.isAccount = !!uuid; + this.address = address; + this.meta = meta || {}; + this.name = name || ''; - this.description = this.meta.description || ''; - this.passwordHint = this.meta.passwordHint || ''; - this.tags = [].concat((meta || {}).tags || []); + this.description = this.meta.description || ''; + this.passwordHint = this.meta.passwordHint || ''; + this.tags = this.meta.tags && this.meta.tags.peek() || []; + }); } @computed get hasError () { @@ -70,7 +72,7 @@ export default class Store { } @action setTags = (tags) => { - this.tags = [].concat(tags); + this.tags = tags.slice(); } save () { @@ -86,7 +88,7 @@ export default class Store { return Promise .all([ this._api.parity.setAccountName(this.address, this.name), - this._api.parity.setAccountMeta(this.address, Object.assign({}, toJS(this.meta), meta)) + this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta)) ]) .catch((error) => { console.error('onSave', error); diff --git a/js/src/modals/EditMeta/store.spec.js b/js/src/modals/EditMeta/store.spec.js index e69656e96..c707d1f6c 100644 --- a/js/src/modals/EditMeta/store.spec.js +++ b/js/src/modals/EditMeta/store.spec.js @@ -14,22 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { toJS } from 'mobx'; -import sinon from 'sinon'; - import Store from './store'; -import { ACCOUNT, ADDRESS } from './editMeta.test.js'; +import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js'; let api; let store; function createStore (account) { - api = { - parity: { - setAccountName: sinon.stub().resolves(), - setAccountMeta: sinon.stub().resolves() - } - }; + api = createApi(); store = new Store(api, account); @@ -56,12 +48,12 @@ describe('modals/EditMeta/Store', () => { }); it('extracts the tags', () => { - expect(store.tags.peek()).to.deep.equal(ACCOUNT.meta.tags); + expect(store.tags).to.deep.equal(ACCOUNT.meta.tags); }); describe('meta', () => { it('extracts the full meta', () => { - expect(toJS(store.meta)).to.deep.equal(ACCOUNT.meta); + expect(store.meta).to.deep.equal(ACCOUNT.meta); }); it('extracts the description', () => { diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 8121e14d2..0f7a08f27 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -14,54 +14,55 @@ // 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 ContentClear from 'material-ui/svg-icons/content/clear'; -import CheckIcon from 'material-ui/svg-icons/navigation/check'; -import SendIcon from 'material-ui/svg-icons/content/send'; - -import { Tabs, Tab } from 'material-ui/Tabs'; import Paper from 'material-ui/Paper'; +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 { showSnackbar } from '~/redux/providers/snackbarActions'; - -import Form, { Input } from '~/ui/Form'; +import { newError, showSnackbar } from '~/redux/actions'; import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; +import Form, { Input } from '~/ui/Form'; +import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons'; +import Store, { CHANGE_ACTION, TEST_ACTION } from './store'; import styles from './passwordManager.css'; -const TEST_ACTION = 'TEST_ACTION'; -const CHANGE_ACTION = 'CHANGE_ACTION'; +const MSG_SUCCESS_STYLE = { + backgroundColor: 'rgba(174, 213, 129, 0.75)' +}; +const MSG_FAILURE_STYLE = { + backgroundColor: 'rgba(229, 115, 115, 0.75)' +}; +const TABS_INKBAR_STYLE = { + backgroundColor: 'rgba(255, 255, 255, 0.55)' +}; +const TABS_ITEM_STYLE = { + backgroundColor: 'rgba(255, 255, 255, 0.05)' +}; -class PasswordManager extends Component { +@observer +export default class PasswordManager extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { account: PropTypes.object.isRequired, - showSnackbar: PropTypes.func.isRequired, onClose: PropTypes.func } - state = { - action: TEST_ACTION, - waiting: false, - showMessage: false, - message: { value: '', success: true }, - currentPass: '', - newPass: '', - repeatNewPass: '', - repeatValid: true, - passwordHint: this.props.account.meta && this.props.account.meta.passwordHint || '' - } + store = new Store(this.context.api, this.props.account); render () { return ( + } visible> { this.renderAccount() } { this.renderPage() } @@ -71,150 +72,168 @@ class PasswordManager extends Component { } renderMessage () { - const { message, showMessage } = this.state; + const { infoMessage } = this.store; - const style = message.success - ? { - backgroundColor: 'rgba(174, 213, 129, 0.75)' - } - : { - backgroundColor: 'rgba(229, 115, 115, 0.75)' - }; - - const classes = [ styles.message ]; - - if (!showMessage) { - classes.push(styles.hideMessage); + if (!infoMessage) { + return null; } return ( - { message.value } + className={ `${styles.message}` } + style={ + infoMessage.success + ? MSG_SUCCESS_STYLE + : MSG_FAILURE_STYLE + } + zDepth={ 1 }> + { infoMessage.value } ); } renderAccount () { - const { account } = this.props; - const { address, meta } = account; - - const passwordHint = meta && meta.passwordHint - ? ( - - Hint - { meta.passwordHint } - - ) - : null; + const { address, passwordHint } = this.store; return (
- +
+ className={ styles.accountName } + unknown /> { address } - { passwordHint } + + Hint + { passwordHint || '-' } +
); } renderPage () { - const { account } = this.props; - const { waiting, repeatValid } = this.state; - const disabled = !!waiting; - - const repeatError = repeatValid - ? null - : 'the two passwords differ'; - - const { meta } = account; - const passwordHint = meta && meta.passwordHint || ''; + const { busy, isRepeatValid, passwordHint } = this.store; return ( + inkBarStyle={ TABS_INKBAR_STYLE } + tabItemContainerStyle={ TABS_ITEM_STYLE }> -
+ label={ + + } + onActive={ this.onActivateTestTab }> +
+ } + label={ + + } + onChange={ this.onEditTestPassword } + onSubmit={ this.testPassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleTestPassword } - onChange={ this.onEditCurrent } /> + type='password' />
-
+ label={ + + } + onActive={ this.onActivateChangeTab }> +
+ } + label={ + + } + onChange={ this.onEditCurrentPassword } + onSubmit={ this.changePassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditCurrent } /> + type='password' /> + } + label={ + + } + onChange={ this.onEditNewPasswordHint } + onSubmit={ this.changePassword } submitOnBlur={ false } - value={ passwordHint } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditHint } /> + value={ passwordHint } />
+ } + label={ + + } + onChange={ this.onEditNewPassword } + onSubmit={ this.changePassword } submitOnBlur={ false } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditNew } /> + type='password' />
+ } + hint={ + + } + label={ + + } + onChange={ this.onEditNewPasswordRepeat } + onSubmit={ this.changePassword } submitOnBlur={ false } - error={ repeatError } - disabled={ disabled } - onSubmit={ this.handleChangePassword } - onChange={ this.onEditRepeatNew } /> + type='password' />
@@ -225,176 +244,118 @@ class PasswordManager extends Component { } renderDialogActions () { + const { actionTab, busy, isRepeatValid } = this.store; const { onClose } = this.props; - const { action, waiting, repeatValid } = this.state; const cancelBtn = (