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 #851 from LiskHQ/806-launch-protocol-for-voting
Browse files Browse the repository at this point in the history
Setup launch protocol for voting - Closes #806
  • Loading branch information
slaweet authored Oct 13, 2017
2 parents abf6258 + c9c5cb4 commit 845ffff
Show file tree
Hide file tree
Showing 32 changed files with 932 additions and 131 deletions.
9 changes: 9 additions & 0 deletions features/step_definitions/generic.step.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => {
waitForElemAndCheckItsText(selectorClass, text, callback);
});

Then('I should see "{elementName}" element with text:', (elementName, text, callback) => {
const selectorClass = `.${elementName.replace(/ /g, '-')}`;
waitForElemAndCheckItsText(selectorClass, text, callback);
});

Then('I should see element "{elementName}" that contains text:', (elementName, text, callback) => {
const selectorClass = `.${elementName.replace(/ /g, '-')}`;
waitForElemAndCheckItsText(selectorClass, text, callback);
Expand All @@ -136,6 +141,10 @@ defineSupportCode(({ Given, When, Then, setDefaultTimeout }) => {
waitForElemAndClickIt('.login-button', callback);
});

When('I go to "{url}"', (url, callback) => {
browser.get(`${browser.params.baseURL}#${url}`).then(callback);
});

When('I {iterations} times move mouse randomly', (iterations, callback) => {
const actions = browser.actions();
/**
Expand Down
2 changes: 1 addition & 1 deletion features/support/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const EC = protractor.ExpectedConditions;
const waitTime = 4000;
const waitTime = 10000;

function waitForElemAndCheckItsText(selector, text, callback) {
const elem = element(by.css(selector));
Expand Down
14 changes: 14 additions & 0 deletions features/voting.feature
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,17 @@ Feature: Voting tab
And I click "vote button"
And I click "cancel button"
Then I should see no "modal dialog"

Scenario: should allow to select delegates by URL
Given I'm logged in as "delegate candidate"
When I go to "/main/voting/vote?votes=standby_27,standby_28,standby_29,nonexisting_22&unvotes=standby_33"
Then I should see text "3 delegate names successfully resolved to add vote to." in "upvotes message" element
And I should see text "1 of delegate names selected for unvote were not voted for:standby_33" in "notVotedYet message" element
And I should see text "1 of entered delegate names could not be resolved:nonexisting_22" in "notFound message" element
And I should see "vote list" element with text:
"""
standby_27
standby_28
standby_29
"""

40 changes: 37 additions & 3 deletions src/actions/voting.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { vote, listAccountDelegates, listDelegates } from '../utils/api/delegate';
import { transactionAdded } from './transactions';
import { errorAlertDialogDisplayed } from './dialog';
import {
listAccountDelegates,
listDelegates,
vote,
} from '../utils/api/delegate';
import { passphraseUsed } from './account';
import actionTypes from '../constants/actions';
import { transactionAdded } from './transactions';
import Fees from '../constants/fees';
import actionTypes from '../constants/actions';
import transactionTypes from '../constants/transactionTypes';

/**
Expand Down Expand Up @@ -45,6 +49,22 @@ export const voteToggled = data => ({
data,
});


/**
* Updates vote lookup status of the given delegate name
*/
export const voteLookupStatusUpdated = data => ({
type: actionTypes.voteLookupStatusUpdated,
data,
});

/**
* Clears all vote lookup statuses
*/
export const voteLookupStatusCleared = () => ({
type: actionTypes.voteLookupStatusCleared,
});

/**
* Makes Api call to register votes
* Adds pending state and then after the duration of one round
Expand Down Expand Up @@ -122,3 +142,17 @@ export const delegatesFetched = ({ activePeer, q, offset, refresh }) =>
dispatch(delegatesAdded({ list: delegates, totalDelegates: totalCount, refresh }));
});
};


/**
* Get list of delegates current account has voted for and dispatch it with votes from url
*/
export const urlVotesFound = ({ activePeer, upvotes, unvotes, address }) =>
(dispatch) => {
const processUrlVotes = (votes) => {
dispatch(votesAdded({ list: votes, upvotes, unvotes }));
};
listAccountDelegates(activePeer, address)
.then(({ delegates }) => { processUrlVotes(delegates); })
.catch(() => { processUrlVotes([]); });
};
96 changes: 90 additions & 6 deletions src/actions/voting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
votesUpdated,
votesAdded,
voteToggled,
voteLookupStatusUpdated,
votePlaced,
votesFetched,
urlVotesFound,
delegatesFetched,
delegatesAdded,
} from './voting';
Expand Down Expand Up @@ -36,6 +38,20 @@ describe('actions: voting', () => {
});
});

describe('voteLookupStatusUpdated', () => {
it('should create an action to update lookup status of any given delegate name', () => {
const data = {
label: 'dummy',
};
const expectedAction = {
data,
type: actionTypes.voteLookupStatusUpdated,
};

expect(voteLookupStatusUpdated(data)).to.be.deep.equal(expectedAction);
});
});

describe('votesAdded', () => {
it('should create an action to remove data from vote list', () => {
const data = delegateList;
Expand Down Expand Up @@ -132,27 +148,45 @@ describe('actions: voting', () => {
});

describe('votesFetched', () => {
let delegateApiMock;
const data = {
activePeer: {},
address: '8096217735672704724L',
};
const delegates = delegateList;
const actionFunction = votesFetched(data);

beforeEach(() => {
delegateApiMock = sinon.stub(delegateApi, 'listAccountDelegates').returnsPromise();
});

afterEach(() => {
delegateApiMock.restore();
});


it('should create an action function', () => {
const actionFunction = votesFetched(data);
expect(typeof actionFunction).to.be.deep.equal('function');
});

it.skip('should dispatch votesAdded action if resolved', () => {
const delegateApiMock = sinon.stub(delegateApi, 'listAccountDelegates');
it('should dispatch votesAdded action when resolved if type !== \'update\'', () => {
const dispatch = sinon.spy();

delegateApiMock.returnsPromise().resolves({ delegates });
delegateApiMock.resolves({ delegates });
const expectedAction = { list: delegates };

actionFunction(dispatch);
votesFetched(data)(dispatch);
expect(dispatch).to.have.been.calledWith(votesAdded(expectedAction));
delegateApiMock.restore();
});

it('should dispatch votesUpdated action when resolved if type === \'update\'', () => {
const dispatch = sinon.spy();

delegateApiMock.resolves({ delegates });
const expectedAction = { list: delegates };

votesFetched({ ...data, type: 'update' })(dispatch);
expect(dispatch).to.have.been.calledWith(votesUpdated(expectedAction));
});
});

Expand Down Expand Up @@ -182,4 +216,54 @@ describe('actions: voting', () => {
delegateApiMock.restore();
});
});

describe('urlVotesFound', () => {
let delegateApiMock;
const data = {
activePeer: {},
address: '8096217735672704724L',
upvotes: [],
unvotes: [],
};
const delegates = delegateList;
let expectedAction = {
list: delegates,
upvotes: [],
unvotes: [],
};

beforeEach(() => {
delegateApiMock = sinon.stub(delegateApi, 'listAccountDelegates').returnsPromise();
});

afterEach(() => {
delegateApiMock.restore();
});

it('should create an action function', () => {
expect(typeof urlVotesFound(data)).to.be.deep.equal('function');
});

it('should dispatch votesAdded action when resolved', () => {
const dispatch = sinon.spy();


urlVotesFound(data)(dispatch);
delegateApiMock.resolves({ delegates });
expect(dispatch).to.have.been.calledWith(votesAdded(expectedAction));
});

it('should dispatch votesAdded action when rejected', () => {
const dispatch = sinon.spy();

expectedAction = {
...expectedAction,
list: [],
};

urlVotesFound(data)(dispatch);
delegateApiMock.rejects();
expect(dispatch).to.have.been.calledWith(votesAdded(expectedAction));
});
});
});
9 changes: 8 additions & 1 deletion src/components/clickToSend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import { fromRawLsk } from '../../utils/lsk';
const ClickToSend = ({ rawAmount, amount, className,
recipient, children, disabled }) => {
const normalizedAmount = rawAmount ? fromRawLsk(rawAmount) : amount;
const urlParams = new URLSearchParams();
if (normalizedAmount) {
urlParams.set('amount', normalizedAmount);
}
if (recipient) {
urlParams.set('recipient', recipient);
}

return (
disabled ?
children :
<RelativeLink className={`${styles.clickable} ${className}`}
to={`send?amount=${normalizedAmount}&&recipient=${recipient}`}>
to={`send?${urlParams}`}>
{children}
</RelativeLink>
);
Expand Down
20 changes: 18 additions & 2 deletions src/components/dialog/dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
}

& header {
margin: -24px;
margin-bottom: 24px;
border-radius: 2px 2px 0 0;
color: rgba(255, 255, 255, 0.87);

Expand All @@ -32,16 +30,34 @@
}
}

.body {
border-radius: 2px 2px 0 0;
padding: 0px;
}

.innerBody {
padding: 24px;
max-height: calc(100vh - 64px); /* stylelint-disable-line */
overflow: auto;
}

@media screen and (min-width: 960px) {
.fullscreen {
width: 75vw; /* stylelint-disable-line */
}
}

@media screen and (min-width: 600px) {
.innerBody {
max-height: calc(100vh - 100px); /* stylelint-disable-line */
}
}

.error {
background-color: #c62828;
}

.success {
background-color: #7cb342;
}

16 changes: 3 additions & 13 deletions src/components/dialog/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Dialog from 'react-toolbox/lib/dialog';
import Navigation from 'react-toolbox/lib/navigation';
import AppBar from 'react-toolbox/lib/app_bar';
import { IconButton } from 'react-toolbox/lib/button';
import { parseSearchParams } from '../../utils/searchParams';
import styles from './dialog.css';
import getDialogs from './dialogs';
import routesReg from '../../utils/routes';
Expand Down Expand Up @@ -43,24 +44,13 @@ class DialogElement extends Component {
}
}

// eslint-disable-next-line class-methods-use-this
parseParams(search) {
return search.replace(/^\?/, '').split('&&').reduce((acc, param) => {
const keyValue = param.split('=');
if (keyValue[0] !== '' && keyValue[1] !== 'undefined') {
acc[keyValue[0]] = keyValue[1];
}
return acc;
}, {});
}

open(config, dialog) {
clearTimeout(this.timeout);
this.setState({ hidden: false });
this.props.dialogDisplayed({
title: dialog.title,
childComponent: dialog.component,
childComponentProps: this.parseParams(this.props.history.location.search),
childComponentProps: parseSearchParams(this.props.history.location.search),
});
}

Expand Down Expand Up @@ -88,7 +78,7 @@ class DialogElement extends Component {
<IconButton className={`${styles['x-button']} x-button`} onClick={this.goBack.bind(this)} icon='close'/>
</Navigation>
</AppBar>
<div className='modal-dialog-body'>
<div className={`modal-dialog-body ${styles.innerBody}`}>
{this.props.dialog.childComponent ?
<this.props.dialog.childComponent
{...(this.props.dialog.childComponentProps || {})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/privateRoute/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { connect } from 'react-redux';

export const PrivateRouteRender = ({ render, isAuthenticated, ...rest }) => (
<Route {...rest} render={ matchProps => (
isAuthenticated ? render(matchProps) : <Redirect to={`/?referrer=${rest.history.location.pathname}`} />
isAuthenticated ? render(matchProps) : <Redirect to={`/?referrer=${rest.history.location.pathname}${rest.history.location.search}`} />
)}/>
);

Expand Down
Loading

0 comments on commit 845ffff

Please sign in to comment.