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

Register as delegate - Closes #354 #543

Merged
merged 13 commits into from
Aug 11, 2017
3 changes: 3 additions & 0 deletions src/components/header/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@
.menu {
right: -16px !important;
}
.hidden {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.hidden is not used.

display: none;
}
11 changes: 10 additions & 1 deletion src/components/header/headerElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import logo from '../../assets/images/LISK-nano.png';
import styles from './header.css';
import VerifyMessage from '../signVerify/verifyMessage';
import SignMessage from '../signVerify/signMessage';
import RegisterDelegate from '../registerDelegate';
import Send from '../send';
import PrivateWrapper from '../privateWrapper';

Expand All @@ -20,7 +21,15 @@ const HeaderElement = props => (
theme={styles}
>
<MenuItem caption="Register second passphrase" />
<MenuItem caption="Register as delegate" />
{
!props.account.isDelegate &&
<MenuItem caption="Register as delegate"
onClick={() => props.setActiveDialog({
title: 'Register as delegate',
childComponent: RegisterDelegate,
})}
/>
}
<MenuItem caption="Sign message"
className='sign-message'
onClick={() => props.setActiveDialog({
Expand Down
4 changes: 3 additions & 1 deletion src/components/header/headerElement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ describe('HeaderElement', () => {
beforeEach(() => {
const mockInputProps = {
setActiveDialog: () => { },
account: {},
};
propsMock = sinon.mock(mockInputProps);
wrapper = shallow(<HeaderElement setActiveDialog={mockInputProps.setActiveDialog} />);
wrapper = shallow(<HeaderElement account={mockInputProps.account}
setActiveDialog={mockInputProps.setActiveDialog} />);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified to wrapper = shallow(<HeaderElement { ...mockInputProps }/>);

});

afterEach(() => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/login/loginFormComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ class LoginFormComponent extends React.Component {
getAccount(this.props.peers.data, accountInfo.address).then((result) => {
onAccountUpdated(result);
getDelegate(this.props.peers.data, accountInfo.publicKey).then((data) => {
console.log('success');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this console.log

onAccountUpdated({ delegate: data.delegate, isDelegate: true });
}).catch(() => {
console.log('error');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this console.log

onAccountUpdated({ delegate: {}, isDelegate: false });
});
// redirect to main/transactions
Expand Down
2 changes: 1 addition & 1 deletion src/components/passphrase/passphraseGenerator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import chai, { expect } from 'chai';
import { spy } from 'sinon';
import sinonChai from 'sinon-chai';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import PassphraseGenerator from './passphraseGenerator';

chai.use(sinonChai);
Expand Down
22 changes: 22 additions & 0 deletions src/components/registerDelegate/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { connect } from 'react-redux';
import RegisterDelegate from './registerDelegate';
import { accountUpdated } from '../../actions/account';
import { successAlertDialogDisplayed, errorAlertDialogDisplayed } from '../../actions/dialog';

const mapStateToProps = state => ({
account: state.account,
peers: state.peers,
});

const mapDispatchToProps = dispatch => ({
onAccountUpdated: data => dispatch(accountUpdated(data)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot find where is this used.

showSuccessAlert: data => dispatch(successAlertDialogDisplayed(data)),
showErrorAlert: data => dispatch(errorAlertDialogDisplayed(data)),
});

const RegisterDelegateConnected = connect(
mapStateToProps,
mapDispatchToProps,
)(RegisterDelegate);

export default RegisterDelegateConnected;
87 changes: 87 additions & 0 deletions src/components/registerDelegate/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import PropTypes from 'prop-types';
import { expect } from 'chai';
import { mount } from 'enzyme';
import { BrowserRouter as Router } from 'react-router-dom';
import RegisterDelegate from './registerDelegate';
import RegisterDelegateConnected from './index';
import { accountUpdated } from '../../actions/account';

describe('RegisterDelegateConnected', () => {
let mountedAccount;
// Mocking store
const peers = {
status: {
online: false,
},
data: {
currentPeer: 'localhost',
port: 4000,
options: {
name: 'Custom Node',
},
},
};

const account = {
isDelegate: false,
address: '16313739661670634666L',
username: 'lisk-nano',
};

const store = {
dispatch: () => {},
subscribe: () => {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getState: () => ({
peers,
account,
}),
onAccountUpdated: () => (data) => {
store.account = data;
return accountUpdated(data);
},
showSuccessAlert: () => {},
showErrorAlert: () => {},
};
const options = {
context: { store },
childContextTypes: { store: PropTypes.object.isRequired },
};

beforeEach(() => {
mountedAccount = mount(<Router><RegisterDelegateConnected/></Router>, options);
});

it('should mount registerDelegate with appropriate properties', () => {
const props = mountedAccount.find(RegisterDelegate).props();
expect(props.peers).to.be.equal(peers);
expect(props.account).to.be.equal(account);
expect(typeof props.onAccountUpdated).to.be.equal('function');
expect(typeof props.showSuccessAlert).to.be.equal('function');
expect(typeof props.showErrorAlert).to.be.equal('function');
});

describe('onAccountUpdated', () => {
it('should return a dispatch object', () => {
const props = mountedAccount.find(RegisterDelegate).props();
const data = props.onAccountUpdated(account);
expect(data).to.be.equal();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. First data should not be undefined, second data comes from your mocked function so it's not the thing that needs to be checked.

It would be better to import sinonChai from 'sinon-chai'; chai.use(sinonChai); then define store.onAccountUpdated = sinon.spy(); and expect(store.onAccountUpdated).to.have.been.calledWith();

});
});

describe('showSuccessAlert', () => {
it('should return a dispatch object', () => {
const props = mountedAccount.find(RegisterDelegate).props();
const data = props.showSuccessAlert('sample text');
expect(data).to.be.equal();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

});
});

describe('showErrorAlert', () => {
it('should return a dispatch object', () => {
const props = mountedAccount.find(RegisterDelegate).props();
const data = props.showErrorAlert('sample text');
expect(data).to.be.equal();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

});
});
});
77 changes: 77 additions & 0 deletions src/components/registerDelegate/registerDelegate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import grid from 'flexboxgrid/dist/flexboxgrid.css';
import Input from 'react-toolbox/lib/input';
import Button from 'react-toolbox/lib/button';
import InfoParagraph from '../infoParagraph';
import { registerDelegate } from '../../utils/api/delegate';

class RegisterDelegate extends React.Component {
constructor() {
super();

this.state = {
name: '',
nameError: '',
};
}

changeHandler(name, value) {
this.setState({ [name]: value });
}

register(username, secondSecret) {
registerDelegate(this.props.peers.data, username,
this.props.account.passphrase, secondSecret)
.then(() => {
this.props.showSuccessAlert({
text: `Delegate registration was successfully submitted with username: "${this.state.name}". It can take several seconds before it is processed.`,
});
})
.catch((error) => {
if (error && error.message === 'Username already exists') {
this.setState({ nameError: error.message });
} else {
this.props.showErrorAlert({
text: error && error.message ? `${error.message}.` : 'An error occurred while registering as delegate.',
});
}
});
}

render() {
// notify use about insufficient balance
return (
<div>
<Input label='Delegate name' required={true}
autoFocus={true}
className='username'
onChange={this.changeHandler.bind(this, 'name')}
error={this.state.nameError}
value={this.state.name} />
{
this.props.account.secondSecret &&
<Input label='Second secret' required={true}
className='second-secret'
value={this.state.secondSecret} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Input won't set this.state.secondSecret without onChange

}
<hr/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hr is not visible due to display:none from passphrase.css. passphrase.css needs to be fixed.

<InfoParagraph>
Becoming a delegate requires registration. You may choose your own
delegate name, which can be used to promote your delegate. Only the
top 101 delegates are eligible to forge. All fees are shared equally
between the top 101 delegates.
</InfoParagraph>
<section className={`${grid.row} ${grid['between-xs']}`}>
<Button label='Cancel' className='cancel-button' onClick={this.props.closeDialog} />
<Button label='Register'
primary={true} raised={true}
disabled={!this.state.name}
className='submit-button'
onClick={this.register.bind(this, this.state.name, this.state.secondSecret)}/>
</section>
</div>
);
}
}

export default RegisterDelegate;
128 changes: 128 additions & 0 deletions src/components/registerDelegate/registerDelegate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react';
import chai, { expect } from 'chai';
import { mount } from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import sinon from 'sinon';
import Lisk from 'lisk-js';
import RegisterDelegate from './registerDelegate';
import * as delegateApi from '../../utils/api/delegate';

chai.use(chaiEnzyme());

const normalAccount = {
isDelegate: false,
address: '16313739661670634666L',
balance: 1000e8,
};

const delegateAccount = {
isDelegate: true,
address: '16313739661670634666L',
balance: 1000e8,
delegate: {
username: 'lisk-nano',
},
};

const withSecondSecretAccount = {
isDelegate: true,
address: '16313739661670634666L',
balance: 1000e8,
delegate: {
username: 'lisk-nano',
},
secondSecret: 'sample phrase',
};

const props = {
peers: {
data: Lisk.api({
name: 'Custom Node',
custom: true,
address: 'http://localhost:4000',
testnet: true,
nethash: '198f2b61a8eb95fbeed58b8216780b68f697f26b849acf00c8c93bb9b24f783d',
}),
},
closeDialog: () => {},
onAccountUpdated: () => {},
showSuccessAlert: () => {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make this showSuccessAlert: sinon.spy() and then expect that it has been called when necessary.

Those tests ending with wrapper.find('.submit-button').simulate('click'); and not checking anything with expect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that I'm not doing this either (in send.test.js). I tried to but the spy does not seem to be called.

showErrorAlert: () => {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

};

const delegateProps = { ...props, account: delegateAccount };
const normalProps = { ...props, account: normalAccount };
const withSecondSecretProps = { ...props, account: withSecondSecretAccount };

describe('RegisterDelegate', () => {
let wrapper;
let delegateApiMock;

beforeEach(() => {
delegateApiMock = sinon.mock(delegateApi);
});

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

describe('Ordinary account', () => {
beforeEach(() => {
wrapper = mount(<RegisterDelegate {...normalProps} />);
});

it('renders an InfoParagraph components', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo "components" -> "component"

expect(wrapper.find('InfoParagraph')).to.have.length(1);
});

it('renders one Input component for a normal account', () => {
expect(wrapper.find('Input')).to.have.length(1);
});

it('allows register as delegate for a non delegate account', () => {
delegateApiMock.expects('registerDelegate').resolves({ success: true });

wrapper.find('.username input').simulate('change', { target: { value: 'sample_username' } });
wrapper.find('.submit-button').simulate('click');
});

it('does not allow registering an existing username', () => {
delegateApiMock.expects('registerDelegate').resolves({ success: false });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be rejects not resolves


wrapper.find('.username input').simulate('change', { target: { value: 'sample_username' } });
wrapper.find('.submit-button').simulate('click');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case for 'Username already exists' missing

});

describe('Ordinary account with second secret', () => {
beforeEach(() => {
wrapper = mount(<RegisterDelegate {...withSecondSecretProps} />);
});

it('renders two Input component for a an account with second secret', () => {
expect(wrapper.find('Input')).to.have.length(2);
});

it('allows register as delegate for a non delegate account with second secret', () => {
delegateApiMock.expects('registerDelegate').resolves({ success: true });

wrapper.find('.username input').simulate('change', { target: { value: 'sample_username' } });
wrapper.find('.second-secret input').simulate('change', { target: { value: 'sample phrase' } });
wrapper.find('.submit-button').simulate('click');
});
});

describe('Delegate account', () => {
beforeEach(() => {
wrapper = mount(<RegisterDelegate {...delegateProps} />);
});

it('does not allow register as delegate for a delegate account', () => {
delegateApiMock.expects('registerDelegate').resolves({ success: false });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be rejects not resolves


wrapper.find('.username input').simulate('change', { target: { value: 'sample_username' } });
wrapper.find('.submit-button').simulate('click');
});
});
});
2 changes: 1 addition & 1 deletion src/components/voting/votingHeader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('VotingHeader', () => {

it('click on i.material-icons should clear vlaue of search input', () => {
wrapper.instance().search('query', '555');
wrapper.find('i.material-icons').simulate('click')
wrapper.find('i.material-icons').simulate('click');
expect(wrapper.state('query')).to.be.equal('');
});
});
Loading