UI component updates (#4010)
* Update blockStatus & test results * IdentityIcon rendering tests for #3950 * Update IdentityName with external messages * Expand to cover basic layout sections * ConfirmDialog rendering tests * TxHash expansion & tests * Cleanup ui/*.spec.js PropType warnings * Use react-intl plural for confirmation/confirmations (verified manually)
This commit is contained in:
parent
602a4429cc
commit
ddeb06d9cc
@ -21,7 +21,7 @@ const TEST_ENV = process.env.NODE_ENV === 'test';
|
||||
|
||||
export function createIdentityImg (address, scale = 8) {
|
||||
return TEST_ENV
|
||||
? ''
|
||||
? 'test-createIdentityImg'
|
||||
: blockies({
|
||||
seed: (address || '').toLowerCase(),
|
||||
size: 8,
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
@ -39,7 +40,12 @@ class BlockStatus extends Component {
|
||||
if (!syncing) {
|
||||
return (
|
||||
<div className={ styles.blockNumber }>
|
||||
{ blockNumber.toFormat() } best block
|
||||
<FormattedMessage
|
||||
id='ui.blockStatus.bestBlock'
|
||||
defaultMessage='{blockNumber} best block'
|
||||
values={ {
|
||||
blockNumber: blockNumber.toFormat()
|
||||
} } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -47,26 +53,45 @@ class BlockStatus extends Component {
|
||||
if (syncing.warpChunksAmount && syncing.warpChunksProcessed && !syncing.warpChunksAmount.eq(syncing.warpChunksProcessed)) {
|
||||
return (
|
||||
<div className={ styles.syncStatus }>
|
||||
{ syncing.warpChunksProcessed.mul(100).div(syncing.warpChunksAmount).toFormat(2) }% warp restore
|
||||
<FormattedMessage
|
||||
id='ui.blockStatus.warpRestore'
|
||||
defaultMessage='{percentage}% warp restore'
|
||||
values={ {
|
||||
percentage: syncing.warpChunksProcessed.mul(100).div(syncing.warpChunksAmount).toFormat(2)
|
||||
} } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let syncStatus = null;
|
||||
let warpStatus = null;
|
||||
|
||||
if (syncing.currentBlock && syncing.highestBlock) {
|
||||
syncStatus = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='ui.blockStatus.syncStatus'
|
||||
defaultMessage='{currentBlock}/{highestBlock} syncing'
|
||||
values={ {
|
||||
currentBlock: syncing.currentBlock.toFormat(),
|
||||
highestBlock: syncing.highestBlock.toFormat()
|
||||
} } />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (syncing.blockGap) {
|
||||
const [first, last] = syncing.blockGap;
|
||||
|
||||
warpStatus = (
|
||||
<span>, { first.mul(100).div(last).toFormat(2) }% historic</span>
|
||||
);
|
||||
}
|
||||
|
||||
let syncStatus = null;
|
||||
|
||||
if (syncing && syncing.currentBlock && syncing.highestBlock) {
|
||||
syncStatus = (
|
||||
<span>{ syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing</span>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='ui.blockStatus.warpStatus'
|
||||
defaultMessage=', {percentage}% historic'
|
||||
values={ {
|
||||
percentage: first.mul(100).div(last).toFormat(2)
|
||||
} } />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
94
js/src/ui/BlockStatus/blockStatus.spec.js
Normal file
94
js/src/ui/BlockStatus/blockStatus.spec.js
Normal file
@ -0,0 +1,94 @@
|
||||
// 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 sinon from 'sinon';
|
||||
|
||||
import BlockStatus from './';
|
||||
|
||||
let component;
|
||||
|
||||
function createRedux (syncing = false, blockNumber = new BigNumber(123)) {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
nodeStatus: {
|
||||
blockNumber,
|
||||
syncing
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (reduxStore = createRedux(), props) {
|
||||
component = shallow(
|
||||
<BlockStatus { ...props } />,
|
||||
{ context: { store: reduxStore } }
|
||||
).find('BlockStatus').shallow();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/BlockStatus', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders null with no blockNumber', () => {
|
||||
expect(render(createRedux(false, null)).find('div')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders only the best block when syncing === false', () => {
|
||||
const messages = render().find('FormattedMessage');
|
||||
|
||||
expect(messages).to.have.length(1);
|
||||
expect(messages).to.have.id('ui.blockStatus.bestBlock');
|
||||
});
|
||||
|
||||
it('renders only the warp restore status when restoring', () => {
|
||||
const messages = render(createRedux({
|
||||
warpChunksAmount: new BigNumber(100),
|
||||
warpChunksProcessed: new BigNumber(5)
|
||||
})).find('FormattedMessage');
|
||||
|
||||
expect(messages).to.have.length(1);
|
||||
expect(messages).to.have.id('ui.blockStatus.warpRestore');
|
||||
});
|
||||
|
||||
it('renders the current/highest when syncing', () => {
|
||||
const messages = render(createRedux({
|
||||
currentBlock: new BigNumber(123),
|
||||
highestBlock: new BigNumber(456)
|
||||
})).find('FormattedMessage');
|
||||
|
||||
expect(messages).to.have.length(1);
|
||||
expect(messages).to.have.id('ui.blockStatus.syncStatus');
|
||||
});
|
||||
|
||||
it('renders warp blockGap when catching up', () => {
|
||||
const messages = render(createRedux({
|
||||
blockGap: [new BigNumber(123), new BigNumber(456)]
|
||||
})).find('FormattedMessage');
|
||||
|
||||
expect(messages).to.have.length(1);
|
||||
expect(messages).to.have.id('ui.blockStatus.warpStatus');
|
||||
});
|
||||
});
|
@ -19,7 +19,11 @@ import { shallow } from 'enzyme';
|
||||
|
||||
import Button from './button';
|
||||
|
||||
function renderShallow (props) {
|
||||
function render (props = {}) {
|
||||
if (props && props.label === undefined) {
|
||||
props.label = 'test';
|
||||
}
|
||||
|
||||
return shallow(
|
||||
<Button { ...props } />
|
||||
);
|
||||
@ -28,11 +32,11 @@ function renderShallow (props) {
|
||||
describe('ui/Button', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(renderShallow()).to.be.ok;
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders with the specified className', () => {
|
||||
expect(renderShallow({ className: 'testClass' })).to.have.className('testClass');
|
||||
expect(render({ className: 'testClass' })).to.have.className('testClass');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,16 +15,27 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ActionDone from 'material-ui/svg-icons/action/done';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import Button from '../Button';
|
||||
import Modal from '../Modal';
|
||||
import { CancelIcon, CheckIcon } from '../Icons';
|
||||
|
||||
import styles from './confirmDialog.css';
|
||||
|
||||
const DEFAULT_NO = (
|
||||
<FormattedMessage
|
||||
id='ui.confirmDialog.no'
|
||||
defaultMessage='no' />
|
||||
);
|
||||
const DEFAULT_YES = (
|
||||
<FormattedMessage
|
||||
id='ui.confirmDialog.yes'
|
||||
defaultMessage='yes' />
|
||||
);
|
||||
|
||||
export default class ConfirmDialog extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
@ -33,10 +44,10 @@ export default class ConfirmDialog extends Component {
|
||||
iconDeny: PropTypes.node,
|
||||
labelConfirm: PropTypes.string,
|
||||
labelDeny: PropTypes.string,
|
||||
title: nodeOrStringProptype().isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onDeny: PropTypes.func.isRequired
|
||||
onDeny: PropTypes.func.isRequired,
|
||||
title: nodeOrStringProptype().isRequired,
|
||||
visible: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -60,12 +71,12 @@ export default class ConfirmDialog extends Component {
|
||||
|
||||
return [
|
||||
<Button
|
||||
label={ labelDeny || 'no' }
|
||||
icon={ iconDeny || <ContentClear /> }
|
||||
icon={ iconDeny || <CancelIcon /> }
|
||||
label={ labelDeny || DEFAULT_NO }
|
||||
onClick={ onDeny } />,
|
||||
<Button
|
||||
label={ labelConfirm || 'yes' }
|
||||
icon={ iconConfirm || <ActionDone /> }
|
||||
icon={ iconConfirm || <CheckIcon /> }
|
||||
label={ labelConfirm || DEFAULT_YES }
|
||||
onClick={ onConfirm } />
|
||||
];
|
||||
}
|
||||
|
157
js/src/ui/ConfirmDialog/confirmDialog.spec.js
Normal file
157
js/src/ui/ConfirmDialog/confirmDialog.spec.js
Normal file
@ -0,0 +1,157 @@
|
||||
// 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, { PropTypes } from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import muiTheme from '../Theme';
|
||||
|
||||
import ConfirmDialog from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let onConfirm;
|
||||
let onDeny;
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
settings: {
|
||||
backgroundSeed: 'xyz'
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
onConfirm = sinon.stub();
|
||||
onDeny = sinon.stub();
|
||||
|
||||
if (props.visible === undefined) {
|
||||
props.visible = true;
|
||||
}
|
||||
|
||||
const baseComponent = shallow(
|
||||
<ConfirmDialog
|
||||
{ ...props }
|
||||
title='test title'
|
||||
onConfirm={ onConfirm }
|
||||
onDeny={ onDeny }>
|
||||
<div id='testContent'>
|
||||
some test content
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
);
|
||||
|
||||
instance = baseComponent.instance();
|
||||
component = baseComponent.find('Connect(Modal)').shallow({
|
||||
childContextTypes: {
|
||||
muiTheme: PropTypes.object,
|
||||
store: PropTypes.object
|
||||
},
|
||||
context: {
|
||||
muiTheme,
|
||||
store: createRedux()
|
||||
}
|
||||
});
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/ConfirmDialog', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders the body as provided', () => {
|
||||
expect(render().find('div[id="testContent"]').text()).to.equal('some test content');
|
||||
});
|
||||
|
||||
describe('properties', () => {
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = render().props();
|
||||
});
|
||||
|
||||
it('passes the actions', () => {
|
||||
expect(props.actions).to.deep.equal(instance.renderActions());
|
||||
});
|
||||
|
||||
it('passes title', () => {
|
||||
expect(props.title).to.equal('test title');
|
||||
});
|
||||
|
||||
it('passes visiblity flag', () => {
|
||||
expect(props.visible).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderActions', () => {
|
||||
describe('defaults', () => {
|
||||
let buttons;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
buttons = instance.renderActions();
|
||||
});
|
||||
|
||||
it('renders with supplied onConfim/onDeny callbacks', () => {
|
||||
expect(buttons[0].props.onClick).to.deep.equal(onDeny);
|
||||
expect(buttons[1].props.onClick).to.deep.equal(onConfirm);
|
||||
});
|
||||
|
||||
it('renders default labels', () => {
|
||||
expect(buttons[0].props.label.props.id).to.equal('ui.confirmDialog.no');
|
||||
expect(buttons[1].props.label.props.id).to.equal('ui.confirmDialog.yes');
|
||||
});
|
||||
|
||||
it('renders default icons', () => {
|
||||
expect(buttons[0].props.icon.type.displayName).to.equal('ContentClear');
|
||||
expect(buttons[1].props.icon.type.displayName).to.equal('NavigationCheck');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrides', () => {
|
||||
let buttons;
|
||||
|
||||
beforeEach(() => {
|
||||
render({
|
||||
labelConfirm: 'labelConfirm',
|
||||
labelDeny: 'labelDeny',
|
||||
iconConfirm: 'iconConfirm',
|
||||
iconDeny: 'iconDeny'
|
||||
});
|
||||
buttons = instance.renderActions();
|
||||
});
|
||||
|
||||
it('renders supplied labels', () => {
|
||||
expect(buttons[0].props.label).to.equal('labelDeny');
|
||||
expect(buttons[1].props.label).to.equal('labelConfirm');
|
||||
});
|
||||
|
||||
it('renders supplied icons', () => {
|
||||
expect(buttons[0].props.icon).to.equal('iconDeny');
|
||||
expect(buttons[1].props.icon).to.equal('iconConfirm');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -19,7 +19,7 @@ import { shallow } from 'enzyme';
|
||||
|
||||
import Container from './container';
|
||||
|
||||
function renderShallow (props) {
|
||||
function render (props) {
|
||||
return shallow(
|
||||
<Container { ...props } />
|
||||
);
|
||||
@ -28,11 +28,24 @@ function renderShallow (props) {
|
||||
describe('ui/Container', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(renderShallow()).to.be.ok;
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders with the specified className', () => {
|
||||
expect(renderShallow({ className: 'testClass' })).to.have.className('testClass');
|
||||
expect(render({ className: 'testClass' })).to.have.className('testClass');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sections', () => {
|
||||
it('renders the Card', () => {
|
||||
expect(render().find('Card')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders the Title', () => {
|
||||
const title = render({ title: 'title' }).find('Title');
|
||||
|
||||
expect(title).to.have.length(1);
|
||||
expect(title.props().title).to.equal('title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,7 @@ const api = {
|
||||
|
||||
const store = {
|
||||
estimated: '123',
|
||||
histogram: {},
|
||||
priceDefault: '456',
|
||||
totalValue: '789',
|
||||
setGas: sinon.stub(),
|
||||
|
@ -34,8 +34,8 @@ class IdentityIcon extends Component {
|
||||
button: PropTypes.bool,
|
||||
center: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
inline: PropTypes.bool,
|
||||
images: PropTypes.object.isRequired,
|
||||
inline: PropTypes.bool,
|
||||
padded: PropTypes.bool,
|
||||
tiny: PropTypes.bool
|
||||
}
|
||||
|
120
js/src/ui/IdentityIcon/identityIcon.spec.js
Normal file
120
js/src/ui/IdentityIcon/identityIcon.spec.js
Normal file
@ -0,0 +1,120 @@
|
||||
// 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 { mount } from 'enzyme';
|
||||
import React, { PropTypes } from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import muiTheme from '../Theme';
|
||||
|
||||
import IdentityIcon from './';
|
||||
|
||||
const ADDRESS0 = '0x0000000000000000000000000000000000000000';
|
||||
const ADDRESS1 = '0x0123456789012345678901234567890123456789';
|
||||
const ADDRESS2 = '0x9876543210987654321098765432109876543210';
|
||||
|
||||
let component;
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
dappsUrl: 'dappsUrl/'
|
||||
};
|
||||
}
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
images: {
|
||||
[ADDRESS2]: 'reduxImage'
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
if (props && props.address === undefined) {
|
||||
props.address = ADDRESS1;
|
||||
}
|
||||
|
||||
component = mount(
|
||||
<IdentityIcon { ...props } />,
|
||||
{
|
||||
childContextTypes: {
|
||||
api: PropTypes.object,
|
||||
muiTheme: PropTypes.object
|
||||
},
|
||||
context: {
|
||||
api: createApi(),
|
||||
muiTheme,
|
||||
store: createRedux()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/IdentityIcon', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
describe('images', () => {
|
||||
it('renders an <img> with address specified', () => {
|
||||
const img = render().find('img');
|
||||
|
||||
expect(img).to.have.length(1);
|
||||
expect(img.props().src).to.equal('test-createIdentityImg');
|
||||
});
|
||||
|
||||
it('renders an <img> with redux source when available', () => {
|
||||
const img = render({ address: ADDRESS2 }).find('img');
|
||||
|
||||
expect(img).to.have.length(1);
|
||||
expect(img.props().src).to.equal('dappsUrl/reduxImage');
|
||||
});
|
||||
|
||||
it('renders an <ContractIcon> with no address specified', () => {
|
||||
expect(render({ address: null }).find('ActionCode')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders an <CancelIcon> with 0x00..00 address specified', () => {
|
||||
expect(render({ address: ADDRESS0 }).find('ContentClear')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sizes', () => {
|
||||
it('renders 56px by default', () => {
|
||||
expect(render().find('img').props().width).to.equal('56px');
|
||||
});
|
||||
|
||||
it('renders 16px for tiny', () => {
|
||||
expect(render({ tiny: true }).find('img').props().width).to.equal('16px');
|
||||
});
|
||||
|
||||
it('renders 24px for button', () => {
|
||||
expect(render({ button: true }).find('img').props().width).to.equal('24px');
|
||||
});
|
||||
|
||||
it('renders 32px for inline', () => {
|
||||
expect(render({ inline: true }).find('img').props().width).to.equal('32px');
|
||||
});
|
||||
});
|
||||
});
|
@ -15,13 +15,23 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { isNullAddress } from '~/util/validation';
|
||||
import ShortenedHash from '../ShortenedHash';
|
||||
|
||||
const defaultName = 'UNNAMED';
|
||||
const defaultName = (
|
||||
<FormattedMessage
|
||||
id='ui.identityName.unnamed'
|
||||
defaultMessage='UNNAMED' />
|
||||
);
|
||||
const defaultNameNull = (
|
||||
<FormattedMessage
|
||||
id='ui.identityName.null'
|
||||
defaultMessage='NULL' />
|
||||
);
|
||||
|
||||
class IdentityName extends Component {
|
||||
static propTypes = {
|
||||
@ -43,7 +53,7 @@ class IdentityName extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nullName = isNullAddress(address) ? 'null' : null;
|
||||
const nullName = isNullAddress(address) ? defaultNameNull : null;
|
||||
const addressFallback = nullName || (shorten ? (<ShortenedHash data={ address } />) : address);
|
||||
const fallback = unknown ? defaultName : addressFallback;
|
||||
const isUuid = account && account.name === account.uuid;
|
||||
|
@ -14,8 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import IdentityName from './identityName';
|
||||
@ -44,9 +46,11 @@ const STORE = {
|
||||
|
||||
function render (props) {
|
||||
return mount(
|
||||
<IdentityName
|
||||
store={ STORE }
|
||||
{ ...props } />
|
||||
<IntlProvider locale='en'>
|
||||
<IdentityName
|
||||
store={ STORE }
|
||||
{ ...props } />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,7 +78,7 @@ describe('ui/IdentityName', () => {
|
||||
});
|
||||
|
||||
it('renders 0x000...000 as null', () => {
|
||||
expect(render({ address: ADDR_NULL }).text()).to.equal('null');
|
||||
expect(render({ address: ADDR_NULL }).text()).to.equal('NULL');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { LinearProgress } from 'material-ui';
|
||||
@ -33,8 +34,8 @@ class TxHash extends Component {
|
||||
static propTypes = {
|
||||
hash: PropTypes.string.isRequired,
|
||||
isTest: PropTypes.bool,
|
||||
summary: PropTypes.bool,
|
||||
maxConfirmations: PropTypes.number
|
||||
maxConfirmations: PropTypes.number,
|
||||
summary: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@ -43,14 +44,14 @@ class TxHash extends Component {
|
||||
|
||||
state = {
|
||||
blockNumber: new BigNumber(0),
|
||||
transaction: null,
|
||||
subscriptionId: null
|
||||
subscriptionId: null,
|
||||
transaction: null
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { api } = this.context;
|
||||
|
||||
api.subscribe('eth_blockNumber', this.onBlockNumber).then((subscriptionId) => {
|
||||
return api.subscribe('eth_blockNumber', this.onBlockNumber).then((subscriptionId) => {
|
||||
this.setState({ subscriptionId });
|
||||
});
|
||||
}
|
||||
@ -59,28 +60,28 @@ class TxHash extends Component {
|
||||
const { api } = this.context;
|
||||
const { subscriptionId } = this.state;
|
||||
|
||||
api.unsubscribe(subscriptionId);
|
||||
return api.unsubscribe(subscriptionId);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { hash, isTest, summary } = this.props;
|
||||
|
||||
const link = (
|
||||
const hashLink = (
|
||||
<a href={ txLink(hash, isTest) } target='_blank'>
|
||||
<ShortenedHash data={ hash } />
|
||||
</a>
|
||||
);
|
||||
|
||||
let header = (
|
||||
<p>The transaction has been posted to the network, with a hash of { link }.</p>
|
||||
);
|
||||
if (summary) {
|
||||
header = (<p>{ link }</p>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ header }
|
||||
<p>{
|
||||
summary
|
||||
? hashLink
|
||||
: <FormattedMessage
|
||||
id='ui.txHash.posted'
|
||||
defaultMessage='The transaction has been posted to the network with a hash of {hashLink}'
|
||||
values={ { hashLink } } />
|
||||
}</p>
|
||||
{ this.renderConfirmations() }
|
||||
</div>
|
||||
);
|
||||
@ -98,20 +99,22 @@ class TxHash extends Component {
|
||||
color='white'
|
||||
mode='indeterminate'
|
||||
/>
|
||||
<div className={ styles.progressinfo }>waiting for confirmations</div>
|
||||
<div className={ styles.progressinfo }>
|
||||
<FormattedMessage
|
||||
id='ui.txHash.waiting'
|
||||
defaultMessage='waiting for confirmations' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const confirmations = blockNumber.minus(transaction.blockNumber).plus(1);
|
||||
const value = Math.min(confirmations.toNumber(), maxConfirmations);
|
||||
let count;
|
||||
if (confirmations.gt(maxConfirmations)) {
|
||||
count = confirmations.toFormat(0);
|
||||
} else {
|
||||
count = confirmations.toFormat(0) + `/${maxConfirmations}`;
|
||||
|
||||
let count = confirmations.toFormat(0);
|
||||
if (confirmations.lte(maxConfirmations)) {
|
||||
count = `${count}/${maxConfirmations}`;
|
||||
}
|
||||
const unit = value === 1 ? 'confirmation' : 'confirmations';
|
||||
|
||||
return (
|
||||
<div className={ styles.confirm }>
|
||||
@ -121,10 +124,17 @@ class TxHash extends Component {
|
||||
max={ maxConfirmations }
|
||||
value={ value }
|
||||
color='white'
|
||||
mode='determinate'
|
||||
/>
|
||||
mode='determinate' />
|
||||
<div className={ styles.progressinfo }>
|
||||
<abbr title={ `block #${blockNumber.toFormat(0)}` }>{ count } { unit }</abbr>
|
||||
<abbr title={ `block #${blockNumber.toFormat(0)}` }>
|
||||
<FormattedMessage
|
||||
id='ui.txHash.confirmations'
|
||||
defaultMessage='{count} {value, plural, one {confirmation} other {confirmations}}'
|
||||
values={ {
|
||||
count,
|
||||
value
|
||||
} } />
|
||||
</abbr>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -138,15 +148,17 @@ class TxHash extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ blockNumber });
|
||||
|
||||
api.eth
|
||||
return api.eth
|
||||
.getTransactionReceipt(hash)
|
||||
.then((transaction) => {
|
||||
this.setState({ transaction });
|
||||
this.setState({
|
||||
blockNumber,
|
||||
transaction
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('onBlockNumber', error);
|
||||
this.setState({ blockNumber });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
132
js/src/ui/TxHash/txHash.spec.js
Normal file
132
js/src/ui/TxHash/txHash.spec.js
Normal file
@ -0,0 +1,132 @@
|
||||
// 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 sinon from 'sinon';
|
||||
|
||||
import TxHash from './';
|
||||
|
||||
const TXHASH = '0xabcdef123454321abcdef';
|
||||
|
||||
let api;
|
||||
let blockNumber;
|
||||
let callback;
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function createApi () {
|
||||
blockNumber = new BigNumber(100);
|
||||
api = {
|
||||
eth: {
|
||||
getTransactionReceipt: (hash) => {
|
||||
return Promise.resolve({
|
||||
blockNumber: new BigNumber(100),
|
||||
hash
|
||||
});
|
||||
}
|
||||
},
|
||||
nextBlock: (increment = 1) => {
|
||||
blockNumber = blockNumber.plus(increment);
|
||||
return callback(null, blockNumber);
|
||||
},
|
||||
subscribe: (type, _callback) => {
|
||||
callback = _callback;
|
||||
return callback(null, blockNumber).then(() => {
|
||||
return Promise.resolve(1);
|
||||
});
|
||||
},
|
||||
unsubscribe: sinon.stub().resolves(true)
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
nodeStatus: { isTest: true }
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (props) {
|
||||
const baseComponent = shallow(
|
||||
<TxHash
|
||||
hash={ TXHASH }
|
||||
{ ...props } />,
|
||||
{ context: { store: createRedux() } }
|
||||
);
|
||||
component = baseComponent.find('TxHash').shallow({ context: { api: createApi() } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/TxHash', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders the summary', () => {
|
||||
expect(component.find('p').find('FormattedMessage').props().id).to.equal('ui.txHash.posted');
|
||||
});
|
||||
|
||||
describe('renderConfirmations', () => {
|
||||
describe('with no transaction retrieved', () => {
|
||||
let child;
|
||||
|
||||
beforeEach(() => {
|
||||
child = shallow(instance.renderConfirmations());
|
||||
});
|
||||
|
||||
it('renders indeterminate progressbar', () => {
|
||||
expect(child.find('LinearProgress[mode="indeterminate"]')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders waiting text', () => {
|
||||
expect(child.find('FormattedMessage').props().id).to.equal('ui.txHash.waiting');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with transaction retrieved', () => {
|
||||
let child;
|
||||
|
||||
beforeEach(() => {
|
||||
return instance.componentDidMount().then(() => {
|
||||
child = shallow(instance.renderConfirmations());
|
||||
});
|
||||
});
|
||||
|
||||
it('renders determinate progressbar', () => {
|
||||
expect(child.find('LinearProgress[mode="determinate"]')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders confirmation text', () => {
|
||||
expect(child.find('FormattedMessage').props().id).to.equal('ui.txHash.confirmations');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -25,7 +25,7 @@ import TxRow from './txRow';
|
||||
|
||||
const api = new Api({ execute: sinon.stub() });
|
||||
|
||||
function renderShallow (props) {
|
||||
function render (props) {
|
||||
return shallow(
|
||||
<TxRow
|
||||
{ ...props } />,
|
||||
@ -33,7 +33,7 @@ function renderShallow (props) {
|
||||
);
|
||||
}
|
||||
|
||||
describe('ui/TxRow', () => {
|
||||
describe('ui/TxList/TxRow', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
const block = {
|
||||
@ -45,7 +45,7 @@ describe('ui/TxRow', () => {
|
||||
value: new BigNumber(1)
|
||||
};
|
||||
|
||||
expect(renderShallow({ block, tx })).to.be.ok;
|
||||
expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ const STORE = {
|
||||
}
|
||||
};
|
||||
|
||||
function renderShallow (props) {
|
||||
function render (props) {
|
||||
return shallow(
|
||||
<TxList
|
||||
store={ STORE }
|
||||
@ -48,7 +48,7 @@ function renderShallow (props) {
|
||||
describe('ui/TxList', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(renderShallow()).to.be.ok;
|
||||
expect(render({ address: '0x123', hashes: [] })).to.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user