From 06433033d9248787cebf58175ab37214030d6813 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 24 Jan 2017 16:18:23 +0100 Subject: [PATCH] AccountCreate updates (#3988) * Add esjify for mocha + ejs * First pass through, intl + basic smoketests * Create store * Update for renames * Pass store around * createType into store * Move stage into store * Update labels * Define stages * address into store * Add @observer * Retrieve name from store * Store phrase in store * isWindowsPhrase into store * gethAddresses to store * Store manages geth addresses * passwordHint into store * Fix build * rawKey into store * import json files * name set direct from component * No parent change callbacks * canCreate from store * createAccounts into store * expand create tests * Windows phrase testcases * Properly bind newError * FirstRun use of new CreateAccount * Add fix & test for selectedAddress match * Call into store from props * onChangeIdentity fix & test * Phrase set fix & test * RecoveryPhrase tested manually (issues addressed via tests) * Hex import manual test (& tests added for errors) * New eslint update fixes * grumble: set default type from store (with test) * grumble: pass copy of accounts (observable injection) * grumble: Summary owners can be array or array-like --- .../AccountDetails/accountDetails.js | 57 +- .../AccountDetails/accountDetails.spec.js | 42 ++ .../AccountDetailsGeth/accountDetailsGeth.css | 3 +- .../AccountDetailsGeth/accountDetailsGeth.js | 37 +- .../accountDetailsGeth.spec.js | 60 ++ .../CreationType/creationType.js | 59 +- .../CreationType/creationType.spec.js | 76 +++ .../CreateAccount/NewAccount/newAccount.js | 287 ++++---- .../NewAccount/newAccount.spec.js | 161 +++++ .../modals/CreateAccount/NewGeth/newGeth.css | 31 +- .../modals/CreateAccount/NewGeth/newGeth.js | 93 +-- .../CreateAccount/NewGeth/newGeth.spec.js | 66 ++ .../CreateAccount/NewImport/newImport.js | 179 +++-- .../CreateAccount/NewImport/newImport.spec.js | 96 +++ js/src/modals/CreateAccount/RawKey/rawKey.js | 206 +++--- .../CreateAccount/RawKey/rawKey.spec.js | 126 ++++ .../RecoveryPhrase/recoveryPhrase.js | 226 +++---- .../RecoveryPhrase/recoveryPhrase.spec.js | 141 ++++ js/src/modals/CreateAccount/createAccount.js | 466 +++++-------- .../CreateAccount/createAccount.spec.js | 51 ++ .../CreateAccount/createAccount.test.js | 71 ++ .../{recovery-page.ejs => recoveryPage.ejs} | 1 - js/src/modals/CreateAccount/store.js | 379 +++++++++++ js/src/modals/CreateAccount/store.spec.js | 624 ++++++++++++++++++ js/src/modals/FirstRun/firstRun.js | 196 +++--- js/src/modals/FirstRun/firstRun.spec.js | 69 ++ js/src/ui/Icons/index.js | 6 + js/src/views/Accounts/Summary/summary.js | 4 +- 28 files changed, 2825 insertions(+), 988 deletions(-) create mode 100644 js/src/modals/CreateAccount/AccountDetails/accountDetails.spec.js create mode 100644 js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.spec.js create mode 100644 js/src/modals/CreateAccount/CreationType/creationType.spec.js create mode 100644 js/src/modals/CreateAccount/NewAccount/newAccount.spec.js create mode 100644 js/src/modals/CreateAccount/NewGeth/newGeth.spec.js create mode 100644 js/src/modals/CreateAccount/NewImport/newImport.spec.js create mode 100644 js/src/modals/CreateAccount/RawKey/rawKey.spec.js create mode 100644 js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.spec.js create mode 100644 js/src/modals/CreateAccount/createAccount.spec.js create mode 100644 js/src/modals/CreateAccount/createAccount.test.js rename js/src/modals/CreateAccount/{recovery-page.ejs => recoveryPage.ejs} (99%) create mode 100644 js/src/modals/CreateAccount/store.js create mode 100644 js/src/modals/CreateAccount/store.spec.js create mode 100644 js/src/modals/FirstRun/firstRun.spec.js diff --git a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js index 8987133b0..e3bff4403 100644 --- a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js +++ b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js @@ -14,33 +14,54 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Form, Input, InputAddress } from '~/ui'; +@observer export default class AccountDetails extends Component { static propTypes = { - address: PropTypes.string, - name: PropTypes.string, - phrase: PropTypes.string + store: PropTypes.object.isRequired } render () { - const { address, name } = this.props; + const { address, name } = this.props.store; return (
+ } + label={ + + } + readOnly value={ name } /> + } + label={ + + } value={ address } /> { this.renderPhrase() } @@ -49,7 +70,7 @@ export default class AccountDetails extends Component { } renderPhrase () { - const { phrase } = this.props; + const { phrase } = this.props.store; if (!phrase) { return null; @@ -57,10 +78,20 @@ export default class AccountDetails extends Component { return ( + } + label={ + + } + readOnly value={ phrase } /> ); diff --git a/js/src/modals/CreateAccount/AccountDetails/accountDetails.spec.js b/js/src/modals/CreateAccount/AccountDetails/accountDetails.spec.js new file mode 100644 index 000000000..e9b921746 --- /dev/null +++ b/js/src/modals/CreateAccount/AccountDetails/accountDetails.spec.js @@ -0,0 +1,42 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { createStore } from '../createAccount.test.js'; + +import AccountDetails from './'; + +let component; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + + return component; +} + +describe('modals/CreateAccount/AccountDetails', () => { + it('renders with defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.css b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.css index fd4da0710..f622a8e03 100644 --- a/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.css +++ b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.css @@ -14,9 +14,10 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .address { color: #999; - padding-top: 1em; line-height: 1.618em; padding-left: 2em; + padding-top: 1em; } diff --git a/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.js b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.js index 49156c126..bb56595bc 100644 --- a/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.js +++ b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.js @@ -14,29 +14,46 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import styles from './accountDetailsGeth.css'; +@observer export default class AccountDetailsGeth extends Component { static propTypes = { - addresses: PropTypes.array + store: PropTypes.object.isRequired } render () { - const { addresses } = this.props; - - const formatted = addresses.map((address, idx) => { - const comma = !idx ? '' : ((idx === addresses.length - 1) ? ' & ' : ', '); - - return `${comma}${address}`; - }).join(''); + const { gethAddresses } = this.props.store; return (
-
You have imported { addresses.length } addresses from the Geth keystore:
-
{ formatted }
+
+ +
+
+ { this.formatAddresses(gethAddresses) } +
); } + + formatAddresses (addresses) { + return addresses.map((address, index) => { + const comma = !index + ? '' + : ((index === addresses.length - 1) ? ' & ' : ', '); + + return `${comma}${address}`; + }).join(''); + } } diff --git a/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.spec.js b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.spec.js new file mode 100644 index 000000000..d49fe8123 --- /dev/null +++ b/js/src/modals/CreateAccount/AccountDetailsGeth/accountDetailsGeth.spec.js @@ -0,0 +1,60 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { createStore } from '../createAccount.test.js'; + +import AccountDetailsGeth from './'; + +let component; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + + return component; +} + +describe('modals/CreateAccount/AccountDetailsGeth', () => { + it('renders with defaults', () => { + expect(render()).to.be.ok; + }); + + describe('utility', () => { + describe('formatAddresses', () => { + let instance; + + beforeEach(() => { + instance = component.instance(); + }); + + it('renders a single item', () => { + expect(instance.formatAddresses(['one'])).to.equal('one'); + }); + + it('renders multiple items', () => { + expect(instance.formatAddresses(['one', 'two', 'three'])).to.equal('one, two & three'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/CreationType/creationType.js b/js/src/modals/CreateAccount/CreationType/creationType.js index fd602e1d5..06688bdf7 100644 --- a/js/src/modals/CreateAccount/CreationType/creationType.js +++ b/js/src/modals/CreateAccount/CreationType/creationType.js @@ -14,50 +14,81 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import styles from '../createAccount.css'; +@observer export default class CreationType extends Component { static propTypes = { - onChange: PropTypes.func.isRequired - } - - componentWillMount () { - this.props.onChange('fromNew'); + store: PropTypes.object.isRequired } render () { + const { createType } = this.props.store; + return (
+ } value='fromNew' /> + } value='fromPhrase' /> + } value='fromGeth' /> + } value='fromJSON' /> + } value='fromPresale' /> + } value='fromRaw' /> @@ -66,6 +97,8 @@ export default class CreationType extends Component { } onChange = (event) => { - this.props.onChange(event.target.value); + const { store } = this.props; + + store.setCreateType(event.target.value); } } diff --git a/js/src/modals/CreateAccount/CreationType/creationType.spec.js b/js/src/modals/CreateAccount/CreationType/creationType.spec.js new file mode 100644 index 000000000..a04f9182d --- /dev/null +++ b/js/src/modals/CreateAccount/CreationType/creationType.spec.js @@ -0,0 +1,76 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { createStore } from '../createAccount.test.js'; + +import CreationType from './'; + +let component; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + + return component; +} + +describe('modals/CreateAccount/CreationType', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(component).to.be.ok; + }); + + describe('selector', () => { + const SELECT_TYPE = 'fromRaw'; + let selector; + + beforeEach(() => { + store.setCreateType(SELECT_TYPE); + selector = component.find('RadioButtonGroup'); + }); + + it('renders the selector', () => { + expect(selector.get(0)).to.be.ok; + }); + + it('passes the store type to defaultSelected', () => { + expect(selector.props().defaultSelected).to.equal(SELECT_TYPE); + }); + }); + + describe('events', () => { + describe('onChange', () => { + beforeEach(() => { + component.instance().onChange({ target: { value: 'testing' } }); + }); + + it('changes the store createType', () => { + expect(store.createType).to.equal('testing'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index 6cab1de02..cb31e2c76 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -14,86 +14,113 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { IconButton } from 'material-ui'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; -import ActionAutorenew from 'material-ui/svg-icons/action/autorenew'; -import { Form, Input, IdentityIcon } from '~/ui'; - -import ERRORS from '../errors'; +import { Form, Input, IdentityIcon, PasswordStrength } from '~/ui'; +import { RefreshIcon } from '~/ui/Icons'; import styles from '../createAccount.css'; +@observer export default class CreateAccount extends Component { - static contextTypes = { - api: PropTypes.object.isRequired, + static propTypes = { + newError: PropTypes.func.isRequired, store: PropTypes.object.isRequired } - static propTypes = { - onChange: PropTypes.func.isRequired - } - state = { - accountName: '', - accountNameError: ERRORS.noName, accounts: null, - isValidName: false, - isValidPass: true, - passwordHint: '', - password1: '', - password1Error: null, - password2: '', - password2Error: null, selectedAddress: '' } componentWillMount () { - this.createIdentities(); - this.props.onChange(false, {}); + return this.createIdentities(); } render () { - const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error } = this.state; + const { name, nameError, password, passwordRepeat, passwordRepeatError, passwordHint } = this.props.store; return ( + } + label={ + + } onChange={ this.onEditAccountName } + value={ name } /> + } + label={ + + } onChange={ this.onEditPasswordHint } + value={ passwordHint } />
+ } + label={ + + } + onChange={ this.onEditPassword } type='password' - error={ password1Error } - value={ password1 } - onChange={ this.onEditPassword1 } + value={ password } />
+ } + label={ + + } + onChange={ this.onEditPasswordRepeat } type='password' - error={ password2Error } - value={ password2 } - onChange={ this.onEditPassword2 } + value={ passwordRepeat } />
+ { this.renderIdentitySelector() } { this.renderIdentities() } @@ -107,22 +134,24 @@ export default class CreateAccount extends Component { return null; } - const buttons = Object.keys(accounts).map((address) => { - return ( - - ); - }); + const buttons = Object + .keys(accounts) + .map((address) => { + return ( + + ); + }); return ( { buttons } @@ -136,31 +165,29 @@ export default class CreateAccount extends Component { return null; } - const identities = Object.keys(accounts).map((address) => { - return ( -
- -
- ); - }); + const identities = Object + .keys(accounts) + .map((address) => { + return ( +
+ +
+ ); + }); return (
{ identities }
- - + +
@@ -168,122 +195,64 @@ export default class CreateAccount extends Component { } createIdentities = () => { - const { api } = this.context; + const { store } = this.props; - Promise - .all([ - api.parity.generateSecretPhrase(), - api.parity.generateSecretPhrase(), - api.parity.generateSecretPhrase(), - api.parity.generateSecretPhrase(), - api.parity.generateSecretPhrase() - ]) - .then((phrases) => { - return Promise - .all(phrases.map((phrase) => api.parity.phraseToAddress(phrase))) - .then((addresses) => { - const accounts = {}; + return store + .createIdentities() + .then((accounts) => { + const selectedAddress = Object.keys(accounts)[0]; + const { phrase } = accounts[selectedAddress]; - phrases.forEach((phrase, idx) => { - accounts[addresses[idx]] = { - address: addresses[idx], - phrase: phrase - }; - }); + store.setAddress(selectedAddress); + store.setPhrase(phrase); - this.setState({ - selectedAddress: addresses[0], - accounts: accounts - }); - }); + this.setState({ + accounts, + selectedAddress + }); }) .catch((error) => { - console.error('createIdentities', error); - setTimeout(this.createIdentities, 1000); - this.newError(error); + this.props.newError(error); }); } - updateParent = () => { - const { isValidName, isValidPass, accounts, accountName, passwordHint, password1, selectedAddress } = this.state; - const isValid = isValidName && isValidPass; - - this.props.onChange(isValid, { - address: selectedAddress, - name: accountName, - passwordHint, - password: password1, - phrase: accounts[selectedAddress].phrase - }); - } - onChangeIdentity = (event) => { - const address = event.target.value || event.target.getAttribute('value'); + const { store } = this.props; + const selectedAddress = event.target.value || event.target.getAttribute('value'); - if (!address) { + if (!selectedAddress) { return; } - this.setState({ - selectedAddress: address - }, this.updateParent); - } + this.setState({ selectedAddress }, () => { + const { phrase } = this.state.accounts[selectedAddress]; - onEditPasswordHint = (event, passwordHint) => { - this.setState({ - passwordHint + store.setAddress(selectedAddress); + store.setPhrase(phrase); }); } - onEditAccountName = (event) => { - const accountName = event.target.value; - let accountNameError = null; + onEditPasswordHint = (event, passwordHint) => { + const { store } = this.props; - if (!accountName || !accountName.trim().length) { - accountNameError = ERRORS.noName; - } - - this.setState({ - accountName, - accountNameError, - isValidName: !accountNameError - }, this.updateParent); + store.setPasswordHint(passwordHint); } - onEditPassword1 = (event) => { - const password1 = event.target.value; - let password2Error = null; + onEditAccountName = (event, name) => { + const { store } = this.props; - if (password1 !== this.state.password2) { - password2Error = ERRORS.noMatchPassword; - } - - this.setState({ - password1, - password1Error: null, - password2Error, - isValidPass: !password2Error - }, this.updateParent); + store.setName(name); } - onEditPassword2 = (event) => { - const password2 = event.target.value; - let password2Error = null; + onEditPassword = (event, password) => { + const { store } = this.props; - if (password2 !== this.state.password1) { - password2Error = ERRORS.noMatchPassword; - } - - this.setState({ - password2, - password2Error, - isValidPass: !password2Error - }, this.updateParent); + store.setPassword(password); } - newError = (error) => { - const { store } = this.context; + onEditPasswordRepeat = (event, password) => { + const { store } = this.props; - store.dispatch({ type: 'newError', error }); + store.setPasswordRepeat(password); } } diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.spec.js b/js/src/modals/CreateAccount/NewAccount/newAccount.spec.js new file mode 100644 index 000000000..25a7d52bc --- /dev/null +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.spec.js @@ -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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { createApi, createStore } from '../createAccount.test.js'; + +import NewAccount from './'; + +let api; +let component; +let instance; +let store; + +function render () { + api = createApi(); + store = createStore(); + component = shallow( + , + { + context: { api } + } + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/NewAccount', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(component).to.be.ok; + }); + + describe('lifecycle', () => { + describe('componentWillMount', () => { + beforeEach(() => { + return instance.componentWillMount(); + }); + + it('creates initial accounts', () => { + expect(Object.keys(instance.state.accounts).length).to.equal(5); + }); + + it('sets the initial selected value', () => { + expect(instance.state.selectedAddress).to.equal(Object.keys(instance.state.accounts)[0]); + }); + }); + }); + + describe('event handlers', () => { + describe('onChangeIdentity', () => { + let address; + + beforeEach(() => { + address = Object.keys(instance.state.accounts)[3]; + + sinon.spy(store, 'setAddress'); + sinon.spy(store, 'setPhrase'); + instance.onChangeIdentity({ target: { value: address } }); + }); + + afterEach(() => { + store.setAddress.restore(); + store.setPhrase.restore(); + }); + + it('sets the state with the new value', () => { + expect(instance.state.selectedAddress).to.equal(address); + }); + + it('sets the new address on the store', () => { + expect(store.setAddress).to.have.been.calledWith(address); + }); + + it('sets the new phrase on the store', () => { + expect(store.setPhrase).to.have.been.calledWith(instance.state.accounts[address].phrase); + }); + }); + + describe('onEditPassword', () => { + beforeEach(() => { + sinon.spy(store, 'setPassword'); + instance.onEditPassword(null, 'test'); + }); + + afterEach(() => { + store.setPassword.restore(); + }); + + it('calls into the store', () => { + expect(store.setPassword).to.have.been.calledWith('test'); + }); + }); + + describe('onEditPasswordRepeat', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordRepeat'); + instance.onEditPasswordRepeat(null, 'test'); + }); + + afterEach(() => { + store.setPasswordRepeat.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordRepeat).to.have.been.calledWith('test'); + }); + }); + + describe('onEditPasswordHint', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordHint'); + instance.onEditPasswordHint(null, 'test'); + }); + + afterEach(() => { + store.setPasswordHint.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordHint).to.have.been.calledWith('test'); + }); + }); + + describe('onEditAccountName', () => { + beforeEach(() => { + sinon.spy(store, 'setName'); + instance.onEditAccountName(null, 'test'); + }); + + afterEach(() => { + store.setName.restore(); + }); + + it('calls into the store', () => { + expect(store.setName).to.have.been.calledWith('test'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/NewGeth/newGeth.css b/js/src/modals/CreateAccount/NewGeth/newGeth.css index 94c143b03..f16226a4d 100644 --- a/js/src/modals/CreateAccount/NewGeth/newGeth.css +++ b/js/src/modals/CreateAccount/NewGeth/newGeth.css @@ -15,29 +15,28 @@ /* along with Parity. If not, see . */ .list { -} - -.list input+div>div { - top: 13px; + input+div>div { + top: 13px; + } } .selection { display: inline-block; margin-bottom: 0.5em; -} -.selection .icon { - display: inline-block; -} + .icon { + display: inline-block; + } -.selection .detail { - display: inline-block; -} + .detail { + display: inline-block; -.detail .address { - color: #aaa; -} + .address { + color: #aaa; + } -.detail .balance { - font-family: 'Roboto Mono', monospace; + .balance { + font-family: 'Roboto Mono', monospace; + } + } } diff --git a/js/src/modals/CreateAccount/NewGeth/newGeth.js b/js/src/modals/CreateAccount/NewGeth/newGeth.js index 8566f76b4..4c40f3b13 100644 --- a/js/src/modals/CreateAccount/NewGeth/newGeth.js +++ b/js/src/modals/CreateAccount/NewGeth/newGeth.js @@ -14,63 +14,68 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Checkbox } from 'material-ui'; import { IdentityIcon } from '~/ui'; import styles from './newGeth.css'; +@observer export default class NewGeth extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { - accounts: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired - } - - state = { - available: [] - } - - componentDidMount () { - this.loadAvailable(); + store: PropTypes.object.isRequired } render () { - const { available } = this.state; + const { gethAccountsAvailable, gethAddresses } = this.props.store; - if (!available.length) { + if (!gethAccountsAvailable.length) { return ( -
There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance
+
+ +
); } - const checkboxes = available.map((account) => { + const checkboxes = gethAccountsAvailable.map((account) => { + const onSelect = (event) => this.onSelectAddress(event, account.address); + const label = (
-
{ account.address }
-
{ account.balance } ETH
+
+ { account.address } +
+
+ { account.balance } ETH +
); return ( ); }); @@ -82,51 +87,9 @@ export default class NewGeth extends Component { ); } - onSelect = (event, checked) => { - const address = event.target.getAttribute('data-address'); + onSelectAddress = (event, address) => { + const { store } = this.props; - if (!address) { - return; - } - - const { available } = this.state; - const account = available.find((_account) => _account.address === address); - - account.checked = checked; - const selected = available.filter((_account) => _account.checked); - - this.setState({ - available - }); - - this.props.onChange(selected.length, selected.map((account) => account.address)); - } - - loadAvailable = () => { - const { api } = this.context; - const { accounts } = this.props; - - api.parity - .listGethAccounts() - .then((_addresses) => { - const addresses = (addresses || []).filter((address) => !accounts[address]); - - return Promise - .all(addresses.map((address) => api.eth.getBalance(address))) - .then((balances) => { - this.setState({ - available: addresses.map((address, idx) => { - return { - address, - balance: api.util.fromWei(balances[idx]).toFormat(5), - checked: false - }; - }) - }); - }); - }) - .catch((error) => { - console.error('loadAvailable', error); - }); + store.selectGethAccount(address); } } diff --git a/js/src/modals/CreateAccount/NewGeth/newGeth.spec.js b/js/src/modals/CreateAccount/NewGeth/newGeth.spec.js new file mode 100644 index 000000000..d929ce62c --- /dev/null +++ b/js/src/modals/CreateAccount/NewGeth/newGeth.spec.js @@ -0,0 +1,66 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { createStore } from '../createAccount.test.js'; + +import NewGeth from './'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/NewGeth', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(render()).to.be.ok; + }); + + describe('events', () => { + describe('onSelectAddress', () => { + beforeEach(() => { + sinon.spy(store, 'selectGethAccount'); + instance.onSelectAddress(null, 'testAddress'); + }); + + afterEach(() => { + store.selectGethAccount.restore(); + }); + + it('calls into the store', () => { + expect(store.selectGethAccount).to.have.been.calledWith('testAddress'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 6e064770f..1de7ee9d1 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -14,91 +14,114 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { FloatingActionButton } from 'material-ui'; +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; -import { FloatingActionButton } from 'material-ui'; -import EditorAttachFile from 'material-ui/svg-icons/editor/attach-file'; +import { FormattedMessage } from 'react-intl'; import { Form, Input } from '~/ui'; - -import ERRORS from '../errors'; +import { AttachFileIcon } from '~/ui/Icons'; import styles from '../createAccount.css'; -const FAKEPATH = 'C:\\fakepath\\'; const STYLE_HIDDEN = { display: 'none' }; +@observer export default class NewImport extends Component { static propTypes = { - onChange: PropTypes.func.isRequired - } - - state = { - accountName: '', - accountNameError: ERRORS.noName, - isValidFile: false, - isValidPass: true, - isValidName: false, - password: '', - passwordError: null, - passwordHint: '', - walletFile: '', - walletFileError: ERRORS.noFile, - walletJson: '' - } - - componentWillMount () { - this.props.onChange(false, {}); + store: PropTypes.object.isRequired } render () { + const { name, nameError, password, passwordHint, walletFile, walletFileError } = this.props.store; + return (
+ } + label={ + + } + onChange={ this.onEditName } + value={ name } /> + } + label={ + + } onChange={ this.onEditpasswordHint } + value={ passwordHint } />
+ } + label={ + + } type='password' - error={ this.state.passwordError } - value={ this.state.password } onChange={ this.onEditPassword } + value={ password } />
+ } + label={ + + } + value={ walletFile } />
- +
@@ -107,73 +130,37 @@ export default class NewImport extends Component { } onFileChange = (event) => { - const el = event.target; - const error = ERRORS.noFile; + const { store } = this.props; - if (el.files.length) { + if (event.target.files.length) { const reader = new FileReader(); - reader.onload = (event) => { - this.setState({ - walletJson: event.target.result, - walletFileError: null, - isValidFile: true - }, this.updateParent); - }; - reader.readAsText(el.files[0]); + reader.onload = (event) => store.setWalletJson(event.target.result); + reader.readAsText(event.target.files[0]); } - this.setState({ - walletFile: el.value.replace(FAKEPATH, ''), - walletFileError: error, - isValidFile: false - }, this.updateParent); + store.setWalletFile(event.target.value); } openFileDialog = () => { ReactDOM.findDOMNode(this.refs.fileUpload).click(); } - updateParent = () => { - const valid = this.state.isValidName && this.state.isValidPass && this.state.isValidFile; + onEditName = (event, name) => { + const { store } = this.props; - this.props.onChange(valid, { - name: this.state.accountName, - passwordHint: this.state.passwordHint, - password: this.state.password, - phrase: null, - json: this.state.walletJson - }); + store.setName(name); + } + + onEditPassword = (event, password) => { + const { store } = this.props; + + store.setPassword(password); } onEditPasswordHint = (event, passwordHint) => { - this.setState({ - passwordHint - }); - } + const { store } = this.props; - onEditAccountName = (event) => { - const accountName = event.target.value; - let accountNameError = null; - - if (!accountName || !accountName.trim().length) { - accountNameError = ERRORS.noName; - } - - this.setState({ - accountName, - accountNameError, - isValidName: !accountNameError - }, this.updateParent); - } - - onEditPassword = (event) => { - const password = event.target.value; - - this.setState({ - password, - passwordError: null, - isValidPass: true - }, this.updateParent); + store.setPasswordHint(passwordHint); } } diff --git a/js/src/modals/CreateAccount/NewImport/newImport.spec.js b/js/src/modals/CreateAccount/NewImport/newImport.spec.js new file mode 100644 index 000000000..273f31207 --- /dev/null +++ b/js/src/modals/CreateAccount/NewImport/newImport.spec.js @@ -0,0 +1,96 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { createStore } from '../createAccount.test.js'; + +import NewImport from './'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/NewImport', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(render()).to.be.ok; + }); + + describe('events', () => { + describe('onEditName', () => { + beforeEach(() => { + sinon.spy(store, 'setName'); + instance.onEditName(null, 'testValue'); + }); + + afterEach(() => { + store.setName.restore(); + }); + + it('calls into the store', () => { + expect(store.setName).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPassword', () => { + beforeEach(() => { + sinon.spy(store, 'setPassword'); + instance.onEditPassword(null, 'testValue'); + }); + + afterEach(() => { + store.setPassword.restore(); + }); + + it('calls into the store', () => { + expect(store.setPassword).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPasswordHint', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordHint'); + instance.onEditPasswordHint(null, 'testValue'); + }); + + afterEach(() => { + store.setPasswordHint.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordHint).to.have.been.calledWith('testValue'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index 5a60fb5df..3f8871354 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -14,172 +14,152 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; -import { Form, Input } from '~/ui'; +import { Form, Input, PasswordStrength } from '~/ui'; import styles from '../createAccount.css'; -import ERRORS from '../errors'; - +@observer export default class RawKey extends Component { static contextTypes = { api: PropTypes.object.isRequired } static propTypes = { - onChange: PropTypes.func.isRequired - } - - state = { - accountName: '', - accountNameError: ERRORS.noName, - isValidKey: false, - isValidName: false, - isValidPass: true, - passwordHint: '', - password1: '', - password1Error: null, - password2: '', - password2Error: null, - rawKey: '', - rawKeyError: ERRORS.noKey - } - - componentWillMount () { - this.props.onChange(false, {}); + store: PropTypes.object.isRequired } render () { - const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error, rawKey, rawKeyError } = this.state; + const { name, nameError, password, passwordRepeat, passwordRepeatError, passwordHint, rawKey, rawKeyError } = this.props.store; return ( + } + label={ + + } onChange={ this.onEditKey } + value={ rawKey } /> + } + label={ + + } + onChange={ this.onEditName } + value={ name } /> + } + label={ + + } onChange={ this.onEditPasswordHint } + value={ passwordHint } />
+ } + label={ + + } + onChange={ this.onEditPassword } type='password' - error={ password1Error } - value={ password1 } - onChange={ this.onEditPassword1 } + value={ password } />
+ } + label={ + + } + onChange={ this.onEditPasswordRepeat } type='password' - error={ password2Error } - value={ password2 } - onChange={ this.onEditPassword2 } + value={ passwordRepeat } />
+ ); } - updateParent = () => { - const { isValidName, isValidPass, isValidKey, accountName, passwordHint, password1, rawKey } = this.state; - const isValid = isValidName && isValidPass && isValidKey; + onEditName = (event, name) => { + const { store } = this.props; - this.props.onChange(isValid, { - name: accountName, - passwordHint, - password: password1, - rawKey - }); + store.setName(name); } - onEditPasswordHint = (event, value) => { - this.setState({ - passwordHint: value - }); + onEditPasswordHint = (event, passwordHint) => { + const { store } = this.props; + + store.setPasswordHint(passwordHint); } - onEditKey = (event) => { - const { api } = this.context; - const rawKey = event.target.value; - let rawKeyError = null; + onEditPassword = (event, password) => { + const { store } = this.props; - if (!rawKey || !rawKey.trim().length) { - rawKeyError = ERRORS.noKey; - } else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !api.util.isHex(rawKey)) { - rawKeyError = ERRORS.invalidKey; - } - - this.setState({ - rawKey, - rawKeyError, - isValidKey: !rawKeyError - }, this.updateParent); + store.setPassword(password); } - onEditAccountName = (event) => { - const accountName = event.target.value; - let accountNameError = null; + onEditPasswordRepeat = (event, password) => { + const { store } = this.props; - if (!accountName || !accountName.trim().length) { - accountNameError = ERRORS.noName; - } - - this.setState({ - accountName, - accountNameError, - isValidName: !accountNameError - }, this.updateParent); + store.setPasswordRepeat(password); } - onEditPassword1 = (event) => { - const password1 = event.target.value; - let password2Error = null; + onEditKey = (event, rawKey) => { + const { store } = this.props; - if (password1 !== this.state.password2) { - password2Error = ERRORS.noMatchPassword; - } - - this.setState({ - password1, - password1Error: null, - password2Error, - isValidPass: !password2Error - }, this.updateParent); - } - - onEditPassword2 = (event) => { - const password2 = event.target.value; - let password2Error = null; - - if (password2 !== this.state.password1) { - password2Error = ERRORS.noMatchPassword; - } - - this.setState({ - password2, - password2Error, - isValidPass: !password2Error - }, this.updateParent); + store.setRawKey(rawKey); } } diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.spec.js b/js/src/modals/CreateAccount/RawKey/rawKey.spec.js new file mode 100644 index 000000000..06ff0c475 --- /dev/null +++ b/js/src/modals/CreateAccount/RawKey/rawKey.spec.js @@ -0,0 +1,126 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { createStore } from '../createAccount.test.js'; + +import RawKey from './'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/RawKey', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(component).to.be.ok; + }); + + describe('events', () => { + describe('onEditName', () => { + beforeEach(() => { + sinon.spy(store, 'setName'); + instance.onEditName(null, 'testValue'); + }); + + afterEach(() => { + store.setName.restore(); + }); + + it('calls into the store', () => { + expect(store.setName).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditKey', () => { + beforeEach(() => { + sinon.spy(store, 'setRawKey'); + instance.onEditKey(null, 'testValue'); + }); + + afterEach(() => { + store.setRawKey.restore(); + }); + + it('calls into the store', () => { + expect(store.setRawKey).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPassword', () => { + beforeEach(() => { + sinon.spy(store, 'setPassword'); + instance.onEditPassword(null, 'testValue'); + }); + + afterEach(() => { + store.setPassword.restore(); + }); + + it('calls into the store', () => { + expect(store.setPassword).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPasswordRepeat', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordRepeat'); + instance.onEditPasswordRepeat(null, 'testValue'); + }); + + afterEach(() => { + store.setPasswordRepeat.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordRepeat).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPasswordHint', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordHint'); + instance.onEditPasswordHint(null, 'testValue'); + }); + + afterEach(() => { + store.setPasswordHint.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordHint).to.have.been.calledWith('testValue'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index da91b4ba2..1da1e6dba 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -14,183 +14,165 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Checkbox } from 'material-ui'; -import { Form, Input } from '~/ui'; +import { Form, Input, PasswordStrength } from '~/ui'; import styles from '../createAccount.css'; -import ERRORS from '../errors'; - +@observer export default class RecoveryPhrase extends Component { static propTypes = { - onChange: PropTypes.func.isRequired - } - - state = { - accountName: '', - accountNameError: ERRORS.noName, - isValidPass: true, - isValidName: false, - isValidPhrase: true, - passwordHint: '', - password1: '', - password1Error: null, - password2: '', - password2Error: null, - recoveryPhrase: '', - recoveryPhraseError: null, - windowsPhrase: false - } - - componentWillMount () { - this.props.onChange(false, {}); + store: PropTypes.object.isRequired } render () { - const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error, recoveryPhrase, windowsPhrase } = this.state; + const { isWindowsPhrase, name, nameError, password, passwordRepeat, passwordRepeatError, passwordHint, phrase } = this.props.store; return (
+ } + label={ + + } onChange={ this.onEditPhrase } + value={ phrase } /> + } + label={ + + } + onChange={ this.onEditName } + value={ name } /> + } + label={ + + } onChange={ this.onEditPasswordHint } + value={ passwordHint } />
+ } + label={ + + } + onChange={ this.onEditPassword } type='password' - error={ password1Error } - value={ password1 } - onChange={ this.onEditPassword1 } + value={ password } />
+ } + label={ + + } + onChange={ this.onEditPasswordRepeat } type='password' - error={ password2Error } - value={ password2 } - onChange={ this.onEditPassword2 } + value={ passwordRepeat } />
+ + } onCheck={ this.onToggleWindowsPhrase } /> ); } - updateParent = () => { - const { accountName, isValidName, isValidPass, isValidPhrase, password1, passwordHint, recoveryPhrase, windowsPhrase } = this.state; - const isValid = isValidName && isValidPass && isValidPhrase; - - this.props.onChange(isValid, { - name: accountName, - password: password1, - passwordHint, - phrase: recoveryPhrase, - windowsPhrase - }); - } - - onEditPasswordHint = (event, value) => { - this.setState({ - passwordHint: value - }); - } - onToggleWindowsPhrase = (event) => { - this.setState({ - windowsPhrase: !this.state.windowsPhrase - }, this.updateParent); + const { store } = this.props; + + store.setWindowsPhrase(!store.isWindowsPhrase); } - onEditPhrase = (event) => { - const recoveryPhrase = event.target.value - .toLowerCase() // wordlists are lowercase - .trim() // remove whitespace at both ends - .replace(/\s/g, ' ') // replace any whitespace with single space - .replace(/ +/g, ' '); // replace multiple spaces with a single space + onEditPhrase = (event, phrase) => { + const { store } = this.props; - const phraseParts = recoveryPhrase - .split(' ') - .map((part) => part.trim()) - .filter((part) => part.length); - - this.setState({ - recoveryPhrase: phraseParts.join(' '), - recoveryPhraseError: null, - isValidPhrase: true - }, this.updateParent); + store.setPhrase(phrase); } - onEditAccountName = (event) => { - const accountName = event.target.value; - let accountNameError = null; + onEditName = (event, name) => { + const { store } = this.props; - if (!accountName || !accountName.trim().length) { - accountNameError = ERRORS.noName; - } - - this.setState({ - accountName, - accountNameError, - isValidName: !accountNameError - }, this.updateParent); + store.setName(name); } - onEditPassword1 = (event) => { - const password1 = event.target.value; - let password2Error = null; + onEditPassword = (event, password) => { + const { store } = this.props; - if (password1 !== this.state.password2) { - password2Error = ERRORS.noMatchPassword; - } - - this.setState({ - password1, - password1Error: null, - password2Error, - isValidPass: !password2Error - }, this.updateParent); + store.setPassword(password); } - onEditPassword2 = (event) => { - const password2 = event.target.value; - let password2Error = null; + onEditPasswordRepeat = (event, password) => { + const { store } = this.props; - if (password2 !== this.state.password1) { - password2Error = ERRORS.noMatchPassword; - } + store.setPasswordRepeat(password); + } - this.setState({ - password2, - password2Error, - isValidPass: !password2Error - }, this.updateParent); + onEditPasswordHint = (event, passwordHint) => { + const { store } = this.props; + + store.setPasswordHint(passwordHint); } } diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.spec.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.spec.js new file mode 100644 index 000000000..cd4f3ad45 --- /dev/null +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.spec.js @@ -0,0 +1,141 @@ +// 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { createStore } from '../createAccount.test.js'; + +import RecoveryPhrase from './'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/RecoveryPhrase', () => { + beforeEach(() => { + render(); + }); + + it('renders with defaults', () => { + expect(component).to.be.ok; + }); + + describe('event handlers', () => { + describe('onEditName', () => { + beforeEach(() => { + sinon.spy(store, 'setName'); + instance.onEditName(null, 'testValue'); + }); + + afterEach(() => { + store.setName.restore(); + }); + + it('calls into the store', () => { + expect(store.setName).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPhrase', () => { + beforeEach(() => { + sinon.spy(store, 'setPhrase'); + instance.onEditPhrase(null, 'testValue'); + }); + + afterEach(() => { + store.setPhrase.restore(); + }); + + it('calls into the store', () => { + expect(store.setPhrase).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPassword', () => { + beforeEach(() => { + sinon.spy(store, 'setPassword'); + instance.onEditPassword(null, 'testValue'); + }); + + afterEach(() => { + store.setPassword.restore(); + }); + + it('calls into the store', () => { + expect(store.setPassword).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPasswordRepeat', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordRepeat'); + instance.onEditPasswordRepeat(null, 'testValue'); + }); + + afterEach(() => { + store.setPasswordRepeat.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordRepeat).to.have.been.calledWith('testValue'); + }); + }); + + describe('onEditPasswordHint', () => { + beforeEach(() => { + sinon.spy(store, 'setPasswordHint'); + instance.onEditPasswordHint(null, 'testValue'); + }); + + afterEach(() => { + store.setPasswordHint.restore(); + }); + + it('calls into the store', () => { + expect(store.setPasswordHint).to.have.been.calledWith('testValue'); + }); + }); + + describe('onToggleWindowsPhrase', () => { + beforeEach(() => { + sinon.spy(store, 'setWindowsPhrase'); + instance.onToggleWindowsPhrase(); + }); + + afterEach(() => { + store.setWindowsPhrase.restore(); + }); + + it('calls into the store', () => { + expect(store.setWindowsPhrase).to.have.been.calledWith(true); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index 7c00510d4..334a278f6 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -14,17 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; -import ActionDone from 'material-ui/svg-icons/action/done'; -import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; -import ContentClear from 'material-ui/svg-icons/content/clear'; -import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; -import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; -import PrintIcon from 'material-ui/svg-icons/action/print'; - -import { Button, Modal, Warning } from '~/ui'; +import { createIdentityImg } from '~/api/util/identity'; +import { newError } from '~/redux/actions'; +import { Button, Modal } from '~/ui'; +import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons'; +import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg'; import AccountDetails from './AccountDetails'; import AccountDetailsGeth from './AccountDetailsGeth'; @@ -34,405 +34,271 @@ import NewGeth from './NewGeth'; import NewImport from './NewImport'; import RawKey from './RawKey'; import RecoveryPhrase from './RecoveryPhrase'; - -import { createIdentityImg } from '~/api/util/identity'; +import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE } from './store'; import print from './print'; -import recoveryPage from './recovery-page.ejs'; -import ParityLogo from '../../../assets/images/parity-logo-black-no-text.svg'; +import recoveryPage from './recoveryPage.ejs'; const TITLES = { - type: 'creation type', - create: 'create account', - info: 'account information', - import: 'import wallet' + type: ( + + ), + create: ( + + ), + info: ( + + ), + import: ( + + ) }; const STAGE_NAMES = [TITLES.type, TITLES.create, TITLES.info]; const STAGE_IMPORT = [TITLES.type, TITLES.import, TITLES.info]; -export default class CreateAccount extends Component { +@observer +class CreateAccount extends Component { static contextTypes = { - api: PropTypes.object.isRequired, - store: PropTypes.object.isRequired + api: PropTypes.object.isRequired } static propTypes = { accounts: PropTypes.object.isRequired, + newError: PropTypes.func.isRequired, onClose: PropTypes.func, onUpdate: PropTypes.func } - state = { - address: null, - name: null, - passwordHint: null, - password: null, - phrase: null, - windowsPhrase: false, - rawKey: null, - json: null, - canCreate: false, - createType: null, - gethAddresses: [], - stage: 0 - } + store = new Store(this.context.api, this.props.accounts); render () { - const { createType, stage } = this.state; - const steps = createType === 'fromNew' - ? STAGE_NAMES - : STAGE_IMPORT; + const { createType, stage } = this.store; return ( - { this.renderWarning() } { this.renderPage() } ); } renderPage () { - const { createType, stage } = this.state; - const { accounts } = this.props; + const { createType, stage } = this.store; switch (stage) { - case 0: + case STAGE_SELECT_TYPE: return ( - + ); - case 1: + case STAGE_CREATE: if (createType === 'fromNew') { return ( - + ); } if (createType === 'fromGeth') { return ( - + ); } if (createType === 'fromPhrase') { return ( - + ); } if (createType === 'fromRaw') { return ( - + ); } return ( - + ); - case 2: + case STAGE_INFO: if (createType === 'fromGeth') { return ( - + ); } return ( - + ); } } renderDialogActions () { - const { createType, stage } = this.state; + const { createType, canCreate, isBusy, stage } = this.store; + + const cancelBtn = ( +