Ivan - Table Component - Add /user/get fixture, retrieve all tickets from api with USER_DATA action, update header styling, add close session to menu, update tests [skip ci]

This commit is contained in:
ivan 2016-08-17 18:04:48 -03:00
parent cfb65f2306
commit 8b6266c6c8
12 changed files with 261 additions and 122 deletions

View File

@ -11,42 +11,55 @@ const SessionActions = requireUnit('actions/session-actions', {
}); });
describe('Session Actions,', function () { describe('Session Actions,', function () {
APICallMock.call.returns('API_RESULT');
describe('login action', function () { describe('login action', function () {
it('should return LOGIN with with API_RESULT promise', function () { it('should return LOGIN with with a result promise', function () {
APICallMock.call.reset(); APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
userId: 14
}
});
}
});
let loginData = { let loginData = {
email: 'SOME_EMAIL', email: 'SOME_EMAIL',
password: 'SOME_PASSWORD', password: 'SOME_PASSWORD',
remember: false remember: false
}; };
expect(SessionActions.login(loginData)).to.deep.equal({ expect(SessionActions.login(loginData).type).to.equal('LOGIN');
type: 'LOGIN', expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
payload: 'API_RESULT'
});
expect(APICallMock.call).to.have.been.calledWith({ expect(APICallMock.call).to.have.been.calledWith({
path: '/user/login', path: '/user/get',
data: loginData data: {
userId: 14
}
}); });
}); });
}); });
describe('autoLogin action', function () { describe('autoLogin action', function () {
it('should return LOGIN_AUTO with remember data from sessionStore', function () { it('should return LOGIN_AUTO with remember data from sessionStore', function () {
APICallMock.call.reset(); APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
userId: 14
}
});
}
});
sessionStoreMock.getRememberData.returns({ sessionStoreMock.getRememberData.returns({
token: 'SOME_TOKEN', token: 'SOME_TOKEN',
userId: 'SOME_ID', userId: 'SOME_ID',
expiration: 'SOME_EXPIRATION' expiration: 'SOME_EXPIRATION'
}); });
expect(SessionActions.autoLogin()).to.deep.equal({ expect(SessionActions.autoLogin().type).to.equal('LOGIN_AUTO');
type: 'LOGIN_AUTO', expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
payload: 'API_RESULT'
});
expect(APICallMock.call).to.have.been.calledWith({ expect(APICallMock.call).to.have.been.calledWith({
path: '/user/login', path: '/user/login',
data: { data: {
@ -60,6 +73,7 @@ describe('Session Actions,', function () {
describe('logout action', function () { describe('logout action', function () {
it('should return LOGOUT and call /user/logout', function () { it('should return LOGOUT and call /user/logout', function () {
APICallMock.call.returns('API_RESULT');
APICallMock.call.reset(); APICallMock.call.reset();
expect(SessionActions.logout()).to.deep.equal({ expect(SessionActions.logout()).to.deep.equal({

View File

@ -9,6 +9,10 @@ export default {
payload: API.call({ payload: API.call({
path: '/user/login', path: '/user/login',
data: loginData data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId));
return result;
}) })
}; };
}, },
@ -25,6 +29,10 @@ export default {
rememberToken: rememberData.token, rememberToken: rememberData.token,
isAutomatic: true isAutomatic: true
} }
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId));
return result;
}) })
}; };
}, },
@ -39,6 +47,18 @@ export default {
}; };
}, },
getUserData(userId) {
return {
type: 'USER_DATA',
payload: API.call({
path: '/user/get',
data: {
userId: userId
}
})
}
},
initSession() { initSession() {
return { return {
type: 'CHECK_SESSION', type: 'CHECK_SESSION',

View File

@ -1,90 +1,16 @@
import React from 'react'; import React from 'react';
import {connect} from 'react-redux';
import Table from 'core-components/table'; import Table from 'core-components/table';
import Button from 'core-components/button'; import Button from 'core-components/button';
let mockTickets = [
{
ticketNumber: '445441',
title: 'Problem with installation',
content: 'I had a problem with the installation of the php server',
department: 'Environment Setup',
date: '15 Apr 2016',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
author: {
name: 'John Smith',
email: 'john@smith.com'
},
owner: {
name: 'Steve Jobs'
},
comments: [
{
content: 'Do you have apache installed? It generally happens if you dont have apache.',
author: {
name: 'Steve Jobs',
email: 'jobs@steve.com',
staff: true
}
},
{
content: 'I have already installed apache, but the problem persists',
author: {
name: 'John Smith',
steve: 'john@smith.com',
staff: false
}
}
]
},
{
ticketNumber: '87852',
title: 'Lorem ipsum door',
content: 'I had a problem with the installation of the php server',
department: 'Environment Setup',
date: '15 Apr 2016',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: false,
closed: false,
author: {
name: 'John Smith',
email: 'john@smith.com'
},
owner: {
name: 'Steve Jobs'
},
comments: [
{
content: 'Do you have apache installed? It generally happens if you dont have apache.',
author: {
name: 'Steve Jobs',
email: 'jobs@steve.com',
staff: true
}
},
{
content: 'I have already installed apache, but the problem persists',
author: {
name: 'John Smith',
steve: 'john@smith.com',
staff: false
}
}
]
}
];
class DashboardListTicketsPage extends React.Component { class DashboardListTicketsPage extends React.Component {
static propTypes = { static propTypes = {
tickets: React.PropTypes.arrayOf(React.PropTypes.object) tickets: React.PropTypes.arrayOf(React.PropTypes.object)
}; };
static defaultProps = { static defaultProps = {
tickets: mockTickets.concat([mockTickets[1], mockTickets[1]]) tickets: []
}; };
render() { render() {
@ -135,11 +61,16 @@ class DashboardListTicketsPage extends React.Component {
{titleText} {titleText}
</Button> </Button>
), ),
department: ticket.department, department: ticket.department.name,
date: ticket.date, date: ticket.date,
highlighted: ticket.unread highlighted: ticket.unread
}; };
} }
} }
export default DashboardListTicketsPage;
export default connect((store) => {
return {
tickets: store.session.userTickets
};
})(DashboardListTicketsPage);

View File

@ -1,14 +1,11 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import Menu from 'core-components/menu'; import {dispatch} from 'app/store';
import SessionActions from 'actions/session-actions';
import i18n from 'lib-app/i18n';
let dashboardRoutes = [ import Menu from 'core-components/menu';
{ path: '/dashboard', text: 'Ticket List', icon: 'file-text-o' },
{ path: '/dashboard/create-ticket', text: 'Create Ticket', icon: 'plus' },
{ path: '/dashboard/articles', text: 'View Articles', icon: 'book' },
{ path: '/dashboard/edit-profile', text: 'Edit Profile', icon: 'pencil' }
];
class DashboardMenu extends React.Component { class DashboardMenu extends React.Component {
static contextTypes = { static contextTypes = {
@ -30,14 +27,18 @@ class DashboardMenu extends React.Component {
header: 'Dashboard', header: 'Dashboard',
items: this.getMenuItems(), items: this.getMenuItems(),
selectedIndex: this.getSelectedIndex(), selectedIndex: this.getSelectedIndex(),
onItemClick: this.goToPathByIndex.bind(this), onItemClick: this.onItemClick.bind(this),
tabbable: true, tabbable: true,
type: 'secondary' type: 'secondary'
}; };
} }
getMenuItems() { getMenuItems() {
return dashboardRoutes.map(this.getMenuItem.bind(this)); let items = this.getDashboardRoutes().map(this.getMenuItem.bind(this));
items.push(this.getCloseSessionItem());
return items;
} }
getMenuItem(item) { getMenuItem(item) {
@ -47,14 +48,38 @@ class DashboardMenu extends React.Component {
}; };
} }
getCloseSessionItem() {
return {
content: i18n('CLOSE_SESSION'),
icon: 'lock'
}
}
getSelectedIndex() { getSelectedIndex() {
let pathname = this.props.location.pathname; let pathname = this.props.location.pathname;
return _.findIndex(dashboardRoutes, {path: pathname}); return _.findIndex(this.getDashboardRoutes(), {path: pathname});
}
onItemClick(itemIndex) {
if (itemIndex < this.getDashboardRoutes().length) {
this.goToPathByIndex(itemIndex)
} else {
dispatch(SessionActions.logout());
}
} }
goToPathByIndex(itemIndex) { goToPathByIndex(itemIndex) {
this.context.router.push(dashboardRoutes[itemIndex].path); this.context.router.push(this.getDashboardRoutes()[itemIndex].path);
}
getDashboardRoutes() {
return [
{ path: '/dashboard', text: i18n('TICKET_LIST'), icon: 'file-text-o' },
{ path: '/dashboard/create-ticket', text: i18n('CREATE_TICKET'), icon: 'plus' },
{ path: '/dashboard/articles', text: i18n('VIEW_ARTICLES'), icon: 'book' },
{ path: '/dashboard/edit-profile', text: i18n('EDIT_PROFILE'), icon: 'pencil' }
];
} }
} }

View File

@ -34,14 +34,14 @@ class MainLayoutHeader extends React.Component {
if (this.props.session.logged) { if (this.props.session.logged) {
result = ( result = (
<div className="main-layout-header--login-links"> <div className="main-layout-header__login-links">
Welcome, John {i18n('WELCOME')},
<Button type="clean" onClick={this.logout.bind(this)}>(Close Session)</Button> <span className="main-layout-header__user-name"> {this.props.session.userName}</span>
</div> </div>
); );
} else { } else {
result = ( result = (
<div className="main-layout-header--login-links"> <div className="main-layout-header__login-links">
<Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button> <Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button>
<Button type="clean" route={{to:'/signup'}}>Sign up</Button> <Button type="clean" route={{to:'/signup'}}>Sign up</Button>
</div> </div>
@ -53,7 +53,7 @@ class MainLayoutHeader extends React.Component {
getLanguageSelectorProps() { getLanguageSelectorProps() {
return { return {
className: 'main-layout-header--languages', className: 'main-layout-header__languages',
items: this.getLanguageList(), items: this.getLanguageList(),
selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language), selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language),
onChange: this.changeLanguage.bind(this) onChange: this.changeLanguage.bind(this)
@ -74,10 +74,6 @@ class MainLayoutHeader extends React.Component {
this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language])); this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language]));
} }
logout() {
this.props.dispatch(SessionActions.logout());
}
} }
export default connect((store) => { export default connect((store) => {

View File

@ -6,7 +6,11 @@
height: 32px; height: 32px;
width: 100%; width: 100%;
&--login-links { &__user-name {
color: $primary-red;
}
&__login-links {
border-top-left-radius: 4px; border-top-left-radius: 4px;
color: white; color: white;
display: inline-block; display: inline-block;
@ -14,7 +18,7 @@
padding: 5px 20px 0 10px; padding: 5px 20px 0 10px;
} }
&--languages { &__languages {
float: right; float: right;
position: relative; position: relative;
top: 5px; top: 5px;

View File

@ -15,7 +15,7 @@ class Menu extends React.Component {
icon: React.PropTypes.string icon: React.PropTypes.string
})).isRequired, })).isRequired,
selectedIndex: React.PropTypes.number, selectedIndex: React.PropTypes.number,
tabbable: React.PropTypes.boolean tabbable: React.PropTypes.bool
}; };
static defaultProps = { static defaultProps = {

View File

@ -103,5 +103,107 @@ module.exports = [
}; };
} }
} }
},
{
path: '/user/get',
time: 100,
response: function () {
return {
status: 'success',
data: {
name: 'Haskell Curry',
email: 'haskell@lambda.com',
tickets: [
{
ticketNumber: '445441',
title: 'Problem with installation',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Environment Setup'
},
date: '15 Apr 2016',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
comments: [
{
content: 'Do you have apache installed? It generally happens if you dont have apache.',
author: {
id: 15,
name: 'Steve Jobs',
email: 'jobs@steve.com',
staff: true
},
date: '12 Dec 2016',
file: ''
},
{
content: 'I have already installed apache, but the problem persists',
author: {
id: 12,
name: 'Haskell Curry',
steve: 'haskell@lambda.com',
staff: false
},
date: '12 Dec 2016',
file: ''
}
]
},
{
ticketNumber: '878552',
title: 'Lorem ipsum door',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Environment Setup'
},
date: '15 Apr 2016',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: false,
closed: false,
author: {
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
name: 'Steve Jobs'
},
comments: [
{
content: 'Do you have apache installed? It generally happens if you dont have apache.',
author: {
name: 'Steve Jobs',
email: 'jobs@steve.com',
staff: true
}
},
{
content: 'I have already installed apache, but the problem persists',
author: {
name: 'Haskell Curry',
steve: 'haskell@lambda.com',
staff: false
}
}
]
}
]
}
};
}
} }
]; ];

