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

Add autocomplete module to 'confirm votes modal' - Closes #587 #625

Merged
merged 22 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
62cfe10
Add some classes for autocomplete to voting.css
yasharAyari Aug 18, 2017
bc70f6b
Fix a bug in delegate api
yasharAyari Aug 18, 2017
8b106af
Add vote autocomplete to confirmVotes
yasharAyari Aug 18, 2017
0bd8284
Pass votedDelegates to votingConfirm modal
yasharAyari Aug 18, 2017
bcca750
Create voteAutocomplete module
yasharAyari Aug 20, 2017
aa11978
Move autocomplete functionality to voteAutocomplete and use in confir…
yasharAyari Aug 20, 2017
26740a2
Remove unnecessary activePeerMock.verify() in peers api test file tha…
yasharAyari Aug 20, 2017
1a188d8
create a test file for voteAutocomplete component
yasharAyari Aug 20, 2017
da0fa40
Fix a bug in voteAutocomplete
yasharAyari Aug 20, 2017
b092c2d
Change label of voting autocomplete inputs
yasharAyari Aug 21, 2017
53d5535
Ative a scenario in voting feature
yasharAyari Aug 22, 2017
6fc2230
Rename votedDict to votedList in voteAutocomplete function
yasharAyari Aug 22, 2017
97a5066
Add description comment for some methods
yasharAyari Aug 22, 2017
262314a
Change some selectors to make them work wit rreact version
yasharAyari Aug 22, 2017
da3d35a
Merge branch 'development' into 587-add-autocomplete-to-confirm-votes
yasharAyari Aug 22, 2017
e9358b7
Use fakeState in voteAutocomplete.test
yasharAyari Aug 22, 2017
df2c1be
Fix a bug in delegates api
yasharAyari Aug 22, 2017
08140aa
Improve test coverage percentage of voteAutocomplete component
yasharAyari Aug 23, 2017
00a84f9
Merge branch 'development' into 587-add-autocomplete-to-confirm-votes
yasharAyari Aug 23, 2017
c14a026
Fix a bug in confirmVotes test file
yasharAyari Aug 23, 2017
319e1ef
Improve test coverage percentage of voteAutocomplete component
yasharAyari Aug 23, 2017
e36c1ad
Fix a bug in voteAutoComplete test file
yasharAyari Aug 23, 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
9 changes: 5 additions & 4 deletions features/step_definitions/voting.step.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ defineSupportCode(({ When, Then }) => {
});

