Connection UI cleanups & tests for prior PR (#4020)
* Cleanups & tests for #3945 * Externalise icons as per PR comments
This commit is contained in:
parent
7cdfaf1a43
commit
cc8e200ed5
@ -18,7 +18,10 @@ import AddIcon from 'material-ui/svg-icons/content/add';
|
|||||||
import CancelIcon from 'material-ui/svg-icons/content/clear';
|
import CancelIcon from 'material-ui/svg-icons/content/clear';
|
||||||
import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
||||||
import CloseIcon from 'material-ui/svg-icons/navigation/close';
|
import CloseIcon from 'material-ui/svg-icons/navigation/close';
|
||||||
|
import CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
|
||||||
|
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
||||||
import ContractIcon from 'material-ui/svg-icons/action/code';
|
import ContractIcon from 'material-ui/svg-icons/action/code';
|
||||||
|
import DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
||||||
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
import LockedIcon from 'material-ui/svg-icons/action/lock-outline';
|
import LockedIcon from 'material-ui/svg-icons/action/lock-outline';
|
||||||
import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
|
import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
@ -27,13 +30,17 @@ import SaveIcon from 'material-ui/svg-icons/content/save';
|
|||||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
import SendIcon from 'material-ui/svg-icons/content/send';
|
||||||
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
||||||
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||||
|
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AddIcon,
|
AddIcon,
|
||||||
CancelIcon,
|
CancelIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
CloseIcon,
|
CloseIcon,
|
||||||
|
CompareIcon,
|
||||||
|
ComputerIcon,
|
||||||
ContractIcon,
|
ContractIcon,
|
||||||
|
DashboardIcon,
|
||||||
DoneIcon,
|
DoneIcon,
|
||||||
LockedIcon,
|
LockedIcon,
|
||||||
NextIcon,
|
NextIcon,
|
||||||
@ -41,5 +48,6 @@ export {
|
|||||||
SaveIcon,
|
SaveIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
SnoozeIcon,
|
SnoozeIcon,
|
||||||
VisibleIcon
|
VisibleIcon,
|
||||||
|
VpnIcon
|
||||||
};
|
};
|
||||||
|
@ -17,13 +17,9 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import ActionCompareArrows from 'material-ui/svg-icons/action/compare-arrows';
|
|
||||||
import ActionDashboard from 'material-ui/svg-icons/action/dashboard';
|
|
||||||
import HardwareDesktopMac from 'material-ui/svg-icons/hardware/desktop-mac';
|
|
||||||
import NotificationVpnLock from 'material-ui/svg-icons/notification/vpn-lock';
|
|
||||||
|
|
||||||
import { Input } from '~/ui';
|
import { Input } from '~/ui';
|
||||||
|
import { CompareIcon, ComputerIcon, DashboardIcon, VpnIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './connection.css';
|
import styles from './connection.css';
|
||||||
|
|
||||||
@ -51,13 +47,6 @@ class Connection extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeIcon = needsToken
|
|
||||||
? <NotificationVpnLock className={ styles.svg } />
|
|
||||||
: <ActionDashboard className={ styles.svg } />;
|
|
||||||
const description = needsToken
|
|
||||||
? this.renderSigner()
|
|
||||||
: this.renderPing();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={ styles.overlay } />
|
<div className={ styles.overlay } />
|
||||||
@ -65,16 +54,24 @@ class Connection extends Component {
|
|||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
<div className={ styles.icons }>
|
<div className={ styles.icons }>
|
||||||
<div className={ styles.icon }>
|
<div className={ styles.icon }>
|
||||||
<HardwareDesktopMac className={ styles.svg } />
|
<ComputerIcon className={ styles.svg } />
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.iconSmall }>
|
<div className={ styles.iconSmall }>
|
||||||
<ActionCompareArrows className={ `${styles.svg} ${styles.pulse}` } />
|
<CompareIcon className={ `${styles.svg} ${styles.pulse}` } />
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.icon }>
|
<div className={ styles.icon }>
|
||||||
{ typeIcon }
|
{
|
||||||
|
needsToken
|
||||||
|
? <VpnIcon className={ styles.svg } />
|
||||||
|
: <DashboardIcon className={ styles.svg } />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ description }
|
{
|
||||||
|
needsToken
|
||||||
|
? this.renderSigner()
|
||||||
|
: this.renderPing()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -144,10 +141,19 @@ class Connection extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeToken = (event, _token) => {
|
validateToken = (_token) => {
|
||||||
const token = _token.trim();
|
const token = _token.trim();
|
||||||
const validToken = /^[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}$/.test(token);
|
const validToken = /^[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}$/.test(token);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
validToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeToken = (event, _token) => {
|
||||||
|
const { token, validToken } = this.validateToken(_token || event.target.value);
|
||||||
|
|
||||||
this.setState({ token, validToken }, () => {
|
this.setState({ token, validToken }, () => {
|
||||||
validToken && this.setToken();
|
validToken && this.setToken();
|
||||||
});
|
});
|
||||||
@ -159,7 +165,7 @@ class Connection extends Component {
|
|||||||
|
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
api
|
return api
|
||||||
.updateToken(token, 0)
|
.updateToken(token, 0)
|
||||||
.then((isValid) => {
|
.then((isValid) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -173,14 +179,14 @@ class Connection extends Component {
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { isConnected, isConnecting, needsToken } = state.nodeStatus;
|
const { isConnected, isConnecting, needsToken } = state.nodeStatus;
|
||||||
|
|
||||||
return { isConnected, isConnecting, needsToken };
|
return {
|
||||||
}
|
isConnected,
|
||||||
|
isConnecting,
|
||||||
function mapDispatchToProps (dispatch) {
|
needsToken
|
||||||
return bindActionCreators({}, dispatch);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
null
|
||||||
)(Connection);
|
)(Connection);
|
||||||
|
156
js/src/views/Connection/connection.spec.js
Normal file
156
js/src/views/Connection/connection.spec.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import Connection from './';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
return {
|
||||||
|
updateToken: sinon.stub().resolves()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRedux (isConnected = true, isConnecting = false, needsToken = false) {
|
||||||
|
return {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
nodeStatus: {
|
||||||
|
isConnected,
|
||||||
|
isConnecting,
|
||||||
|
needsToken
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (store) {
|
||||||
|
api = createApi();
|
||||||
|
component = shallow(
|
||||||
|
<Connection />,
|
||||||
|
{ context: { store: store || createRedux() } }
|
||||||
|
).find('Connection').shallow({ context: { api } });
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Connection', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render when connected', () => {
|
||||||
|
expect(render(createRedux(true)).find('div')).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderPing', () => {
|
||||||
|
it('renders the connecting to node message', () => {
|
||||||
|
render();
|
||||||
|
const ping = shallow(instance.renderPing());
|
||||||
|
|
||||||
|
expect(ping.find('FormattedMessage').props().id).to.equal('connection.connectingNode');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderSigner', () => {
|
||||||
|
it('renders the connecting to api message when isConnecting === true', () => {
|
||||||
|
render(createRedux(false, true));
|
||||||
|
const signer = shallow(instance.renderSigner());
|
||||||
|
|
||||||
|
expect(signer.find('FormattedMessage').props().id).to.equal('connection.connectingAPI');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders token input when needsToken == true & isConnecting === false', () => {
|
||||||
|
render(createRedux(false, false, true));
|
||||||
|
const signer = shallow(instance.renderSigner());
|
||||||
|
|
||||||
|
expect(signer.find('FormattedMessage').first().props().id).to.equal('connection.noConnection');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateToken', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trims whitespace from passed tokens', () => {
|
||||||
|
expect(instance.validateToken(' \t test ing\t ').token).to.equal('test ing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates 4-4-4-4 format', () => {
|
||||||
|
expect(instance.validateToken('1234-5678-90ab-cdef').validToken).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates 4-4-4-4 format (with trimmable whitespace)', () => {
|
||||||
|
expect(instance.validateToken(' \t 1234-5678-90ab-cdef \t ').validToken).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates 4444 format', () => {
|
||||||
|
expect(instance.validateToken('1234567890abcdef').validToken).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates 4444 format (with trimmable whitespace)', () => {
|
||||||
|
expect(instance.validateToken(' \t 1234567890abcdef \t ').validToken).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onChangeToken', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
sinon.spy(instance, 'setToken');
|
||||||
|
sinon.spy(instance, 'validateToken');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.setToken.restore();
|
||||||
|
instance.validateToken.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates tokens passed', () => {
|
||||||
|
instance.onChangeToken({ target: { value: 'testing' } });
|
||||||
|
expect(instance.validateToken).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the token on the api when valid', () => {
|
||||||
|
instance.onChangeToken({ target: { value: '1234-5678-90ab-cdef' } });
|
||||||
|
expect(instance.setToken).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setToken', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the api.updateToken', () => {
|
||||||
|
component.setState({ token: 'testing' });
|
||||||
|
|
||||||
|
return instance.setToken().then(() => {
|
||||||
|
expect(api.updateToken).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user