Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #549 from LiskHQ/514-pending-transactions
Browse files Browse the repository at this point in the history
Implement pending transactions in React - Closes #514
  • Loading branch information
slaweet authored Aug 4, 2017
2 parents 53e1abe + 67f10d8 commit bad3ba3
Show file tree
Hide file tree
Showing 17 changed files with 292 additions and 70 deletions.
28 changes: 28 additions & 0 deletions src/actions/transactions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import actionTypes from '../constants/actions';

/**
* An action to dispatch transactionAdded
*
*/
export const transactionAdded = data => ({
data,
type: actionTypes.transactionAdded,
});

/**
* An action to dispatch transactionsUpdated
*
*/
export const transactionsUpdated = data => ({
data,
type: actionTypes.transactionsUpdated,
});

/**
* An action to dispatch transactionsLoaded
*
*/
export const transactionsLoaded = data => ({
data,
type: actionTypes.transactionsLoaded,
});
39 changes: 39 additions & 0 deletions src/actions/transactions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from 'chai';
import actionTypes from '../constants/actions';
import { transactionAdded, transactionsUpdated, transactionsLoaded } from './transactions';

describe('actions: transactions', () => {
const data = {
id: 'dummy',
};

describe('transactionAdded', () => {
it('should create an action to transactionAdded', () => {
const expectedAction = {
data,
type: actionTypes.transactionAdded,
};
expect(transactionAdded(data)).to.be.deep.equal(expectedAction);
});
});

describe('transactionsUpdated', () => {
it('should create an action to transactionsUpdated', () => {
const expectedAction = {
data,
type: actionTypes.transactionsUpdated,
};
expect(transactionsUpdated(data)).to.be.deep.equal(expectedAction);
});
});

describe('errorToastDisplayed', () => {
it('should create an action to transactionsLoaded', () => {
const expectedAction = {
data,
type: actionTypes.transactionsLoaded,
};
expect(transactionsLoaded(data)).to.be.deep.equal(expectedAction);
});
});
});
13 changes: 12 additions & 1 deletion src/components/account/accountComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import grid from 'flexboxgrid/dist/flexboxgrid.css';
import styles from './account.css';
import Address from './address';
import LiskAmount from '../liskAmount';
import { getAccountStatus } from '../../utils/api/account';
import { getAccountStatus, getAccount, transactions } from '../../utils/api/account';
import ClickToSend from '../send/clickToSend';
import { toRawLsk } from '../../utils/lsk';

Expand All @@ -27,6 +27,17 @@ class AccountComponent extends React.Component {
}

