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

Setup launch protocol for voting - Closes #806 #851

Merged
merged 27 commits into from
Oct 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e555c12
Fix login redirect to preserve search params
slaweet Oct 10, 2017
d80f06b
Change getDelegate to accept options instead of publicKey
slaweet Oct 10, 2017
6dbf057
Create voteUrlProcessor component
slaweet Oct 10, 2017
145a359
Use voteUrlPreprocessor in voteDialog
slaweet Oct 10, 2017
49c9c30
Update voteUrlProcessor to fetch votes first
slaweet Oct 10, 2017
ce26e61
Refactor voteUrlProcessor
slaweet Oct 11, 2017
6cef916
Add e2e test for voting dialog launch protocol
slaweet Oct 11, 2017
c35b66d
Fix voteUrlProcessor for new account
slaweet Oct 11, 2017
24a28d7
Refactor delegate name lookup from voteUrlProcessor to redux
slaweet Oct 11, 2017
6fa27ba
Update voting middleware to get delegate api call
slaweet Oct 11, 2017
0fc234a
Extract parseSearchParams function to utils
slaweet Oct 11, 2017
aeb507b
Refactor voteUrlProcessor
slaweet Oct 11, 2017
0e3a4d2
Fix clickToSend component not to put undefined in url
slaweet Oct 11, 2017
076ba9c
Fix unit test coverage of voting actions
slaweet Oct 11, 2017
3d5b7ee
Remove unused code
slaweet Oct 12, 2017
384a6be
Rename downvote to unvote
slaweet Oct 12, 2017
e173810
Change voting params to votes and unvotes
slaweet Oct 12, 2017
e4fff41
Add unit tests for voting reducer
slaweet Oct 12, 2017
049838e
Add unit tests for voting middleware
slaweet Oct 12, 2017
85a97bb
Update translation sources
slaweet Oct 12, 2017
f315189
Add unit tests for voteUrlProcessor component
slaweet Oct 12, 2017
32ceca1
Fix modal dialog overflow
slaweet Oct 12, 2017
8f02de8
Fix plural translation strings
slaweet Oct 12, 2017
cb28be1
Fix i18n-scanner to preserve already translated values
slaweet Oct 12, 2017
9dd4b39
Increase e2e test wait time from 4 to 10 seconds
slaweet Oct 12, 2017
16eb189
Fix baseURL of "I go to {url}" e2e step
slaweet Oct 13, 2017
c9c5cb4
Merge branch '1.2.0' into 806-launch-protocol-for-voting
slaweet Oct 13, 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: 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