When('Search twice for "{searchTerm}" in vote dialog', (searchTerm, callback) => {
element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope');
element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope', callback);
element.all(by.css('.votedListSearch input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('#votedResult ul li:nth-child(1)');
element.all(by.css('.votedListSearch input')).get(0).sendKeys(searchTerm);
browser.sleep(500);
waitForElemAndClickIt('#votedResult ul li:nth-child(1)', callback);
});

Then('I should see delegates list with {count} lines', (count, callback) => {
Expand Down
1 change: 0 additions & 1 deletion features/voting.feature
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ Feature: Voting tab
And I click "submit button"
Then I should see alert dialog with title "Success" and text "Your votes were successfully submitted. It can take several seconds before they are processed."

@ignore
Scenario: should allow to select delegates in the "Vote" dialog and vote for them
Given I'm logged in as "delegate candidate"
When I click tab number 2
Expand Down
16 changes: 5 additions & 11 deletions src/components/voting/confirmVotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { votePlaced } from '../../actions/voting';
import InfoParagraph from '../infoParagraph';
import ActionBar from '../actionBar';
import Fees from '../../constants/fees';
import Autocomplete from './voteAutocomplete';

export class ConfirmVotes extends React.Component {
constructor() {
Expand Down Expand Up @@ -39,20 +40,13 @@ export class ConfirmVotes extends React.Component {

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

<Autocomplete voted={this.props.voted} />
{secondPassphrase}

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

Expand Down
5 changes: 2 additions & 3 deletions src/components/voting/confirmVotes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ describe('ConfirmVotes', () => {
expect(wrapper.find('InfoParagraph')).to.have.lengthOf(1);
});

it('should render two unordered list of voted and unvoted delegates', () => {
expect(wrapper.find('ul.voted-list li')).to.have.lengthOf(props.votedList.length);
expect(wrapper.find('ul.unvoted-list li')).to.have.lengthOf(props.unvotedList.length);
it('should render Autocomplete', () => {
expect(wrapper.find('VoteAutocomplete')).to.have.lengthOf(1);
});

it('should render an ActionBar', () => {
Expand Down
234 changes: 234 additions & 0 deletions src/components/voting/voteAutocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import React from 'react';
import { connect } from 'react-redux';
import Input from 'react-toolbox/lib/input';
import Chip from 'react-toolbox/lib/chip';
import { Card } from 'react-toolbox/lib/card';
import { List, ListItem } from 'react-toolbox/lib/list';
import { voteAutocomplete, unvoteAutocomplete } from '../../utils/api/delegate';
import { addedToVoteList, removedFromVoteList } from '../../actions/voting';
import styles from './voting.css';

export class VoteAutocomplete extends React.Component {
constructor() {
super();
this.state = {
votedSuggestionClass: styles.hidden,
unvotedSuggestionClass: styles.hidden,
votedResult: [],
unvotedResult: [],
votedListSearch: '',
unvotedListSearch: '',
};
}

suggestionStatus(value, name) {
const className = value ? '' : styles.hidden;
setTimeout(() => {
this.setState({ [name]: className });
}, 200);
}
/**
* Update state and call a suitable api to create a list of suggestion
*
* @param {*} name - name of input in react state
* @param {*} value - value that we want save it in react state
*/
search(name, value) {
this.setState({ ...this.state, [name]: value });
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (value.length > 0) {
if (name === 'votedListSearch') {
voteAutocomplete(this.props.activePeer, value, this.props.voted)
.then((res) => {
this.setState({
votedResult: res,
votedSuggestionClass: '',
});
});
} else {
unvoteAutocomplete(value, this.props.voted)
.then((res) => {
this.setState({
unvotedResult: res,
unvotedSuggestionClass: '',
});
});
}
} else {
this.setState({
votedResult: [],
unvotedResult: [],
votedSuggestionClass: styles.hidden,
unvotedSuggestionClass: styles.hidden,
});
}
}, 250);
}
votedSearchKeyDown(event) {
this.keyPress(event, 'votedSuggestionClass', 'votedResult');
}
unvotedSearchKeyDown(event) {
this.keyPress(event, 'unvotedSuggestionClass', 'unvotedResult');
}
/**
* handle key down event on autocomplete inputs
*
* @param {object} event - event of javascript
* @param {*} className - class name of suggestion box
* @param {*} listName - name of the list that we want to use as a suggestion list
*/
keyPress(event, className, listName) {
const selectFunc = listName === 'votedResult' ? 'addToVoted' : 'removeFromVoted';
const selected = this.state[listName].filter(d => d.hovered);
switch (event.keyCode) {
case 40: // 40 is keyCode of arrow down key in keyboard
this.handleArrowDown(this.state[listName], listName);
return false;
case 38: // 38 is keyCode of arrow up key in keyboard
this.handleArrowUp(this.state[listName], listName);
return false;
case 27 : // 27 is keyCode of enter key in keyboard
this.setState({
[className]: styles.hidden,
});
return false;
case 13 : // 27 is keyCode of escape key in keyboard
if (selected.length > 0) {
this[selectFunc](selected[0]);
}
return false;
default:
break;
}
return true;
}
/**
* move to the next item when arrow down is pressed
*
* @param {*} list - suggested list
* @param {*} className - name of the list that we want to use as a suggestion list in react state
*/
handleArrowDown(list, name) {
const selected = list.filter(d => d.hovered);
const index = list.indexOf(selected[0]);
if (selected.length > 0 && list[index + 1]) {
list[index].hovered = false;
list[index + 1].hovered = true;
// document.getElementById('votedResult').scrollTop = 56
} else if (list[index + 1]) {
list[0].hovered = true;
}
this.setState({ [name]: list });
}

/**
* move to the next item when up down is pressed
*
* @param {*} list - suggested list
* @param {*} className - name of the list that we want to use as a suggestion list in react state
*/
handleArrowUp(list, name) {
const selected = list.filter(d => d.hovered);
const index = list.indexOf(selected[0]);
if (index - 1 > -1) {
list[index].hovered = false;
list[index - 1].hovered = true;
}
this.setState({ [name]: list });
}
addToVoted(item) {
this.props.addedToVoteList(item);
this.setState({
votedListSearch: '',
votedSuggestionClass: styles.hidden,
});
}
removeFromVoted(item) {
this.props.removedFromVoteList(item);
this.setState({
unvotedListSearch: '',
unvotedSuggestionClass: styles.hidden,
});
}

render() {
return (
<article>
<h3>Add vote to</h3>
<div>
{this.props.votedList.map(
item => <Chip key={item.username}
deletable
onDeleteClick={this.props.removedFromVoteList.bind(this, item)}>
{item.username}
</Chip>,
)}
</div>
<section className={styles.searchContainer}>
<Input type='text' label='Search by username' name='votedListSearch'
className='votedListSearch' value={this.state.votedListSearch}
onBlur={this.suggestionStatus.bind(this, false, 'votedSuggestionClass')}
onKeyDown={this.votedSearchKeyDown.bind(this)}
onChange={this.search.bind(this, 'votedListSearch')}/>
<Card id='votedResult' className={`${styles.searchResult} ${this.state.votedSuggestionClass}`}>
<List>
{this.state.votedResult.map(
item => <ListItem
key={item.username}
caption={item.username}
selectable={true}
selected={true}
className={item.hovered ? styles.selectedRow : ''}
onClick={this.addToVoted.bind(this, item)} />,
)}
</List>
</Card>
</section>
<h3>Remove vote from</h3>
<div>
{this.props.unvotedList.map(
item => <Chip key={item.username}
deletable
onDeleteClick={this.props.addedToVoteList.bind(this, item)}>
{item.username}
</Chip>,
)}
</div>
<section className={styles.searchContainer}>
<Input type='text' label='Search by username' name='unvotedListSearch'
className='unvotedListSearch' value={this.state.unvotedListSearch}
onBlur={this.suggestionStatus.bind(this, false, 'unvotedSuggestionClass')}
onKeyDown={this.unvotedSearchKeyDown.bind(this)}
onChange={this.search.bind(this, 'unvotedListSearch')}/>
<Card id='unvotedResult' className={`${styles.searchResult} ${this.state.unvotedSuggestionClass}`}>
<List>
{this.state.unvotedResult.map(
item => <ListItem
key={item.username}
caption={item.username}
selectable={true}
selected={true}
className={item.hovered ? styles.selectedRow : ''}
onClick={this.removeFromVoted.bind(this, item)} />,
)}
</List>
</Card>
</section>
</article>
);
}
}

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

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

export default connect(mapStateToProps, mapDispatchToProps)(VoteAutocomplete);
Loading