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
This commit is contained in:
		
							parent
							
								
									3067a8de3e
								
							
						
					
					
						commit
						7e600b5a82
					
				| @ -85,7 +85,7 @@ export default class EditMeta extends Component { | |||||||
|                 defaultMessage='(optional) tags' /> |                 defaultMessage='(optional) tags' /> | ||||||
|             } |             } | ||||||
|             onTokensChange={ this.store.setTags } |             onTokensChange={ this.store.setTags } | ||||||
|             tokens={ tags } /> |             tokens={ tags.slice() } /> | ||||||
|         </Form> |         </Form> | ||||||
|       </Modal> |       </Modal> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import sinon from 'sinon'; | |||||||
| 
 | 
 | ||||||
| import EditMeta from './'; | import EditMeta from './'; | ||||||
| 
 | 
 | ||||||
| import { ACCOUNT } from './editMeta.test.js'; | import { ACCOUNT, createApi } from './editMeta.test.js'; | ||||||
| 
 | 
 | ||||||
| let component; | let component; | ||||||
| let onClose; | let onClose; | ||||||
| @ -35,12 +35,7 @@ function render (props) { | |||||||
|       onClose={ onClose } />, |       onClose={ onClose } />, | ||||||
|     { |     { | ||||||
|       context: { |       context: { | ||||||
|         api: { |         api: createApi() | ||||||
|           parity: { |  | ||||||
|             setAccountName: sinon.stub().resolves(), |  | ||||||
|             setAccountMeta: sinon.stub().resolves() |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|  | |||||||
| @ -14,6 +14,8 @@ | |||||||
| // You should have received a copy of the GNU General Public License
 | // You should have received a copy of the GNU General Public License
 | ||||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
| 
 | 
 | ||||||
|  | import sinon from 'sinon'; | ||||||
|  | 
 | ||||||
| const ACCOUNT = { | const ACCOUNT = { | ||||||
|   address: '0x123456789a123456789a123456789a123456789a', |   address: '0x123456789a123456789a123456789a123456789a', | ||||||
|   meta: { |   meta: { | ||||||
| @ -39,7 +41,17 @@ const ADDRESS = { | |||||||
|   name: 'Random address' |   name: 'Random address' | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | function createApi () { | ||||||
|  |   return { | ||||||
|  |     parity: { | ||||||
|  |       setAccountName: sinon.stub().resolves(), | ||||||
|  |       setAccountMeta: sinon.stub().resolves() | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export { | export { | ||||||
|   ACCOUNT, |   ACCOUNT, | ||||||
|   ADDRESS |   ADDRESS, | ||||||
|  |   createApi | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
| // You should have received a copy of the GNU General Public License
 | // You should have received a copy of the GNU General Public License
 | ||||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
| 
 | 
 | ||||||
| import { action, computed, observable, toJS, transaction } from 'mobx'; | import { action, computed, observable, transaction } from 'mobx'; | ||||||
| 
 | 
 | ||||||
| import { newError } from '~/redux/actions'; | import { newError } from '~/redux/actions'; | ||||||
| import { validateName } from '~/util/validation'; | import { validateName } from '~/util/validation'; | ||||||
| @ -23,25 +23,27 @@ export default class Store { | |||||||
|   @observable address = null; |   @observable address = null; | ||||||
|   @observable isAccount = false; |   @observable isAccount = false; | ||||||
|   @observable description = null; |   @observable description = null; | ||||||
|   @observable meta = {}; |   @observable meta = null; | ||||||
|   @observable name = null; |   @observable name = null; | ||||||
|   @observable nameError = null; |   @observable nameError = null; | ||||||
|   @observable passwordHint = null; |   @observable passwordHint = null; | ||||||
|   @observable tags = []; |   @observable tags = null; | ||||||
| 
 | 
 | ||||||
|   constructor (api, account) { |   constructor (api, account) { | ||||||
|     const { address, name, meta, uuid } = account; |     const { address, name, meta, uuid } = account; | ||||||
| 
 | 
 | ||||||
|     this._api = api; |     this._api = api; | ||||||
| 
 | 
 | ||||||
|  |     transaction(() => { | ||||||
|       this.isAccount = !!uuid; |       this.isAccount = !!uuid; | ||||||
|       this.address = address; |       this.address = address; | ||||||
|     this.meta = Object.assign({}, meta || {}); |       this.meta = meta || {}; | ||||||
|       this.name = name || ''; |       this.name = name || ''; | ||||||
| 
 | 
 | ||||||
|       this.description = this.meta.description || ''; |       this.description = this.meta.description || ''; | ||||||
|       this.passwordHint = this.meta.passwordHint || ''; |       this.passwordHint = this.meta.passwordHint || ''; | ||||||
|     this.tags = [].concat((meta || {}).tags || []); |       this.tags = this.meta.tags && this.meta.tags.peek() || []; | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @computed get hasError () { |   @computed get hasError () { | ||||||
| @ -70,7 +72,7 @@ export default class Store { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @action setTags = (tags) => { |   @action setTags = (tags) => { | ||||||
|     this.tags = [].concat(tags); |     this.tags = tags.slice(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   save () { |   save () { | ||||||
| @ -86,7 +88,7 @@ export default class Store { | |||||||
|     return Promise |     return Promise | ||||||
|       .all([ |       .all([ | ||||||
|         this._api.parity.setAccountName(this.address, this.name), |         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) => { |       .catch((error) => { | ||||||
|         console.error('onSave', error); |         console.error('onSave', error); | ||||||
|  | |||||||
| @ -14,22 +14,14 @@ | |||||||
| // You should have received a copy of the GNU General Public License
 | // You should have received a copy of the GNU General Public License
 | ||||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
| 
 | 
 | ||||||
| import { toJS } from 'mobx'; |  | ||||||
| import sinon from 'sinon'; |  | ||||||
| 
 |  | ||||||
| import Store from './store'; | import Store from './store'; | ||||||
| import { ACCOUNT, ADDRESS } from './editMeta.test.js'; | import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js'; | ||||||
| 
 | 
 | ||||||
| let api; | let api; | ||||||
| let store; | let store; | ||||||
| 
 | 
 | ||||||
| function createStore (account) { | function createStore (account) { | ||||||
|   api = { |   api = createApi(); | ||||||
|     parity: { |  | ||||||
|       setAccountName: sinon.stub().resolves(), |  | ||||||
|       setAccountMeta: sinon.stub().resolves() |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 | 
 | ||||||
|   store = new Store(api, account); |   store = new Store(api, account); | ||||||
| 
 | 
 | ||||||
| @ -56,12 +48,12 @@ describe('modals/EditMeta/Store', () => { | |||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('extracts the tags', () => { |       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', () => { |       describe('meta', () => { | ||||||
|         it('extracts the full 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', () => { |         it('extracts the description', () => { | ||||||
|  | |||||||
| @ -14,54 +14,55 @@ | |||||||
| // You should have received a copy of the GNU General Public License
 | // You should have received a copy of the GNU General Public License
 | ||||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
| 
 | 
 | ||||||
| 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 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 { newError, showSnackbar } from '~/redux/actions'; | ||||||
| import { bindActionCreators } from 'redux'; |  | ||||||
| import { showSnackbar } from '~/redux/providers/snackbarActions'; |  | ||||||
| 
 |  | ||||||
| import Form, { Input } from '~/ui/Form'; |  | ||||||
| import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; | 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'; | import styles from './passwordManager.css'; | ||||||
| 
 | 
 | ||||||
| const TEST_ACTION = 'TEST_ACTION'; | const MSG_SUCCESS_STYLE = { | ||||||
| const CHANGE_ACTION = 'CHANGE_ACTION'; |   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 = { |   static contextTypes = { | ||||||
|     api: PropTypes.object.isRequired |     api: PropTypes.object.isRequired | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     account: PropTypes.object.isRequired, |     account: PropTypes.object.isRequired, | ||||||
|     showSnackbar: PropTypes.func.isRequired, |  | ||||||
|     onClose: PropTypes.func |     onClose: PropTypes.func | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   state = { |   store = new Store(this.context.api, this.props.account); | ||||||
|     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 || '' |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     return ( |     return ( | ||||||
|       <Modal |       <Modal | ||||||
|         actions={ this.renderDialogActions() } |         actions={ this.renderDialogActions() } | ||||||
|         title='Password Manager' |         title={ | ||||||
|  |           <FormattedMessage | ||||||
|  |             id='passwordChange.title' | ||||||
|  |             defaultMessage='Password Manager' /> | ||||||
|  |         } | ||||||
|         visible> |         visible> | ||||||
|         { this.renderAccount() } |         { this.renderAccount() } | ||||||
|         { this.renderPage() } |         { this.renderPage() } | ||||||
| @ -71,150 +72,168 @@ class PasswordManager extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderMessage () { |   renderMessage () { | ||||||
|     const { message, showMessage } = this.state; |     const { infoMessage } = this.store; | ||||||
| 
 | 
 | ||||||
|     const style = message.success |     if (!infoMessage) { | ||||||
|       ? { |       return null; | ||||||
|         backgroundColor: 'rgba(174, 213, 129, 0.75)' |  | ||||||
|       } |  | ||||||
|       : { |  | ||||||
|         backgroundColor: 'rgba(229, 115, 115, 0.75)' |  | ||||||
|       }; |  | ||||||
| 
 |  | ||||||
|     const classes = [ styles.message ]; |  | ||||||
| 
 |  | ||||||
|     if (!showMessage) { |  | ||||||
|       classes.push(styles.hideMessage); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Paper |       <Paper | ||||||
|         zDepth={ 1 } |         className={ `${styles.message}` } | ||||||
|         style={ style } |         style={ | ||||||
|         className={ classes.join(' ') }> |           infoMessage.success | ||||||
|         { message.value } |             ? MSG_SUCCESS_STYLE | ||||||
|  |             : MSG_FAILURE_STYLE | ||||||
|  |         } | ||||||
|  |         zDepth={ 1 }> | ||||||
|  |         { infoMessage.value } | ||||||
|       </Paper> |       </Paper> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderAccount () { |   renderAccount () { | ||||||
|     const { account } = this.props; |     const { address, passwordHint } = this.store; | ||||||
|     const { address, meta } = account; |  | ||||||
| 
 |  | ||||||
|     const passwordHint = meta && meta.passwordHint |  | ||||||
|       ? ( |  | ||||||
|         <span className={ styles.passwordHint }> |  | ||||||
|           <span className={ styles.hintLabel }>Hint </span> |  | ||||||
|           { meta.passwordHint } |  | ||||||
|         </span> |  | ||||||
|       ) |  | ||||||
|       : null; |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className={ styles.accountContainer }> |       <div className={ styles.accountContainer }> | ||||||
|         <IdentityIcon |         <IdentityIcon address={ address } /> | ||||||
|           address={ address } |  | ||||||
|         /> |  | ||||||
|         <div className={ styles.accountInfos }> |         <div className={ styles.accountInfos }> | ||||||
|           <IdentityName |           <IdentityName | ||||||
|             className={ styles.accountName } |  | ||||||
|             address={ address } |             address={ address } | ||||||
|             unknown |             className={ styles.accountName } | ||||||
|           /> |             unknown /> | ||||||
|           <span className={ styles.accountAddress }> |           <span className={ styles.accountAddress }> | ||||||
|             { address } |             { address } | ||||||
|           </span> |           </span> | ||||||
|           { passwordHint } |           <span className={ styles.passwordHint }> | ||||||
|  |             <span className={ styles.hintLabel }>Hint </span> | ||||||
|  |             { passwordHint || '-' } | ||||||
|  |           </span> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderPage () { |   renderPage () { | ||||||
|     const { account } = this.props; |     const { busy, isRepeatValid, passwordHint } = this.store; | ||||||
|     const { waiting, repeatValid } = this.state; |  | ||||||
|     const disabled = !!waiting; |  | ||||||
| 
 |  | ||||||
|     const repeatError = repeatValid |  | ||||||
|       ? null |  | ||||||
|       : 'the two passwords differ'; |  | ||||||
| 
 |  | ||||||
|     const { meta } = account; |  | ||||||
|     const passwordHint = meta && meta.passwordHint || ''; |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Tabs |       <Tabs | ||||||
|         inkBarStyle={ { |         inkBarStyle={ TABS_INKBAR_STYLE } | ||||||
|           backgroundColor: 'rgba(255, 255, 255, 0.55)' |         tabItemContainerStyle={ TABS_ITEM_STYLE }> | ||||||
|         } } |  | ||||||
|         tabItemContainerStyle={ { |  | ||||||
|           backgroundColor: 'rgba(255, 255, 255, 0.05)' |  | ||||||
|         } } |  | ||||||
|       > |  | ||||||
|         <Tab |         <Tab | ||||||
|           onActive={ this.handleTestActive } |           label={ | ||||||
|           label='Test Password' |             <FormattedMessage | ||||||
|         > |               id='passwordChange.tabTest.label' | ||||||
|           <Form |               defaultMessage='Test Password' /> | ||||||
|             className={ styles.form } |           } | ||||||
|           > |           onActive={ this.onActivateTestTab }> | ||||||
|  |           <Form className={ styles.form }> | ||||||
|             <div> |             <div> | ||||||
|               <Input |               <Input | ||||||
|                 label='password' |                 disabled={ busy } | ||||||
|                 hint='your current password for this account' |                 hint={ | ||||||
|                 type='password' |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.testPassword.hint' | ||||||
|  |                     defaultMessage='your account password' /> | ||||||
|  |                 } | ||||||
|  |                 label={ | ||||||
|  |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.testPassword.label' | ||||||
|  |                     defaultMessage='password' /> | ||||||
|  |                 } | ||||||
|  |                 onChange={ this.onEditTestPassword } | ||||||
|  |                 onSubmit={ this.testPassword } | ||||||
|                 submitOnBlur={ false } |                 submitOnBlur={ false } | ||||||
|                 disabled={ disabled } |                 type='password' /> | ||||||
|                 onSubmit={ this.handleTestPassword } |  | ||||||
|                 onChange={ this.onEditCurrent } /> |  | ||||||
|             </div> |             </div> | ||||||
|           </Form> |           </Form> | ||||||
|         </Tab> |         </Tab> | ||||||
|         <Tab |         <Tab | ||||||
|           onActive={ this.handleChangeActive } |           label={ | ||||||
|           label='Change Password' |             <FormattedMessage | ||||||
|         > |               id='passwordChange.tabChange.label' | ||||||
|           <Form |               defaultMessage='Change Password' /> | ||||||
|             className={ styles.form } |           } | ||||||
|           > |           onActive={ this.onActivateChangeTab }> | ||||||
|  |           <Form className={ styles.form }> | ||||||
|             <div> |             <div> | ||||||
|               <Input |               <Input | ||||||
|                 label='current password' |                 disabled={ busy } | ||||||
|                 hint='your current password for this account' |                 hint={ | ||||||
|                 type='password' |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.currentPassword.hint' | ||||||
|  |                     defaultMessage='your current password for this account' /> | ||||||
|  |                 } | ||||||
|  |                 label={ | ||||||
|  |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.currentPassword.label' | ||||||
|  |                     defaultMessage='current password' /> | ||||||
|  |                 } | ||||||
|  |                 onChange={ this.onEditCurrentPassword } | ||||||
|  |                 onSubmit={ this.changePassword } | ||||||
|                 submitOnBlur={ false } |                 submitOnBlur={ false } | ||||||
|                 disabled={ disabled } |                 type='password' /> | ||||||
|                 onSubmit={ this.handleChangePassword } |  | ||||||
|                 onChange={ this.onEditCurrent } /> |  | ||||||
|               <Input |               <Input | ||||||
|                 label='(optional) new password hint' |                 disabled={ busy } | ||||||
|                 hint='hint for the new password' |                 hint={ | ||||||
|  |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.passwordHint.hint' | ||||||
|  |                     defaultMessage='hint for the new password' /> | ||||||
|  |                 } | ||||||
|  |                 label={ | ||||||
|  |                   <FormattedMessage | ||||||
|  |                     id='passwordChange.passwordHint.label' | ||||||
|  |                     defaultMessage='(optional) new password hint' /> | ||||||
|  |                 } | ||||||
|  |                 onChange={ this.onEditNewPasswordHint } | ||||||
|  |                 onSubmit={ this.changePassword } | ||||||
|                 submitOnBlur={ false } |                 submitOnBlur={ false } | ||||||
|                 value={ passwordHint } |                 value={ passwordHint } /> | ||||||
|                 disabled={ disabled } |  | ||||||
|                 onSubmit={ this.handleChangePassword } |  | ||||||
|                 onChange={ this.onEditHint } /> |  | ||||||
|               <div className={ styles.passwords }> |               <div className={ styles.passwords }> | ||||||
|                 <div className={ styles.password }> |                 <div className={ styles.password }> | ||||||
|                   <Input |                   <Input | ||||||
|                     label='new password' |                     disabled={ busy } | ||||||
|                     hint='the new password for this account' |                     hint={ | ||||||
|                     type='password' |                       <FormattedMessage | ||||||
|  |                         id='passwordChange.newPassword.hint' | ||||||
|  |                         defaultMessage='the new password for this account' /> | ||||||
|  |                     } | ||||||
|  |                     label={ | ||||||
|  |                       <FormattedMessage | ||||||
|  |                         id='passwordChange.newPassword.label' | ||||||
|  |                         defaultMessage='new password' /> | ||||||
|  |                     } | ||||||
|  |                     onChange={ this.onEditNewPassword } | ||||||
|  |                     onSubmit={ this.changePassword } | ||||||
|                     submitOnBlur={ false } |                     submitOnBlur={ false } | ||||||
|                     disabled={ disabled } |                     type='password' /> | ||||||
|                     onSubmit={ this.handleChangePassword } |  | ||||||
|                     onChange={ this.onEditNew } /> |  | ||||||
|                 </div> |                 </div> | ||||||
|                 <div className={ styles.password }> |                 <div className={ styles.password }> | ||||||
|                   <Input |                   <Input | ||||||
|                     label='repeat new password' |                     disabled={ busy } | ||||||
|                     hint='repeat the new password for this account' |                     error={ | ||||||
|                     type='password' |                       isRepeatValid | ||||||
|  |                         ? null | ||||||
|  |                         : <FormattedMessage | ||||||
|  |                           id='passwordChange.repeatPassword.error' | ||||||
|  |                           defaultMessage='the supplied passwords do not match' /> | ||||||
|  |                     } | ||||||
|  |                     hint={ | ||||||
|  |                       <FormattedMessage | ||||||
|  |                         id='passwordChange.repeatPassword.hint' | ||||||
|  |                         defaultMessage='repeat the new password for this account' /> | ||||||
|  |                     } | ||||||
|  |                     label={ | ||||||
|  |                       <FormattedMessage | ||||||
|  |                         id='passwordChange.repeatPassword.label' | ||||||
|  |                         defaultMessage='repeat new password' /> | ||||||
|  |                     } | ||||||
|  |                     onChange={ this.onEditNewPasswordRepeat } | ||||||
|  |                     onSubmit={ this.changePassword } | ||||||
|                     submitOnBlur={ false } |                     submitOnBlur={ false } | ||||||
|                     error={ repeatError } |                     type='password' /> | ||||||
|                     disabled={ disabled } |  | ||||||
|                     onSubmit={ this.handleChangePassword } |  | ||||||
|                     onChange={ this.onEditRepeatNew } /> |  | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
| @ -225,176 +244,118 @@ class PasswordManager extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderDialogActions () { |   renderDialogActions () { | ||||||
|  |     const { actionTab, busy, isRepeatValid } = this.store; | ||||||
|     const { onClose } = this.props; |     const { onClose } = this.props; | ||||||
|     const { action, waiting, repeatValid } = this.state; |  | ||||||
| 
 | 
 | ||||||
|     const cancelBtn = ( |     const cancelBtn = ( | ||||||
|       <Button |       <Button | ||||||
|         icon={ <ContentClear /> } |         icon={ <CancelIcon /> } | ||||||
|         label='Cancel' |         key='cancel' | ||||||
|  |         label={ | ||||||
|  |           <FormattedMessage | ||||||
|  |             id='passwordChange.button.cancel' | ||||||
|  |             defaultMessage='Cancel' /> | ||||||
|  |         } | ||||||
|         onClick={ onClose } /> |         onClick={ onClose } /> | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     if (waiting) { |     if (busy) { | ||||||
|       const waitingBtn = ( |       return [ | ||||||
|  |         cancelBtn, | ||||||
|         <Button |         <Button | ||||||
|           disabled |           disabled | ||||||
|           label='Wait...' /> |           key='wait' | ||||||
|       ); |           label={ | ||||||
| 
 |             <FormattedMessage | ||||||
|       return [ cancelBtn, waitingBtn ]; |               id='passwordChange.button.wait' | ||||||
|  |               defaultMessage='Wait...' /> | ||||||
|  |           } /> | ||||||
|  |       ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (action === TEST_ACTION) { |     if (actionTab === TEST_ACTION) { | ||||||
|       const testBtn = ( |       return [ | ||||||
|  |         cancelBtn, | ||||||
|         <Button |         <Button | ||||||
|           icon={ <CheckIcon /> } |           icon={ <CheckIcon /> } | ||||||
|           label='Test' |           key='test' | ||||||
|           onClick={ this.handleTestPassword } /> |           label={ | ||||||
|       ); |             <FormattedMessage | ||||||
| 
 |               id='passwordChange.button.test' | ||||||
|       return [ cancelBtn, testBtn ]; |               defaultMessage='Test' /> | ||||||
|  |           } | ||||||
|  |           onClick={ this.testPassword } /> | ||||||
|  |       ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const changeBtn = ( |     return [ | ||||||
|  |       cancelBtn, | ||||||
|       <Button |       <Button | ||||||
|         disabled={ !repeatValid } |         disabled={ !isRepeatValid } | ||||||
|         icon={ <SendIcon /> } |         icon={ <SendIcon /> } | ||||||
|         label='Change' |         key='change' | ||||||
|         onClick={ this.handleChangePassword } /> |         label={ | ||||||
|  |           <FormattedMessage | ||||||
|  |             id='passwordChange.button.change' | ||||||
|  |             defaultMessage='Change' /> | ||||||
|  |         } | ||||||
|  |         onClick={ this.changePassword } /> | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onActivateChangeTab = () => { | ||||||
|  |     this.store.setActionTab(CHANGE_ACTION); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onActivateTestTab = () => { | ||||||
|  |     this.store.setActionTab(TEST_ACTION); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onEditCurrentPassword = (event, password) => { | ||||||
|  |     this.store.setPassword(password); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onEditNewPassword = (event, password) => { | ||||||
|  |     this.store.setNewPassword(password); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onEditNewPasswordHint = (event, passwordHint) => { | ||||||
|  |     this.store.setNewPasswordHint(passwordHint); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onEditNewPasswordRepeat = (event, password) => { | ||||||
|  |     this.store.setNewPasswordRepeat(password); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onEditTestPassword = (event, password) => { | ||||||
|  |     this.store.setValidatePassword(password); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   changePassword = () => { | ||||||
|  |     return this.store | ||||||
|  |       .changePassword() | ||||||
|  |       .then((result) => { | ||||||
|  |         if (result) { | ||||||
|  |           showSnackbar( | ||||||
|  |             <div> | ||||||
|  |               <FormattedMessage | ||||||
|  |                 id='passwordChange.success' | ||||||
|  |                 defaultMessage='Your password has been successfully changed' /> | ||||||
|  |             </div> | ||||||
|           ); |           ); | ||||||
| 
 |           this.props.onClose(); | ||||||
|     return [ cancelBtn, changeBtn ]; |  | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|   onEditCurrent = (event, value) => { |  | ||||||
|     this.setState({ |  | ||||||
|       currentPass: value, |  | ||||||
|       showMessage: false |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onEditNew = (event, value) => { |  | ||||||
|     const repeatValid = value === this.state.repeatNewPass; |  | ||||||
| 
 |  | ||||||
|     this.setState({ |  | ||||||
|       newPass: value, |  | ||||||
|       showMessage: false, |  | ||||||
|       repeatValid |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onEditRepeatNew = (event, value) => { |  | ||||||
|     const repeatValid = value === this.state.newPass; |  | ||||||
| 
 |  | ||||||
|     this.setState({ |  | ||||||
|       repeatNewPass: value, |  | ||||||
|       showMessage: false, |  | ||||||
|       repeatValid |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onEditHint = (event, value) => { |  | ||||||
|     this.setState({ |  | ||||||
|       passwordHint: value, |  | ||||||
|       showMessage: false |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleTestActive = () => { |  | ||||||
|     this.setState({ |  | ||||||
|       action: TEST_ACTION, |  | ||||||
|       showMessage: false |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleChangeActive = () => { |  | ||||||
|     this.setState({ |  | ||||||
|       action: CHANGE_ACTION, |  | ||||||
|       showMessage: false |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleTestPassword = () => { |  | ||||||
|     const { account } = this.props; |  | ||||||
|     const { currentPass } = this.state; |  | ||||||
| 
 |  | ||||||
|     this.setState({ waiting: true, showMessage: false }); |  | ||||||
| 
 |  | ||||||
|     this.context |  | ||||||
|       .api.parity |  | ||||||
|       .testPassword(account.address, currentPass) |  | ||||||
|       .then(correct => { |  | ||||||
|         const message = correct |  | ||||||
|           ? { value: 'This password is correct', success: true } |  | ||||||
|           : { value: 'This password is not correct', success: false }; |  | ||||||
| 
 |  | ||||||
|         this.setState({ waiting: false, message, showMessage: true }); |  | ||||||
|       }) |       }) | ||||||
|       .catch(e => { |       .catch((error) => { | ||||||
|         console.error('passwordManager::handleTestPassword', e); |         newError(error); | ||||||
|         this.setState({ waiting: false }); |  | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleChangePassword = () => { |   testPassword = () => { | ||||||
|     const { account, showSnackbar, onClose } = this.props; |     return this.store | ||||||
|     const { currentPass, newPass, repeatNewPass, passwordHint } = this.state; |       .testPassword() | ||||||
| 
 |       .catch((error) => { | ||||||
|     if (repeatNewPass !== newPass) { |         newError(error); | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.setState({ waiting: true, showMessage: false }); |  | ||||||
| 
 |  | ||||||
|     this.context |  | ||||||
|       .api.parity |  | ||||||
|       .testPassword(account.address, currentPass) |  | ||||||
|       .then(correct => { |  | ||||||
|         if (!correct) { |  | ||||||
|           const message = { |  | ||||||
|             value: 'This provided current password is not correct', |  | ||||||
|             success: false |  | ||||||
|           }; |  | ||||||
| 
 |  | ||||||
|           this.setState({ waiting: false, message, showMessage: true }); |  | ||||||
| 
 |  | ||||||
|           return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const meta = Object.assign({}, account.meta, { |  | ||||||
|           passwordHint |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         return Promise.all([ |  | ||||||
|           this.context |  | ||||||
|             .api.parity |  | ||||||
|             .setAccountMeta(account.address, meta), |  | ||||||
| 
 |  | ||||||
|           this.context |  | ||||||
|             .api.parity |  | ||||||
|             .changePassword(account.address, currentPass, newPass) |  | ||||||
|         ]) |  | ||||||
|           .then(() => { |  | ||||||
|             showSnackbar(<div>Your password has been successfully changed.</div>); |  | ||||||
|             this.setState({ waiting: false, showMessage: false }); |  | ||||||
|             onClose(); |  | ||||||
|           }); |  | ||||||
|       }) |  | ||||||
|       .catch(e => { |  | ||||||
|         console.error('passwordManager::handleChangePassword', e); |  | ||||||
|         this.setState({ waiting: false }); |  | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| function mapDispatchToProps (dispatch) { |  | ||||||
|   return bindActionCreators({ |  | ||||||
|     showSnackbar |  | ||||||
|   }, dispatch); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default connect( |  | ||||||
|   null, |  | ||||||
|   mapDispatchToProps |  | ||||||
| )(PasswordManager); |  | ||||||
|  | |||||||
							
								
								
									
										81
									
								
								js/src/modals/PasswordManager/passwordManager.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								js/src/modals/PasswordManager/passwordManager.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | // Copyright 2015, 2016 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 <http://www.gnu.org/licenses/>.
 | ||||||
|  | 
 | ||||||
|  | import { shallow } from 'enzyme'; | ||||||
|  | import React from 'react'; | ||||||
|  | import sinon from 'sinon'; | ||||||
|  | 
 | ||||||
|  | import PasswordManager from './'; | ||||||
|  | 
 | ||||||
|  | import { ACCOUNT, createApi } from './passwordManager.test.js'; | ||||||
|  | 
 | ||||||
|  | let component; | ||||||
|  | let onClose; | ||||||
|  | 
 | ||||||
|  | function render (props) { | ||||||
|  |   onClose = sinon.stub(); | ||||||
|  | 
 | ||||||
|  |   component = shallow( | ||||||
|  |     <PasswordManager | ||||||
|  |       { ...props } | ||||||
|  |       account={ ACCOUNT } | ||||||
|  |       onClose={ onClose } />, | ||||||
|  |     { | ||||||
|  |       context: { | ||||||
|  |         api: createApi() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return component; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | describe('modals/PasswordManager', () => { | ||||||
|  |   describe('rendering', () => { | ||||||
|  |     it('renders defaults', () => { | ||||||
|  |       expect(render()).to.be.ok; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('actions', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       render(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('changePassword', () => { | ||||||
|  |       it('calls store.changePassword & props.onClose', () => { | ||||||
|  |         const instance = component.instance(); | ||||||
|  |         sinon.spy(instance.store, 'changePassword'); | ||||||
|  | 
 | ||||||
|  |         instance.changePassword().then(() => { | ||||||
|  |           expect(instance.store.changePassword).to.have.been.called; | ||||||
|  |           expect(onClose).to.have.been.called; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('testPassword', () => { | ||||||
|  |       it('calls store.testPassword', () => { | ||||||
|  |         const instance = component.instance(); | ||||||
|  |         sinon.spy(instance.store, 'testPassword'); | ||||||
|  | 
 | ||||||
|  |         instance.testPassword().then(() => { | ||||||
|  |           expect(instance.store.testPassword).to.have.been.called; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										43
									
								
								js/src/modals/PasswordManager/passwordManager.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								js/src/modals/PasswordManager/passwordManager.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | // Copyright 2015, 2016 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 <http://www.gnu.org/licenses/>.
 | ||||||
|  | 
 | ||||||
|  | import sinon from 'sinon'; | ||||||
|  | 
 | ||||||
|  | const ACCOUNT = { | ||||||
|  |   address: '0x123456789a123456789a123456789a123456789a', | ||||||
|  |   meta: { | ||||||
|  |     description: 'Call me bob', | ||||||
|  |     passwordHint: 'some hint', | ||||||
|  |     tags: ['testing'] | ||||||
|  |   }, | ||||||
|  |   name: 'Bobby', | ||||||
|  |   uuid: '123-456' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function createApi (result = true) { | ||||||
|  |   return { | ||||||
|  |     parity: { | ||||||
|  |       changePassword: sinon.stub().resolves(result), | ||||||
|  |       setAccountMeta: sinon.stub().resolves(result), | ||||||
|  |       testPassword: sinon.stub().resolves(result) | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   ACCOUNT, | ||||||
|  |   createApi | ||||||
|  | }; | ||||||
							
								
								
									
										161
									
								
								js/src/modals/PasswordManager/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								js/src/modals/PasswordManager/store.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,161 @@ | |||||||
|  | // Copyright 2015, 2016 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 <http://www.gnu.org/licenses/>.
 | ||||||
|  | 
 | ||||||
|  | import { action, computed, observable, transaction } from 'mobx'; | ||||||
|  | 
 | ||||||
|  | const CHANGE_ACTION = 'CHANGE_ACTION'; | ||||||
|  | const TEST_ACTION = 'TEST_ACTION'; | ||||||
|  | 
 | ||||||
|  | export default class Store { | ||||||
|  |   @observable actionTab = TEST_ACTION; | ||||||
|  |   @observable address = null; | ||||||
|  |   @observable busy = false; | ||||||
|  |   @observable infoMessage = null; | ||||||
|  |   @observable meta = null; | ||||||
|  |   @observable newPassword = ''; | ||||||
|  |   @observable newPasswordHint = ''; | ||||||
|  |   @observable newPasswordRepeat = ''; | ||||||
|  |   @observable password = ''; | ||||||
|  |   @observable passwordHint = ''; | ||||||
|  |   @observable validatePassword = ''; | ||||||
|  | 
 | ||||||
|  |   constructor (api, account) { | ||||||
|  |     this._api = api; | ||||||
|  | 
 | ||||||
|  |     this.address = account.address; | ||||||
|  |     this.meta = account.meta || {}; | ||||||
|  |     this.passwordHint = this.meta.passwordHint || ''; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @computed get isRepeatValid () { | ||||||
|  |     return this.newPasswordRepeat === this.newPassword; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setActionTab = (actionTab) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.actionTab = actionTab; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setBusy = (busy, message) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.busy = busy; | ||||||
|  |       this.setInfoMessage(message); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setInfoMessage = (message = null) => { | ||||||
|  |     this.infoMessage = message; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setPassword = (password) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.password = password; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setNewPassword = (password) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.newPassword = password; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setNewPasswordHint = (passwordHint) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.newPasswordHint = passwordHint; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setNewPasswordRepeat = (password) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.newPasswordRepeat = password; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action setValidatePassword = (password) => { | ||||||
|  |     transaction(() => { | ||||||
|  |       this.validatePassword = password; | ||||||
|  |       this.setInfoMessage(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   changePassword = () => { | ||||||
|  |     if (!this.isRepeatValid) { | ||||||
|  |       return Promise.resolve(false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.setBusy(true); | ||||||
|  | 
 | ||||||
|  |     return this | ||||||
|  |       .testPassword(this.password) | ||||||
|  |       .then((result) => { | ||||||
|  |         if (!result) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const meta = Object.assign({}, this.meta, { | ||||||
|  |           passwordHint: this.newPasswordHint | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return Promise | ||||||
|  |           .all([ | ||||||
|  |             this._api.parity.setAccountMeta(this.address, meta), | ||||||
|  |             this._api.parity.changePassword(this.address, this.password, this.newPassword) | ||||||
|  |           ]) | ||||||
|  |           .then(() => { | ||||||
|  |             this.setBusy(false); | ||||||
|  |             return true; | ||||||
|  |           }); | ||||||
|  |       }) | ||||||
|  |       .catch((error) => { | ||||||
|  |         console.error('changePassword', error); | ||||||
|  |         this.setBusy(false); | ||||||
|  |         throw error; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   testPassword = (password) => { | ||||||
|  |     this.setBusy(false); | ||||||
|  | 
 | ||||||
|  |     return this._api.parity | ||||||
|  |       .testPassword(this.address, password || this.validatePassword) | ||||||
|  |       .then((success) => { | ||||||
|  |         this.setBusy(false, { | ||||||
|  |           success, | ||||||
|  |           value: success | ||||||
|  |             ? 'This password is correct' | ||||||
|  |             : 'This password is not correct' | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return success; | ||||||
|  |       }) | ||||||
|  |       .catch((error) => { | ||||||
|  |         console.error('testPassword', error); | ||||||
|  |         this.setBusy(false); | ||||||
|  |         throw error; | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   CHANGE_ACTION, | ||||||
|  |   TEST_ACTION | ||||||
|  | }; | ||||||
							
								
								
									
										103
									
								
								js/src/modals/PasswordManager/store.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								js/src/modals/PasswordManager/store.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | // Copyright 2015, 2016 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 <http://www.gnu.org/licenses/>.
 | ||||||
|  | 
 | ||||||
|  | import Store from './store'; | ||||||
|  | import { ACCOUNT, createApi } from './passwordManager.test.js'; | ||||||
|  | 
 | ||||||
|  | let api; | ||||||
|  | let store; | ||||||
|  | 
 | ||||||
|  | function createStore (account) { | ||||||
|  |   api = createApi(); | ||||||
|  |   store = new Store(api, account); | ||||||
|  | 
 | ||||||
|  |   return store; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | describe('modals/PasswordManager/Store', () => { | ||||||
|  |   beforeEach(() => { | ||||||
|  |     createStore(ACCOUNT); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('constructor', () => { | ||||||
|  |     it('extracts the address', () => { | ||||||
|  |       expect(store.address).to.equal(ACCOUNT.address); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('meta', () => { | ||||||
|  |       it('extracts the full meta', () => { | ||||||
|  |         expect(store.meta).to.deep.equal(ACCOUNT.meta); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('extracts the passwordHint', () => { | ||||||
|  |         expect(store.passwordHint).to.equal(ACCOUNT.meta.passwordHint); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('operations', () => { | ||||||
|  |     const CUR_PASSWORD = 'aPassW0rd'; | ||||||
|  |     const NEW_PASSWORD = 'br@ndNEW'; | ||||||
|  |     const NEW_HINT = 'something new to test'; | ||||||
|  | 
 | ||||||
|  |     describe('changePassword', () => { | ||||||
|  |       beforeEach(() => { | ||||||
|  |         store.setPassword(CUR_PASSWORD); | ||||||
|  |         store.setNewPasswordHint(NEW_HINT); | ||||||
|  |         store.setNewPassword(NEW_PASSWORD); | ||||||
|  |         store.setNewPasswordRepeat(NEW_PASSWORD); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('calls parity.testPassword with current password', () => { | ||||||
|  |         return store.changePassword().then(() => { | ||||||
|  |           expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('calls parity.setAccountMeta with new hint', () => { | ||||||
|  |         return store.changePassword().then(() => { | ||||||
|  |           expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, { | ||||||
|  |             passwordHint: NEW_HINT | ||||||
|  |           })); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('calls parity.changePassword with the new password', () => { | ||||||
|  |         return store.changePassword().then(() => { | ||||||
|  |           expect(api.parity.changePassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD, NEW_PASSWORD); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('testPassword', () => { | ||||||
|  |       beforeEach(() => { | ||||||
|  |         store.setValidatePassword(CUR_PASSWORD); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('calls parity.testPassword', () => { | ||||||
|  |         return store.testPassword().then(() => { | ||||||
|  |           expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('sets the infoMessage for success/failure', () => { | ||||||
|  |         return store.testPassword().then(() => { | ||||||
|  |           expect(store.infoMessage).not.to.be.null; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @ -16,6 +16,7 @@ | |||||||
| 
 | 
 | ||||||
| import { newError } from '~/ui/Errors/actions'; | import { newError } from '~/ui/Errors/actions'; | ||||||
| import { setAddressImage } from './providers/imagesActions'; | import { setAddressImage } from './providers/imagesActions'; | ||||||
|  | import { showSnackbar } from './providers/snackbarActions'; | ||||||
| import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions'; | import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions'; | ||||||
| import { toggleView } from '~/views/Settings/actions'; | import { toggleView } from '~/views/Settings/actions'; | ||||||
| 
 | 
 | ||||||
| @ -23,6 +24,7 @@ export { | |||||||
|   newError, |   newError, | ||||||
|   clearStatusLogs, |   clearStatusLogs, | ||||||
|   setAddressImage, |   setAddressImage, | ||||||
|  |   showSnackbar, | ||||||
|   toggleStatusLogs, |   toggleStatusLogs, | ||||||
|   toggleStatusRefresh, |   toggleStatusRefresh, | ||||||
|   toggleView |   toggleView | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ import LockedIcon from 'material-ui/svg-icons/action/lock-outline'; | |||||||
| import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; | import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||||
| import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; | import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; | ||||||
| import SaveIcon from 'material-ui/svg-icons/content/save'; | import SaveIcon from 'material-ui/svg-icons/content/save'; | ||||||
|  | import SendIcon from 'material-ui/svg-icons/content/send'; | ||||||
| import SnoozeIcon from 'material-ui/svg-icons/av/snooze'; | import SnoozeIcon from 'material-ui/svg-icons/av/snooze'; | ||||||
| import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; | import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; | ||||||
| 
 | 
 | ||||||
| @ -38,6 +39,7 @@ export { | |||||||
|   NextIcon, |   NextIcon, | ||||||
|   PrevIcon, |   PrevIcon, | ||||||
|   SaveIcon, |   SaveIcon, | ||||||
|  |   SendIcon, | ||||||
|   SnoozeIcon, |   SnoozeIcon, | ||||||
|   VisibleIcon |   VisibleIcon | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -72,7 +72,7 @@ export default class Header extends Component { | |||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <div className={ styles.tags }> |           <div className={ styles.tags }> | ||||||
|             <Tags tags={ meta.tags } /> |             <Tags tags={ meta.tags.slice() } /> | ||||||
|           </div> |           </div> | ||||||
|           <div className={ styles.balances }> |           <div className={ styles.balances }> | ||||||
|             <Balance |             <Balance | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user