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

Enhancements in routing - Closes #499 #509

Merged
merged 8 commits into from
Jul 26, 2017
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
45 changes: 17 additions & 28 deletions src/components/app/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Route, Link } from 'react-router-dom';
import { Route } from 'react-router-dom';
import DefaultLayout from '../defaultLayout';
import Header from '../header';
import Account from '../account';
import Login from '../login';
import Transactions from '../transactions';
import Voting from '../voting';
Expand All @@ -11,32 +11,21 @@ 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 (
<section className={styles['body-wrapper']}>
<Header />
<main className=''>
<Route path="/main" render={({ match }) => (
<main className=''>
<Account />
<Link to='/main/transactions'>Transactions</Link>
<Link to='/main/voting'>Voting</Link>
<Link to='/main/forging'>Forging</Link>
<Route path={`${match.url}/transactions`} component={Transactions}/>
<Route path={`${match.url}/voting`} component={Voting}/>
<Route path={`${match.url}/forging`} component={Forging}/>
</main>
)} />
<Route exact path="/" component={Login} />
</main>

<Dialog />
</section>
);
};
const App = () => (
<section className={styles['body-wrapper']}>
<Header />
<main>
<DefaultLayout path='/transactions' component={Transactions} />
<DefaultLayout path='/voting' component={Voting} />
<DefaultLayout path='/forging' component={Forging} />
<Route exact path="/" component={Login} />
</main>
<Dialog />
</section>
);

export default App;
65 changes: 57 additions & 8 deletions src/components/app/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Provider {...props}>
Expand All @@ -19,18 +21,65 @@ const addRouter = Component => (props, path) =>
</Provider>,
);

const routesComponent = [
const publicComponent = [
{ route: '/', component: Login },
{ route: '/main/transactions', component: Transactions },
{ route: '/main/voting', component: Voting },
{ route: '/main/forging', component: Forging },
];

const privateComponent = [
{ route: '/transactions', component: Transactions },
{ route: '/voting', component: Voting },
{ route: '/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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the skip or add a comment on why it needs to be skipped

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);
});
Expand Down
18 changes: 18 additions & 0 deletions src/components/defaultLayout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Account from '../account';
import PrivateRoute from '../privateRoute';

const DefaultLayout = ({ component: Component, ...rest }) => (
<PrivateRoute {...rest} component={ matchProps => (
<main>
<Account />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Links component was moved on @slaweet request. I think that the performance change here is negligible especially if you will move the logic from account component and keep it dumb and allow only render the passed props. I do not know how the app will be looked after rebranding, and I think that the layout can help combine some components for differents views.
If you think that is a bad solution than I can return the nested route.

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 you render Account component and Link components every time changing the route, I think rending them once will help improving performance. to do so, I’d suggest find a way to keep those components mounted once or adapting unit tests. nested routes are used in many apps. there should be some other applications facing this situation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yasharAyari fixed

Copy link
Contributor

Choose a reason for hiding this comment

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

good job. thank you.

<Link to='/transactions'>Transactions</Link>
<Link to='/voting'>Voting</Link>
<Link to='/forging'>Forging</Link>
<Component {...matchProps} />
</main>
)} />
);

export default DefaultLayout;
2 changes: 1 addition & 1 deletion src/components/login/loginFormComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('/transactions');
});
}, 5);
}
Expand Down
18 changes: 18 additions & 0 deletions src/components/privateRoute/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';

export const PrivateRouteComponent = ({ component: Component, isAuthenticated, ...rest }) => (
<Route {...rest} render={ matchProps => (
isAuthenticated ? (
<Component {...matchProps}/>
) :
<Redirect to='/' />
)}/>
);

const mapStateToProps = state => ({
isAuthenticated: !!state.account.publicKey,
});

export default withRouter(connect(mapStateToProps)(PrivateRouteComponent));
33 changes: 33 additions & 0 deletions src/components/privateRoute/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import { MemoryRouter, Route } from 'react-router';
import { PrivateRouteComponent } from './index';

const Public = () => <h1>Public</h1>;
const Private = () => <h1>Private</h1>;

describe('<PrivateRoute />', () => {
const isAuth = isAuthenticated => (
mount(
<MemoryRouter initialEntries={['/private']}>
<div>
<Route path='/' component={Public} />
<PrivateRouteComponent
path='/private'
component={Private}
isAuthenticated={isAuthenticated} />
</div>
</MemoryRouter>,
)
);
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);
});
});
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ module.exports = (env) => {
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['es2015', 'react'],
presets: ['es2015', 'react', 'stage-3'],
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you use stage-3 for? I tried to read some basics about what it is, but I cannot find what new features you use.

Copy link
Contributor

Choose a reason for hiding this comment

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

Now I see it is because of { component: Component, ...rest }

plugins: ['syntax-trailing-function-commas'],
env: {
test: {
Expand Down