View File

@ -1,4 +1,5 @@
export default { export default {
'WELCOME': 'Welcome',
'SUBMIT': 'Submit', 'SUBMIT': 'Submit',
'LOG_IN': 'Log in', 'LOG_IN': 'Log in',
'SIGN_UP': 'Sign up', 'SIGN_UP': 'Sign up',
@ -8,6 +9,11 @@ export default {
'NEW_PASSWORD': 'New password', 'NEW_PASSWORD': 'New password',
'REPEAT_NEW_PASSWORD': 'Repeat new password', 'REPEAT_NEW_PASSWORD': 'Repeat new password',
'BACK_LOGIN_FORM': 'Back to login form', 'BACK_LOGIN_FORM': 'Back to login form',
'TICKET_LIST': 'Ticket List',
'CREATE_TICKET': 'Create Ticket',
'VIEW_ARTICLES': 'View Articles',
'EDIT_PROFILE': 'Edit Profile',
'CLOSE_SESSION': 'Close session',
//ERRORS //ERRORS
'EMAIL_NOT_EXIST': 'Email does not exist', 'EMAIL_NOT_EXIST': 'Email does not exist',

View File

@ -1,5 +1,8 @@
export default { export default {
call: stub().returns(new Promise(function (resolve) { call: stub().returns(new Promise(function (resolve) {
resolve(); resolve({
status: 'success',
data: {}
});
})) }))
}; };

View File

@ -29,6 +29,17 @@ class SessionStore {
closeSession() { closeSession() {
this.removeItem('userId'); this.removeItem('userId');
this.removeItem('token'); this.removeItem('token');
this.clearRememberData();
this.clearUserData();
}
storeUserData(data) {
this.setItem('userData', JSON.stringify(data));
}
getUserData() {
return JSON.parse(this.getItem('userData'));
} }
storeRememberData({token, userId, expiration}) { storeRememberData({token, userId, expiration}) {
@ -73,6 +84,10 @@ class SessionStore {
this.removeItem('rememberData-expiration'); this.removeItem('rememberData-expiration');
} }
clearUserData() {
this.removeItem('userData');
}
getItem(key) { getItem(key) {
return this.storage.getItem(key); return this.storage.getItem(key);
} }

View File

@ -19,8 +19,9 @@ class SessionReducer extends Reducer {
'LOGIN_FULFILLED': this.onLoginCompleted.bind(this), 'LOGIN_FULFILLED': this.onLoginCompleted.bind(this),
'LOGIN_REJECTED': this.onLoginFailed, 'LOGIN_REJECTED': this.onLoginFailed,
'LOGOUT_FULFILLED': this.onLogout, 'LOGOUT_FULFILLED': this.onLogout,
'USER_DATA_FULFILLED': this.onUserDataRetrieved,
'CHECK_SESSION_REJECTED': (state) => { return _.extend({}, state, {initDone: true})}, 'CHECK_SESSION_REJECTED': (state) => { return _.extend({}, state, {initDone: true})},
'SESSION_CHECKED': (state) => { return _.extend({}, state, {initDone: true, logged: true})}, 'SESSION_CHECKED': this.onSessionChecked,
'LOGIN_AUTO_FULFILLED': this.onAutoLogin.bind(this), 'LOGIN_AUTO_FULFILLED': this.onAutoLogin.bind(this),
'LOGIN_AUTO_REJECTED': this.onAutoLoginFail 'LOGIN_AUTO_REJECTED': this.onAutoLoginFail
}; };
@ -54,7 +55,6 @@ class SessionReducer extends Reducer {
onLogout(state) { onLogout(state) {
sessionStore.closeSession(); sessionStore.closeSession();
sessionStore.clearRememberData();
return _.extend({}, state, { return _.extend({}, state, {
initDone: true, initDone: true,
@ -77,7 +77,6 @@ class SessionReducer extends Reducer {
onAutoLoginFail(state) { onAutoLoginFail(state) {
sessionStore.closeSession(); sessionStore.closeSession();
sessionStore.clearRememberData();
return _.extend({}, state, { return _.extend({}, state, {
initDone: true initDone: true
@ -95,6 +94,30 @@ class SessionReducer extends Reducer {
sessionStore.createSession(resultData.userId, resultData.token); sessionStore.createSession(resultData.userId, resultData.token);
} }
onUserDataRetrieved(state, payload) {
let userData = payload.data;
sessionStore.storeUserData(payload.data);
return _.extend({}, state, {
userName: userData.name,
userEmail: userData.email,
userTickets: userData.tickets
});
}
onSessionChecked(state) {
let userData = sessionStore.getUserData();
return _.extend({}, state, {
initDone: true,
logged: true,
userName: userData.name,
userEmail: userData.email,
userTickets: userData.tickets
});
}
} }
export default SessionReducer.getInstance(); export default SessionReducer.getInstance();