update() {
getAccount(this.props.peers.data, this.props.account.address).then((result) => {
if (result.balance !== this.props.account.balance) {
const maxBlockSize = 25;
transactions(this.props.peers.data, this.props.account.address, maxBlockSize)
.then((res) => {
this.props.onTransactionsUpdated(res.transactions);
});
}
this.props.onAccountUpdated(result);
});

const { onActivePeerUpdated } = this.props;
return getAccountStatus(this.props.peers.data).then(() => {
onActivePeerUpdated({ online: true });
Expand Down
4 changes: 4 additions & 0 deletions src/components/account/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { connect } from 'react-redux';
import AccountComponent from './accountComponent';
import { activePeerUpdate } from '../../actions/peers';
import { accountUpdated } from '../../actions/account';
import { transactionsUpdated } from '../../actions/transactions';

/**
* Passing state
Expand All @@ -12,6 +14,8 @@ const mapStateToProps = state => ({

const mapDispatchToProps = dispatch => ({
onActivePeerUpdated: data => dispatch(activePeerUpdate(data)),
onAccountUpdated: data => dispatch(accountUpdated(data)),
onTransactionsUpdated: data => dispatch(transactionsUpdated(data)),
});

const Account = connect(
Expand Down
2 changes: 2 additions & 0 deletions src/components/send/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import Send from './send';
import { successAlertDialogDisplayed, errorAlertDialogDisplayed } from '../../actions/dialog';
import { transactionAdded } from '../../actions/transactions';

const mapStateToProps = state => ({
account: state.account,
Expand All @@ -10,6 +11,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
showSuccessAlert: data => dispatch(successAlertDialogDisplayed(data)),
showErrorAlert: data => dispatch(errorAlertDialogDisplayed(data)),
addTransaction: data => dispatch(transactionAdded(data)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Send);
Expand Down
10 changes: 9 additions & 1 deletion src/components/send/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,18 @@ class Send extends React.Component {
toRawLsk(this.state.amount.value),
this.props.account.passphrase,
this.props.account.sencodPassphrase,
).then(() => {
).then((data) => {
this.props.showSuccessAlert({
text: `Your transaction of ${this.state.amount.value} LSK to ${this.state.recipient.value} was accepted and will be processed in a few seconds.`,
});
this.props.addTransaction({
id: data.transactionId,
senderPublicKey: this.props.account.publicKey,
senderId: this.props.account.address,
recipientId: this.state.recipient.value,
amount: toRawLsk(this.state.amount.value),
fee: toRawLsk(this.fee),
});
}).catch((res) => {
this.props.showErrorAlert({
text: res && res.message ? res.message : 'An error occurred while creating the transaction.',
Expand Down
24 changes: 20 additions & 4 deletions src/components/send/send.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import chai, { expect } from 'chai';
import { mount } from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import Send from './send';
import * as accountApi from '../../utils/api/account';


chai.use(chaiEnzyme()); // Note the invocation at the end
chai.use(sinonChai);
chai.use(chaiEnzyme());

describe('Send', () => {
let wrapper;
let accountApiMock;
let props;

beforeEach(() => {
accountApiMock = sinon.mock(accountApi);
const props = {
props = {
activePeer: {},
account: {
balance: 1000e8,
},
closeDialog: () => {},
showSuccessAlert: sinon.spy(),
showErrorAlert: sinon.spy(),
addTransaction: sinon.spy(),
};
wrapper = mount(<Send {...props} />);
});
Expand Down Expand Up @@ -79,25 +86,34 @@ describe('Send', () => {
expect(wrapper.find('.amount input').props().value).to.equal('999.9');
});

it('allows to send a transaction and handles success', () => {
it('allows to send a transaction, handles success and adds pending transaction', () => {
accountApiMock.expects('send').resolves({ success: true });

wrapper.find('.amount input').simulate('change', { target: { value: '120.25' } });
wrapper.find('.recipient input').simulate('change', { target: { value: '11004588490103196952L' } });
wrapper.find('.submit-button').simulate('click');
// TODO: this doesn't work for some reason
// expect(props.showSuccessAlert).to.have.been.calledWith();
// expect(props.addTransaction).to.have.been.calledWith();
});

it('allows to send a transaction and handles error response with message', () => {
accountApiMock.expects('send').rejects({ message: 'Some server-side error' });
const response = { message: 'Some server-side error' };
accountApiMock.expects('send').rejects(response);
wrapper.find('.amount input').simulate('change', { target: { value: '120.25' } });
wrapper.find('.recipient input').simulate('change', { target: { value: '11004588490103196952L' } });
wrapper.find('.submit-button').simulate('click');
// TODO: this doesn't work for some reason
// expect(props.showErrorAlert).to.have.been.calledWith({ text: response.message });
});

it('allows to send a transaction and handles error response without message', () => {
accountApiMock.expects('send').rejects({ success: false });
wrapper.find('.amount input').simulate('change', { target: { value: '120.25' } });
wrapper.find('.recipient input').simulate('change', { target: { value: '11004588490103196952L' } });
wrapper.find('.submit-button').simulate('click');
// TODO: this doesn't work for some reason
// expect(props.showErrorAlert).to.have.been.calledWith({
// text: 'An error occurred while creating the transaction.' });
});
});
11 changes: 1 addition & 10 deletions src/components/spinner/spinner.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,16 @@

border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}

.bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}

// Spinner animations
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0.5) }
40% { -webkit-transform: scale(1.0) }
}

@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0.5);
Expand All @@ -38,4 +29,4 @@
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
}
9 changes: 8 additions & 1 deletion src/components/transactions/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { connect } from 'react-redux';
import Transactions from './transactionsComponent';
import { transactionsLoaded } from '../../actions/transactions';

const mapStateToProps = state => ({
address: state.account.address,
activePeer: state.peers.data,
transactions: [...state.transactions.pending, ...state.transactions.confirmed],
});
export default connect(mapStateToProps)(Transactions);

const mapDispatchToProps = dispatch => ({
transactionsLoaded: data => dispatch(transactionsLoaded(data)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Transactions);

5 changes: 4 additions & 1 deletion src/components/transactions/transactionRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import TransactionType from './transactionType';
import styles from './transactions.css';
import Status from './status';
import Amount from './amount';
import Spinner from '../spinner';

const TransactionRow = props => (
<tr>
<td className={`${props.tableStyle.rowCell} ${styles.centerText}`}>
<TooltipTime label={props.value.timestamp}></TooltipTime>
{props.value.confirmations ?
<TooltipTime label={props.value.timestamp}></TooltipTime> :
<Spinner />}
</td>
<td className={`${props.tableStyle.rowCell} ${styles.centerText}`}>
<TooltipWrapper tooltip={`${props.value.confirmations} confirmations`}>{props.value.id}</TooltipWrapper>
Expand Down
47 changes: 28 additions & 19 deletions src/components/transactions/transactionRow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,39 @@ import tableStyle from 'react-toolbox/lib/table/theme.css';
import TransactionRow from './transactionRow';

describe('TransactionRow', () => {
it('expect to have 6 "td"', () => {
const address = '16313739661670634666L';
const rowData = {
id: '1038520263604146911',
height: 5,
blockId: '12520699228609837463',
type: 0,
timestamp: 35929631,
senderPublicKey: 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f',
senderId: '16313739661670634666L',
recipientId: '537318935439898807L',
recipientPublicKey: '86499879448d1b0215d59cbf078836e3d7d9d2782d56a2274a568761bff36f19',
amount: 464000000000,
fee: 10000000,
signature: '3d276c1cb00edbc803e8911033727fe4a77f931868f89dc2f42deeefd7aa2eef1a58cd289517546ac3135f804499d1406234597d5b6198c4b9dac373c2b1bd03',
signatures: [],
confirmations: 892,
asset: {},
};
const rowData = {
id: '1038520263604146911',
height: 5,
blockId: '12520699228609837463',
type: 0,
timestamp: 35929631,
senderPublicKey: 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f',
senderId: '16313739661670634666L',
recipientId: '537318935439898807L',
recipientPublicKey: '86499879448d1b0215d59cbf078836e3d7d9d2782d56a2274a568761bff36f19',
amount: 464000000000,
fee: 10000000,
signature: '3d276c1cb00edbc803e8911033727fe4a77f931868f89dc2f42deeefd7aa2eef1a58cd289517546ac3135f804499d1406234597d5b6198c4b9dac373c2b1bd03',
signatures: [],
confirmations: 892,
asset: {},
};
const address = '16313739661670634666L';

it('should render 6 "td"', () => {
const wrapper = shallow(<TransactionRow
tableStyle={tableStyle}
address={address}
value={rowData}
></TransactionRow>);
expect(wrapper.find('td')).to.have.lengthOf(6);
});

it('should render Spinner if no value.confirmations" ', () => {
rowData.confirmations = undefined;
const wrapper = shallow(
<TransactionRow tableStyle={tableStyle} address={address} value={rowData}>
</TransactionRow>);
expect(wrapper.find('Spinner')).to.have.lengthOf(1);
});
});
Loading

0 comments on commit bad3ba3

Please sign in to comment.