diff --git a/src/components/resultBox/resultBox.js b/src/components/resultBox/resultBox.js index 2b3b376d34..a19897b90e 100644 --- a/src/components/resultBox/resultBox.js +++ b/src/components/resultBox/resultBox.js @@ -25,7 +25,7 @@ class ResultBox extends React.Component { : } -

{this.props.title}

+

{this.props.title}

diff --git a/test/e2e/voting.feature b/test/e2e/voting.feature index e9546dba99..10ce6d1c60 100644 --- a/test/e2e/voting.feature +++ b/test/e2e/voting.feature @@ -24,14 +24,6 @@ Feature: Voting page Then I should see 0 instances of "delegate row" And I should see text "No delegates found." in "empty message" element - @integration - Scenario: should allow to view my votes - Given I'm logged in as "genesis" - And I wait 0.1 seconds - When I go to "main/voting/" - And I click "filter voted" - Then I should see 100 instances of "delegate row" - @testnet Scenario: should allow to select delegates in the "Voting" tab and vote for them Given I'm logged in as "delegate candidate" @@ -58,19 +50,7 @@ Feature: Voting page And I click "confirm" And I wait 0.5 seconds Then I should see text "You’re votes are being processed and will be confirmed. It may take up to 10 minutes to be secured in the blockchain." in "result box message" element - - @integration - Scenario: should allow to remove votes form delegates - Given I'm logged in as "genesis" - And I wait 0.1 seconds - When I go to "main/voting/" - And I click checkbox on list item no. 3 - And I click checkbox on list item no. 5 - And I click "next" - And I click "confirm" - And I wait 0.5 seconds - Then I should see text "You’re votes are being processed and will be confirmed. It may take up to 10 minutes to be secured in the blockchain." in "result box message" element - + 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" @@ -84,23 +64,3 @@ Feature: Voting page standby_2[789] standby_2[789] """ - - @integration - @pending - 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 "voting" menu - And I click "vote button" - And Search twice for "genesis_7" in vote dialog - 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." - - @integration - @pending - Scenario: should not allow to vote if not enough funds for the fee - Given I'm logged in as "empty account" - When I go to "main/voting/" - And I click checkbox on list item no. 3 - And I click "vote button" - Then I should see "Insufficient funds for 1 LSK fee" error message - And "submit button" should be disabled diff --git a/test/integration/voteDialog.test.js b/test/integration/voteDialog.test.js deleted file mode 100644 index af3c3571f0..0000000000 --- a/test/integration/voteDialog.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import { step } from 'mocha-steps'; -import { expect } from 'chai'; -import { mount } from 'enzyme'; -import sinon from 'sinon'; -import { prepareStore, renderWithRouter } from '../utils/applicationInit'; -import accounts from '../constants/accounts'; -import actionTypes from '../../src/constants/actions'; -import accountReducer from '../../src/store/reducers/account'; -import votingReducer from '../../src/store/reducers/voting'; -import peersReducer from '../../src/store/reducers/peers'; -import { accountLoggedIn } from '../../src/actions/account'; -import { delegatesAdded } from '../../src/actions/voting'; -import * as delegateApi from '../../src/utils/api/delegate'; -import VoteDialog from '../../src/components/voteDialog'; - -const delegates = [ - { username: 'username1', publicKey: '123HG3452245L' }, - { username: 'username2', publicKey: '123HG3522345L' }, -]; -const unvotedDelegate = [ - { username: 'username3', publicKey: '123HG3522445L' }, - { username: 'username4', publicKey: '123HG3522545L' }, -]; - -const keyCodes = { - arrowDown: 40, - arrowUp: 38, - enter: 13, - escape: 27, -}; - -const realAccount = { - ...accounts.genesis, - delegate: {}, - multisignatures: [], - u_multisignatures: [], - unconfirmedBalance: '0', -}; - -const peers = { - defaultPeers: [ - 'node01.lisk.io', - 'node02.lisk.io', - ], - defaultSSLPeers: [ - 'node01.lisk.io', - 'node02.lisk.io', - ], - defaultTestnetPeers: [ - 'testnet.lisk.io', - ], - options: { - name: 'Testnet', - testnet: true, - ssl: true, - port: 443, - code: 1, - }, - ssl: true, - randomPeer: true, - testnet: true, - bannedPeers: [], - currentPeer: 'testnet.lisk.io', - port: 443, - nethash: { - 'Content-Type': 'application/json', - nethash: 'da3ed6a45429278bac2666961289ca17ad86595d33b31037615d4b8e8f158bba', - broadhash: 'da3ed6a45429278bac2666961289ca17ad86595d33b31037615d4b8e8f158bba', - os: 'lisk-js-api', - version: '1.0.0', - minVersion: '>=0.5.0', - port: 443, - }, -}; - -describe('@integration test of VoteDialog', () => { - let store; - let wrapper; - let clock; - - beforeEach(() => { - clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); - }); - afterEach(() => { - clock.restore(); - }); - - step('Given user is login in', () => { - store = prepareStore({ - account: accountReducer, - voting: votingReducer, - peers: peersReducer, - }); - - store.dispatch(accountLoggedIn(realAccount)); - store.dispatch({ - data: peers, - type: actionTypes.activePeerSet, - }); - wrapper = mount(renderWithRouter(VoteDialog, store)); - expect(store.getState().account).to.be.an('Object'); - expect(store.getState().voting).to.be.an('Object'); - expect(store.getState().peers).to.be.an('Object'); - }); - - step('When user doesn\'t vote to any delegates confirm button should be disabled', () => { - expect(wrapper.find('.primary-button button').props().disabled).to.be.equal(true); - }); - - step('Then user add an item to voteList and confirm button should become enabled', () => { - store.dispatch(delegatesAdded({ - list: delegates, - totalDelegates: 100, - refresh: true, - })); - expect(store.getState().voting.refresh).to.be.equal(true); - wrapper.update(); - const voteAutocompleteApiStub = sinon.stub(delegateApi, 'voteAutocomplete'); - voteAutocompleteApiStub.returnsPromise().resolves(unvotedDelegate); - // write a username - wrapper.find('.votedListSearch.vote-auto-complete input').simulate('change', { target: { value: 'user' } }); - clock.tick(400); - - // select it with arrow down - wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); - clock.tick(400); - wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.enter }); - wrapper.update(); - expect(wrapper.find('.primary-button button').props().disabled).to.be.equal(false); - voteAutocompleteApiStub.restore(); - }); - - step('When user deletes all items form voteList confirm button should become disabled', () => { - wrapper.find('.vote-list span').last().simulate('click'); - wrapper.update(); - expect(wrapper.find('.primary-button button').props().disabled).to.be.equal(true); - }); -}); diff --git a/test/integration/voting.test.js b/test/integration/voting.test.js new file mode 100644 index 0000000000..b4ccc7f3bd --- /dev/null +++ b/test/integration/voting.test.js @@ -0,0 +1,197 @@ +import { step } from 'mocha-steps'; +import thunk from 'redux-thunk'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { stub, match } from 'sinon'; +// import thunk from 'redux-thunk'; +import { prepareStore, renderWithRouter } from '../utils/applicationInit'; +import GenericStepDefinition from '../utils/genericStepDefinition'; +import accounts from '../constants/accounts'; +import transactionReducer from '../../src/store/reducers/transactions'; +import accountReducer from '../../src/store/reducers/account'; +import votingReducer from '../../src/store/reducers/voting'; +import { accountLoggedIn } from '../../src/actions/account'; +import * as delegateApi from '../../src/utils/api/delegate'; +import * as accountAPI from '../../src/utils/api/account'; +import Voting from '../../src/components/voting'; +import peersReducer from '../../src/store/reducers/peers'; +import loginMiddleware from '../../src/store/middlewares/login'; +import accountMiddleware from '../../src/store/middlewares/account'; +import peerMiddleware from '../../src/store/middlewares/peers'; +import transactionsMiddleware from '../../src/store/middlewares/transactions'; +import { activePeerSet } from '../../src/actions/peers'; +import networks from './../../src/constants/networks'; +import getNetwork from './../../src/utils/getNetwork'; + +const delegates = [ + { + address: '10839494368003872009L', + approval: 32.82, + missedblocks: 658, + producedblocks: 37236, + productivity: 98.26, + publicKey: 'b002f58531c074c7190714523eec08c48db8c7cfc0c943097db1a2e82ed87f84', + rank: 1, + rate: 1, + username: 'thepool', + vote: '3883699551759500', + }, + { + address: '14593474056247442712L', + approval: 32.08, + missedblocks: 283, + producedblocks: 38035, + productivity: 99.26, + publicKey: 'ec111c8ad482445cfe83d811a7edd1f1d2765079c99d7d958cca1354740b7614', + rank: 2, + rate: 2, + username: 'liskpool_com_01', + vote: '3796476912180144', + }, + { + address: '13943256167405531820L', + approval: 32.03, + missedblocks: 164, + producedblocks: 38088, + productivity: 99.57, + publicKey: '00de7d28ec3f55f42329667f08352d0d90faa3d2d4e62c883d86d1d7b083dd7c', + rank: 3, + rate: 3, + username: 'iii.element.iii', + vote: '3789594637157356', + }, +]; + +const account = { + ...accounts.genesis, + delegate: {}, + multisignatures: [], + u_multisignatures: [], + unconfirmedBalance: '0', +}; + +let store; +let wrapper; +let listDelegatesApiStub; +let listAccountDelegatesStub; +let voteApiStub; +let accountAPIStub; +let localStorageStub; +let helper; + +/** + * extend GenericStepDefinition and add some specific function to it for testing voting + */ +class Helper extends GenericStepDefinition { + /** + * + * @param {String} index - index of the delegate in the list + * @param {Boolean} value - new value of the input + */ + voteToDelegates(index, value) { + this.wrapper.find('.delegate-list input').at(index) + .simulate('change', { target: { value } }); + } + + goToConfirmation() { + expect(this.wrapper.find('button.confirm')).to.be.not.present(); + this.wrapper.find('button.next').simulate('click'); + expect(this.wrapper.find('button.confirm')).to.be.present(); + } +} + +/** + * required steps for login + */ +const loginProcess = (votes = []) => { + accountAPIStub = stub(accountAPI, 'getAccount'); + localStorageStub = stub(localStorage, 'getItem'); + localStorageStub.withArgs('accounts').returns(JSON.stringify([{}, {}])); + listDelegatesApiStub = stub(delegateApi, 'listDelegates'); + listAccountDelegatesStub = stub(delegateApi, 'listAccountDelegates'); + voteApiStub = stub(delegateApi, 'vote'); + store = prepareStore({ + account: accountReducer, + peers: peersReducer, + voting: votingReducer, + transactions: transactionReducer, + }, [ + thunk, + accountMiddleware, + loginMiddleware, + transactionsMiddleware, + peerMiddleware, + ]); + + accountAPIStub.withArgs(match.any).returnsPromise().resolves({ ...account }); + store.dispatch(activePeerSet({ network: getNetwork(networks.mainnet.code) })); + accountAPIStub.withArgs(match.any).returnsPromise().resolves({ ...account }); + store.dispatch(accountLoggedIn(account)); + + listDelegatesApiStub.returnsPromise() + .resolves({ delegates, success: true, totalCount: 20 }); + listAccountDelegatesStub.returnsPromise() + .resolves({ delegates: votes, success: true }); + + voteApiStub.returnsPromise() + .resolves({ + transactionId: 12341234123432412, + account, + }); + + wrapper = mount(renderWithRouter(Voting, store)); + helper = new Helper(wrapper); + expect(store.getState().account).to.be.an('Object'); + expect(store.getState().voting).to.be.an('Object'); + expect(store.getState().peers).to.be.an('Object'); +}; + +/** + * restore all Api mocks + */ +const restoreApiMocks = () => { + listDelegatesApiStub.restore(); + listAccountDelegatesStub.restore(); + voteApiStub.restore(); + accountAPIStub.restore(); + localStorageStub.restore(); +}; + +describe('@integration test of Voting', () => { + describe('Scenario: should allow to select delegates in the "Voting" and vote for them', () => { + step('I\'m logged in as "genesis"', loginProcess); + step('And next button should be disabled', () => helper.checkDisableInput('button.next')); + step('When I click checkbox on list item no. 0', () => helper.voteToDelegates(0, true)); + step('When I click checkbox on list item no. 1', () => helper.voteToDelegates(1, true)); + step('Then next button should be enabled', () => helper.checkDisableInput('button.next', 'not')); + step('And selectionHeader should be equal to "2"', () => helper.haveTextOf('.selection h4', 2)); + step('Then I go to confirmation step', () => helper.goToConfirmation()); + step('When I click on confirm button', () => helper.clickOnElement('button.confirm')); + step('Then I should see result box', () => helper.haveTextOf('h2.result-box-header', 'Votes submitted')); + step('Then I restore Api mocks', restoreApiMocks); + }); + + describe('Scenario: should allow me to filter my votes', () => { + step('I\'m logged in as "genesis"', () => loginProcess([delegates[0]])); + step('And I should see 3 rows', () => helper.haveLengthOf('ul.delegate-row', 3)); + step('When I click filter-voted', () => helper.clickOnElement('li.filter-voted')); + step('Then I should see 1 rows', () => helper.haveLengthOf('ul.delegate-row', 1)); + step('When I click filter-not-voted', () => helper.clickOnElement('li.filter-not-voted')); + step('Then I should see 2 rows', () => helper.haveLengthOf('ul.delegate-row', 2)); + step('When I click filter-all', () => helper.clickOnElement('li.filter-all')); + step('Then I should see all votes again', () => helper.haveLengthOf('ul.delegate-row', 3)); + step('Then I restore Api mocks', restoreApiMocks); + }); + + describe('Scenario: should allow to select delegates in the "Voting" and unvote them', () => { + step('I\'m logged in as "genesis"', () => loginProcess([delegates[0]])); + step('And next button should be disabled', () => helper.checkDisableInput('button.next')); + step('When I click checkbox on list item no. 0', () => helper.voteToDelegates(0, false)); + step('Then next button should be enabled', () => helper.checkDisableInput('button.next', 'not')); + step('And selectionHeader should be equal to "2"', () => helper.haveTextOf('.selection h4', 1)); + step('Then I go to confirmation step', () => helper.goToConfirmation()); + step('When I click on confirm button', () => helper.clickOnElement('button.confirm')); + step('Then I should see result box', () => helper.haveTextOf('h2.result-box-header', 'Votes submitted')); + step('Then I restore Api mocks', restoreApiMocks); + }); +}); diff --git a/test/utils/genericStepDefinition.js b/test/utils/genericStepDefinition.js new file mode 100644 index 0000000000..2ae44a7202 --- /dev/null +++ b/test/utils/genericStepDefinition.js @@ -0,0 +1,44 @@ + +import { expect } from 'chai'; + +export default class GenericStepDefinition { + constructor(input) { + this.wrapper = input; + } + /** + * simulate click on a dom query + * @param {String} query - dom query that we need to simulate clink on it + */ + clickOnElement(query) { + this.wrapper.find(query).simulate('click'); + } + /** + * check that dom query entry is disable or enable + * if value of status is equal to 'not' query shouldn't be disabled + * @param {String} query - dom query that we need to check that is disable or not + * @param {String} status - possible values are 'not' and '' + */ + checkDisableInput(query, status = '') { + if (status === 'not') { + expect(this.wrapper.find(query)).to.not.be.disabled(); + } else { + expect(this.wrapper.find(query)).to.be.disabled(); + } + } + /** + * + * @param {String} query - dom query that we need to check length of that + * @param {Integer} length + */ + haveLengthOf(query, length) { + expect(this.wrapper).to.have.exactly(length).descendants(query); + } + /** + * + * @param {String} query - dom query that we need to check text of that + * @param {String} text - expect text of the dom query entry + */ + haveTextOf(query, text) { + expect(this.wrapper.find(query)).to.have.text(text); + } +} diff --git a/test/utils/mountHelpers.js b/test/utils/mountHelpers.js index 42ae4f555b..363e1bec55 100644 --- a/test/utils/mountHelpers.js +++ b/test/utils/mountHelpers.js @@ -33,3 +33,4 @@ export const mountWithContext = (component, { storeState = {}, location = {} }) }; return mount(component, options); }; +