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

View File

@ -9,6 +9,10 @@ export default {
payload: API.call({
path: '/user/login',
data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId));
return result;
})
};
},
@ -25,6 +29,10 @@ export default {
rememberToken: rememberData.token,
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() {
return {
type: 'CHECK_SESSION',

View File

@ -1,90 +1,16 @@
import React from 'react';
import {connect} from 'react-redux';
import Table from 'core-components/table';
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 {
static propTypes = {
tickets: React.PropTypes.arrayOf(React.PropTypes.object)
};
static defaultProps = {
tickets: mockTickets.concat([mockTickets[1], mockTickets[1]])
tickets: []
};
render() {
@ -135,11 +61,16 @@ class DashboardListTicketsPage extends React.Component {
{titleText}
</Button>
),
department: ticket.department,
department: ticket.department.name,
date: ticket.date,
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 _ 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 = [
{ 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' }
];
import Menu from 'core-components/menu';
class DashboardMenu extends React.Component {
static contextTypes = {
@ -30,14 +27,18 @@ class DashboardMenu extends React.Component {
header: 'Dashboard',
items: this.getMenuItems(),
selectedIndex: this.getSelectedIndex(),
onItemClick: this.goToPathByIndex.bind(this),
onItemClick: this.onItemClick.bind(this),
tabbable: true,
type: 'secondary'
};
}
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) {
@ -47,14 +48,38 @@ class DashboardMenu extends React.Component {
};
}
getCloseSessionItem() {
return {
content: i18n('CLOSE_SESSION'),
icon: 'lock'
}
}
getSelectedIndex() {
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) {
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) {
result = (
<div className="main-layout-header--login-links">
Welcome, John
<Button type="clean" onClick={this.logout.bind(this)}>(Close Session)</Button>
<div className="main-layout-header__login-links">
{i18n('WELCOME')},
<span className="main-layout-header__user-name"> {this.props.session.userName}</span>
</div>
);
} else {
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:'/signup'}}>Sign up</Button>
</div>
@ -53,7 +53,7 @@ class MainLayoutHeader extends React.Component {
getLanguageSelectorProps() {
return {
className: 'main-layout-header--languages',
className: 'main-layout-header__languages',
items: this.getLanguageList(),
selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language),
onChange: this.changeLanguage.bind(this)
@ -74,10 +74,6 @@ class MainLayoutHeader extends React.Component {
this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language]));
}
logout() {
this.props.dispatch(SessionActions.logout());
}
}
export default connect((store) => {

View File

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

View File

@ -15,7 +15,7 @@ class Menu extends React.Component {
icon: React.PropTypes.string
})).isRequired,
selectedIndex: React.PropTypes.number,
tabbable: React.PropTypes.boolean
tabbable: React.PropTypes.bool
};
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 {
'WELCOME': 'Welcome',
'SUBMIT': 'Submit',
'LOG_IN': 'Log in',
'SIGN_UP': 'Sign up',
@ -8,6 +9,11 @@ export default {
'NEW_PASSWORD': 'New password',
'REPEAT_NEW_PASSWORD': 'Repeat new password',
'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
'EMAIL_NOT_EXIST': 'Email does not exist',

View File

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

View File

@ -29,6 +29,17 @@ class SessionStore {
closeSession() {
this.removeItem('userId');
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}) {
@ -73,6 +84,10 @@ class SessionStore {
this.removeItem('rememberData-expiration');
}
clearUserData() {
this.removeItem('userData');
}
getItem(key) {
return this.storage.getItem(key);
}

View File

@ -19,8 +19,9 @@ class SessionReducer extends Reducer {
'LOGIN_FULFILLED': this.onLoginCompleted.bind(this),
'LOGIN_REJECTED': this.onLoginFailed,
'LOGOUT_FULFILLED': this.onLogout,
'USER_DATA_FULFILLED': this.onUserDataRetrieved,
'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_REJECTED': this.onAutoLoginFail
};
@ -54,7 +55,6 @@ class SessionReducer extends Reducer {
onLogout(state) {
sessionStore.closeSession();
sessionStore.clearRememberData();
return _.extend({}, state, {
initDone: true,
@ -77,7 +77,6 @@ class SessionReducer extends Reducer {
onAutoLoginFail(state) {
sessionStore.closeSession();
sessionStore.clearRememberData();
return _.extend({}, state, {
initDone: true
@ -95,6 +94,30 @@ class SessionReducer extends Reducer {
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();