-
Notifications
You must be signed in to change notification settings - Fork 60
Add selected votes counts bar - Closes #445 #754
Changes from 8 commits
7d3d9ae
79dc67c
4760423
5b6dc01
dfada0d
50a91cf
9348eb9
0cd6351
3125bd0
baf06de
a9c40b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.red { | ||
color: #c62828; | ||
} | ||
|
||
.fixedAtBottom { | ||
position: fixed; | ||
bottom: 0; | ||
left: 8px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React from 'react'; | ||
import grid from 'flexboxgrid/dist/flexboxgrid.css'; | ||
|
||
import votingConst from '../../constants/voting'; | ||
import style from './votingBar.css'; | ||
|
||
const VotingBar = ({ votes }) => { | ||
const { maxCountOfVotes, maxCountOfVotesInOneTurn } = votingConst; | ||
const votedList = Object.keys(votes).filter(key => votes[key].confirmed); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I set up a jsperf to see the difference: ForEach is 3 or 4 times faster, but the absolute values of how long it takes on a list of 100 still make the difference negligible. I like using |
||
const voteList = Object.keys(votes).filter( | ||
key => votes[key].unconfirmed && !votes[key].confirmed); | ||
const unvoteList = Object.keys(votes).filter( | ||
key => !votes[key].unconfirmed && votes[key].confirmed); | ||
const totalVotesCount = (votedList.length - unvoteList.length) + voteList.length; | ||
const totalNewVotesCount = voteList.length + unvoteList.length; | ||
|
||
return (voteList.length + unvoteList.length ? | ||
<div className={`${grid.row} ${style.fixedAtBottom} box voting-bar`}> | ||
<div className={ | ||
`${grid['col-sm-12']} ${grid['col-md-10']} ${grid['col-md-offset-1']} | ||
${grid.row} ${grid['center-xs']} ${grid['middle-xs']}`}> | ||
<span className={`${grid['col-sm-3']} ${grid['col-xs-12']} upvotes`}> | ||
<span>Upvotes: </span> | ||
<strong>{voteList.length}</strong> | ||
</span> | ||
<span className={`${grid['col-sm-3']} ${grid['col-xs-12']} downvotes`}> | ||
<span>Downvotes: </span> | ||
<strong>{unvoteList.length}</strong> | ||
</span> | ||
<span className={`${grid['col-sm-3']} ${grid['col-xs-12']} total-new-votes`}> | ||
<span>Total new votes: </span> | ||
<strong className={totalNewVotesCount > maxCountOfVotesInOneTurn && style.red}> | ||
{totalNewVotesCount} | ||
</strong> | ||
<span> / {maxCountOfVotesInOneTurn}</span> | ||
</span> | ||
<span className={`${grid['col-sm-3']} ${grid['col-xs-12']} total-votes`}> | ||
<span>Total votes: </span> | ||
<strong className={totalVotesCount > 101 && style.red}> | ||
{totalVotesCount} | ||
</strong> | ||
<span> / {maxCountOfVotes}</span> | ||
</span> | ||
</div> | ||
</div> : | ||
null | ||
); | ||
}; | ||
|
||
export default VotingBar; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import React from 'react'; | ||
import { expect } from 'chai'; | ||
import { mount } from 'enzyme'; | ||
|
||
import VotingBar from './votingBar'; | ||
|
||
import styles from './votingBar.css'; | ||
|
||
describe('VotingBar', () => { | ||
let wrapper; | ||
const props = { | ||
votes: { | ||
voted: { | ||
confirmed: true, | ||
unconfirmed: false, | ||
}, | ||
downvote: { | ||
confirmed: true, | ||
unconfirmed: true, | ||
}, | ||
upvote: { | ||
confirmed: false, | ||
unconfirmed: true, | ||
}, | ||
upvote2: { | ||
confirmed: false, | ||
unconfirmed: true, | ||
}, | ||
notVoted: { | ||
confirmed: false, | ||
unconfirmed: false, | ||
}, | ||
}, | ||
}; | ||
|
||
const generateNVotes = n => ( | ||
[...Array(n)].map((item, i) => i).reduce( | ||
(dict, value) => { | ||
dict[`genesis_${value}`] = { unconfirmed: true }; | ||
return dict; | ||
}, {}) | ||
); | ||
|
||
beforeEach(() => { | ||
wrapper = mount(<VotingBar {...props} />); | ||
}); | ||
|
||
it('should render number of upvotes', () => { | ||
expect(wrapper.find('.upvotes')).to.have.text('Upvotes: 2'); | ||
}); | ||
|
||
it('should render number of downvotes', () => { | ||
expect(wrapper.find('.downvotes')).to.have.text('Downvotes: 1'); | ||
}); | ||
|
||
it('should render number of downvotes', () => { | ||
expect(wrapper.find('.total-new-votes')).to.have.text('Total new votes: 3 / 33'); | ||
}); | ||
|
||
it('should render number of total votes', () => { | ||
expect(wrapper.find('.total-votes')).to.have.text('Total votes: 3 / 101'); | ||
}); | ||
|
||
it('should not render if no upvotes or downvotes', () => { | ||
wrapper.setProps({ votes: {} }); | ||
expect(wrapper.html()).to.equal(null); | ||
}); | ||
|
||
it('should render number of total votes in red if 101 exceeded', () => { | ||
const votes = generateNVotes(102); | ||
|
||
expect(wrapper.find(`.total-votes .${styles.red}`)).to.be.not.present(); | ||
wrapper.setProps({ votes }); | ||
expect(wrapper.find('.total-votes')).to.have.text('Total votes: 102 / 101'); | ||
expect(wrapper.find(`.total-votes .${styles.red}`)).to.have.text('102'); | ||
}); | ||
|
||
it('should render number of total new votes in red if 33 exceeded', () => { | ||
const votes = generateNVotes(34); | ||
expect(wrapper.find(`.total-new-votes .${styles.red}`)).to.be.not.present(); | ||
wrapper.setProps({ votes }); | ||
expect(wrapper.find('.total-new-votes')).to.have.text('Total new votes: 34 / 33'); | ||
expect(wrapper.find(`.total-new-votes .${styles.red}`)).to.have.text('34'); | ||
}); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const votingConst = { | ||
maxCountOfVotesInOneTurn: 33, | ||
maxCountOfVotes: 101, | ||
}; | ||
|
||
export default votingConst; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import actionTypes from '../../constants/actions'; | ||
import votingConst from '../../constants/voting'; | ||
import { errorToastDisplayed } from '../../actions/toaster'; | ||
|
||
const votingMiddleware = store => next => (action) => { | ||
next(action); | ||
if (action.type === actionTypes.voteToggled) { | ||
const { votes } = store.getState().voting; | ||
const voteCount = Object.keys(votes).filter( | ||
key => votes[key].confirmed !== votes[key].unconfirmed).length; | ||
const currentVote = votes[action.data.username] || { unconfirmed: true, confirmed: false }; | ||
console.log(voteCount, votingConst.maxCountOfVotesInOneTurn, currentVote); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove |
||
if (voteCount === votingConst.maxCountOfVotesInOneTurn + 1 && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be great if we show a toast for when we exceed 101 overall votes. |
||
currentVote.unconfirmed !== currentVote.confirmed) { | ||
const label = `Maximum of ${votingConst.maxCountOfVotesInOneTurn} votes in one transaction exceeded.`; | ||
const newAction = errorToastDisplayed({ label }); | ||
store.dispatch(newAction); | ||
} | ||
} | ||
}; | ||
|
||
export default votingMiddleware; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { expect } from 'chai'; | ||
import { spy, stub } from 'sinon'; | ||
import { errorToastDisplayed } from '../../actions/toaster'; | ||
import middleware from './voting'; | ||
import actionTypes from '../../constants/actions'; | ||
import votingConst from '../../constants/voting'; | ||
|
||
describe('voting middleware', () => { | ||
let store; | ||
let next; | ||
const label = `Maximum of ${votingConst.maxCountOfVotesInOneTurn} votes in one transaction exceeded.`; | ||
|
||
const generateNVotes = n => ( | ||
[...Array(n)].map((item, i) => i).reduce( | ||
(dict, value) => { | ||
dict[`genesis_${value}`] = { confirmed: false, unconfirmed: true }; | ||
return dict; | ||
}, {}) | ||
); | ||
|
||
beforeEach(() => { | ||
store = stub(); | ||
store.getState = () => ({ | ||
voting: { | ||
votes: { | ||
...generateNVotes(votingConst.maxCountOfVotesInOneTurn + 1), | ||
test2: { | ||
unconfirmed: false, | ||
confirmed: false, | ||
}, | ||
}, | ||
}, | ||
}); | ||
store.dispatch = spy(); | ||
next = spy(); | ||
}); | ||
|
||
it('should passes the action to next middleware', () => { | ||
const givenAction = { | ||
type: 'TEST_ACTION', | ||
}; | ||
|
||
middleware(store)(next)(givenAction); | ||
expect(next).to.have.been.calledWith(givenAction); | ||
}); | ||
|
||
it('should dispatch errorToastDisplayed if 34 new votes and new vote unconfirmed !== confirmed ', () => { | ||
const givenAction = { | ||
type: actionTypes.voteToggled, | ||
data: { | ||
username: 'test', | ||
}, | ||
}; | ||
middleware(store)(next)(givenAction); | ||
expect(store.dispatch).to.have.been.calledWith(errorToastDisplayed({ label })); | ||
}); | ||
|
||
it('should not dispatch errorToastDisplayed if 34 new votes and new vote unconfirmed === confirmed ', () => { | ||
const givenAction = { | ||
type: actionTypes.voteToggled, | ||
data: { | ||
username: 'test2', | ||
}, | ||
}; | ||
middleware(store)(next)(givenAction); | ||
expect(store.dispatch).to.not.have.been.calledWith(errorToastDisplayed({ label })); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent verbosity please perform this shorthand assignment while importing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to use it like that, but it doesn't work.
ends up in both
maxCountOfVotes
andmaxCountOfVotesInOneTurn
beingundefined