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

Migrate voting for delegates on voting component - Closes #492 #593

Merged
merged 55 commits into from
Aug 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6a5e37e
Create voting action file
yasharAyari Jul 29, 2017
f2a491b
Create confirmVotes component
yasharAyari Jul 29, 2017
5b7463d
Create voting reducer file
yasharAyari Jul 29, 2017
cb72c39
Create votingHeaderWrapper component
yasharAyari Jul 29, 2017
8877866
Use connect in votingHeader component
yasharAyari Jul 29, 2017
edf34d9
Add mapDispatchToProps to voting component
yasharAyari Jul 29, 2017
2d6a23f
Add voting reducer to main reducer
yasharAyari Jul 29, 2017
df83a8a
Use redux dispach in votingComponent
yasharAyari Jul 29, 2017
59bcd90
Add some new items to actionTypes object
yasharAyari Jul 29, 2017
d579793
Merge branch 'development' into 492-MIGRATE-VOTING-FOR-DELEGATES-ON-V…
yasharAyari Jul 31, 2017
e53f40c
Fix some bugs in voting reducer
yasharAyari Aug 1, 2017
5d4281d
Add git addToUnvoted dispacher to votingHeaderWrapper
yasharAyari Aug 1, 2017
e79170c
add voting unvote menu to votingHeader
yasharAyari Aug 1, 2017
4b87f38
Fix some bugs in votingComponent
yasharAyari Aug 1, 2017
b5d0d55
Change logic of voting reducer
yasharAyari Aug 2, 2017
48bb7d5
Optimize voting actions
yasharAyari Aug 2, 2017
ab70058
Chnage name of some actions
yasharAyari Aug 2, 2017
4d22c46
Use optimized voting actions in voting components
yasharAyari Aug 2, 2017
be0e3a5
Merge branch 'development' into 492-MIGRATE-VOTING-FOR-DELEGATES-ON-V…
yasharAyari Aug 4, 2017
2b5dcaf
Add two new actions to voting reducer
yasharAyari Aug 4, 2017
5112838
Fix some style bugs in voting components
yasharAyari Aug 4, 2017
19340ad
Add delegate api utils in confirmVotes and use it to confrim votes
yasharAyari Aug 4, 2017
bacf336
Create a new theme for some componets of react-toolbox and use them i…
yasharAyari Aug 4, 2017
eb6c1be
fix a bug in votingComponent
yasharAyari Aug 4, 2017
56e3baa
Merge branch 'development' into 492-MIGRATE-VOTING-FOR-DELEGATES-ON-V…
yasharAyari Aug 4, 2017
3fb8fae
Add description comment for an action
yasharAyari Aug 10, 2017
da0f082
Remove some store dispachs from voting component
yasharAyari Aug 10, 2017
b23a793
Add new classes to voting.css
yasharAyari Aug 10, 2017
0748741
Create a custom for react-toolbox Table and use it in votingComponent
yasharAyari Aug 10, 2017
69dfd94
Add second passphrase field to voting confirm
yasharAyari Aug 10, 2017
8d19cf6
add confirmVoteText function to votingHeader component
yasharAyari Aug 10, 2017
0d4daa9
Add new test cases to votingHeader test file
yasharAyari Aug 10, 2017
3913a5c
Fix a bug in voting reducer
yasharAyari Aug 10, 2017
53cf223
Create selectableRow component
yasharAyari Aug 10, 2017
db6761a
votingRow component
yasharAyari Aug 10, 2017
a05b071
Create a test file for voting reducer
yasharAyari Aug 10, 2017
c1744a8
Merge branch 'development' into 492-MIGRATE-VOTING-FOR-DELEGATES-ON-V…
yasharAyari Aug 10, 2017
0c08abc
Add sinon-stub-promise to package.json
yasharAyari Aug 12, 2017
204b869
Create a test file for voting actions
yasharAyari Aug 12, 2017
4040f8d
Create a test file for confirmVotes component
yasharAyari Aug 12, 2017
01d627c
Create a test file for selectableRow component
yasharAyari Aug 12, 2017
67ce416
Create a test file for votingRow component
yasharAyari Aug 12, 2017
fd45bc8
Rename some action names
yasharAyari Aug 12, 2017
0a3adb3
Fix some bugs in voting reducer
yasharAyari Aug 12, 2017
9544b7a
Rename some action names in voting actions file
yasharAyari Aug 12, 2017
df260d1
Fix some bugs in votingComponent
yasharAyari Aug 12, 2017
dba410b
fix a minor bug in selectableRow
yasharAyari Aug 12, 2017
fe055e6
Add some new test cases to votingHeader test file
yasharAyari Aug 12, 2017
ca69944
Use new actions in confirmVotes
yasharAyari Aug 12, 2017
e76d285
Use new actions in votingHeaderWrapper
yasharAyari Aug 12, 2017
2077a8c
Merge branch 'development' with 492-MIGRATE-VOTING-FOR-DELEGATES-ON-V…
yasharAyari Aug 14, 2017
924a4ac
Rename selectableRow to voteCheckbox
yasharAyari Aug 14, 2017
c134aa1
Rename votingComponent to voting
yasharAyari Aug 14, 2017
dee1efc
Rename votingComponent to voting in voting index file
yasharAyari Aug 14, 2017
be92b01
Fix some bugs in votings
yasharAyari Aug 14, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@
"imports-loader": "=0.6.5",
"js-nacl": "=1.2.2",
"json-loader": "=0.5.4",
"karma": "=1.6.0",
"karma": "^1.7.0",
"karma-chai": "=0.1.0",
"karma-chrome-launcher": "=2.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "=1.1.1",
"karma-jenkins-reporter": "0.0.2",
"karma-mocha": "=1.3.0",
Expand All @@ -103,6 +103,7 @@
"should": "=11.2.0",
"sinon": "=2.0.0",
"sinon-chai": "=2.8.0",
"sinon-stub-promise": "^4.0.0",
"style-loader": "=0.16.1",
"stylelint": "=7.12.0",
"url-loader": "=0.5.7",
Expand Down
31 changes: 31 additions & 0 deletions src/actions/voting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import actionTypes from '../constants/actions';

