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: {