diff --git a/src/components/dialog/dialog.test.js b/src/components/dialog/dialog.test.js index 17c83829f..ebb3ee972 100644 --- a/src/components/dialog/dialog.test.js +++ b/src/components/dialog/dialog.test.js @@ -46,7 +46,10 @@ describe('Dialog', () => { }); it('should fix the route if there are two dialog names', () => { - wrapper.instance().componentDidUpdate(); + const newProps = Object.assign({}, { dialog: dialogProps, history }, props); + newProps.dialog.title = 'Send1'; + // trying to update the component + wrapper.setProps(newProps); expect(history.push).to.have.been.calledWith(); }); }); diff --git a/src/components/formattedNumber/index.js b/src/components/formattedNumber/index.js index db86adf89..d82ba47d6 100644 --- a/src/components/formattedNumber/index.js +++ b/src/components/formattedNumber/index.js @@ -4,10 +4,10 @@ import * as locales from 'numeral/locales'; // eslint-disable-line import { translate } from 'react-i18next'; import i18n from '../../i18n'; -const FormattedNumber = (props) => { +const FormattedNumber = ({ val }) => { // set numeral language numeral.locale(i18n.language); - const formatedNumber = numeral(props.val).format('0,0.[0000000000000]'); + const formatedNumber = numeral(val).format('0,0.[0000000000000]'); return {formatedNumber}; }; diff --git a/src/components/formattedNumber/index.test.js b/src/components/formattedNumber/index.test.js index e69de29bb..2acba6f7a 100644 --- a/src/components/formattedNumber/index.test.js +++ b/src/components/formattedNumber/index.test.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import FormattedNumber from './index'; +import i18n from '../../i18n'; + + +describe('FormattedNumber', () => { + const options = { + context: { i18n }, + childContextTypes: { + i18n: PropTypes.object.isRequired, + }, + }; + + it('renders 0 if raw value is 0', () => { + const value = { + raw: 0, + formatted: '0', + }; + const wrapper = mount(, options); + expect(wrapper.find('span').text()).to.equal(value.formatted); + }); + + it('renders 1,000 if raw value is 1000', () => { + const value = { + raw: 1234, + formatted: '1,234', + }; + const wrapper = mount(, options); + expect(wrapper.find('span').text()).to.equal(value.formatted); + }); + + it('renders 1,000.95 if raw value is 1000', () => { + const value = { + raw: 1234.56, + formatted: '1,234.56', + }; + const wrapper = mount(, options); + expect(wrapper.find('span').text()).to.equal(value.formatted); + }); + + it('renders 10.01 if raw value is 10.01', () => { + const value = { + raw: 123.45, + formatted: '123.45', + }; + const wrapper = mount(, options); + expect(wrapper.find('span').text()).to.equal(value.formatted); + }); +}); diff --git a/src/components/header/header.test.js b/src/components/header/header.test.js index 3fd74d72b..2550a535e 100644 --- a/src/components/header/header.test.js +++ b/src/components/header/header.test.js @@ -49,7 +49,7 @@ describe('Header', () => { expect(wrapper.find(RelativeLink)).to.have.length(9); }); - it('should have an image with srouce of "logo"', () => { + it('should have an image with source of "logo"', () => { expect(wrapper.contains(logo)) .to.be.equal(true); }); diff --git a/src/components/loadingBar/loadingBar.test.js b/src/components/loadingBar/loadingBar.test.js index 4f366b24d..355adc14c 100644 --- a/src/components/loadingBar/loadingBar.test.js +++ b/src/components/loadingBar/loadingBar.test.js @@ -10,7 +10,7 @@ describe('LoadingBar Container', () => { expect(wrapper.find('ProgressBar')).to.have.lengthOf(1); }); - it('should not show ProgresBar if props.loading.length is 0', () => { + it('should not show ProgressBar if props.loading.length is 0', () => { const wrapper = mount(); expect(wrapper.find('ProgressBar')).to.have.lengthOf(0); }); diff --git a/src/components/login/login.test.js b/src/components/login/login.test.js index 9e3dc1b71..9c2817cd2 100644 --- a/src/components/login/login.test.js +++ b/src/components/login/login.test.js @@ -11,6 +11,8 @@ import Login from './login'; describe('Login', () => { let wrapper; + const address = 'http:localhost:8080'; + const passphrase = 'recipe bomb asset salon coil symbol tiger engine assist pact pumpkin'; // Mocking store const account = { isDelegate: false, @@ -58,7 +60,6 @@ describe('Login', () => { }); it('should show error about passphrase length if passphrase is have wrong length', () => { - const passphrase = 'recipe bomb asset salon coil symbol tiger engine assist pact pumpkin'; const expectedError = 'Passphrase should have 12 words, entered passphrase has 11'; wrapper.find('.passphrase input').simulate('change', { target: { value: ' ' } }); wrapper.find('.passphrase input').simulate('change', { target: { value: passphrase } }); @@ -66,8 +67,7 @@ describe('Login', () => { }); }); - describe('componentDidUpdate', () => { - const address = 'http:localhost:8080'; + describe('History management', () => { props.account = { address: 'dummy' }; it('calls this.props.history.replace(\'/main/transactions\')', () => { @@ -96,8 +96,13 @@ describe('Login', () => { it('calls localStorage.setItem(\'address\', address) if this.state.address', () => { const spyFn = spy(localStorage, 'setItem'); - wrapper = shallow(, options); - wrapper.setState({ address }); + wrapper = mount(, options); + // set the network dropdown + wrapper.find('div.network').simulate('click'); + // select custom node + wrapper.find('div.network ul li').at(2).simulate('click'); + // fill the address + wrapper.find('Input.address input').simulate('change', { target: { value: address } }); wrapper.setProps(props); expect(spyFn).to.have.been.calledWith('address', address); @@ -106,23 +111,17 @@ describe('Login', () => { }); }); - describe('changeHandler', () => { - it('call setState with matching data', () => { - wrapper = shallow(, options); - const key = 'network'; - const value = 0; - const spyFn = spy(Login.prototype, 'setState'); - wrapper.instance().changeHandler(key, value); - expect(spyFn).to.have.been.calledWith({ [key]: value }); - }); - }); - - describe('onLoginSubmission', () => { + describe('After submission', () => { it('it should call activePeerSet', () => { const spyActivePeerSet = spy(props, 'activePeerSet'); - wrapper = shallow(, options); - wrapper.instance().onLoginSubmission(); + wrapper = mount(, options); + // Filling the login form + wrapper.find('div.network').simulate('click'); + wrapper.find('div.network ul li').at(2).simulate('click'); + wrapper.find('Input.address input').simulate('change', { target: { value: address } }); + wrapper.find('Input.passphrase input').simulate('change', { target: { value: passphrase } }); + wrapper.find('form').simulate('submit'); expect(spyActivePeerSet).to.have.been.calledWith(); }); }); diff --git a/src/components/passphrase/passphraseGenerator.js b/src/components/passphrase/passphraseGenerator.js index ef1a97fa0..6ee04b06d 100644 --- a/src/components/passphrase/passphraseGenerator.js +++ b/src/components/passphrase/passphraseGenerator.js @@ -38,12 +38,12 @@ class PassphraseGenerator extends React.Component { /** * Tests useragent with a regexp and defines if the account is mobile device + * it is on class so that we can mock it in unit tests * * @param {String} [agent] - The useragent string, This parameter is used for * unit testing purpose * @returns {Boolean} - whether the agent represents a mobile device or not */ - // it is on class so that we can mock it in unit tests // eslint-disable-next-line class-methods-use-this isTouchDevice(agent) { return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(agent || navigator.userAgent || navigator.vendor || window.opera)); @@ -88,10 +88,11 @@ class PassphraseGenerator extends React.Component { } render() { + const isTouch = this.isTouchDevice(this.props.agent); return (
- {this.isTouchDevice() ? + {isTouch ?

Enter text below to generate random bytes

{ @@ -11,34 +12,14 @@ describe('PassphraseGenerator', () => { changeHandler: () => {}, t: key => key, }; - const mockEvent = { - pageX: 140, - pageY: 140, - }; - - it('calls setState to setValues locally', () => { - const wrapper = shallow(); - const spyFn = spy(wrapper.instance(), 'setState'); - wrapper.instance().seedGenerator(mockEvent); - expect(spyFn).to.have.been.calledWith(); - wrapper.instance().setState.restore(); - }); it('shows an Input fallback if this.isTouchDevice()', () => { - const wrapper = mount(); - const isTouchDeviceMock = mock(wrapper.instance()).expects('isTouchDevice'); - isTouchDeviceMock.returns(true); - wrapper.instance().setState({}); // to rerender the component - wrapper.update(); + const wrapper = mount(); expect(wrapper.find('Input.touch-fallback textarea')).to.have.lengthOf(1); }); it('shows at least some progress on pressing input if this.isTouchDevice()', () => { - const wrapper = mount(); - const isTouchDeviceMock = mock(wrapper.instance()).expects('isTouchDevice'); - isTouchDeviceMock.returns(true).twice(); - wrapper.instance().setState({}); // to rerender the component - wrapper.update(); + const wrapper = mount(); wrapper.find('Input.touch-fallback textarea').simulate('change', { target: { value: 'random key presses' } }); expect(wrapper.find('ProgressBar').props().value).to.be.at.least(1); }); @@ -46,53 +27,28 @@ describe('PassphraseGenerator', () => { it('removes mousemove event listener in componentWillUnmount', () => { const wrapper = mount(); const documentSpy = spy(document, 'removeEventListener'); - wrapper.instance().componentWillUnmount(); + wrapper.unmount(); expect(documentSpy).to.have.be.been.calledWith('mousemove'); documentSpy.restore(); }); - it('sets "data" and "lastCaptured" if distance is over 120', () => { - const wrapper = shallow(); - wrapper.instance().seedGenerator(mockEvent); - - expect(wrapper.instance().state.data).to.not.equal(undefined); - expect(wrapper.instance().state.lastCaptured).to.deep.equal({ - x: 140, - y: 140, - }); - }); - - it('should do nothing if distance is bellow 120', () => { - const wrapper = shallow(); - const nativeEvent = { - pageX: 10, - pageY: 10, - }; - wrapper.instance().seedGenerator(nativeEvent); + it('should call generateSeed for every event triggered', () => { + const generateSeedSpy = spy(passphraseUtil, 'generateSeed'); + const wrapper = mount(); + wrapper.find('Input.touch-fallback textarea').simulate('change', { target: { value: 'random key presses' } }); - expect(wrapper.instance().state.data).to.be.equal(undefined); - expect(wrapper.instance().state.lastCaptured).to.deep.equal({ - x: 0, - y: 0, - }); + expect(generateSeedSpy.callCount).to.be.equal(1); }); - it('should generate passphrase if seed is completed', () => { - const wrapper = shallow(); - // set mock data - wrapper.instance().setState({ - data: { - seed: ['e6', '3c', 'd1', '36', 'e9', '70', '5f', - 'c0', '4d', '31', 'ef', 'b8', 'd6', '53', '48', '11'], - percentage: 100, - step: 1, - byte: [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0], - }, - }); - - wrapper.instance().seedGenerator(mockEvent); + it('should call generatePassphrase after enough event passed', () => { + const generatePassphraseSpy = spy(passphraseUtil, 'generatePassphrase'); + const wrapper = mount(); + const input = wrapper.find('Input.touch-fallback textarea'); + for (let i = 0; i < 101; i++) { + input.simulate('change', { target: { value: 'random key presses' } }); + } - expect(wrapper.instance().state.passphrase).to.not.equal(undefined); + expect(generatePassphraseSpy.callCount).to.be.equal(1); }); }); }); diff --git a/src/components/passphrase/passphraseVerifier.js b/src/components/passphrase/passphraseVerifier.js index baeb11b3c..752928c41 100644 --- a/src/components/passphrase/passphraseVerifier.js +++ b/src/components/passphrase/passphraseVerifier.js @@ -13,7 +13,8 @@ class PassphraseConfirmator extends React.Component { componentDidMount() { this.props.updateAnswer(false); - this.hideRandomWord.call(this); + // this.props.randomIndex is used in unit teasing + this.hideRandomWord.call(this, this.props.randomIndex); } hideRandomWord(rand = Math.random()) { @@ -40,7 +41,7 @@ class PassphraseConfirmator extends React.Component { return (
-

+

{this.state.passphraseParts[0]} ----- {this.state.passphraseParts[1]} diff --git a/src/components/passphrase/passphraseVerifier.test.js b/src/components/passphrase/passphraseVerifier.test.js index f16a84bb4..09731596c 100644 --- a/src/components/passphrase/passphraseVerifier.test.js +++ b/src/components/passphrase/passphraseVerifier.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; import PassphraseVerifier from './passphraseVerifier'; @@ -11,44 +11,50 @@ describe('PassphraseVerifier', () => { passphrase: 'survey stereo pool fortune oblige slight gravity goddess mistake sentence anchor pool', t: key => key, }; + let updateAnswerSpy; - describe('componentDidMount', () => { - it('should call updateAnswer with "false"', () => { - const spyFn = spy(props, 'updateAnswer'); - mount(); - expect(spyFn).to.have.been.calledWith(); - props.updateAnswer.restore(); - }); + let wrapper; + + beforeEach(() => { + updateAnswerSpy = spy(props, 'updateAnswer'); + wrapper = mount(); }); - describe('changeHandler', () => { - it('call updateAnswer with received value', () => { - const spyFn = spy(props, 'updateAnswer'); - const value = 'sample'; - const wrapper = shallow(); - wrapper.instance().changeHandler(value); - expect(spyFn).to.have.been.calledWith(); - props.updateAnswer.restore(); - }); + afterEach(() => { + updateAnswerSpy.restore(); + }); + + it('should initially call updateAnswer with "false"', () => { + expect(updateAnswerSpy).to.have.been.calledWith(false); + }); + + it('should call updateAnswer every time the input has changed', () => { + const value = 'sample value'; + + updateAnswerSpy.restore(); + wrapper.find('input').simulate('change', { target: { value } }); + expect(updateAnswerSpy.callCount).to.be.equal(2); }); - describe('hideRandomWord', () => { - it('should break passphrase, hide a word and store all in state', () => { - const wrapper = shallow(); - - const randomIndex = 0.6; - const expectedValues = { - passphraseParts: [ - 'survey stereo pool fortune oblige slight ', - ' goddess mistake sentence anchor pool', - ], - missing: 'gravity', - answer: '', - }; - const spyFn = spy(wrapper.instance(), 'setState'); - - wrapper.instance().hideRandomWord(randomIndex); - expect(spyFn).to.have.been.calledWith(expectedValues); + it('should refocus if use blurs the input', () => { + const focusSpy = spy(); + wrapper.find('input').simulate('blur', { + nativeEvent: { target: { focus: focusSpy } }, }); + + expect(focusSpy.callCount).to.be.equal(1); + }); + + it('should break passphrase, hide a word and show it', () => { + const expectedValues = [ + 'survey stereo pool fortune oblige ', + '-----', + ' gravity goddess mistake sentence anchor pool', + ]; + const spanTags = wrapper.find('.passphrase-holder span'); + + expect(spanTags.at(0).text()).to.be.equal(expectedValues[0]); + expect(spanTags.at(1).text()).to.be.equal(expectedValues[1]); + expect(spanTags.at(2).text()).to.be.equal(expectedValues[2]); }); }); diff --git a/src/components/spinner/index.test.js b/src/components/spinner/index.test.js index 9e38050fb..d4d77f5a8 100644 --- a/src/components/spinner/index.test.js +++ b/src/components/spinner/index.test.js @@ -4,7 +4,7 @@ import { mount } from 'enzyme'; import Spinner from './index'; describe('Spinner', () => { - it('should render 1 span and 3 divs', () => { + it('should render 1 span and 3 div tags', () => { const wrapper = mount(); expect(wrapper.find('span')).to.have.lengthOf(1); expect(wrapper.find('div')).to.have.lengthOf(3); diff --git a/src/components/toaster/toaster.test.js b/src/components/toaster/toaster.test.js index 38a9f7292..f43b99086 100644 --- a/src/components/toaster/toaster.test.js +++ b/src/components/toaster/toaster.test.js @@ -5,7 +5,6 @@ import { mount } from 'enzyme'; import Toaster from './toaster'; describe('Toaster', () => { - let wrapper; const toasts = [{ label: 'test', type: 'success', @@ -16,25 +15,33 @@ describe('Toaster', () => { hideToast: sinon.spy(), }; - beforeEach(() => { - wrapper = mount(); - }); - it('renders component from react-toolbox', () => { + const wrapper = mount(); expect(wrapper.find('Snackbar')).to.have.length(1); }); describe('hideToast', () => { it('hides the toast and after the animation ends calls this.props.hideToast()', () => { + document.body.prepend(document.createElement('div')); const clock = sinon.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'], }); - wrapper.instance().hideToast(toasts[0]); - expect(wrapper.state('hidden')).to.deep.equal({ [toasts[0].index]: true }); - clock.tick(510); - expect(wrapper.state('hidden')).to.deep.equal({ [toasts[0].index]: false }); - clock.restore(); + mount(, { attachTo: document.body.firstChild }); + let toastElement = document.getElementsByClassName('toast'); + + // check if the toast is activated + expect(toastElement.length > 0 && + toastElement[0].className.indexOf('active') > 0) + .to.equal(true); + + clock.tick(4510); + + // check if the toast is deactivated + toastElement = document.getElementsByClassName('toast'); + + // check if hideToast is called expect(toasterProps.hideToast).to.have.been.calledWith(toasts[0]); + clock.restore(); }); }); }); diff --git a/src/components/voteDialog/voteAutocomplete.js b/src/components/voteDialog/voteAutocomplete.js index d9b3de4b8..5dd2f5c0c 100644 --- a/src/components/voteDialog/voteAutocomplete.js +++ b/src/components/voteDialog/voteAutocomplete.js @@ -208,14 +208,14 @@ export class VoteAutocompleteRaw extends React.Component {

- + {this.state.votedResult.map( item => - + {this.state.unvotedResult.map( item => key, }; let wrapper; +const keyCodes = { + arrowDown: 40, + arrowUp: 38, + enter: 13, + escape: 27, +}; const store = configureMockStore([])({ peers: {}, @@ -36,8 +46,12 @@ const store = configureMockStore([])({ describe('VoteAutocomplete', () => { let voteAutocompleteApiMock; let unvoteAutocompleteApiMock; + let clock; beforeEach(() => { + clock = sinon.useFakeTimers({ + toFake: ['setTimeout', 'clearTimeout', 'Date'], + }); sinon.spy(VoteAutocomplete.prototype, 'keyPress'); sinon.spy(VoteAutocomplete.prototype, 'handleArrowDown'); sinon.spy(VoteAutocomplete.prototype, 'handleArrowUp'); @@ -48,6 +62,7 @@ describe('VoteAutocomplete', () => { }); afterEach(() => { + clock.restore(); voteAutocompleteApiMock.restore(); unvoteAutocompleteApiMock.restore(); VoteAutocomplete.prototype.keyPress.restore(); @@ -55,42 +70,27 @@ describe('VoteAutocomplete', () => { VoteAutocomplete.prototype.handleArrowUp.restore(); }); - it('should suggestionStatus(false, className) change value of className in state', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); - wrapper.instance().suggestionStatus(false, 'className'); - clock.tick(200); - expect(wrapper.state('className').match(/hidden/g)).to.have.lengthOf(1); - }); + it('should suggest with full username if finds a non-voted delegate with a username starting with given string', () => { + const voteAutocompleteApiStub = sinon.stub(delegateApi, 'voteAutocomplete'); + voteAutocompleteApiStub.returnsPromise().resolves([unvotedDelegate[0]]); + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('change', { target: { value: 'user' } }); - it('should suggestionStatus(true, className) clear value of className in state', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); - wrapper.instance().suggestionStatus(true, 'className'); - clock.tick(200); - expect(wrapper.state('className')).to.be.equal(''); + clock.tick(300); + expect(wrapper.find('Card .vote-auto-complete-list ul').html().indexOf(unvotedDelegate[0].username)).to.be.greaterThan(-1); + voteAutocompleteApiStub.restore(); }); - it('should search hide suggestion boxes when value is equal to ""', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); - sinon.spy(VoteAutocomplete.prototype, 'setState'); - wrapper.instance().search('votedListSearch', ''); - clock.tick(250); - expect(VoteAutocomplete.prototype.setState).to.have.property('callCount', 2); - expect(wrapper.state('votedResult')).to.have.lengthOf(0); - expect(wrapper.state('unvotedResult')).to.have.lengthOf(0); - expect(wrapper.state('votedSuggestionClass').match(/hidden/g)).to.have.lengthOf(1); - expect(wrapper.state('unvotedSuggestionClass').match(/hidden/g)).to.have.lengthOf(1); - VoteAutocomplete.prototype.setState.restore(); + it('should suggest with full username if dows not find a non-voted delegate with a username starting with given string', () => { + const voteAutocompleteApiStub = sinon.stub(delegateApi, 'voteAutocomplete'); + voteAutocompleteApiStub.returnsPromise().rejects([]); + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('change', { target: { value: 'user' } }); + + clock.tick(400); + expect(wrapper.find('Card .vote-auto-complete-list ul')).to.be.blank(); + voteAutocompleteApiStub.restore(); }); + it('search should call "voteAutocomplete" when name is equal to "votedListSearch" when search term exists', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); const existingSearchTerm = 'username2'; const delegateApiMock = sinon.mock(delegateApi).expects('voteAutocomplete'); @@ -105,9 +105,6 @@ describe('VoteAutocomplete', () => { }); it('search should call "voteAutocomplete" when name is equal to "votedListSearch" when search term does not exist', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); const nonExistingSearchTerm = 'doesntexist'; const delegateApiMock = sinon.mock(delegateApi).expects('voteAutocomplete'); @@ -123,9 +120,6 @@ describe('VoteAutocomplete', () => { }); it('search should call "unvoteAutocomplete" when name is equal to "unvotedListSearch" when search term exists', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); const existingSearchTerm = 'username1'; const delegateApiMock = sinon.mock(delegateApi).expects('unvoteAutocomplete'); @@ -140,9 +134,6 @@ describe('VoteAutocomplete', () => { }); it('search should call "unvoteAutocomplete" when name is equal to "unvotedListSearch" when search term does not exists', () => { - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout', 'clearTimeout', 'Date'], - }); const nonExistingSearchTerm = 'username2'; const delegateApiMock = sinon.mock(delegateApi).expects('unvoteAutocomplete'); @@ -155,81 +146,119 @@ describe('VoteAutocomplete', () => { delegateApiMock.restore(); }); - it('should "votedSearchKeydown" call "keyPress"', () => { - wrapper.instance().votedSearchKeyDown({}); - expect(VoteAutocomplete.prototype.keyPress).to.have.property('callCount', 1); - }); - it('should "unvotedSearchKeydown" call "keyPress"', () => { - wrapper.instance().unvotedSearchKeyDown({}); - expect(VoteAutocomplete.prototype.keyPress).to.have.property('callCount', 1); + it('should let you choose one of the options by arrow down', () => { + 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(200); + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.enter }); + voteAutocompleteApiStub.restore(); + expect(props.voteToggled).to.have.been.calledWith({ + publicKey: unvotedDelegate[0].publicKey, + username: unvotedDelegate[0].username, + }); }); - it('should keyPress call "handleArrowDown" when event.keyCode is equal to 40', () => { - const list = [ - { address: 'address 0' }, - { address: 'address 1', hovered: true }, - { address: 'address 2' }, - ]; - wrapper.setState({ votedResult: list }); - wrapper.instance().keyPress({ keyCode: 40 }, 'votedSuggestionClass', 'votedResult'); - expect(VoteAutocomplete.prototype.handleArrowDown).to.have.property('callCount', 1); + it('should let you navigate and choose one of the options in voteList using arrow up/down', () => { + 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); + + // Arrow down + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); + // Arrow down + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); + // Arrow up + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowUp }); + // Hit enter + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.enter }); + voteAutocompleteApiStub.restore(); + expect(props.voteToggled).to.have.been.calledWith({ + publicKey: unvotedDelegate[0].publicKey, + username: unvotedDelegate[0].username, + }); }); - it('should handleArrowDown select first item in votedResult when no one is selected', () => { - const list = [ - { address: 'address 0' }, - { address: 'address 1' }, - { address: 'address 2' }, - ]; - wrapper.setState({ votedResult: list }); - wrapper.instance().handleArrowDown(wrapper.state('votedResult'), 'votedResult'); - expect(wrapper.state('votedResult')[0].hovered).to.have.be.equal(true); + + it('should let you navigate and then escape the suggestion list', () => { + 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); + + // Arrow down + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); + // Arrow down + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); + // Arrow up + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.arrowUp }); + // Hit enter + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('keyDown', { keyCode: keyCodes.escape }); + voteAutocompleteApiStub.restore(); + clock.tick(400); + expect(wrapper.find('Card .vote-auto-complete-list').at(0).prop('className')).to.include('hidden'); }); - it('should handleArrowDown select first item in unvotedResult when no one is selected', () => { - const list = [ - { address: 'address 0' }, - { address: 'address 1' }, - { address: 'address 2' }, - ]; - wrapper.setState({ unvotedResult: list }); - wrapper.instance().handleArrowDown(wrapper.state('unvotedResult'), 'unvotedResult'); - expect(wrapper.state('unvotedResult')[0].hovered).to.have.be.equal(true); + it('should remove suggestion list if you clean the input', () => { + 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); + wrapper.update(); + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('change', { target: { value: '' } }); + voteAutocompleteApiStub.restore(); + clock.tick(400); + wrapper.update(); + expect(wrapper.find('Card .vote-auto-complete-list ul')).to.be.blank(); }); - it('should keyPress call "handleArrowUp" when event.keyCode is equal to 38', () => { - const list = [ - { address: 'address 0' }, - { address: 'address 1', hovered: true }, - ]; - wrapper.setState({ votedResult: list }); - wrapper.instance().keyPress({ keyCode: 38 }, 'votedSuggestionClass', 'votedResult'); - expect(VoteAutocomplete.prototype.handleArrowUp).to.have.property('callCount', 1); + + it('should hide suggestion list if you blur the input', () => { + 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); + wrapper.find('.votedListSearch.vote-auto-complete input').simulate('blur', {}); + voteAutocompleteApiStub.restore(); + clock.tick(200); + wrapper.update(); + expect(wrapper.find('Card .vote-auto-complete-list').at(0).prop('className')).to.include('hidden'); }); - it('should keyPress hide suggestions when event.keyCode is equal to 27', () => { - const returnValue = wrapper.instance() - .keyPress({ keyCode: 27 }, 'votedSuggestionClass', 'votedResult'); - expect(wrapper.state('votedSuggestionClass').match(/hidden/g)).to.have.lengthOf(1); - expect(returnValue).to.be.equal(false); + it('should suggest with full username to unvote if finds a voted delegate with a username starting with given string', () => { + const unvoteAutocompleteApiStub = sinon.stub(delegateApi, 'unvoteAutocomplete'); + unvoteAutocompleteApiStub.returnsPromise().resolves([delegates[1]]); + wrapper.find('.unvotedListSearch input').simulate('change', { target: { value: 'user' } }); + + clock.tick(300); + expect(wrapper.find('Card .unvote-auto-complete-list ul').html()).to.include(delegates[1].username); + unvoteAutocompleteApiStub.restore(); }); - it(`should keyPress call "addToVoted" when event.keyCode is equal to 13 and - list name is equal to votedResult`, () => { - const list = [{ address: 'address 1', hovered: true }]; - wrapper.setState({ votedResult: list }); - const returnValue = wrapper.instance() - .keyPress({ keyCode: 13 }, 'votedSuggestionClass', 'votedResult'); - expect(props.voteToggled).to.have.property('callCount', 1); - expect(returnValue).to.be.equal(false); - }); + it('should let you navigate and choose one of the options in unvoteList using arrow up/down', () => { + const unvoteAutocompleteApiStub = sinon.stub(delegateApi, 'unvoteAutocomplete'); + unvoteAutocompleteApiStub.returnsPromise().resolves([delegates[1]]); + // write a username + wrapper.find('.unvotedListSearch input').simulate('change', { target: { value: 'user' } }); + clock.tick(400); - it(`should keyPress call "voteToggled" when event.keyCode is equal to 13 and - list name is equal to unvotedResult`, () => { - const list = [{ address: 'address 1', hovered: true }]; - wrapper.setState({ unvotedResult: list }); - const returnValue = wrapper.instance() - .keyPress({ keyCode: 13 }, 'unvotedSuggestionClass', 'unvotedResult'); - expect(props.voteToggled).to.have.property('callCount', 2); - expect(returnValue).to.be.equal(false); + // Arrow down + wrapper.find('.unvotedListSearch input').simulate('keyDown', { keyCode: keyCodes.arrowDown }); + // Hit enter + wrapper.find('.unvotedListSearch input').simulate('keyDown', { keyCode: keyCodes.enter }); + unvoteAutocompleteApiStub.restore(); + expect(props.voteToggled).to.have.been.calledWith({ + publicKey: delegates[1].publicKey, + username: delegates[1].username, }); + }); });