/**
* Add data to the list of voted delegates
*/
export const addedToVoteList = data => ({
type: actionTypes.addedToVoteList,
data,
});

/**
* Remove data from the list of voted delegates
*/
export const removedFromVoteList = data => ({
type: actionTypes.removedFromVoteList,
data,
});

/**
* Remove all data from the list of voted delegates and list of unvoted delegates
*/
export const clearVoteLists = () => ({
type: actionTypes.votesCleared,
});

/**
* Add pending variable to the list of voted delegates and list of unvoted delegates
*/
export const pendingVotesAdded = () => ({
type: actionTypes.pendingVotesAdded,
});
50 changes: 50 additions & 0 deletions src/actions/voting.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from 'chai';
import actionTypes from '../constants/actions';
import {
addedToVoteList,
removedFromVoteList,
clearVoteLists,
pendingVotesAdded,
} from './voting';

describe('actions: voting', () => {
const data = {
label: 'dummy',
};

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

describe('removedFromVoteList', () => {
it('should create an action to remove data from vote list', () => {
const expectedAction = {
data,
type: actionTypes.removedFromVoteList,
};
expect(removedFromVoteList(data)).to.be.deep.equal(expectedAction);
});
});
describe('clearVoteLists', () => {
it('should create an action to remove all pending rows from vote list', () => {
const expectedAction = {
type: actionTypes.votesCleared,
};
expect(clearVoteLists()).to.be.deep.equal(expectedAction);
});
});
describe('pendingVotesAdded', () => {
it('should create an action to remove all pending rows from vote list', () => {
const expectedAction = {
type: actionTypes.pendingVotesAdded,
};
expect(pendingVotesAdded()).to.be.deep.equal(expectedAction);
});
});
});
116 changes: 116 additions & 0 deletions src/components/voting/confirmVotes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import { connect } from 'react-redux';
import Input from 'react-toolbox/lib/input';
import { vote } from '../../utils/api/delegate';
import { alertDialogDisplayed } from '../../actions/dialog';
import { clearVoteLists, pendingVotesAdded } from '../../actions/voting';
import InfoParagraph from '../infoParagraph';
import ActionBar from '../actionBar';
import { SYNC_ACTIVE_INTERVAL } from '../../constants/api';
import Fees from '../../constants/fees';

