Merged in OS-48-login-logout-frontend-logic (pull request #20)
Os 48 login logout frontend logic
This commit is contained in:
commit
bc66d74f73
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
changeLanguage: stub(),
|
||||
logged: stub(),
|
||||
loggedOut: stub()
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
checkLoginStatus: stub(),
|
||||
login: stub(),
|
||||
logout: stub()
|
||||
};
|
|
@ -1,7 +1,9 @@
|
|||
import Reflux from 'reflux';
|
||||
|
||||
let CommonActions = Reflux.createActions([
|
||||
'changeLanguage'
|
||||
'changeLanguage',
|
||||
'logged',
|
||||
'loggedOut'
|
||||
]);
|
||||
|
||||
export default CommonActions;
|
|
@ -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]();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
<App><span>MOCK_CHILD</span></App>
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(<DashboardLayout />);
|
||||
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(<DashboardLayout />);
|
||||
expect(CommonActions.loggedOut).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<div>
|
||||
|
|
|
@ -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(
|
||||
<MainHomePageLoginWidget />
|
||||
);
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(<MainHomePage />);
|
||||
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(<MainHomePage />);
|
||||
expect(CommonActions.logged).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<Widget className="main-home-page--widget" title="Login">
|
||||
<Form className="login-widget--form" onSubmit={this.handleLoginFormSubmit}>
|
||||
<Form className="login-widget--form" ref="loginForm" onSubmit={this.handleLoginFormSubmit} errors={this.state.loginFormErrors} onValidateErrors={this.handleLoginFormErrorsValidation}>
|
||||
<div className="login-widget--inputs">
|
||||
<Input placeholder="email" name="email" className="login-widget--input" validation="EMAIL" required/>
|
||||
<Input placeholder="password" name="password" className="login-widget--input" password required/>
|
||||
|
@ -41,7 +46,7 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
<Button type="primary">LOG IN</Button>
|
||||
</div>
|
||||
</Form>
|
||||
<Button className="login-widget--forgot-password" type="link" onClick={this.handleForgetPasswordClick}>
|
||||
<Button className="login-widget--forgot-password" type="link" onClick={this.handleForgotPasswordClick}>
|
||||
{'Forgot your password?'}
|
||||
</Button>
|
||||
</Widget>
|
||||
|
@ -51,7 +56,7 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
renderPasswordRecovery() {
|
||||
return (
|
||||
<Widget className="main-home-page--widget main-home-page--password-widget" title="Password Recovery">
|
||||
<Form className="login-widget--form" onSubmit={this.handleSubmit}>
|
||||
<Form className="login-widget--form" onSubmit={this.handleForgotPasswordSubmit}>
|
||||
<div className="login-widget--inputs">
|
||||
<Input placeholder="email" name="email" className="login-widget--input" validation="EMAIL"/>
|
||||
</div>
|
||||
|
@ -67,11 +72,20 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
},
|
||||
|
||||
handleLoginFormSubmit(formState) {
|
||||
console.log(formState);
|
||||
UserActions.login(formState);
|
||||
},
|
||||
|
||||
handleForgetPasswordClick() {
|
||||
handleForgotPasswordSubmit() {
|
||||
|
||||
},
|
||||
|
||||
handleLoginFormErrorsValidation(errors) {
|
||||
this.setState({
|
||||
loginFormErrors: errors
|
||||
});
|
||||
},
|
||||
|
||||
handleForgotPasswordClick() {
|
||||
this.setState({
|
||||
sideToShow: 'back'
|
||||
});
|
||||
|
@ -81,6 +95,18 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
this.setState({
|
||||
sideToShow: 'front'
|
||||
});
|
||||
},
|
||||
|
||||
onUserStoreChanged(event) {
|
||||
if (event === 'LOGIN_FAIL') {
|
||||
this.setState({
|
||||
loginFormErrors: {
|
||||
password: 'Password does not match'
|
||||
}
|
||||
}, function () {
|
||||
this.refs.loginForm.refs.password.focus()
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,17 @@ const React = require( 'react');
|
|||
const MainHomePageLoginWidget = require('app/main/main-home/main-home-page-login-widget');
|
||||
const MainHomePagePortal = require('app/main/main-home/main-home-page-portal');
|
||||
|
||||
const CommonActions = require('actions/common-actions');
|
||||
const UserStore = require('stores/user-store');
|
||||
|
||||
const MainHomePage = React.createClass({
|
||||
|
||||
componentWillMount() {
|
||||
if (UserStore.isLoggedIn()) {
|
||||
CommonActions.logged();
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-home-page">
|
||||
|
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||
|
||||
import i18n from 'lib-app/i18n';
|
||||
import CommonActions from 'actions/common-actions';
|
||||
import UserActions from 'actions/user-actions';
|
||||
import UserStore from 'stores/user-store';
|
||||
|
||||
import Button from 'core-components/button';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
|
@ -22,13 +24,31 @@ let MainLayoutHeader = React.createClass({
|
|||
render() {
|
||||
return (
|
||||
<div className="main-layout-header">
|
||||
{this.renderAccessLinks()}
|
||||
<DropDown className="main-layout-header--languages" items={this.getLanguageList()} onChange={this.changeLanguage}/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderAccessLinks() {
|
||||
let result;
|
||||
if (UserStore.isLoggedIn()) {
|
||||
result = (
|
||||
<div className="main-layout-header--login-links">
|
||||
Welcome, pepito
|
||||
<Button type="clean" onClick={this.logout}>(Close Session)</Button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
result = (
|
||||
<div className="main-layout-header--login-links">
|
||||
<Button type="clean" route={{to:'/app'}}>{i18n('LOG_IN')}</Button>
|
||||
<Button type="clean" route={{to:'/app/signup'}}>Sign up</Button>
|
||||
</div>
|
||||
<DropDown className="main-layout-header--languages" items={this.getLanguageList()} onChange={this.changeLanguage}/>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getLanguageList() {
|
||||
|
@ -44,6 +64,10 @@ let MainLayoutHeader = React.createClass({
|
|||
let language = languageList[event.index];
|
||||
|
||||
CommonActions.changeLanguage(codeLanguages[language]);
|
||||
},
|
||||
|
||||
logout() {
|
||||
UserActions.logout();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
|
||||
const MainSignupPage = requireUnit('app/main/main-signup/main-signup-page', {
|
||||
'actions/common-actions': CommonActions,
|
||||
'stores/user-store': UserStore,
|
||||
'react-google-recaptcha': ReactMock()
|
||||
});
|
||||
|
||||
|
||||
describe('Signup 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(<MainSignupPage />);
|
||||
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(<MainSignupPage />);
|
||||
expect(CommonActions.logged).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -11,8 +11,16 @@ import Input from 'core-components/input';
|
|||
import Widget from 'core-components/widget';
|
||||
import WidgetTransition from 'core-components/widget-transition';
|
||||
|
||||
const CommonActions = require('actions/common-actions');
|
||||
|
||||
let MainSignUpPageWidget = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
if (UserStore.isLoggedIn()) {
|
||||
CommonActions.logged();
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-signup-page">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock');
|
||||
const Input = ReactMock();
|
||||
|
||||
// COMPONENTS
|
||||
// COMPONENT
|
||||
const Form = requireUnit('core-components/form', {
|
||||
'lib-app/validations/validations-factory': ValidationFactoryMock,
|
||||
'core-components/input': Input
|
||||
|
@ -11,9 +11,9 @@ const Form = requireUnit('core-components/form', {
|
|||
describe('Form component', function () {
|
||||
let form, fields, onSubmit = stub();
|
||||
|
||||
function renderForm() {
|
||||
function renderForm(props = {}) {
|
||||
form = TestUtils.renderIntoDocument(
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Form {...props} onSubmit={onSubmit}>
|
||||
<div>
|
||||
<Input name="first" value="value1" required/>
|
||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
||||
|
@ -90,17 +90,77 @@ describe('Form component', function () {
|
|||
expect(fields[0].props.error).to.equal(undefined);
|
||||
expect(fields[0].props.error).to.equal(undefined);
|
||||
|
||||
TestUtils.Simulate.blur(ReactDOM.findDOMNode(fields[0]));
|
||||
fields[0].props.onBlur();
|
||||
expect(fields[0].props.error).to.equal('MOCK_ERROR');
|
||||
|
||||
TestUtils.Simulate.blur(ReactDOM.findDOMNode(fields[1]));
|
||||
fields[1].props.onBlur();
|
||||
expect(fields[1].props.error).to.equal('MOCK_ERROR_2');
|
||||
|
||||
TestUtils.Simulate.blur(ReactDOM.findDOMNode(fields[2]));
|
||||
fields[2].props.onBlur();
|
||||
expect(fields[2].props.error).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using controlled errors', function () {
|
||||
let onValidateErrors;
|
||||
|
||||
beforeEach(function () {
|
||||
onValidateErrors = stub();
|
||||
|
||||
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub().returns('MOCK_ERROR');
|
||||
ValidationFactoryMock.validators.customValidatorMock.validate = stub().returns('MOCK_ERROR_2');
|
||||
|
||||
renderForm({
|
||||
errors: {first: 'MOCK_ERROR_CONTROLLED'},
|
||||
onValidateErrors: onValidateErrors
|
||||
});
|
||||
});
|
||||
afterEach(resetStubs);
|
||||
|
||||
it('should pass the errors to inputs', function () {
|
||||
expect(fields[0].props.error).to.equal('MOCK_ERROR_CONTROLLED');
|
||||
expect(fields[1].props.error).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should prioritize prop error over state error', function () {
|
||||
fields[1].props.onBlur();
|
||||
expect(fields[1].props.error).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should call onValidateErrors when state changes', function () {
|
||||
fields[1].props.onBlur();
|
||||
expect(onValidateErrors).to.have.been.calledWith({second: 'MOCK_ERROR_2'});
|
||||
|
||||
});
|
||||
|
||||
it('should still working if the error prop changes', function () {
|
||||
function setErrorsOrRender(errors = {}) {
|
||||
form = reRenderIntoDocument(
|
||||
<Form errors={errors}>
|
||||
<div>
|
||||
<Input name="first" value="value1" required/>
|
||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
||||
</div>
|
||||
<Input name="third" value="value3" />
|
||||
</Form>
|
||||
);
|
||||
fields = TestUtils.scryRenderedComponentsWithType(form, Input);
|
||||
}
|
||||
|
||||
setErrorsOrRender();
|
||||
expect(fields[0].props.error).to.equal(undefined);
|
||||
expect(fields[1].props.error).to.equal(undefined);
|
||||
|
||||
setErrorsOrRender({second: 'MOCK_ERROR_CONTROLLED_2'});
|
||||
expect(fields[0].props.error).to.equal(undefined);
|
||||
expect(fields[1].props.error).to.equal('MOCK_ERROR_CONTROLLED_2');
|
||||
|
||||
setErrorsOrRender({first: 'MOCK_ERROR_CONTROLLED', second: 'MOCK_ERROR_CONTROLLED_2'});
|
||||
expect(fields[0].props.error).to.equal('MOCK_ERROR_CONTROLLED');
|
||||
expect(fields[1].props.error).to.equal('MOCK_ERROR_CONTROLLED_2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when submitting the form', function () {
|
||||
beforeEach(renderForm);
|
||||
afterEach(resetStubs);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var callback = require('lib-core/callback');
|
||||
// VENDOR LIBS
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
// CORE LIBS
|
||||
import callback from 'lib-core/callback';
|
||||
|
||||
let Button = React.createClass({
|
||||
|
||||
|
@ -30,12 +34,24 @@ let Button = React.createClass({
|
|||
|
||||
render() {
|
||||
return (
|
||||
<button {...this.props} onClick={callback(this.handleClick, this.props.onClick)} className={this.getClass()}>
|
||||
<button {...this.getProps()}>
|
||||
{this.props.children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
getProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
||||
props.onClick = callback(this.handleClick, this.props.onClick);
|
||||
props.className = this.getClass();
|
||||
|
||||
delete props.route;
|
||||
delete props.type;
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
'button': true
|
||||
|
|
|
@ -46,7 +46,11 @@ let CheckBox = React.createClass({
|
|||
props.className = 'checkbox--box';
|
||||
props.checked = this.getValue();
|
||||
props.onChange = callback(this.handleChange, this.props.onChange);
|
||||
props.value = null;
|
||||
|
||||
delete props.alignment;
|
||||
delete props.error;
|
||||
delete props.label;
|
||||
delete props.value;
|
||||
|
||||
return props;
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const _ = require('lodash');
|
||||
|
||||
const {reactDFS, renderChildrenWithProps} = require('lib-core/react-dfs');
|
||||
|
@ -10,6 +9,12 @@ const Checkbox = require('core-components/checkbox');
|
|||
|
||||
const Form = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
errors: React.PropTypes.object,
|
||||
onValidateErrors: React.PropTypes.func,
|
||||
onSubmit: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
form: {},
|
||||
|
@ -34,6 +39,9 @@ const Form = React.createClass({
|
|||
let props = _.clone(this.props);
|
||||
|
||||
props.onSubmit = this.handleSubmit;
|
||||
|
||||
delete props.errors;
|
||||
delete props.onValidateErrors;
|
||||
|
||||
return props;
|
||||
},
|
||||
|
@ -47,7 +55,7 @@ const Form = React.createClass({
|
|||
additionalProps = {
|
||||
ref: fieldName,
|
||||
value: this.state.form[fieldName] || props.value,
|
||||
error: this.state.errors[fieldName],
|
||||
error: this.getFieldError(fieldName),
|
||||
onChange: this.handleFieldChange.bind(this, fieldName, type),
|
||||
onBlur: this.validateField.bind(this, fieldName)
|
||||
}
|
||||
|
@ -56,6 +64,15 @@ const Form = React.createClass({
|
|||
return additionalProps;
|
||||
},
|
||||
|
||||
getFieldError(fieldName) {
|
||||
let error = this.state.errors[fieldName];
|
||||
|
||||
if (this.props.errors) {
|
||||
error = this.props.errors[fieldName]
|
||||
}
|
||||
return error;
|
||||
},
|
||||
|
||||
getFirstErrorField() {
|
||||
let fieldName = _.findKey(this.state.errors);
|
||||
let fieldNode;
|
||||
|
@ -119,9 +136,7 @@ const Form = React.createClass({
|
|||
event.preventDefault();
|
||||
|
||||
if (this.hasFormErrors()) {
|
||||
this.setState({
|
||||
errors: this.getAllFieldErrors()
|
||||
}, this.focusFirstErrorField);
|
||||
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField);
|
||||
} else if (this.props.onSubmit) {
|
||||
this.props.onSubmit(this.state.form);
|
||||
}
|
||||
|
@ -150,9 +165,17 @@ const Form = React.createClass({
|
|||
},
|
||||
|
||||
validateField(fieldName) {
|
||||
this.updateErrors(this.getErrorsWithValidatedField(fieldName));
|
||||
},
|
||||
|
||||
updateErrors(errors, callback) {
|
||||
this.setState({
|
||||
errors: this.getErrorsWithValidatedField(fieldName)
|
||||
});
|
||||
errors
|
||||
}, callback);
|
||||
|
||||
if (this.props.onValidateErrors) {
|
||||
this.props.onValidateErrors(errors);
|
||||
}
|
||||
},
|
||||
|
||||
focusFirstErrorField() {
|
||||
|
|
|
@ -57,11 +57,16 @@ const Input = React.createClass({
|
|||
getInputProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
||||
props.required = null;
|
||||
props['aria-required'] = this.props.required;
|
||||
props.type = (this.props.password) ? 'password' : 'text';
|
||||
props.ref = 'nativeInput';
|
||||
|
||||
delete props.required;
|
||||
delete props.validation;
|
||||
delete props.inputType;
|
||||
delete props.error;
|
||||
delete props.password;
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
|
|
|
@ -48,7 +48,11 @@ const Menu = React.createClass({
|
|||
var props = _.clone(this.props);
|
||||
|
||||
props.className = this.getClass();
|
||||
props.type = null;
|
||||
|
||||
delete props.items;
|
||||
delete props.onItemClick;
|
||||
delete props.selectedIndex;
|
||||
delete props.type;
|
||||
|
||||
return props;
|
||||
},
|
||||
|
|
|
@ -22,5 +22,15 @@ module.exports = [
|
|||
|
||||
return response;
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user/logout',
|
||||
time: 1000,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
call: stub()
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
createSession: stub(),
|
||||
getSessionData: stub().returns({}),
|
||||
isLoggedIn: stub().returns(false),
|
||||
closeSession: stub()
|
||||
};
|
|
@ -1,22 +1,21 @@
|
|||
const _ = require('lodash');
|
||||
const APIUtils = require('lib-core/APIUtils');
|
||||
const SessionStorage = require('sessionstorage');
|
||||
const SessionStore = require('lib-app/session-store');
|
||||
|
||||
const root = 'http://localhost:3000/api/';
|
||||
|
||||
function processData (data) {
|
||||
return _.extend({
|
||||
userId: SessionStorage.getItem('userId'),
|
||||
token: SessionStorage.getItem('token')
|
||||
}, data);
|
||||
return _.extend(SessionStore.getSessionData(), data);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
call: function (path, data, callback) {
|
||||
APIUtils.post(root + path, processData(data)).then(callback);
|
||||
},
|
||||
setConfig: function (userId, token) {
|
||||
SessionStorage.setItem('userId', userId);
|
||||
SessionStorage.setItem('token', token);
|
||||
call: function ({path, data, onSuccess, onFail}) {
|
||||
APIUtils.post(root + path, processData(data)).then(function (result) {
|
||||
if (result.status === 'success') {
|
||||
onSuccess && onSuccess(result);
|
||||
} else {
|
||||
onFail && onFail(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import SessionStorage from 'sessionstorage';
|
||||
|
||||
|
||||
class SessionStore {
|
||||
static initialize() {
|
||||
if (!SessionStorage.getItem('language')) {
|
||||
SessionStorage.setItem('language', 'english');
|
||||
}
|
||||
}
|
||||
|
||||
static createSession(userId, token) {
|
||||
SessionStorage.setItem('userId', userId);
|
||||
SessionStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
static getSessionData() {
|
||||
return {
|
||||
userId: SessionStorage.getItem('userId'),
|
||||
token: SessionStorage.getItem('token')
|
||||
};
|
||||
}
|
||||
|
||||
static isLoggedIn() {
|
||||
return !!SessionStorage.getItem('userId');
|
||||
}
|
||||
|
||||
static closeSession() {
|
||||
SessionStorage.removeItem('userId');
|
||||
SessionStorage.removeItem('token');
|
||||
}
|
||||
}
|
||||
|
||||
SessionStore.initialize();
|
||||
|
||||
export default SessionStore;
|
|
@ -19,3 +19,14 @@ global.TestUtils = require('react-addons-test-utils');
|
|||
global.requireUnit = function (path, mocks) {
|
||||
return proxyquire(process.cwd() + '/src/' + path + '.js', mocks)
|
||||
};
|
||||
global.reRenderIntoDocument = (function () {
|
||||
let div;
|
||||
|
||||
return function (jsx) {
|
||||
if (!div) {
|
||||
div = document.createElement('div')
|
||||
}
|
||||
|
||||
return ReactDOM.render(jsx, div);
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -4,7 +4,7 @@ const _ = require('lodash');
|
|||
module.exports = function (options) {
|
||||
return React.createClass(_.extend({
|
||||
render() {
|
||||
return <div {...this.props}></div>;
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}, options));
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
changeLanguage: stub(),
|
||||
logged: stub(),
|
||||
loggedOut: stub(),
|
||||
listen: stub()
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
loginUser: stub(),
|
||||
logoutUser: stub(),
|
||||
isLoggedIn: stub().returns(false),
|
||||
listen: stub()
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
// MOCKS
|
||||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const SessionStore = require('lib-app/__mocks__/session-store-mock');
|
||||
const API = require('lib-app/__mocks__/api-call-mock');
|
||||
const UserActions = {
|
||||
checkLoginStatus: {listen: stub()},
|
||||
login: {listen: stub()},
|
||||
logout: {listen: stub()}
|
||||
};
|
||||
|
||||
const UserStore = requireUnit('stores/user-store', {
|
||||
'actions/user-actions': UserActions,
|
||||
'actions/common-actions': CommonActions,
|
||||
'lib-app/session-store': SessionStore,
|
||||
'lib-app/api-call': API
|
||||
});
|
||||
|
||||
describe('UserStore', function () {
|
||||
describe('when login user', function () {
|
||||
it('should call /user/login api path', function () {
|
||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
||||
|
||||
UserStore.loginUser(mockLoginData);
|
||||
expect(API.call).to.have.been.calledWith({
|
||||
path: 'user/login',
|
||||
data: mockLoginData,
|
||||
onSuccess: sinon.match.func,
|
||||
onFail: sinon.match.func
|
||||
});
|
||||
});
|
||||
|
||||
it('should create session, trigger success event and inform common action when having a successful login', function () {
|
||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
||||
let mockSuccessData = {
|
||||
status: 'success',
|
||||
data: {
|
||||
userId: 12,
|
||||
token: 'RANDOM_TOKEN'
|
||||
}
|
||||
};
|
||||
|
||||
spy(UserStore, 'trigger');
|
||||
CommonActions.logged.reset();
|
||||
SessionStore.createSession.reset();
|
||||
API.call = ({onSuccess}) => {onSuccess(mockSuccessData)};
|
||||
|
||||
UserStore.loginUser(mockLoginData);
|
||||
|
||||
expect(SessionStore.createSession).to.have.been.calledWith(12, 'RANDOM_TOKEN');
|
||||
expect(UserStore.trigger).to.have.been.calledWith('LOGIN_SUCCESS');
|
||||
expect(CommonActions.logged).to.have.been.called;
|
||||
UserStore.trigger.restore();
|
||||
});
|
||||
|
||||
it('should trigger fail event if login fails', function () {
|
||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
||||
let mockSuccessData = {
|
||||
status: 'success',
|
||||
data: {
|
||||
userId: 12,
|
||||
token: 'RANDOM_TOKEN'
|
||||
}
|
||||
};
|
||||
|
||||
spy(UserStore, 'trigger');
|
||||
API.call = ({onFail}) => {onFail(mockSuccessData)};
|
||||
|
||||
UserStore.loginUser(mockLoginData);
|
||||
|
||||
expect(UserStore.trigger).to.have.been.calledWith('LOGIN_FAIL');
|
||||
UserStore.trigger.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when login out', function () {
|
||||
|
||||
it('should call /user/logout api path', function () {
|
||||
API.call = stub();
|
||||
|
||||
UserStore.logoutUser();
|
||||
expect(API.call).to.have.been.calledWith({
|
||||
path: 'user/logout',
|
||||
onSuccess: sinon.match.func
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete session, trigger LOGOUT event and inform common action of logout', function () {
|
||||
API.call = ({onSuccess}) => {onSuccess()};
|
||||
spy(UserStore, 'trigger');
|
||||
|
||||
UserStore.logoutUser();
|
||||
expect(SessionStore.closeSession).to.have.been.called;
|
||||
expect(UserStore.trigger).to.have.been.calledWith('LOGOUT');
|
||||
expect(CommonActions.loggedOut).to.have.been.called;
|
||||
UserStore.trigger.restore()
|
||||
})
|
||||
});
|
||||
|
||||
it ('should inform is the user is logged based on SessionStores\' info', function () {
|
||||
SessionStore.isLoggedIn.returns(true);
|
||||
expect(UserStore.isLoggedIn()).to.equal(true);
|
||||
SessionStore.isLoggedIn.returns(false);
|
||||
expect(UserStore.isLoggedIn()).to.equal(false);
|
||||
});
|
||||
});
|
|
@ -8,11 +8,21 @@ let CommonStore = Reflux.createStore({
|
|||
this.language = 'us';
|
||||
|
||||
this.listenTo(CommonActions.changeLanguage, this.changeLanguage);
|
||||
this.listenTo(CommonActions.logged, this.logged);
|
||||
this.listenTo(CommonActions.loggedOut, this.loggedOut);
|
||||
},
|
||||
|
||||
changeLanguage(lang) {
|
||||
this.language = lang;
|
||||
this.trigger('i18n');
|
||||
},
|
||||
|
||||
logged() {
|
||||
this.trigger('logged');
|
||||
},
|
||||
|
||||
loggedOut() {
|
||||
this.trigger('loggedOut');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
const Reflux = require('reflux');
|
||||
const API = require('lib-app/api-call');
|
||||
const SessionStore = require('lib-app/session-store');
|
||||
|
||||
const UserActions = require('actions/user-actions');
|
||||
const CommonActions = require('actions/common-actions');
|
||||
|
||||
const UserStore = Reflux.createStore({
|
||||
|
||||
init() {
|
||||
this.user = null;
|
||||
this.hasBeenChecked = false;
|
||||
|
||||
this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus);
|
||||
this.listenTo(UserActions.login, this.loginUser);
|
||||
|
@ -15,11 +16,37 @@ const UserStore = Reflux.createStore({
|
|||
},
|
||||
|
||||
loginUser(loginData) {
|
||||
API.call('user/login', loginData, result => {
|
||||
console.log(result);
|
||||
|
||||
API.setConfig(result.userId, result.token);
|
||||
API.call({
|
||||
path: 'user/login',
|
||||
data: loginData,
|
||||
onSuccess: this.handleLoginSuccess,
|
||||
onFail: this.handleLoginFail
|
||||
});
|
||||
},
|
||||
|
||||
logoutUser() {
|
||||
API.call({
|
||||
path: 'user/logout',
|
||||
onSuccess: function () {
|
||||
SessionStore.closeSession();
|
||||
this.trigger('LOGOUT');
|
||||
CommonActions.loggedOut();
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
isLoggedIn() {
|
||||
return SessionStore.isLoggedIn();
|
||||
},
|
||||
|
||||
handleLoginSuccess(result) {
|
||||
SessionStore.createSession(result.data.userId, result.data.token);
|
||||
this.trigger('LOGIN_SUCCESS');
|
||||
CommonActions.logged();
|
||||
},
|
||||
|
||||
handleLoginFail() {
|
||||
this.trigger('LOGIN_FAIL');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue