From cc8e200ed50bc5b3f56d57d647b7182123aa70e2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 4 Jan 2017 15:15:11 +0100 Subject: [PATCH] Connection UI cleanups & tests for prior PR (#4020) * Cleanups & tests for #3945 * Externalise icons as per PR comments --- js/src/ui/Icons/index.js | 10 +- js/src/views/Connection/connection.js | 54 +++---- js/src/views/Connection/connection.spec.js | 156 +++++++++++++++++++++ 3 files changed, 195 insertions(+), 25 deletions(-) create mode 100644 js/src/views/Connection/connection.spec.js diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index 8901d7dc7..4cf5a2d7d 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -18,7 +18,10 @@ import AddIcon from 'material-ui/svg-icons/content/add'; import CancelIcon from 'material-ui/svg-icons/content/clear'; import CheckIcon from 'material-ui/svg-icons/navigation/check'; 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 DashboardIcon from 'material-ui/svg-icons/action/dashboard'; import DoneIcon from 'material-ui/svg-icons/action/done-all'; import LockedIcon from 'material-ui/svg-icons/action/lock-outline'; 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 SnoozeIcon from 'material-ui/svg-icons/av/snooze'; import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; +import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock'; export { AddIcon, CancelIcon, CheckIcon, CloseIcon, + CompareIcon, + ComputerIcon, ContractIcon, + DashboardIcon, DoneIcon, LockedIcon, NextIcon, @@ -41,5 +48,6 @@ export { SaveIcon, SendIcon, SnoozeIcon, - VisibleIcon + VisibleIcon, + VpnIcon }; diff --git a/js/src/views/Connection/connection.js b/js/src/views/Connection/connection.js index 4f2ae7b1d..505840e1e 100644 --- a/js/src/views/Connection/connection.js +++ b/js/src/views/Connection/connection.js @@ -17,13 +17,9 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; 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 { CompareIcon, ComputerIcon, DashboardIcon, VpnIcon } from '~/ui/Icons'; import styles from './connection.css'; @@ -51,13 +47,6 @@ class Connection extends Component { return null; } - const typeIcon = needsToken - ? - : ; - const description = needsToken - ? this.renderSigner() - : this.renderPing(); - return (
@@ -65,16 +54,24 @@ class Connection extends Component {
- +
- +
- { typeIcon } + { + needsToken + ? + : + }
- { description } + { + needsToken + ? this.renderSigner() + : this.renderPing() + }
@@ -144,10 +141,19 @@ class Connection extends Component { ); } - onChangeToken = (event, _token) => { + validateToken = (_token) => { 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); + return { + token, + validToken + }; + } + + onChangeToken = (event, _token) => { + const { token, validToken } = this.validateToken(_token || event.target.value); + this.setState({ token, validToken }, () => { validToken && this.setToken(); }); @@ -159,7 +165,7 @@ class Connection extends Component { this.setState({ loading: true }); - api + return api .updateToken(token, 0) .then((isValid) => { this.setState({ @@ -173,14 +179,14 @@ class Connection extends Component { function mapStateToProps (state) { const { isConnected, isConnecting, needsToken } = state.nodeStatus; - return { isConnected, isConnecting, needsToken }; -} - -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); + return { + isConnected, + isConnecting, + needsToken + }; } export default connect( mapStateToProps, - mapDispatchToProps + null )(Connection); diff --git a/js/src/views/Connection/connection.spec.js b/js/src/views/Connection/connection.spec.js new file mode 100644 index 000000000..20c41b3e4 --- /dev/null +++ b/js/src/views/Connection/connection.spec.js @@ -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 . + +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( + , + { 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'); + }); + }); + }); +});