export class ConfirmVotes extends React.Component {
constructor() {
super();
this.state = {
secondSecret: '',
};
}

confirm() {
const secondSecret = this.state.secondSecret.length === 0 ? null : this.state.secondSecret;
const text = 'Your votes were successfully submitted. It can take several seconds before they are processed.';

vote(
this.props.activePeer,
this.props.account.passphrase,
this.props.account.publicKey,
this.props.votedList,
this.props.unvotedList,
secondSecret,
).then((data) => {
this.props.pendingVotesAdded();

// add to pending transaction
this.props.addTransaction({
id: data.transactionId,
senderPublicKey: this.props.account.publicKey,
senderId: this.props.account.address,
amount: 0,
fee: Fees.vote,
type: 3,
});

// remove pending votes
setTimeout(() => {
this.props.clearVoteLists();
}, SYNC_ACTIVE_INTERVAL);
this.props.showSuccessAlert({
title: 'Success',
type: 'success',
text,
});
});
}

setSecondPass(name, value) {
this.setState({ ...this.state, [name]: value });
}

render() {
const secondPassphrase = this.props.account.secondSignature === 1 ?
<Input type='text' label='Second Passphrase' name='secondSecret'
className='secondSecret' value={this.state.secondSecret}
onChange={this.setSecondPass.bind(this, 'secondSecret')}/> : null;

return (
<article>
<h3>Add vote to</h3>
<ul>
{this.props.votedList.map(item => <li key={item.username}>{item.username}</li>)}
</ul>
<h3>Remove vote from</h3>
<ul>
{this.props.unvotedList.map(item => <li key={item.username}>{item.username}</li>)}
</ul>

{secondPassphrase}

<InfoParagraph>
You can select up to 33 delegates in one voting turn.
<br />
You can vote for up to 101 delegates in total.
</InfoParagraph>

<ActionBar
secondaryButton={{
onClick: this.props.closeDialog,
}}
primaryButton={{
label: 'Vote',
fee: Fees.vote,
disabled: (
this.props.votedList.length === 0 &&
this.props.unvotedList.length === 0),
onClick: this.confirm.bind(this),
}} />
</article>
);
}
}


const mapStateToProps = state => ({
votedList: state.voting.votedList,
unvotedList: state.voting.unvotedList,
account: state.account,
activePeer: state.peers.data,
});

const mapDispatchToProps = dispatch => ({
showSuccessAlert: data => dispatch(alertDialogDisplayed(data)),
clearVoteLists: () => dispatch(clearVoteLists()),
pendingVotesAdded: () => dispatch(pendingVotesAdded()),
});

export default connect(mapStateToProps, mapDispatchToProps)(ConfirmVotes);
85 changes: 85 additions & 0 deletions src/components/voting/confirmVotes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import chai, { expect } from 'chai';
import { mount } from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import PropTypes from 'prop-types';
import sinonStubPromise from 'sinon-stub-promise';
import store from '../../store';
import ConfrimVotesContainer, { ConfirmVotes } from './confirmVotes';
import * as delegateApi from '../../utils/api/delegate';

sinonStubPromise(sinon);
chai.use(sinonChai);
chai.use(chaiEnzyme());

