diff --git a/package.json b/package.json index 9c81baa65..b95cb9530 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "babel-plugin-syntax-trailing-function-commas": "=6.22.0", "babel-preset-es2015": "=6.18.0", "babel-preset-react": "=6.16.0", + "babel-preset-stage-3": "=6.24.1", "chai": "=3.5.0", "chai-as-promised": "=6.0.0", "chai-enzyme": "=0.6.1", @@ -96,6 +97,7 @@ "react-hot-loader": "^1.3.1", "react-test-renderer": "=15.6.1", "react-waypoint": "^7.0.4", + "redux-mock-store": "=1.2.3", "should": "=11.2.0", "sinon": "=2.0.0", "sinon-chai": "=2.8.0", diff --git a/src/components/app/index.js b/src/components/app/index.js index d5af8bfe5..251091877 100644 --- a/src/components/app/index.js +++ b/src/components/app/index.js @@ -1,7 +1,8 @@ import React from 'react'; import { Route, Link } from 'react-router-dom'; -import Header from '../header'; +import PrivateRoutes from '../privateRoute'; import Account from '../account'; +import Header from '../header'; import Login from '../login'; import Transactions from '../transactions'; import Voting from '../voting'; @@ -11,32 +12,30 @@ import Metronome from '../../utils/metronome'; import Dialog from '../dialog'; // temporary, will be deleted with #347 -const App = () => { - // start dispatching sync ticks - const metronome = new Metronome(); - metronome.init(); +// start dispatching sync ticks +const metronome = new Metronome(); +metronome.init(); - return ( -
-
-
- ( -
- - Transactions - Voting - Forging - - - -
- )} /> - -
+const App = () => ( +
+
+
+ ( +
+ + Transactions + Voting + Forging - -
- ); -}; + + + + + )} /> + + + +
+); export default App; diff --git a/src/components/app/index.test.js b/src/components/app/index.test.js index e43253010..ab864a635 100644 --- a/src/components/app/index.test.js +++ b/src/components/app/index.test.js @@ -3,13 +3,15 @@ import { mount } from 'enzyme'; import { MemoryRouter } from 'react-router'; import { Provider } from 'react-redux'; import { expect } from 'chai'; -import store from '../../store'; +import configureStore from 'redux-mock-store'; import App from './'; import Login from '../login'; import Transactions from '../transactions'; import Voting from '../voting'; import Forging from '../forging'; +const fakeStore = configureStore(); + const addRouter = Component => (props, path) => mount( @@ -19,18 +21,65 @@ const addRouter = Component => (props, path) => , ); -const routesComponent = [ +const publicComponent = [ { route: '/', component: Login }, +]; + +const privateComponent = [ { route: '/main/transactions', component: Transactions }, { route: '/main/voting', component: Voting }, { route: '/main/forging', component: Forging }, ]; describe('App', () => { + const navigateTo = addRouter(App); describe('renders correct routes', () => { - const navigateTo = addRouter(App); - routesComponent.forEach(({ route, component }) => { - it.skip(`should render ${component.name} component at "${route}" route`, () => { + const store = fakeStore({ + account: {}, + dialog: {}, + peers: {}, + }); + publicComponent.forEach(({ route, component }) => { + it(`should render ${component.name} component at "${route}" route`, () => { + const wrapper = navigateTo({ store }, [route]); + expect(wrapper.find(component).exists()).to.be.equal(true); + }); + }); + + privateComponent.forEach(({ route, component }) => { + it(`should redirect from ${component.name} component if user is not authenticated`, () => { + const wrapper = navigateTo({ store }, [route]); + expect(wrapper.find(component).exists()).to.be.equal(false); + expect(wrapper.find(Login).exists()).to.be.equal(true); + }); + }); + }); + + // These tests are skipped because App component use many components and all of them need + // specific data to render. Each time you will add new components to App, this tests can be fall. + // Need solution for these kinds of tests. + describe.skip('allow to render private components after logged in', () => { + const store = fakeStore({ + account: { + publicKey: '000', + }, + forging: { + statics: {}, + }, + dialog: {}, + peers: { + status: { + online: true, + }, + data: { + options: { + name: 'Test', + }, + }, + }, + }); + privateComponent.forEach(({ route, component }) => { + it(`should reder ${component.name} component at "${route}" route if user is authenticated`, () => { const wrapper = navigateTo({ store }, [route]); expect(wrapper.find(component).exists()).to.be.equal(true); }); diff --git a/src/components/login/loginFormComponent.js b/src/components/login/loginFormComponent.js index ffb79571b..c01e9a722 100644 --- a/src/components/login/loginFormComponent.js +++ b/src/components/login/loginFormComponent.js @@ -101,7 +101,7 @@ class LoginFormComponent extends React.Component { onAccountUpdated({ delegate: {}, isDelegate: false }); }); // redirect to main/transactions - this.props.history.push('/main/transactions'); + this.props.history.replace('/main/transactions'); }); }, 5); } diff --git a/src/components/privateRoute/index.js b/src/components/privateRoute/index.js new file mode 100644 index 000000000..0a21f9e8e --- /dev/null +++ b/src/components/privateRoute/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Route, Redirect, withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; + +export const PrivateRouteRender = ({ render, isAuthenticated, ...rest }) => ( + ( + isAuthenticated ? render(matchProps) : + )}/> + ); + +const mapStateToProps = state => ({ + isAuthenticated: !!state.account.publicKey, +}); + +export default withRouter(connect(mapStateToProps)(PrivateRouteRender)); diff --git a/src/components/privateRoute/index.test.js b/src/components/privateRoute/index.test.js new file mode 100644 index 000000000..22794f8c0 --- /dev/null +++ b/src/components/privateRoute/index.test.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { MemoryRouter, Route } from 'react-router'; +import { PrivateRouteRender } from './index'; + +const Public = () =>

Public

; +const Private = () =>

Private

; + +describe('PrivateRouteRender', () => { + const isAuth = isAuthenticated => ( + mount( + +
+ + } + isAuthenticated={isAuthenticated} /> +
+
, + ) + ); + it('should render Component if user is authenticated', () => { + const wrapper = isAuth(true); + expect(wrapper.find(Private)).to.have.length(1); + }); + + it('should redirect to root path if user is not authenticated', () => { + const wrapper = isAuth(false); + expect(wrapper.find(Public)).to.have.length(1); + }); +}); diff --git a/webpack.config.js b/webpack.config.js index 18a5178ff..1666fabb6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -70,7 +70,7 @@ module.exports = (env) => { exclude: /node_modules/, loader: 'babel-loader', options: { - presets: ['es2015', 'react'], + presets: ['es2015', 'react', 'stage-3'], plugins: ['syntax-trailing-function-commas'], env: { test: {