diff --git a/client/package.json b/client/package.json
index 07a0990f..d6e54b3b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,7 +13,7 @@
"npm": "^2.1.x"
},
"scripts": {
- "test": "export NODE_PATH=src && mocha src/lib-test/preprocessor.js --compilers js:babel-core/register --recursive src/**/__tests__/*-test.js"
+ "test": "export NODE_PATH=src && mocha src/lib-test/preprocessor.js --compilers js:babel-core/register --recursive src/**/**/__tests__/*-test.js"
},
"devDependencies": {
"babel-core": "^5.8.22",
diff --git a/client/src/actions/__mocks__/common-actions-mock.js b/client/src/actions/__mocks__/common-actions-mock.js
new file mode 100644
index 00000000..1027e3bf
--- /dev/null
+++ b/client/src/actions/__mocks__/common-actions-mock.js
@@ -0,0 +1,5 @@
+export default {
+ changeLanguage: stub(),
+ logged: stub(),
+ loggedOut: stub()
+};
\ No newline at end of file
diff --git a/client/src/actions/__mocks__/user-actions-mock.js b/client/src/actions/__mocks__/user-actions-mock.js
new file mode 100644
index 00000000..b8a568db
--- /dev/null
+++ b/client/src/actions/__mocks__/user-actions-mock.js
@@ -0,0 +1,5 @@
+export default {
+ checkLoginStatus: stub(),
+ login: stub(),
+ logout: stub()
+};
\ No newline at end of file
diff --git a/client/src/actions/common-actions.js b/client/src/actions/common-actions.js
index e15290ef..8112df05 100644
--- a/client/src/actions/common-actions.js
+++ b/client/src/actions/common-actions.js
@@ -1,7 +1,9 @@
import Reflux from 'reflux';
let CommonActions = Reflux.createActions([
- 'changeLanguage'
+ 'changeLanguage',
+ 'logged',
+ 'loggedOut'
]);
export default CommonActions;
\ No newline at end of file
diff --git a/client/src/app/App.js b/client/src/app/App.js
index cb34adc4..91b2ce24 100644
--- a/client/src/app/App.js
+++ b/client/src/app/App.js
@@ -1,13 +1,14 @@
import React from 'react';
import Reflux from 'reflux';
-import {ListenerMixin} from 'reflux';
-import {RouteHandler} from 'react-router';
-import CommonActions from 'actions/common-actions';
import CommonStore from 'stores/common-store';
let App = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+
mixins: [Reflux.listenTo(CommonStore, 'onCommonStoreChanged')],
render() {
@@ -19,8 +20,14 @@ let App = React.createClass({
},
onCommonStoreChanged(change) {
- if (change === 'i18n') {
- this.forceUpdate();
+ let handle = {
+ 'i18n': () => {this.forceUpdate()},
+ 'logged': () => {this.context.router.push('/app/dashboard')},
+ 'loggedOut': () => {this.context.router.push('/app')}
+ };
+
+ if (handle[change]) {
+ handle[change]();
}
}
});
diff --git a/client/src/app/__tests__/App-test.js b/client/src/app/__tests__/App-test.js
new file mode 100644
index 00000000..6bc02936
--- /dev/null
+++ b/client/src/app/__tests__/App-test.js
@@ -0,0 +1,43 @@
+const CommonStore = require('stores/__mocks__/common-store-mock');
+
+const App = requireUnit('app/App', {
+ 'store/common-store': CommonStore
+});
+
+describe('App component', function () {
+ describe('when reacting to CommonStore', function () {
+ let app;
+
+ beforeEach(function () {
+ app = TestUtils.renderIntoDocument(
+ MOCK_CHILD
+ );
+
+ app.context = {
+ router: {
+ push: stub()
+ }
+ };
+
+ spy(app, 'forceUpdate');
+ });
+
+ it('should update with i18n', function () {
+ app.forceUpdate.reset();
+ app.onCommonStoreChanged('i18n');
+ expect(app.forceUpdate).to.have.been.called;
+ });
+
+ it('should redirect when logged in', function () {
+ app.context.router.push.reset();
+ app.onCommonStoreChanged('logged');
+ expect(app.context.router.push).to.have.been.calledWith('/app/dashboard');
+ });
+
+ it('should redirect when logged out', function () {
+ app.context.router.push.reset();
+ app.onCommonStoreChanged('loggedOut');
+ expect(app.context.router.push).to.have.been.calledWith('/app');
+ });
+ });
+});
diff --git a/client/src/app/main/dashboard/__tests__/dashboard-layout-test.js b/client/src/app/main/dashboard/__tests__/dashboard-layout-test.js
new file mode 100644
index 00000000..29cfac8c
--- /dev/null
+++ b/client/src/app/main/dashboard/__tests__/dashboard-layout-test.js
@@ -0,0 +1,32 @@
+const CommonActions = require('actions/__mocks__/common-actions-mock');
+const UserStore = require('stores/__mocks__/user-store-mock');
+
+const DashboardLayout = requireUnit('app/main/dashboard/dashboard-layout', {
+ 'actions/common-actions': CommonActions,
+ 'stores/user-store': UserStore,
+ 'app/main/dashboard/dashboard-menu': ReactMock()
+});
+
+
+describe('Dashboard page', function () {
+
+ afterEach(function () {
+ UserStore.isLoggedIn.returns(false);
+ });
+
+ it('should trigger common action if user is not logged', function () {
+ CommonActions.loggedOut.reset();
+ UserStore.isLoggedIn.returns(false);
+
+ TestUtils.renderIntoDocument();
+ expect(CommonActions.loggedOut).to.have.been.called;
+ });
+
+ it('should not trigger common action user if is logged', function () {
+ CommonActions.loggedOut.reset();
+ UserStore.isLoggedIn.returns(true);
+
+ TestUtils.renderIntoDocument();
+ expect(CommonActions.loggedOut).to.not.have.been.called;
+ });
+});
\ No newline at end of file
diff --git a/client/src/app/main/dashboard/dashboard-layout.js b/client/src/app/main/dashboard/dashboard-layout.js
index 29f3fc69..d8bfa4bd 100644
--- a/client/src/app/main/dashboard/dashboard-layout.js
+++ b/client/src/app/main/dashboard/dashboard-layout.js
@@ -1,8 +1,18 @@
import React from 'react';
+
+import UserStore from 'stores/user-store';
+import CommonActions from 'actions/common-actions';
+
import DashboardMenu from 'app/main/dashboard/dashboard-menu';
const DashboardLayout = React.createClass({
+ componentWillMount() {
+ if (!UserStore.isLoggedIn()) {
+ CommonActions.loggedOut();
+ }
+ },
+
render() {
return (
diff --git a/client/src/app/main/main-home/__tests__/main-home-page-login-widget-test.js b/client/src/app/main/main-home/__tests__/main-home-page-login-widget-test.js
new file mode 100644
index 00000000..e34d5965
--- /dev/null
+++ b/client/src/app/main/main-home/__tests__/main-home-page-login-widget-test.js
@@ -0,0 +1,75 @@
+const UserActions = require('actions/__mocks__/user-actions-mock');
+const UserStore = require('stores/__mocks__/user-store-mock');
+
+const Button = ReactMock();
+const Input = ReactMock();
+const Form = ReactMock();
+const Checkbox = ReactMock();
+const Widget = ReactMock();
+const WidgetTransition = ReactMock();
+
+const MainHomePageLoginWidget = requireUnit('app/main/main-home/main-home-page-login-widget', {
+ 'core-components/button': Button,
+ 'core-components/input': Input,
+ 'core-components/form': Form,
+ 'core-components/checkbox': Checkbox,
+ 'core-components/widget': Widget,
+ 'core-components/widget-transition': WidgetTransition,
+ 'actions/user-actions': UserActions,
+ 'stores/user-store': UserStore
+});
+
+
+describe('Login/Recover Widget', function () {
+ describe('Login Form', function () {
+ let loginWidget, loginForm, widgetTransition, inputs, checkbox, component,
+ forgotPasswordButton;
+
+ beforeEach(function () {
+ component = TestUtils.renderIntoDocument(
+
+ );
+ widgetTransition = TestUtils.scryRenderedComponentsWithType(component, WidgetTransition)[0];
+ loginWidget = TestUtils.scryRenderedComponentsWithType(component, Widget)[0];
+ loginForm = TestUtils.scryRenderedComponentsWithType(component, Form)[0];
+ inputs = TestUtils.scryRenderedComponentsWithType(component, Input);
+ checkbox = TestUtils.scryRenderedComponentsWithType(component, Checkbox)[0];
+ forgotPasswordButton = TestUtils.scryRenderedComponentsWithType(component, Button)[1];
+
+ component.refs.loginForm = {
+ refs: {
+ password: {
+ focus: stub()
+ }
+ }
+ };
+ });
+
+ it('should control form errors by prop', function () {
+ expect(loginForm.props.errors).to.deep.equal({});
+ loginForm.props.onValidateErrors({email: 'MOCK_ERROR'});
+ expect(loginForm.props.errors).to.deep.equal({email: 'MOCK_ERROR'});
+ });
+
+ it('should trigger login action when submitted', function () {
+ let mockSubmitData = {email: 'MOCK_VALUE', password: 'MOCK_VALUE'};
+
+ UserActions.login.reset();
+ loginForm.props.onSubmit(mockSubmitData);
+ expect(UserActions.login).to.have.been.calledWith(mockSubmitData);
+ });
+
+ it('should add error if login fails', function () {
+ component.refs.loginForm.refs.password.focus.reset();
+ component.onUserStoreChanged('LOGIN_FAIL');
+ expect(loginForm.props.errors).to.deep.equal({password: 'Password does not match'});
+ expect(component.refs.loginForm.refs.password.focus).to.have.been.called;
+ });
+
+ it('should show back side if \'Forgot your password?\' link is clicked', function () {
+ expect(widgetTransition.props.sideToShow).to.equal('front');
+ forgotPasswordButton.props.onClick();
+ expect(widgetTransition.props.sideToShow).to.equal('back');
+ });
+ });
+});
\ No newline at end of file
diff --git a/client/src/app/main/main-home/__tests__/main-home-page-test.js b/client/src/app/main/main-home/__tests__/main-home-page-test.js
new file mode 100644
index 00000000..736b0ed4
--- /dev/null
+++ b/client/src/app/main/main-home/__tests__/main-home-page-test.js
@@ -0,0 +1,31 @@
+const CommonActions = require('actions/__mocks__/common-actions-mock');
+const UserStore = require('stores/__mocks__/user-store-mock');
+
+const MainHomePage = requireUnit('app/main/main-home/main-home-page', {
+ 'actions/common-actions': CommonActions,
+ 'stores/user-store': UserStore
+});
+
+
+describe('Main home page', function () {
+
+ afterEach(function () {
+ UserStore.isLoggedIn.returns(false);
+ });
+
+ it('should trigger common action if user is currently logged', function () {
+ CommonActions.logged.reset();
+ UserStore.isLoggedIn.returns(true);
+
+ TestUtils.renderIntoDocument(
);
+ expect(CommonActions.logged).to.have.been.called;
+ });
+
+ it('should not trigger common action user if is not logged', function () {
+ CommonActions.logged.reset();
+ UserStore.isLoggedIn.returns(false);
+
+ TestUtils.renderIntoDocument(
);
+ expect(CommonActions.logged).to.not.have.been.called;
+ });
+});
\ No newline at end of file
diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js
index fce0d34e..9b5f90ee 100644
--- a/client/src/app/main/main-home/main-home-page-login-widget.js
+++ b/client/src/app/main/main-home/main-home-page-login-widget.js
@@ -1,4 +1,6 @@
-const React = require( 'react');
+const React = require('react');
+const Reflux = require('reflux');
+const _ = require('lodash');
const classNames = require('classnames');
const UserActions = require('actions/user-actions');
@@ -12,10 +14,13 @@ const Widget = require('core-components/widget');
const WidgetTransition = require('core-components/widget-transition');
let MainHomePageLoginWidget = React.createClass({
+
+ mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
getInitialState() {
return {
- sideToShow: 'front'
+ sideToShow: 'front',
+ loginFormErrors: {}
};
},
@@ -31,7 +36,7 @@ let MainHomePageLoginWidget = React.createClass({
renderLogin() {
return (
-
-
@@ -51,7 +56,7 @@ let MainHomePageLoginWidget = React.createClass({
renderPasswordRecovery() {
return (
-