const props = {
activePeer: {},
account: {
passphrase: 'pass',
publicKey: 'key',
secondSignature: 0,
},
votedList: [
{
username: 'yashar',
},
{
username: 'tom',
},
],
unvotedList: [
{
username: 'john',
},
{
username: 'test',
},
],
closeDialog: sinon.spy(),
showSuccessAlert: sinon.spy(),
clearVoteLists: sinon.spy(),
pendingVotesAdded: sinon.spy(),
addTransaction: sinon.spy(),
};
describe('ConfrimVotesContainer', () => {
it('should render ConfrimVotes', () => {
const wrapper = mount(<ConfrimVotesContainer {...props} store={store} />, {
context: { store },
childContextTypes: { store: PropTypes.object.isRequired },
});
expect(wrapper.find('ConfirmVotes').exists()).to.be.equal(true);
});
});
describe('ConfrimVotes', () => {
let wrapper;
const delegateApiMock = sinon.stub(delegateApi, 'vote');
beforeEach(() => {
wrapper = mount(<ConfirmVotes {...props} />, {
context: { store },
childContextTypes: { store: PropTypes.object.isRequired },
});
});

it('should call vote api when confirm button is pressed', () => {
const clock = sinon.useFakeTimers();
delegateApiMock.returnsPromise().resolves({ success: true });
wrapper.instance().confirm();
expect(props.pendingVotesAdded).to.have.been.calledWith();
expect(props.addTransaction).to.have.been.calledWith();
expect(props.showSuccessAlert).to.have.been.calledWith();
// it should triger 'props.clearVoteLists' after 10000 ms
clock.tick(10000);
expect(props.clearVoteLists).to.have.been.calledWith();
});

it('should update state when "setSecondPass" is called', () => {
wrapper.setProps({
account: Object.assign(props.account, { secondSignature: 1 }),
});
wrapper.find('.secondSecret input').simulate('change', { target: { value: 'this is test' } });
expect(wrapper.state('secondSecret')).to.be.equal('this is test');
});
});

7 changes: 5 additions & 2 deletions src/components/voting/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { connect } from 'react-redux';
import VotingComponent from './votingComponent';
import Voting from './voting';

const mapStateToProps = state => ({
address: state.account.address,
activePeer: state.peers.data,
votedList: state.voting.votedList,
unvotedList: state.voting.unvotedList,
refreshDelegates: state.voting.refresh,
});

export default connect(mapStateToProps)(VotingComponent);
export default connect(mapStateToProps)(Voting);
2 changes: 1 addition & 1 deletion src/components/voting/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ describe('Voting', () => {
});

it('should render VotingComponent', () => {
expect(wrapper.find('VotingComponent')).to.have.lengthOf(1);
expect(wrapper.find('Voting')).to.have.lengthOf(1);
});
});
39 changes: 39 additions & 0 deletions src/components/voting/voteCheckbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import Checkbox from 'react-toolbox/lib/checkbox';
import { connect } from 'react-redux';
import { addedToVoteList, removedFromVoteList } from '../../actions/voting';
import Spinner from '../spinner';

export class VoteCheckbox extends React.Component {
/**
* change status of selected row
* @param {Number} index - index of row that we want to change status of that
* @param {Boolean} value - value of checkbox
*/
toggle(delegate, value) {
if (value) {
this.props.addToVoteList(delegate);
} else {
this.props.removeFromVoteList(delegate);
}
}

render() {
const template = this.props.pending ?
<Spinner /> :
<Checkbox
className={this.props.styles.field}
checked={this.props.value}
onChange={this.toggle.bind(this, this.props.data)}
/>;
return template;
}
}

const mapDispatchToProps = dispatch => ({
addToVoteList: data => dispatch(addedToVoteList(data)),
removeFromVoteList: data => dispatch(removedFromVoteList(data)),
});

export default connect(null, mapDispatchToProps)(VoteCheckbox);

Loading