diff --git a/features/step_definitions/generic.step.js b/features/step_definitions/generic.step.js
index f749f811b..865c6fdfe 100644
--- a/features/step_definitions/generic.step.js
+++ b/features/step_definitions/generic.step.js
@@ -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);
@@ -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();
/**
diff --git a/features/support/util.js b/features/support/util.js
index ba95c7315..4ba37f43e 100644
--- a/features/support/util.js
+++ b/features/support/util.js
@@ -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));
diff --git a/features/voting.feature b/features/voting.feature
index c90313778..c20029afd 100644
--- a/features/voting.feature
+++ b/features/voting.feature
@@ -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
+ """
+
diff --git a/src/actions/voting.js b/src/actions/voting.js
index 7dee77a66..d17776ff6 100644
--- a/src/actions/voting.js
+++ b/src/actions/voting.js
@@ -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';
/**
@@ -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
@@ -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([]); });
+ };
diff --git a/src/actions/voting.test.js b/src/actions/voting.test.js
index 9e6614b18..ab6742902 100644
--- a/src/actions/voting.test.js
+++ b/src/actions/voting.test.js
@@ -6,8 +6,10 @@ import {
votesUpdated,
votesAdded,
voteToggled,
+ voteLookupStatusUpdated,
votePlaced,
votesFetched,
+ urlVotesFound,
delegatesFetched,
delegatesAdded,
} from './voting';
@@ -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;
@@ -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));
});
});
@@ -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));
+ });
+ });
});
diff --git a/src/components/clickToSend/index.js b/src/components/clickToSend/index.js
index fea11486a..1d1d713d6 100644
--- a/src/components/clickToSend/index.js
+++ b/src/components/clickToSend/index.js
@@ -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 :