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;
|
||||||
|
|
||||||
this.isAccount = !!uuid;
|
transaction(() => {
|
||||||
this.address = address;
|
this.isAccount = !!uuid;
|
||||||
this.meta = Object.assign({}, meta || {});
|
this.address = address;
|
||||||
this.name = name || '';
|
this.meta = meta || {};
|
||||||
|
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'
|
||||||
return [ cancelBtn, changeBtn ];
|
defaultMessage='Change' />
|
||||||
}
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
console.error('passwordManager::handleTestPassword', e);
|
|
||||||
this.setState({ waiting: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangePassword = () => {
|
|
||||||
const { account, showSnackbar, onClose } = this.props;
|
|
||||||
const { currentPass, newPass, repeatNewPass, passwordHint } = this.state;
|
|
||||||
|
|
||||||
if (repeatNewPass !== newPass) {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
onClick={ this.changePassword } />
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const meta = Object.assign({}, account.meta, {
|
onActivateChangeTab = () => {
|
||||||
passwordHint
|
this.store.setActionTab(CHANGE_ACTION);
|
||||||
});
|
}
|
||||||
|
|
||||||
return Promise.all([
|
onActivateTestTab = () => {
|
||||||
this.context
|
this.store.setActionTab(TEST_ACTION);
|
||||||
.api.parity
|
}
|
||||||
.setAccountMeta(account.address, meta),
|
|
||||||
|
|
||||||
this.context
|
onEditCurrentPassword = (event, password) => {
|
||||||
.api.parity
|
this.store.setPassword(password);
|
||||||
.changePassword(account.address, currentPass, newPass)
|
}
|
||||||
])
|
|
||||||
.then(() => {
|
onEditNewPassword = (event, password) => {
|
||||||
showSnackbar(<div>Your password has been successfully changed.</div>);
|
this.store.setNewPassword(password);
|
||||||
this.setState({ waiting: false, showMessage: false });
|
}
|
||||||
onClose();
|
|
||||||
});
|
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();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((error) => {
|
||||||
console.error('passwordManager::handleChangePassword', e);
|
newError(error);
|
||||||
this.setState({ waiting: false });
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testPassword = () => {
|
||||||
|
return this.store
|
||||||
|
.testPassword()
|
||||||
|
.catch((error) => {
|
||||||
|
newError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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