Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"root": true,
"extends": "airbnb-base",
"env": {
"node": true,
"browser": true,
"es6": true,
"jest": true,
"mocha": true,
"browser": true
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
Expand Down
61 changes: 61 additions & 0 deletions __mocks__/localStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* LocalStorageMock serve as a mock of the localStorage for testing in Jest
* @class LocalStorageMock
*/
class LocalStorage {
/**
* @description creates a new instance of this class
* @constructor
* @memberOf LocalStorageMock
*/
constructor() {
/** @type {Object} */
this.store = {};
}

/**
* @description clears the store
* @method
* @memberOf LocalStorageMock
* @returns {void}
*/
clear() {
this.store = {};
}

/**
* @description returns the value stored on the supplied key
* @method
* @memberOf LocalStorageMock
* @param {string} key The item's key to retrieve from
* @returns {void}
*/
getItem(key) {
return this.store[key] || null;
}

/**
* @description sets the store with the supplied key
* @method
* @memberOf LocalStorageMock
* @param {Object} key The key to store
* @param {string} value The value to set the key to
* @returns {void}
*/
setItem(key, value) {
this.store[key] = value;
}

/**
* @description removes the item from the store corresponding to the key
* @method
* @memberOf LocalStorageMock
* @param {Object} key The key to remove
* @returns {void}
*/
removeItem(key) {
delete this.store[key];
}
}

global.localStorage = new LocalStorage();
127 changes: 127 additions & 0 deletions __tests__/client/actions/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import configureMockStore from 'redux-mock-store';
import moxios from 'moxios';
import thunk from 'redux-thunk';

import * as actions from '../../../client/src/actions';
import * as actionTypes from '../../../client/src/actions/actionTypes';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('Auth Actions', () => {
beforeEach(() => {
moxios.install();
});

afterEach(() => {
moxios.uninstall();
});

describe('Sign In', () => {
it('dispatches AUTH_USER after successful authentication', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
data: {
user: {},
token: 'jdjjf133aafnffn'
}
},
});
});

const expectedActions = [
{ type: actionTypes.AUTH_USER },
{ type: actionTypes.AUTH_ERROR, payload: 'Bad Login Info' }
];

const store = mockStore({ auth: {} });

return store.dispatch(actions.signInUser({ email: 'jd@jd.com', password: 'testing' })).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});

it('dispatches AUTH_ERROR on failed authentication', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: {},
});
});

const expectedActions = [
{ type: actionTypes.AUTH_ERROR }
];

const store = mockStore({ auth: {} });

return store.dispatch(actions.signInUser({ email: 'jd@jd.com', password: 'testing' })).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});

describe('Sign Up', () => {
it('dispatches AUTH_USER after successful registration', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
data: {
user: {},
token: 'jdjjf133aafnffn'
}
},
});
});

const expectedActions = [
{ type: actionTypes.AUTH_ERROR, payload: 'Sign up failed' }
];

const store = mockStore({ auth: {} });

return store.dispatch(actions.signUpUser({ email: 'jd@jd.com', password: 'testing' })).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});

it('dispatches AUTH_ERROR on failed registration', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: {},
});
});

const expectedActions = [
{ type: actionTypes.AUTH_ERROR }
];

const store = mockStore({ auth: {} });

return store.dispatch(actions.signUpUser({ email: 'jd@jd.com', password: 'testing' })).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});

describe('Sign Out', () => {
it('dispatches UNAUTH_USER on sign out', () => {
const expectedActions = [
{ type: actionTypes.UNAUTH_USER }
];

const store = mockStore({ auth: {} });

store.dispatch(actions.signOutUser());
expect(store.getActions()).toEqual(expectedActions);
});
});
});
41 changes: 41 additions & 0 deletions __tests__/client/reducers/authReducer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import reducer from '../../../client/src/reducers/authReducer';
import * as actionTypes from '../../../client/src/actions/actionTypes';

describe('Auth Reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({});
});

it('should handle AUTH_USER', () => {
const authAction = {
type: actionTypes.AUTH_USER
};

expect(reducer({}, authAction)).toEqual({
authenticated: true,
error: ''
});
});

