Account view updates (#4008)
* Fix null account render issue, add tests * Add tests for #3999 fix (merged in #4000) * Only include sinon-as-promised globally for mocha * Move transactions state into tested store * Add esjify for mocha + ejs (cherry-picked) * Extract store state into store, test it * Use address (as per PR comments) * Fix failing test after master merge
This commit is contained in:
parent
881066243b
commit
602a4429cc
@ -43,8 +43,8 @@
|
|||||||
"lint:css": "stylelint ./src/**/*.css",
|
"lint:css": "stylelint ./src/**/*.css",
|
||||||
"lint:js": "eslint --ignore-path .gitignore ./src/",
|
"lint:js": "eslint --ignore-path .gitignore ./src/",
|
||||||
"lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
"lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
||||||
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
"test": "NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
|
||||||
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'",
|
||||||
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
||||||
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && (rm -rf .npmjs/node_modules)",
|
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && (rm -rf .npmjs/node_modules)",
|
||||||
"prepush": "npm run lint:cached"
|
"prepush": "npm run lint:cached"
|
||||||
@ -80,6 +80,7 @@
|
|||||||
"coveralls": "2.11.15",
|
"coveralls": "2.11.15",
|
||||||
"css-loader": "0.26.1",
|
"css-loader": "0.26.1",
|
||||||
"ejs-loader": "0.3.0",
|
"ejs-loader": "0.3.0",
|
||||||
|
"ejsify": "1.0.0",
|
||||||
"enzyme": "2.7.0",
|
"enzyme": "2.7.0",
|
||||||
"eslint": "3.11.1",
|
"eslint": "3.11.1",
|
||||||
"eslint-config-semistandard": "7.0.0",
|
"eslint-config-semistandard": "7.0.0",
|
||||||
|
6
js/src/3rdparty/etherscan/account.js
vendored
6
js/src/3rdparty/etherscan/account.js
vendored
@ -49,17 +49,17 @@ function transactions (address, page, test = false) {
|
|||||||
// page offset from 0
|
// page offset from 0
|
||||||
return _call('txlist', {
|
return _call('txlist', {
|
||||||
address: address,
|
address: address,
|
||||||
page: (page || 0) + 1,
|
|
||||||
offset: PAGE_SIZE,
|
offset: PAGE_SIZE,
|
||||||
|
page: (page || 0) + 1,
|
||||||
sort: 'desc'
|
sort: 'desc'
|
||||||
}, test).then((transactions) => {
|
}, test).then((transactions) => {
|
||||||
return transactions.map((tx) => {
|
return transactions.map((tx) => {
|
||||||
return {
|
return {
|
||||||
|
blockNumber: new BigNumber(tx.blockNumber || 0),
|
||||||
from: util.toChecksumAddress(tx.from),
|
from: util.toChecksumAddress(tx.from),
|
||||||
to: util.toChecksumAddress(tx.to),
|
|
||||||
hash: tx.hash,
|
hash: tx.hash,
|
||||||
blockNumber: new BigNumber(tx.blockNumber),
|
|
||||||
timeStamp: tx.timeStamp,
|
timeStamp: tx.timeStamp,
|
||||||
|
to: util.toChecksumAddress(tx.to),
|
||||||
value: tx.value
|
value: tx.value
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
38
js/src/3rdparty/etherscan/helpers.spec.js
vendored
Normal file
38
js/src/3rdparty/etherscan/helpers.spec.js
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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 nock from 'nock';
|
||||||
|
import { stringify } from 'qs';
|
||||||
|
|
||||||
|
import { url } from './links';
|
||||||
|
|
||||||
|
function mockget (requests, test) {
|
||||||
|
let scope = nock(url(test));
|
||||||
|
|
||||||
|
requests.forEach((request) => {
|
||||||
|
scope = scope
|
||||||
|
.get(`/api?${stringify(request.query)}`)
|
||||||
|
.reply(request.code || 200, () => {
|
||||||
|
return { result: request.reply };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
mockget
|
||||||
|
};
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import 'sinon-as-promised';
|
|
||||||
|
|
||||||
import Eth from './eth';
|
import Eth from './eth';
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
// 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';
|
import sinon from 'sinon';
|
||||||
import 'sinon-as-promised';
|
|
||||||
|
|
||||||
import Personal from './personal';
|
import Personal from './personal';
|
||||||
|
|
||||||
|
@ -50,8 +50,7 @@ export default class Actionbar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarGroup
|
<ToolbarGroup className={ styles.toolbuttons }>
|
||||||
className={ styles.toolbuttons }>
|
|
||||||
{ buttons }
|
{ buttons }
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,7 @@ import styles from './certifications.css';
|
|||||||
|
|
||||||
class Certifications extends Component {
|
class Certifications extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
certifications: PropTypes.array.isRequired,
|
certifications: PropTypes.array.isRequired,
|
||||||
dappsUrl: PropTypes.string.isRequired
|
dappsUrl: PropTypes.string.isRequired
|
||||||
}
|
}
|
||||||
@ -60,10 +60,10 @@ class Certifications extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (_, initProps) {
|
function mapStateToProps (_, initProps) {
|
||||||
const { account } = initProps;
|
const { address } = initProps;
|
||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const certifications = state.certifications[account] || [];
|
const certifications = state.certifications[address] || [];
|
||||||
const dappsUrl = state.api.dappsUrl;
|
const dappsUrl = state.api.dappsUrl;
|
||||||
|
|
||||||
return { certifications, dappsUrl };
|
return { certifications, dappsUrl };
|
||||||
|
@ -22,13 +22,16 @@ import CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
|
|||||||
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
||||||
import ContractIcon from 'material-ui/svg-icons/action/code';
|
import ContractIcon from 'material-ui/svg-icons/action/code';
|
||||||
import DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
import DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
||||||
|
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||||
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
import LockedIcon from 'material-ui/svg-icons/action/lock-outline';
|
import EditIcon from 'material-ui/svg-icons/content/create';
|
||||||
|
import LockedIcon from 'material-ui/svg-icons/action/lock';
|
||||||
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 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 VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
||||||
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||||
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
||||||
|
|
||||||
@ -41,13 +44,16 @@ export {
|
|||||||
ComputerIcon,
|
ComputerIcon,
|
||||||
ContractIcon,
|
ContractIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
|
DeleteIcon,
|
||||||
DoneIcon,
|
DoneIcon,
|
||||||
|
EditIcon,
|
||||||
LockedIcon,
|
LockedIcon,
|
||||||
NextIcon,
|
NextIcon,
|
||||||
PrevIcon,
|
PrevIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
SnoozeIcon,
|
SnoozeIcon,
|
||||||
|
VerifyIcon,
|
||||||
VisibleIcon,
|
VisibleIcon,
|
||||||
VpnIcon
|
VpnIcon
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// 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 React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags } from '~/ui';
|
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags } from '~/ui';
|
||||||
import CopyToClipboard from '~/ui/CopyToClipboard';
|
import CopyToClipboard from '~/ui/CopyToClipboard';
|
||||||
@ -26,50 +27,45 @@ export default class Header extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
className: PropTypes.string,
|
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
isContract: PropTypes.bool,
|
className: PropTypes.string,
|
||||||
hideName: PropTypes.bool
|
hideName: PropTypes.bool,
|
||||||
|
isContract: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: '',
|
|
||||||
children: null,
|
children: null,
|
||||||
isContract: false,
|
className: '',
|
||||||
hideName: false
|
hideName: false,
|
||||||
|
isContract: false
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, balance, className, children, hideName } = this.props;
|
const { account, balance, children, className, hideName } = this.props;
|
||||||
const { address, meta, uuid } = account;
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuidText = !uuid
|
const { address } = account;
|
||||||
? null
|
const meta = account.meta || {};
|
||||||
: <div className={ styles.uuidline }>uuid: { uuid }</div>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className }>
|
||||||
<Container>
|
<Container>
|
||||||
<IdentityIcon
|
<IdentityIcon address={ address } />
|
||||||
address={ address } />
|
|
||||||
<div className={ styles.floatleft }>
|
<div className={ styles.floatleft }>
|
||||||
{ this.renderName(address) }
|
{ this.renderName() }
|
||||||
|
|
||||||
<div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }>
|
<div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }>
|
||||||
<CopyToClipboard data={ address } />
|
<CopyToClipboard data={ address } />
|
||||||
<div className={ styles.address }>{ address }</div>
|
<div className={ styles.address }>{ address }</div>
|
||||||
</div>
|
</div>
|
||||||
|
{ this.renderUuid() }
|
||||||
{ uuidText }
|
|
||||||
<div className={ styles.infoline }>
|
<div className={ styles.infoline }>
|
||||||
{ meta.description }
|
{ meta.description }
|
||||||
</div>
|
</div>
|
||||||
{ this.renderTxCount() }
|
{ this.renderTxCount() }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={ styles.tags }>
|
<div className={ styles.tags }>
|
||||||
<Tags tags={ meta.tags } />
|
<Tags tags={ meta.tags } />
|
||||||
</div>
|
</div>
|
||||||
@ -77,9 +73,7 @@ export default class Header extends Component {
|
|||||||
<Balance
|
<Balance
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance } />
|
balance={ balance } />
|
||||||
<Certifications
|
<Certifications address={ address } />
|
||||||
account={ account.address }
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{ children }
|
{ children }
|
||||||
</Container>
|
</Container>
|
||||||
@ -87,15 +81,22 @@ export default class Header extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderName (address) {
|
renderName () {
|
||||||
const { hideName } = this.props;
|
const { hideName } = this.props;
|
||||||
|
|
||||||
if (hideName) {
|
if (hideName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { address } = this.props.account;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContainerTitle title={ <IdentityName address={ address } unknown /> } />
|
<ContainerTitle
|
||||||
|
title={
|
||||||
|
<IdentityName
|
||||||
|
address={ address }
|
||||||
|
unknown />
|
||||||
|
} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +115,31 @@ export default class Header extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.infoline }>
|
<div className={ styles.infoline }>
|
||||||
{ txCount.toFormat() } outgoing transactions
|
<FormattedMessage
|
||||||
|
id='account.header.outgoingTransactions'
|
||||||
|
defaultMessage='{count} outgoing transactions'
|
||||||
|
values={ {
|
||||||
|
count: txCount.toFormat()
|
||||||
|
} } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUuid () {
|
||||||
|
const { uuid } = this.props.account;
|
||||||
|
|
||||||
|
if (!uuid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.uuidline }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.header.uuid'
|
||||||
|
defaultMessage='uuid: {uuid}'
|
||||||
|
values={ {
|
||||||
|
uuid
|
||||||
|
} } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
156
js/src/views/Account/Header/header.spec.js
Normal file
156
js/src/views/Account/Header/header.spec.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// 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 BigNumber from 'bignumber.js';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Header from './';
|
||||||
|
|
||||||
|
const ACCOUNT = {
|
||||||
|
address: '0x0123456789012345678901234567890123456789',
|
||||||
|
meta: {
|
||||||
|
description: 'the description',
|
||||||
|
tags: ['taga', 'tagb']
|
||||||
|
},
|
||||||
|
uuid: '0xabcdef'
|
||||||
|
};
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
if (props && !props.account) {
|
||||||
|
props.account = ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<Header { ...props } />
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Account/Header', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null with no account', () => {
|
||||||
|
expect(render(null).find('div')).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders when no account meta', () => {
|
||||||
|
expect(render({ account: { address: ACCOUNT.address } })).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders when no account description', () => {
|
||||||
|
expect(render({ account: { address: ACCOUNT.address, meta: { tags: [] } } })).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders when no account tags', () => {
|
||||||
|
expect(render({ account: { address: ACCOUNT.address, meta: { description: 'something' } } })).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sections', () => {
|
||||||
|
it('renders the Balance', () => {
|
||||||
|
render({ balance: { balance: 'testing' } });
|
||||||
|
const balance = component.find('Connect(Balance)');
|
||||||
|
|
||||||
|
expect(balance).to.have.length(1);
|
||||||
|
expect(balance.props().account).to.deep.equal(ACCOUNT);
|
||||||
|
expect(balance.props().balance).to.deep.equal({ balance: 'testing' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Certifications', () => {
|
||||||
|
render();
|
||||||
|
const certs = component.find('Connect(Certifications)');
|
||||||
|
|
||||||
|
expect(certs).to.have.length(1);
|
||||||
|
expect(certs.props().address).to.deep.equal(ACCOUNT.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the IdentityIcon', () => {
|
||||||
|
render();
|
||||||
|
const icon = component.find('Connect(IdentityIcon)');
|
||||||
|
|
||||||
|
expect(icon).to.have.length(1);
|
||||||
|
expect(icon.props().address).to.equal(ACCOUNT.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Tags', () => {
|
||||||
|
render();
|
||||||
|
const tags = component.find('Tags');
|
||||||
|
|
||||||
|
expect(tags).to.have.length(1);
|
||||||
|
expect(tags.props().tags).to.deep.equal(ACCOUNT.meta.tags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderName', () => {
|
||||||
|
it('renders null with hideName', () => {
|
||||||
|
render({ hideName: true });
|
||||||
|
expect(instance.renderName()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the name', () => {
|
||||||
|
render();
|
||||||
|
expect(instance.renderName()).not.to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders when no address specified', () => {
|
||||||
|
render({ account: {} });
|
||||||
|
expect(instance.renderName()).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderTxCount', () => {
|
||||||
|
it('renders null when contract', () => {
|
||||||
|
render({ balance: { txCount: new BigNumber(1) }, isContract: true });
|
||||||
|
expect(instance.renderTxCount()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null when no balance', () => {
|
||||||
|
render({ balance: null, isContract: false });
|
||||||
|
expect(instance.renderTxCount()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null when txCount is null', () => {
|
||||||
|
render({ balance: { txCount: null }, isContract: false });
|
||||||
|
expect(instance.renderTxCount()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the tx count', () => {
|
||||||
|
render({ balance: { txCount: new BigNumber(1) }, isContract: false });
|
||||||
|
expect(instance.renderTxCount()).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderUuid', () => {
|
||||||
|
it('renders null with no uuid', () => {
|
||||||
|
render({ account: Object.assign({}, ACCOUNT, { uuid: null }) });
|
||||||
|
expect(instance.renderUuid()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the uuid', () => {
|
||||||
|
render();
|
||||||
|
expect(instance.renderUuid()).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
118
js/src/views/Account/Transactions/store.js
Normal file
118
js/src/views/Account/Transactions/store.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// 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, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
|
import etherscan from '~/3rdparty/etherscan';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable address = null;
|
||||||
|
@observable isLoading = false;
|
||||||
|
@observable isTest = undefined;
|
||||||
|
@observable isTracing = false;
|
||||||
|
@observable txHashes = [];
|
||||||
|
|
||||||
|
constructor (api) {
|
||||||
|
this._api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setHashes = (transactions) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setLoading(false);
|
||||||
|
this.txHashes = transactions.map((transaction) => transaction.hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddress = (address) => {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setLoading = (isLoading) => {
|
||||||
|
this.isLoading = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setTest = (isTest) => {
|
||||||
|
this.isTest = isTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setTracing = (isTracing) => {
|
||||||
|
this.isTracing = isTracing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action updateProps = (props) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setAddress(props.address);
|
||||||
|
this.setTest(props.isTest);
|
||||||
|
|
||||||
|
// TODO: When tracing is enabled again, adjust to actually set
|
||||||
|
this.setTracing(false && props.traceMode);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.getTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransactions () {
|
||||||
|
if (this.isTest === undefined) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoading(true);
|
||||||
|
|
||||||
|
// TODO: When supporting other chains (eg. ETC). call to be made to other endpoints
|
||||||
|
return (
|
||||||
|
this.isTracing
|
||||||
|
? this.fetchTraceTransactions()
|
||||||
|
: this.fetchEtherscanTransactions()
|
||||||
|
)
|
||||||
|
.then((transactions) => {
|
||||||
|
this.setHashes(transactions);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('getTransactions', error);
|
||||||
|
this.setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEtherscanTransactions () {
|
||||||
|
return etherscan.account.transactions(this.address, 0, this.isTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTraceTransactions () {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._api.trace.filter({
|
||||||
|
fromAddress: this.address,
|
||||||
|
fromBlock: 0
|
||||||
|
}),
|
||||||
|
this._api.trace.filter({
|
||||||
|
fromBlock: 0,
|
||||||
|
toAddress: this.address
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.then(([fromTransactions, toTransactions]) => {
|
||||||
|
return fromTransactions
|
||||||
|
.concat(toTransactions)
|
||||||
|
.map((transaction) => {
|
||||||
|
return {
|
||||||
|
blockNumber: transaction.blockNumber,
|
||||||
|
from: transaction.action.from,
|
||||||
|
hash: transaction.transactionHash,
|
||||||
|
to: transaction.action.to
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
193
js/src/views/Account/Transactions/store.spec.js
Normal file
193
js/src/views/Account/Transactions/store.spec.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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 BigNumber from 'bignumber.js';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import { mockget as mockEtherscan } from '~/3rdparty/etherscan/helpers.spec.js';
|
||||||
|
import { ADDRESS, createApi } from './transactions.test.js';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
api = createApi();
|
||||||
|
store = new Store(api);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockQuery () {
|
||||||
|
mockEtherscan([{
|
||||||
|
query: {
|
||||||
|
module: 'account',
|
||||||
|
action: 'txlist',
|
||||||
|
address: ADDRESS,
|
||||||
|
offset: 25,
|
||||||
|
page: 1,
|
||||||
|
sort: 'desc'
|
||||||
|
},
|
||||||
|
reply: [{ hash: '123' }]
|
||||||
|
}], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Account/Transactions/store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockQuery();
|
||||||
|
createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('sets the api', () => {
|
||||||
|
expect(store._api).to.deep.equals(api);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts with isLoading === false', () => {
|
||||||
|
expect(store.isLoading).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts with isTracing === false', () => {
|
||||||
|
expect(store.isTracing).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@action', () => {
|
||||||
|
describe('setHashes', () => {
|
||||||
|
it('clears the loading state', () => {
|
||||||
|
store.setLoading(true);
|
||||||
|
store.setHashes([]);
|
||||||
|
expect(store.isLoading).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the hashes from the transactions', () => {
|
||||||
|
store.setHashes([{ hash: '123' }, { hash: '456' }]);
|
||||||
|
expect(store.txHashes.peek()).to.deep.equal(['123', '456']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setAddress', () => {
|
||||||
|
it('sets the address', () => {
|
||||||
|
store.setAddress(ADDRESS);
|
||||||
|
expect(store.address).to.equal(ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setLoading', () => {
|
||||||
|
it('sets the isLoading flag', () => {
|
||||||
|
store.setLoading(true);
|
||||||
|
expect(store.isLoading).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setTest', () => {
|
||||||
|
it('sets the isTest flag', () => {
|
||||||
|
store.setTest(true);
|
||||||
|
expect(store.isTest).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setTracing', () => {
|
||||||
|
it('sets the isTracing flag', () => {
|
||||||
|
store.setTracing(true);
|
||||||
|
expect(store.isTracing).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateProps', () => {
|
||||||
|
it('retrieves transactions once updated', () => {
|
||||||
|
sinon.spy(store, 'getTransactions');
|
||||||
|
store.updateProps({});
|
||||||
|
|
||||||
|
expect(store.getTransactions).to.have.been.called;
|
||||||
|
store.getTransactions.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('operations', () => {
|
||||||
|
describe('getTransactions', () => {
|
||||||
|
it('retrieves the hashes via etherscan', () => {
|
||||||
|
sinon.spy(store, 'fetchEtherscanTransactions');
|
||||||
|
store.setAddress(ADDRESS);
|
||||||
|
store.setTest(true);
|
||||||
|
store.setTracing(false);
|
||||||
|
|
||||||
|
return store.getTransactions().then(() => {
|
||||||
|
expect(store.fetchEtherscanTransactions).to.have.been.called;
|
||||||
|
expect(store.txHashes.peek()).to.deep.equal(['123']);
|
||||||
|
store.fetchEtherscanTransactions.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves the hashes via tracing', () => {
|
||||||
|
sinon.spy(store, 'fetchTraceTransactions');
|
||||||
|
store.setAddress(ADDRESS);
|
||||||
|
store.setTest(true);
|
||||||
|
store.setTracing(true);
|
||||||
|
|
||||||
|
return store.getTransactions().then(() => {
|
||||||
|
expect(store.fetchTraceTransactions).to.have.been.called;
|
||||||
|
expect(store.txHashes.peek()).to.deep.equal(['123', '098']);
|
||||||
|
store.fetchTraceTransactions.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchEtherscanTransactions', () => {
|
||||||
|
it('retrieves the transactions', () => {
|
||||||
|
store.setAddress(ADDRESS);
|
||||||
|
store.setTest(true);
|
||||||
|
|
||||||
|
return store.fetchEtherscanTransactions().then((transactions) => {
|
||||||
|
expect(transactions).to.deep.equal([{
|
||||||
|
blockNumber: new BigNumber(0),
|
||||||
|
from: '',
|
||||||
|
hash: '123',
|
||||||
|
timeStamp: undefined,
|
||||||
|
to: '',
|
||||||
|
value: undefined
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchTraceTransactions', () => {
|
||||||
|
it('retrieves the transactions', () => {
|
||||||
|
store.setAddress(ADDRESS);
|
||||||
|
store.setTest(true);
|
||||||
|
|
||||||
|
return store.fetchTraceTransactions().then((transactions) => {
|
||||||
|
expect(transactions).to.deep.equal([
|
||||||
|
{
|
||||||
|
blockNumber: undefined,
|
||||||
|
from: undefined,
|
||||||
|
hash: '123',
|
||||||
|
to: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockNumber: undefined,
|
||||||
|
from: undefined,
|
||||||
|
hash: '098',
|
||||||
|
to: undefined
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -14,15 +14,18 @@
|
|||||||
// 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 { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import etherscan from '~/3rdparty/etherscan';
|
|
||||||
import { Container, TxList, Loading } from '~/ui';
|
import { Container, TxList, Loading } from '~/ui';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
import styles from './transactions.css';
|
import styles from './transactions.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
class Transactions extends Component {
|
class Transactions extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
@ -34,34 +37,35 @@ class Transactions extends Component {
|
|||||||
traceMode: PropTypes.bool
|
traceMode: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
store = new Store(this.context.api);
|
||||||
hashes: [],
|
|
||||||
loading: true,
|
|
||||||
callInfo: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentWillMount () {
|
||||||
this.getTransactions(this.props);
|
this.store.updateProps(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
if (this.props.traceMode === undefined && newProps.traceMode !== undefined) {
|
if (this.props.traceMode === undefined && newProps.traceMode !== undefined) {
|
||||||
this.getTransactions(newProps);
|
this.store.updateProps(newProps);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasChanged = [ 'isTest', 'address' ]
|
const hasChanged = ['isTest', 'address']
|
||||||
.map(key => newProps[key] !== this.props[key])
|
.map(key => newProps[key] !== this.props[key])
|
||||||
.reduce((truth, keyTruth) => truth || keyTruth, false);
|
.reduce((truth, keyTruth) => truth || keyTruth, false);
|
||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
this.getTransactions(newProps);
|
this.store.updateProps(newProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<Container title='transactions'>
|
<Container
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.transactions.title'
|
||||||
|
defaultMessage='transactions' />
|
||||||
|
}>
|
||||||
{ this.renderTransactionList() }
|
{ this.renderTransactionList() }
|
||||||
{ this.renderEtherscanFooter() }
|
{ this.renderEtherscanFooter() }
|
||||||
</Container>
|
</Container>
|
||||||
@ -69,10 +73,9 @@ class Transactions extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTransactionList () {
|
renderTransactionList () {
|
||||||
const { address } = this.props;
|
const { address, isLoading, txHashes } = this.store;
|
||||||
const { hashes, loading } = this.state;
|
|
||||||
|
|
||||||
if (loading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Loading />
|
<Loading />
|
||||||
);
|
);
|
||||||
@ -81,85 +84,29 @@ class Transactions extends Component {
|
|||||||
return (
|
return (
|
||||||
<TxList
|
<TxList
|
||||||
address={ address }
|
address={ address }
|
||||||
hashes={ hashes }
|
hashes={ txHashes }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEtherscanFooter () {
|
renderEtherscanFooter () {
|
||||||
const { traceMode } = this.props;
|
const { isTracing } = this.store;
|
||||||
|
|
||||||
if (traceMode) {
|
if (isTracing) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.etherscan }>
|
<div className={ styles.etherscan }>
|
||||||
Transaction list powered by <a href='https://etherscan.io/' target='_blank'>etherscan.io</a>
|
<FormattedMessage
|
||||||
|
id='account.transactions.poweredBy'
|
||||||
|
defaultMessage='Transaction list powered by {etherscan}'
|
||||||
|
values={ {
|
||||||
|
etherscan: <a href='https://etherscan.io/' target='_blank'>etherscan.io</a>
|
||||||
|
} } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransactions = (props) => {
|
|
||||||
const { isTest, address, traceMode } = props;
|
|
||||||
|
|
||||||
// Don't fetch the transactions if we don't know in which
|
|
||||||
// network we are yet...
|
|
||||||
if (isTest === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
.fetchTransactions(isTest, address, traceMode)
|
|
||||||
.then((transactions) => {
|
|
||||||
this.setState({
|
|
||||||
hashes: transactions.map((transaction) => transaction.hash),
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchTransactions = (isTest, address, traceMode) => {
|
|
||||||
// if (traceMode) {
|
|
||||||
// return this.fetchTraceTransactions(address);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return this.fetchEtherscanTransactions(isTest, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchEtherscanTransactions = (isTest, address) => {
|
|
||||||
return etherscan.account
|
|
||||||
.transactions(address, 0, isTest)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('getTransactions', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchTraceTransactions = (address) => {
|
|
||||||
return Promise
|
|
||||||
.all([
|
|
||||||
this.context.api.trace
|
|
||||||
.filter({
|
|
||||||
fromBlock: 0,
|
|
||||||
fromAddress: address
|
|
||||||
}),
|
|
||||||
this.context.api.trace
|
|
||||||
.filter({
|
|
||||||
fromBlock: 0,
|
|
||||||
toAddress: address
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.then(([fromTransactions, toTransactions]) => {
|
|
||||||
const transactions = [].concat(fromTransactions, toTransactions);
|
|
||||||
|
|
||||||
return transactions.map(transaction => ({
|
|
||||||
from: transaction.action.from,
|
|
||||||
to: transaction.action.to,
|
|
||||||
blockNumber: transaction.blockNumber,
|
|
||||||
hash: transaction.transactionHash
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
55
js/src/views/Account/Transactions/transactions.spec.js
Normal file
55
js/src/views/Account/Transactions/transactions.spec.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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 { ADDRESS, createApi, createRedux } from './transactions.test.js';
|
||||||
|
|
||||||
|
import Transactions from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
|
||||||
|
function render (props) {
|
||||||
|
component = shallow(
|
||||||
|
<Transactions
|
||||||
|
address={ ADDRESS }
|
||||||
|
{ ...props } />,
|
||||||
|
{ context: { store: createRedux() } }
|
||||||
|
).find('Transactions').shallow({ context: { api: createApi() } });
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Account/Transactions', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderTransactionList', () => {
|
||||||
|
it('renders Loading when isLoading === true', () => {
|
||||||
|
instance.store.setLoading(true);
|
||||||
|
expect(instance.renderTransactionList().type).to.match(/Loading/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders TxList when isLoading === true', () => {
|
||||||
|
instance.store.setLoading(false);
|
||||||
|
expect(instance.renderTransactionList().type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
31
js/src/views/Account/Transactions/transactions.test.js
Normal file
31
js/src/views/Account/Transactions/transactions.test.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 { ADDRESS, createRedux } from '../account.test.js';
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
return {
|
||||||
|
trace: {
|
||||||
|
filter: (options) => Promise.resolve([{ transactionHash: options.fromAddress ? '123' : '098', action: {} }])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ADDRESS,
|
||||||
|
createApi,
|
||||||
|
createRedux
|
||||||
|
};
|
@ -14,47 +14,38 @@
|
|||||||
// 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 { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import ActionDelete from 'material-ui/svg-icons/action/delete';
|
|
||||||
import ContentCreate from 'material-ui/svg-icons/content/create';
|
|
||||||
import ContentSend from 'material-ui/svg-icons/content/send';
|
|
||||||
import LockIcon from 'material-ui/svg-icons/action/lock';
|
|
||||||
import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
|
||||||
|
|
||||||
import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
|
|
||||||
import { Actionbar, Button, Page } from '~/ui';
|
|
||||||
|
|
||||||
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
||||||
|
import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
|
||||||
import Header from './Header';
|
|
||||||
import Transactions from './Transactions';
|
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
||||||
|
import { Actionbar, Button, Page } from '~/ui';
|
||||||
|
import { DeleteIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import Header from './Header';
|
||||||
|
import Store from './store';
|
||||||
|
import Transactions from './Transactions';
|
||||||
import styles from './account.css';
|
import styles from './account.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
class Account extends Component {
|
class Account extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
|
||||||
fetchCertifiers: PropTypes.func.isRequired,
|
fetchCertifiers: PropTypes.func.isRequired,
|
||||||
fetchCertifications: PropTypes.func.isRequired,
|
fetchCertifications: PropTypes.func.isRequired,
|
||||||
images: PropTypes.object.isRequired,
|
images: PropTypes.object.isRequired,
|
||||||
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
params: PropTypes.object,
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object
|
balances: PropTypes.object,
|
||||||
|
params: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
store = new Store();
|
||||||
showDeleteDialog: false,
|
|
||||||
showEditDialog: false,
|
|
||||||
showFundDialog: false,
|
|
||||||
showVerificationDialog: false,
|
|
||||||
showTransferDialog: false,
|
|
||||||
showPasswordDialog: false
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.fetchCertifiers();
|
this.props.fetchCertifiers();
|
||||||
@ -76,7 +67,8 @@ class Account extends Component {
|
|||||||
|
|
||||||
setVisibleAccounts (props = this.props) {
|
setVisibleAccounts (props = this.props) {
|
||||||
const { params, setVisibleAccounts, fetchCertifications } = props;
|
const { params, setVisibleAccounts, fetchCertifications } = props;
|
||||||
const addresses = [ params.address ];
|
const addresses = [params.address];
|
||||||
|
|
||||||
setVisibleAccounts(addresses);
|
setVisibleAccounts(addresses);
|
||||||
fetchCertifications(params.address);
|
fetchCertifications(params.address);
|
||||||
}
|
}
|
||||||
@ -97,15 +89,14 @@ class Account extends Component {
|
|||||||
{ this.renderDeleteDialog(account) }
|
{ this.renderDeleteDialog(account) }
|
||||||
{ this.renderEditDialog(account) }
|
{ this.renderEditDialog(account) }
|
||||||
{ this.renderFundDialog() }
|
{ this.renderFundDialog() }
|
||||||
|
{ this.renderPasswordDialog(account) }
|
||||||
|
{ this.renderTransferDialog(account, balance) }
|
||||||
{ this.renderVerificationDialog() }
|
{ this.renderVerificationDialog() }
|
||||||
{ this.renderTransferDialog() }
|
{ this.renderActionbar(balance) }
|
||||||
{ this.renderPasswordDialog() }
|
|
||||||
{ this.renderActionbar() }
|
|
||||||
<Page>
|
<Page>
|
||||||
<Header
|
<Header
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance } />
|
||||||
/>
|
|
||||||
<Transactions
|
<Transactions
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
address={ address } />
|
address={ address } />
|
||||||
@ -114,86 +105,108 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActionbar () {
|
renderActionbar (balance) {
|
||||||
const { address } = this.props.params;
|
|
||||||
const { balances } = this.props;
|
|
||||||
const balance = balances[address];
|
|
||||||
|
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
const showTransferButton = !!(balance && balance.tokens);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
key='transferFunds'
|
|
||||||
icon={ <ContentSend /> }
|
|
||||||
label='transfer'
|
|
||||||
disabled={ !showTransferButton }
|
disabled={ !showTransferButton }
|
||||||
onClick={ this.onTransferClick } />,
|
icon={ <SendIcon /> }
|
||||||
|
key='transferFunds'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.button.transfer'
|
||||||
|
defaultMessage='transfer' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleTransferDialog } />,
|
||||||
<Button
|
<Button
|
||||||
|
icon={
|
||||||
|
<img
|
||||||
|
className={ styles.btnicon }
|
||||||
|
src={ shapeshiftBtn } />
|
||||||
|
}
|
||||||
key='shapeshift'
|
key='shapeshift'
|
||||||
icon={ <img src={ shapeshiftBtn } className={ styles.btnicon } /> }
|
label={
|
||||||
label='shapeshift'
|
<FormattedMessage
|
||||||
onClick={ this.onShapeshiftAccountClick } />,
|
id='account.button.shapeshift'
|
||||||
|
defaultMessage='shapeshift' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleFundDialog } />,
|
||||||
<Button
|
<Button
|
||||||
key='sms-verification'
|
|
||||||
icon={ <VerifyIcon /> }
|
icon={ <VerifyIcon /> }
|
||||||
label='Verify'
|
key='sms-verification'
|
||||||
onClick={ this.openVerification } />,
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.button.verify'
|
||||||
|
defaultMessage='verify' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleVerificationDialog } />,
|
||||||
<Button
|
<Button
|
||||||
|
icon={ <EditIcon /> }
|
||||||
key='editmeta'
|
key='editmeta'
|
||||||
icon={ <ContentCreate /> }
|
label={
|
||||||
label='edit'
|
<FormattedMessage
|
||||||
onClick={ this.onEditClick } />,
|
id='account.button.edit'
|
||||||
|
defaultMessage='edit' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleEditDialog } />,
|
||||||
<Button
|
<Button
|
||||||
|
icon={ <LockedIcon /> }
|
||||||
key='passwordManager'
|
key='passwordManager'
|
||||||
icon={ <LockIcon /> }
|
label={
|
||||||
label='password'
|
<FormattedMessage
|
||||||
onClick={ this.onPasswordClick } />,
|
id='account.button.password'
|
||||||
|
defaultMessage='password' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.togglePasswordDialog } />,
|
||||||
<Button
|
<Button
|
||||||
|
icon={ <DeleteIcon /> }
|
||||||
key='delete'
|
key='delete'
|
||||||
icon={ <ActionDelete /> }
|
label={
|
||||||
label='delete account'
|
<FormattedMessage
|
||||||
onClick={ this.onDeleteClick } />
|
id='account.button.delete'
|
||||||
|
defaultMessage='delete account' />
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleDeleteDialog } />
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Actionbar
|
<Actionbar
|
||||||
title='Account Management'
|
buttons={ buttons }
|
||||||
buttons={ buttons } />
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.title'
|
||||||
|
defaultMessage='Account Management' />
|
||||||
|
} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDeleteDialog (account) {
|
renderDeleteDialog (account) {
|
||||||
const { showDeleteDialog } = this.state;
|
if (!this.store.isDeleteVisible) {
|
||||||
|
|
||||||
if (!showDeleteDialog) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DeleteAccount
|
<DeleteAccount
|
||||||
account={ account }
|
account={ account }
|
||||||
onClose={ this.onDeleteClose } />
|
onClose={ this.store.toggleDeleteDialog } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditDialog (account) {
|
renderEditDialog (account) {
|
||||||
const { showEditDialog } = this.state;
|
if (!this.store.isEditVisible) {
|
||||||
|
|
||||||
if (!showEditDialog) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditMeta
|
<EditMeta
|
||||||
account={ account }
|
account={ account }
|
||||||
onClose={ this.onEditClick } />
|
onClose={ this.store.toggleEditDialog } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFundDialog () {
|
renderFundDialog () {
|
||||||
const { showFundDialog } = this.state;
|
if (!this.store.isFundVisible) {
|
||||||
|
|
||||||
if (!showFundDialog) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,12 +215,41 @@ class Account extends Component {
|
|||||||
return (
|
return (
|
||||||
<Shapeshift
|
<Shapeshift
|
||||||
address={ address }
|
address={ address }
|
||||||
onClose={ this.onShapeshiftAccountClose } />
|
onClose={ this.store.toggleFundDialog } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPasswordDialog (account) {
|
||||||
|
if (!this.store.isPasswordVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PasswordManager
|
||||||
|
account={ account }
|
||||||
|
onClose={ this.store.togglePasswordDialog } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTransferDialog (account, balance) {
|
||||||
|
if (!this.store.isTransferVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { balances, images } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transfer
|
||||||
|
account={ account }
|
||||||
|
balance={ balance }
|
||||||
|
balances={ balances }
|
||||||
|
images={ images }
|
||||||
|
onClose={ this.store.toggleTransferDialog } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderVerificationDialog () {
|
renderVerificationDialog () {
|
||||||
if (!this.state.showVerificationDialog) {
|
if (!this.store.isVerificationVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,102 +258,9 @@ class Account extends Component {
|
|||||||
return (
|
return (
|
||||||
<Verification
|
<Verification
|
||||||
account={ address }
|
account={ address }
|
||||||
onClose={ this.onVerificationClose }
|
onClose={ this.store.toggleVerificationDialog } />
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTransferDialog () {
|
|
||||||
const { showTransferDialog } = this.state;
|
|
||||||
|
|
||||||
if (!showTransferDialog) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { address } = this.props.params;
|
|
||||||
const { accounts, balances, images } = this.props;
|
|
||||||
const account = accounts[address];
|
|
||||||
const balance = balances[address];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Transfer
|
|
||||||
account={ account }
|
|
||||||
balance={ balance }
|
|
||||||
balances={ balances }
|
|
||||||
images={ images }
|
|
||||||
onClose={ this.onTransferClose } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordDialog () {
|
|
||||||
const { showPasswordDialog } = this.state;
|
|
||||||
|
|
||||||
if (!showPasswordDialog) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { address } = this.props.params;
|
|
||||||
const { accounts } = this.props;
|
|
||||||
const account = accounts[address];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PasswordManager
|
|
||||||
account={ account }
|
|
||||||
onClose={ this.onPasswordClose } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteClick = () => {
|
|
||||||
this.setState({ showDeleteDialog: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteClose = () => {
|
|
||||||
this.setState({ showDeleteDialog: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditClick = () => {
|
|
||||||
this.setState({
|
|
||||||
showEditDialog: !this.state.showEditDialog
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onShapeshiftAccountClick = () => {
|
|
||||||
this.setState({
|
|
||||||
showFundDialog: !this.state.showFundDialog
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onShapeshiftAccountClose = () => {
|
|
||||||
this.onShapeshiftAccountClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
openVerification = () => {
|
|
||||||
this.setState({ showVerificationDialog: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onVerificationClose = () => {
|
|
||||||
this.setState({ showVerificationDialog: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onTransferClick = () => {
|
|
||||||
this.setState({
|
|
||||||
showTransferDialog: !this.state.showTransferDialog
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onTransferClose = () => {
|
|
||||||
this.onTransferClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
onPasswordClick = () => {
|
|
||||||
this.setState({
|
|
||||||
showPasswordDialog: !this.state.showPasswordDialog
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onPasswordClose = () => {
|
|
||||||
this.onPasswordClick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
@ -328,9 +277,9 @@ function mapStateToProps (state) {
|
|||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return bindActionCreators({
|
return bindActionCreators({
|
||||||
setVisibleAccounts,
|
|
||||||
fetchCertifiers,
|
fetchCertifiers,
|
||||||
fetchCertifications
|
fetchCertifications,
|
||||||
|
setVisibleAccounts
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
226
js/src/views/Account/account.spec.js
Normal file
226
js/src/views/Account/account.spec.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// 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 { ADDRESS, createRedux } from './account.test.js';
|
||||||
|
|
||||||
|
import Account from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function render (props) {
|
||||||
|
component = shallow(
|
||||||
|
<Account
|
||||||
|
params={ { address: ADDRESS } }
|
||||||
|
{ ...props } />,
|
||||||
|
{ context: { store: createRedux() } }
|
||||||
|
).find('Account').shallow();
|
||||||
|
instance = component.instance();
|
||||||
|
store = instance.store;
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Account', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sections', () => {
|
||||||
|
it('renders the Actionbar', () => {
|
||||||
|
expect(component.find('Actionbar')).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Page', () => {
|
||||||
|
expect(component.find('Page')).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Header', () => {
|
||||||
|
expect(component.find('Header')).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Transactions', () => {
|
||||||
|
expect(component.find('Connect(Transactions)')).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders no other sections', () => {
|
||||||
|
expect(component.find('div').children()).to.have.length(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sub-renderers', () => {
|
||||||
|
describe('renderActionBar', () => {
|
||||||
|
let bar;
|
||||||
|
let barShallow;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
bar = instance.renderActionbar({ tokens: {} });
|
||||||
|
barShallow = shallow(bar);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the bar', () => {
|
||||||
|
expect(bar.type).to.match(/Actionbar/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Finding by index is not optimal, however couldn't find a better method atm
|
||||||
|
// since we cannot find by key (prop not visible in shallow debug())
|
||||||
|
describe('clicks', () => {
|
||||||
|
it('toggles transfer on click', () => {
|
||||||
|
barShallow.find('Button').at(0).simulate('click');
|
||||||
|
expect(store.isTransferVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles fund on click', () => {
|
||||||
|
barShallow.find('Button').at(1).simulate('click');
|
||||||
|
expect(store.isFundVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles fund on click', () => {
|
||||||
|
barShallow.find('Button').at(1).simulate('click');
|
||||||
|
expect(store.isFundVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles verify on click', () => {
|
||||||
|
barShallow.find('Button').at(2).simulate('click');
|
||||||
|
expect(store.isVerificationVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles edit on click', () => {
|
||||||
|
barShallow.find('Button').at(3).simulate('click');
|
||||||
|
expect(store.isEditVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles password on click', () => {
|
||||||
|
barShallow.find('Button').at(4).simulate('click');
|
||||||
|
expect(store.isPasswordVisible).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles delete on click', () => {
|
||||||
|
barShallow.find('Button').at(5).simulate('click');
|
||||||
|
expect(store.isDeleteVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderDeleteDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isDeleteVisible).to.be.false;
|
||||||
|
expect(instance.renderDeleteDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.toggleDeleteDialog();
|
||||||
|
expect(instance.renderDeleteDialog().type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderEditDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isEditVisible).to.be.false;
|
||||||
|
expect(instance.renderEditDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.toggleEditDialog();
|
||||||
|
expect(instance.renderEditDialog({ address: ADDRESS }).type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderFundDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isFundVisible).to.be.false;
|
||||||
|
expect(instance.renderFundDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.toggleFundDialog();
|
||||||
|
expect(instance.renderFundDialog().type).to.match(/Shapeshift/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderPasswordDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isPasswordVisible).to.be.false;
|
||||||
|
expect(instance.renderPasswordDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.togglePasswordDialog();
|
||||||
|
expect(instance.renderPasswordDialog({ address: ADDRESS }).type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderTransferDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isTransferVisible).to.be.false;
|
||||||
|
expect(instance.renderTransferDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.toggleTransferDialog();
|
||||||
|
expect(instance.renderTransferDialog().type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderVerificationDialog', () => {
|
||||||
|
it('renders null when not visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
expect(store.isVerificationVisible).to.be.false;
|
||||||
|
expect(instance.renderVerificationDialog()).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the modal when visible', () => {
|
||||||
|
render();
|
||||||
|
|
||||||
|
store.toggleVerificationDialog();
|
||||||
|
expect(instance.renderVerificationDialog().type).to.match(/Connect/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
52
js/src/views/Account/account.test.js
Normal file
52
js/src/views/Account/account.test.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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 ADDRESS = '0x0123456789012345678901234567890123456789';
|
||||||
|
|
||||||
|
function createRedux () {
|
||||||
|
return {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
balances: {
|
||||||
|
balances: {
|
||||||
|
[ADDRESS]: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
images: {},
|
||||||
|
nodeStatus: {
|
||||||
|
isTest: false,
|
||||||
|
traceMode: false
|
||||||
|
},
|
||||||
|
personal: {
|
||||||
|
accounts: {
|
||||||
|
[ADDRESS]: {
|
||||||
|
address: ADDRESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ADDRESS,
|
||||||
|
createRedux
|
||||||
|
};
|
50
js/src/views/Account/store.js
Normal file
50
js/src/views/Account/store.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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, observable } from 'mobx';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable isDeleteVisible = false;
|
||||||
|
@observable isEditVisible = false;
|
||||||
|
@observable isFundVisible = false;
|
||||||
|
@observable isPasswordVisible = false;
|
||||||
|
@observable isTransferVisible = false;
|
||||||
|
@observable isVerificationVisible = false;
|
||||||
|
|
||||||
|
@action toggleDeleteDialog = () => {
|
||||||
|
this.isDeleteVisible = !this.isDeleteVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action toggleEditDialog = () => {
|
||||||
|
this.isEditVisible = !this.isEditVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action toggleFundDialog = () => {
|
||||||
|
this.isFundVisible = !this.isFundVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action togglePasswordDialog = () => {
|
||||||
|
this.isPasswordVisible = !this.isPasswordVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action toggleTransferDialog = () => {
|
||||||
|
this.isTransferVisible = !this.isTransferVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action toggleVerificationDialog = () => {
|
||||||
|
this.isVerificationVisible = !this.isVerificationVisible;
|
||||||
|
}
|
||||||
|
}
|
84
js/src/views/Account/store.spec.js
Normal file
84
js/src/views/Account/store.spec.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
store = new Store();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Account/Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('sets all modal visibility to false', () => {
|
||||||
|
expect(store.isDeleteVisible).to.be.false;
|
||||||
|
expect(store.isEditVisible).to.be.false;
|
||||||
|
expect(store.isFundVisible).to.be.false;
|
||||||
|
expect(store.isPasswordVisible).to.be.false;
|
||||||
|
expect(store.isTransferVisible).to.be.false;
|
||||||
|
expect(store.isVerificationVisible).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@action', () => {
|
||||||
|
describe('toggleDeleteDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleDeleteDialog();
|
||||||
|
expect(store.isDeleteVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleEditDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleEditDialog();
|
||||||
|
expect(store.isEditVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleFundDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleFundDialog();
|
||||||
|
expect(store.isFundVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('togglePasswordDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.togglePasswordDialog();
|
||||||
|
expect(store.isPasswordVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleTransferDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleTransferDialog();
|
||||||
|
expect(store.isTransferVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleVerificationDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleVerificationDialog();
|
||||||
|
expect(store.isVerificationVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -197,7 +197,7 @@ export default class Summary extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Certifications account={ account.address } />
|
<Certifications address={ account.address } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ injectTapEventPlugin();
|
|||||||
import chai from 'chai';
|
import chai from 'chai';
|
||||||
import chaiAsPromised from 'chai-as-promised';
|
import chaiAsPromised from 'chai-as-promised';
|
||||||
import chaiEnzyme from 'chai-enzyme';
|
import chaiEnzyme from 'chai-enzyme';
|
||||||
|
import 'sinon-as-promised';
|
||||||
import sinonChai from 'sinon-chai';
|
import sinonChai from 'sinon-chai';
|
||||||
import { WebSocket } from 'mock-socket';
|
import { WebSocket } from 'mock-socket';
|
||||||
import jsdom from 'jsdom';
|
import jsdom from 'jsdom';
|
||||||
|
Loading…
Reference in New Issue
Block a user