diff --git a/src/actions/index.js b/src/actions/index.js
index 46d42ad..94972d6 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -33,6 +33,27 @@ export const signUpUser = userData => async (dispatch) => {
}
};
+export const logInUser = userData => async (dispatch) => {
+ dispatch(startFetching());
+ try {
+ const { status, user } = (await axios.post('/auth/login', userData)).data;
+ if (status === 'success') saveToken(user.auth_token);
+ const { userName: name, userStatus: role } = jwt.decode(user.auth_token);
+ dispatch({
+ type: actionTypes.LOG_IN,
+ payload: {
+ name,
+ role,
+ },
+ });
+ return dispatch(stopFetching());
+ } catch (error) {
+ return dispatch(
+ stopFetching(false, error.response ? error.response.data.message : 'something went wrong'),
+ );
+ }
+};
+
export const checkAuthStatus = () => {
try {
const { userName: name, userStatus: role } = jwt.decode(getToken());
diff --git a/src/components/LoginPage.jsx b/src/components/LoginPage.jsx
index cd7afdf..0ac20f7 100644
--- a/src/components/LoginPage.jsx
+++ b/src/components/LoginPage.jsx
@@ -1,12 +1,88 @@
-import React from 'react';
+import React, { Component, Fragment } from 'react';
import { Link } from 'react-router-dom';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
-const LoginPage = () => (
-
- This is the login page.
-
- Go Home
-
-);
+import Nav from './Nav';
+import { logInUser } from '../actions';
-export default LoginPage;
+export class Login extends Component {
+ state = { email: '', password: '' };
+
+ componentDidMount() {
+ const { isLoggedIn, fetching, history } = this.props;
+ if (!fetching && isLoggedIn) history.push('/menu');
+ }
+
+ onFormSubmit = async (e) => {
+ e.preventDefault();
+ const { email, password } = this.state;
+ const { logInUser: logIn } = this.props;
+
+ await logIn({ email, password });
+ const { isLoggedIn, fetching, history } = this.props;
+ if (!fetching && isLoggedIn) history.push('/menu');
+ };
+
+ render() {
+ const { email, password } = this.state;
+ return (
+
+
+
+
+ Access Your Account
+
+
+
+ No account yet?
+ {' '}
+ Sign Up
+
+
+
+
+
+ );
+ }
+}
+
+Login.propTypes = {
+ logInUser: PropTypes.func.isRequired,
+ isLoggedIn: PropTypes.bool,
+ fetching: PropTypes.bool,
+ history: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
+};
+
+Login.defaultProps = {
+ fetching: false,
+ isLoggedIn: false,
+};
+
+const mapStateToProps = state => ({
+ isLoggedIn: state.user.isLoggedIn,
+ fetching: state.fetching.fetching,
+});
+
+export default connect(
+ mapStateToProps,
+ { logInUser },
+)(Login);
diff --git a/src/reducers/authReducer.js b/src/reducers/authReducer.js
index 46cf26b..511fc15 100644
--- a/src/reducers/authReducer.js
+++ b/src/reducers/authReducer.js
@@ -1,4 +1,4 @@
-import actionsTypes from '../actions/types';
+import types from '../actions/types';
const initialState = {
isLoggedIn: null,
@@ -8,15 +8,16 @@ const initialState = {
export default (state = initialState, action) => {
switch (action.type) {
- case actionsTypes.SIGN_UP:
- case actionsTypes.CHECK_AUTH_STATUS:
+ case types.SIGN_UP:
+ case types.LOG_IN:
+ case types.CHECK_AUTH_STATUS:
return {
...state,
isLoggedIn: true,
name: action.payload.name,
role: action.payload.role,
};
- case actionsTypes.CHECK_AUTH_STATUS_FAIL:
+ case types.CHECK_AUTH_STATUS_FAIL:
default:
return state;
}
diff --git a/src/tests/actions/logInUser.test.js b/src/tests/actions/logInUser.test.js
new file mode 100644
index 0000000..3db20cc
--- /dev/null
+++ b/src/tests/actions/logInUser.test.js
@@ -0,0 +1,36 @@
+import axios from '../../services/axios';
+import { logInUser } from '../../actions';
+import jwt from '../../utils/jwt';
+
+describe('logInUser()', () => {
+ afterEach(() => jest.resetAllMocks());
+
+ const dispatch = jest.fn();
+ const response = { data: { status: 'success', user: { token: 'something' } } };
+ jest
+ .spyOn(jwt, 'decode')
+ .mockImplementation(() => ({ userName: 'jamjum', userStatus: 'customer' }));
+
+ it('should login user successfully', async () => {
+ jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve(response));
+ await logInUser()(dispatch);
+ expect(dispatch).toHaveBeenCalledTimes(3);
+ expect(dispatch.mock.calls[1][0]).toEqual({
+ payload: { name: 'jamjum', role: 'customer' },
+ type: 'LOG_IN',
+ });
+ });
+
+ it('should fail to login user if errors exist', async () => {
+ jest
+ .spyOn(axios, 'post')
+ .mockImplementation(() => Promise.reject(new Error('something went wrong')));
+
+ await logInUser()(dispatch);
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ expect(dispatch).toHaveBeenLastCalledWith({
+ payload: { error: true, message: 'something went wrong' },
+ type: 'STOP_FETCHING',
+ });
+ });
+});
diff --git a/src/tests/components/LoginPage.test.jsx b/src/tests/components/LoginPage.test.jsx
index 89c2b81..79ddeb6 100644
--- a/src/tests/components/LoginPage.test.jsx
+++ b/src/tests/components/LoginPage.test.jsx
@@ -1,10 +1,55 @@
import React from 'react';
import { shallow } from 'enzyme';
-import LoginPage from '../../components/LoginPage';
+
+import { Login } from '../../components/LoginPage';
+import { mountWrap } from '../helpers/contextWrapper';
+import { updateInput } from '../helpers';
describe('LoginPage', () => {
+ const props = {
+ logInUser: jest.fn(),
+ history: { push: jest.fn() },
+ };
+
it('should render LoginPage correctly', () => {
- const wrapper = shallow();
+ const wrapper = mountWrap();
expect(wrapper).toMatchSnapshot();
+ wrapper.unmount();
+ });
+
+ it('should allow user to fill login form', () => {
+ const wrapper = shallow();
+ const form = wrapper.find('form');
+ updateInput(form, 'input[name="email"]', 'an@email.address');
+ updateInput(form, 'input[name="password"]', 'secretpass');
+
+ expect(wrapper.state('email')).toEqual('an@email.address');
+ expect(wrapper.state('password')).toEqual('secretpass');
+ });
+
+ it('should correctly submit login form with user details', () => {
+ const wrapper = shallow();
+ wrapper.find('form').simulate('submit', { preventDefault: () => undefined });
+
+ expect(props.logInUser).toHaveBeenCalled();
+ });
+
+ it('should redirect user after login', () => {
+ const wrapper = shallow();
+ const form = wrapper.find('form');
+ updateInput(form, 'input[name="email"]', 'an@email.address');
+ updateInput(form, 'input[name="password"]', 'secretpass');
+
+ form.simulate('submit', { preventDefault: () => undefined });
+ expect(props.logInUser).toHaveBeenCalledWith({
+ email: 'an@email.address',
+ password: 'secretpass',
+ });
+ expect(props.history.push).toHaveBeenCalled();
+ });
+
+ it('should redirect user already logged in', () => {
+ mountWrap();
+ expect(props.history.push).toHaveBeenCalled();
});
});
diff --git a/src/tests/components/SignupPage.test.jsx b/src/tests/components/SignupPage.test.jsx
index aff1cd7..63ed5ed 100644
--- a/src/tests/components/SignupPage.test.jsx
+++ b/src/tests/components/SignupPage.test.jsx
@@ -1,14 +1,9 @@
import React from 'react';
import { shallow } from 'enzyme';
+
import { SignupPage } from '../../components/SignupPage';
import { mountWrap } from '../helpers/contextWrapper';
-
-const updateInput = (wrapper, instance, newValue) => {
- const input = wrapper.find(instance);
- input.simulate('change', {
- target: { value: newValue },
- });
-};
+import { updateInput } from '../helpers';
describe('SignupPage Component', () => {
const props = {
diff --git a/src/tests/components/__snapshots__/LoginPage.test.jsx.snap b/src/tests/components/__snapshots__/LoginPage.test.jsx.snap
index 652e634..93e2000 100644
--- a/src/tests/components/__snapshots__/LoginPage.test.jsx.snap
+++ b/src/tests/components/__snapshots__/LoginPage.test.jsx.snap
@@ -1,14 +1,122 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LoginPage should render LoginPage correctly 1`] = `
-
- This is the login page.
-
-
+
+
- Go Home
-
-
+
+
+ Access Your Account
+
+
+
+
+
+
`;
diff --git a/src/tests/helpers/contextWrapper.js b/src/tests/helpers/contextWrapper.js
index 04ce86e..9b4ba7e 100644
--- a/src/tests/helpers/contextWrapper.js
+++ b/src/tests/helpers/contextWrapper.js
@@ -18,3 +18,7 @@ const createContext = () => ({
export const mountWrap = node => mount(node, createContext());
export const shallowWrap = node => shallow(node, createContext());
+
+/*
+ Credit: https://stackoverflow.com/a/50438273
+*/
diff --git a/src/tests/helpers/index.js b/src/tests/helpers/index.js
new file mode 100644
index 0000000..5867f4d
--- /dev/null
+++ b/src/tests/helpers/index.js
@@ -0,0 +1,11 @@
+/*
+ Credit: https://github.com/twclark0/react-enzyme-jest
+*/
+export const updateInput = (wrapper, instance, newValue) => {
+ const input = wrapper.find(instance);
+ input.simulate('change', {
+ target: { value: newValue },
+ });
+};
+
+export const placeholder = {};