it('should handle UNAUTH_USER', () => {
const unauthAction = {
type: actionTypes.UNAUTH_USER
};

expect(reducer({}, unauthAction)).toEqual({
authenticated: false,
error: ''
});
});

it('should handle AUTH_ERROR', () => {
const errorAction = {
type: actionTypes.AUTH_ERROR,
payload: 'Auth failed'
};

expect(reducer({}, errorAction)).toEqual({
error: 'Auth failed'
});
});
});
70 changes: 33 additions & 37 deletions client/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,23 @@ export function authError(error) {
export function signInUser(userData, browserHistory) {
const url = '/v1/auth/signin';

return (dispatch) => {
axios.post(url, userData)
.then((response) => {
dispatch({ type: AUTH_USER });
const user = {
...response.data.user,
accessToken: response.data.token
};
localStorage.setItem('cUser', JSON.stringify(user));
browserHistory.push('/');
})
.catch((error) => {
if (error.response) {
dispatch(authError(error.response.data.message));
} else {
dispatch(authError('Bad Login Info'));
}
});
};
return dispatch => axios.post(url, userData)
.then((response) => {
dispatch({ type: AUTH_USER });
const user = {
...response.data.user,
accessToken: response.data.token
};
localStorage.setItem('cUser', JSON.stringify(user));
browserHistory.push('/');
})
.catch((error) => {
if (error.response) {
dispatch(authError(error.response.data.message));
} else {
dispatch(authError('Bad Login Info'));
}
});
}

/**
Expand All @@ -58,24 +56,22 @@ export function signInUser(userData, browserHistory) {
export function signUpUser(userData, browserHistory) {
const url = '/v1/auth/signup';

return (dispatch) => {
axios.post(url, userData)
.then((response) => {
const user = {
...response.data.user,
accessToken: response.data.token
};
localStorage.setItem('cUser', JSON.stringify(user));
browserHistory.push('/');
})
.catch((error) => {
if (error.response) {
dispatch(authError(error.response.data.message));
} else {
dispatch(authError('Sign up failed'));
}
});
};
return dispatch => axios.post(url, userData)
.then((response) => {
const user = {
...response.data.user,
accessToken: response.data.token
};
localStorage.setItem('cUser', JSON.stringify(user));
browserHistory.push('/');
})
.catch((error) => {
if (error.response) {
dispatch(authError(error.response.data.message));
} else {
dispatch(authError('Sign up failed'));
}
});
}

/**
Expand Down
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
setupFiles: [
'<rootDir>/__mocks__/localStorage.js'
],
testMatch: ['**/__tests__/client/**/*.js?(x)'],
verbose: true,
};
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
"main": "server/server.js",
"scripts": {
"build": "webpack",
"client:test": "jest --coverage",
"client:test:watch": "jest --coverage --watch",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"start:dev": "nodemon -w server --exec babel-node -- server/server.js",
"heroku-postbuild": "gulp compile && yarn run build",
"migrate": "sequelize db:migrate && yarn run seed",
"seed": "sequelize db:seed:all",
"start": "babel-node server/server.js",
"server:test": "NODE_ENV=test nyc mocha -- ./__tests__/server/**/*.spec.js",
"start": "babel-node server/server.js",
"start:dev": "nodemon -w server --exec babel-node -- server/server.js",
"test": "gulp compile && yarn run server:test",
"test:migrate": "NODE_ENV=test sequelize db:migrate",
"undo": "sequelize db:migrate:undo:all"
Expand Down Expand Up @@ -86,17 +88,23 @@
"webpack-hot-middleware": "^2.21.0"
},
"devDependencies": {
"babel-core": "^7.0.0-0",
"babel-eslint": "^8.2.1",
"babel-jest": "^22.4.1",
"chai": "^4.1.0",
"codacy-coverage": "^2.0.3",
"coveralls": "^2.13.1",
"enzyme": "^3.3.0",
"eslint": "^4.17.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.6.1",
"jest": "^22.4.2",
"mocha": "^5.0.1",
"moxios": "^0.4.0",
"nyc": "^11.4.1",
"redux-mock-store": "^1.5.1",
"sequelize-cli": "^2.7.0",
"supertest": "^3.0.0"
}
Expand Down
Loading