Skip to content

Commit

Permalink
Merge pull request #19 from andela/ft-request-actions-168049953
Browse files Browse the repository at this point in the history
#168049953 A manager should get a re-confirmation dialogue for approving or rejecting travel requests

[Deliver #168049953, Deliver#168049962 , Deliver #168049969, Deliver #168050023]
  • Loading branch information
Jordybastien committed Nov 19, 2019
2 parents 99702e7 + c893a84 commit b88d6bc
Show file tree
Hide file tree
Showing 24 changed files with 588 additions and 62 deletions.
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@storybook/addons": "^5.2.5",
"@storybook/react": "^5.2.5",
"axios": "^0.19.0",
"focus-trap-react": "^6.0.0",
"history": "^4.10.1",
"joi-browser": "^13.4.0",
"jwt-decode": "^2.2.0",
Expand Down
18 changes: 18 additions & 0 deletions src/__mocks__/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,21 @@ export const payload = {
};

export const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTAsImZpcnN0bmFtZSI6IkZyZWQiLCJsYXN0bmFtZSI6Ik11Y3lvIiwiZW1haWwiOiJtdWN5b21pbGxlckBnbWFpbC5jb20iLCJ1c2VybmFtZSI6bnVsbCwiaXNfdmVyaWZpZWQiOnRydWUsInJvbGVfdmFsdWUiOjEsInBob25lIjpudWxsLCJnZW5kZXIiOiJNYWxlIiwiZG9iIjoiMTk5NC0wNS0wMlQwMDowMDowMC4wMDBaIiwiYWRkcmVzcyI6Im51bGwiLCJjb3VudHJ5IjoibnVsbCIsImxhbmd1YWdlIjoibnVsbCIsImN1cnJlbmN5IjoibnVsbCIsImltYWdlX3VybCI6Imh0dHA6Ly9yZXMuY2xvdWRpbmFyeS5jb20vdGVjaG5pdGVzL2ltYWdlL3VwbG9hZC92MTU3MzgwODYzNy9tdHd3a2ExZHh0aWVrd3Nncmlvai5qcGciLCJjb21wYW55IjoiQW5kZWxhIiwiZGVwYXJ0bWVudCI6IkVuZ2luZWVyaW5nIiwibGluZV9tYW5hZ2VyIjoicnVndW1iaXJham9yZHliYXN0aWVuQGdtYWlsLmNvbSIsImlzRW1haWxBbGxvd2VkIjoidHJ1ZSIsImlhdCI6MTU3MzgwODg1NiwiZXhwIjoxNTczODk1MjU2fQ._fvFcTCo7YdJ1yL_X_iVFOrby5k5b3bcrOb0f9DQpEo";

export const request = {
User: {
id: 5,
firstname: "Dennis",
lastname: "Lohan",
username: "requesting",
email: "requester@request.com",
},
id: 9,
user_id: 5,
request_type: "ReturnTrip",
location_id: 1,
departure_date: "2020-09-25",
status: "Pending",
};

export const requests = [request];
7 changes: 7 additions & 0 deletions src/__mocks__/mockStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import initialState from '../redux/store/initialState';

export const middleWares = [thunk];
export const mockStore = configureMockStore(middleWares);
export default mockStore(initialState);
44 changes: 16 additions & 28 deletions src/__tests__/components/protected-route/ProtectedRoute.test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable import/no-named-as-default-member */
/* eslint-disable quotes */
/* eslint-disable import/named */
/* eslint-disable no-undef */
import React from "react";
import { mount } from "enzyme";
import App from "../../../components/App";
import { setJwtToLocalStorage } from "../../../services/authServices";
import VerifyEmailPage from "../../../components/register-page/VerifyEmail";
import NotFound from "../../../components/not-found/NotFound";
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import { ProtectedRoute } from '../../../components/protected-route/ProtectedRoute';

describe("<ProtectedRoute />", () => {
it("should mount the protected route with the token", () => {
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFtaWx5a2Fzc2ltMDEyQGdtYWlsLmNvbSIsImlhdCI6MTU3MzUzODEyNywiZXhwIjoxNTczNjI0NTI3fQ.sEM3HC_fOraqXEZBMcMzf1Olmpv2XrxpnebB0ZRcFSo";
setJwtToLocalStorage(token);
const wrapper = mount(<App />);
const protectedRoute = wrapper.find("ProtectedRoute");
expect(wrapper).toHaveLength(1);
// expect(protectedRoute).toHaveLength(1);
});
const props = {
isAuthenticated: false,
};

it("should mount the protected route without the token thus redirecting the user to login page", () => {
setJwtToLocalStorage(null);
const wrapper = mount(<App />);
const loginRoute = wrapper.find('Route[path="/login"]');
expect(wrapper).toHaveLength(1);
// expect(loginRoute).toHaveLength(1);
});
describe('Protected Route', () => {
const protectedRoute = mount(
<BrowserRouter>
<ProtectedRoute {...props} />
</BrowserRouter>,
);

it("should mount the verify email component", () => {
setJwtToLocalStorage(null);
const wrapper = mount(<VerifyEmailPage />);
const form = wrapper.find('form[className="card"]');
expect(form).toHaveLength(1);
test('should redirect to login when user is not authenticated', () => {
expect(protectedRoute.instance().history.location.pathname).toEqual('/login');
});
});
67 changes: 67 additions & 0 deletions src/__tests__/components/shared/modal/Modal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import { Modal } from '../../../../components/shared/modal/Modal';
import mockStore from '../../../../__mocks__/mockStore';
import { request } from '../../../../__mocks__/fixtures';

const props = {
showModal: jest.fn(),
triggerText: 'Trigger text',
buttonRef: jest.fn(),
handleAction: jest.fn(),
data: request
};

describe('Modal', () => {
let modal;
let modalProvider;

modal = mount(<Modal {...props} />);
modalProvider = mount(
<Provider store={mockStore}>
<Modal {...props} />
</Provider>
);

test('should have an initial `isShown` state of false', () => {
expect(modal.state().isShown).toBe(false);
});

test('should have `ModalTrigger` present', () => {
expect(modal.find('ModalTrigger').exists()).toBe(true);
});

describe('when the modal is triggered', () => {
beforeEach(() => {
modalProvider.find('ModalTrigger').simulate('click');
});

test('should have an action button', () => {
expect(modalProvider.find('.action').text()).toEqual(props.triggerText);
});

test('should have a cancel action button', () => {
expect(modalProvider.find('.cancel').text()).toEqual('Cancel');
});

describe('and the action button is clicked', () => {
beforeEach(() => {
modalProvider.find('.action').simulate('click');
});

test('should close the modal', () => {
expect(modalProvider.find('Connect(RequestActionModal)').exist).toBe(undefined);
});
});

describe('and the cancel button is clicked', () => {
beforeEach(() => {
modalProvider.find('.modal-footer .cancel').simulate('click')
});
test('should not have the `RequestActionModal` on clicking the cancel button', () => {
expect(modalProvider.find('Connect(RequestActionModal)').exist).toBe(undefined);
});
});
});
});
33 changes: 33 additions & 0 deletions src/__tests__/components/shared/modal/RequestActinModal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { shallow } from 'enzyme';
import { RequestActionModal } from '../../../../components/shared/modal/RequestActionModal';
import { request } from '../../../../__mocks__/fixtures';

const props = {
data: request,
triggerText: 'TestButton',
handleAction: jest.fn(),
approveReject: jest.fn(),
closeModal: jest.fn(),
};

describe('RequestActionModal', () => {
let requestActionModal;
requestActionModal = shallow(<RequestActionModal {...props} />);

test('should have the confirm action button', () => {
expect(requestActionModal.find('.action').text()).toEqual(
props.triggerText
);
});

test('should have the cancle action button', () => {
expect(requestActionModal.find('.cancel').text()).toEqual('Cancel');
});

test('should call the `handleAction` function when the action button click', () => {
const spyAction = jest.spyOn(requestActionModal.instance(), 'handleAction');
requestActionModal.find('.action').simulate('click');
expect(spyAction).toHaveBeenCalled();
});
});
18 changes: 17 additions & 1 deletion src/__tests__/redux/RequestsReducer.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import UserRequestsReducer from "../../redux/reducers/RequestsReducer";
import * as types from "../../redux/actions/actionType";
import initialState from "../../redux/store/initialState";
import { requests } from "../../__mocks__/fixtures";

describe("UserRequestsReducer unit tests", () => {
it("should reduce user requests", () => {
Expand Down Expand Up @@ -60,7 +63,6 @@ describe("UserRequestsReducer unit tests", () => {
});
});


describe("Admin viewing all requests unit tests", () => {
it("should reduce all requests", () => {
const state = UserRequestsReducer(
Expand Down Expand Up @@ -162,3 +164,17 @@ describe("Admin viewing all requests unit tests", () => {
});
});
});

describe("Approve/Reject", () => {
beforeEach(() => {
initialState.requests = requests;
});

test("should update the status of an approved request to `Approved`", () => {
const newState = UserRequestsReducer(
initialState,
{ type: types.REQUEST_ACTION, response: { data: { id: 5 }, status: "Approved" } },
);
expect(newState.requests).toEqual(requests);
});
});
33 changes: 30 additions & 3 deletions src/__tests__/redux/actions/RequestActions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import thunk from "redux-thunk";
import moxios from "moxios";
import configureStore from "redux-mock-store";
import http from "../../../services/httpServices";
import { GET_REQUESTS, GET_ERRORS, GET_ALL_REQUESTS } from "../../../redux/actions/actionType";
import { getUserRequests, getMyUsersRequests } from "../../../redux/actions/RequestActions";
import {
GET_REQUESTS, GET_ERRORS, GET_ALL_REQUESTS, REQUEST_ACTION,
} from "../../../redux/actions/actionType";
import { getUserRequests, getMyUsersRequests, approveReject } from "../../../redux/actions/RequestActions";
import successresponse from "../../../__mocks__/__get_user_request_success__.json";
import errorreponse from "../../../__mocks__/__get_user_request_failure__.json";
import { token } from "../../../__mocks__/fixtures";
import { token, request as testRequest } from "../../../__mocks__/fixtures";

const mockedStore = configureStore([thunk]);
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
Expand Down Expand Up @@ -81,3 +83,28 @@ describe("Get All Requests by Admin actions", () => {
expect(calledActions[0].type).toEqual(GET_ERRORS);
});
});

describe("Approve/Reject Action", () => {
describe("and `REQUEST_ACTION` dispatched with success", () => {
beforeEach(() => {
store = mockedStore();
moxios.install(http.dbCall);
Storage.prototype.getItem = jest.fn(() => token);
});

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

test("should dispatch the action", async () => {
moxios.wait(async () => {
const requestAction = moxios.requests.mostRecent();
requestAction.respondWith({ status: 200 });
await flushPromises();
});
await store.dispatch(approveReject({ testRequest, action: "test_actio", status: "test_status" }));
const calledActions = store.getActions();
expect(calledActions[0].type).toEqual(REQUEST_ACTION);
});
});
});
7 changes: 6 additions & 1 deletion src/__tests__/redux/reducers/socialAuthReducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ describe("Login Reducers", () => {
it("should update the login state", () => {
const newState = socialAuthReducer(initialState, {
type: SOCIAL_LOGIN_SUCCESS,
payload
payload,
});

expect(newState.isAuthenticated).toBe(true);
});

test("should return the default state by default when there is no action type passed", () => {
const newState = socialAuthReducer(initialState, { type: "" });
expect(newState).toEqual(initialState);
});
});
18 changes: 14 additions & 4 deletions src/components/admin-requests/AdminRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import HomeNav from "../home-nav/HomeNav";
import { Table } from "../table";
import SideBar from "../side-bar";
import Footer from "../footer";
import Modal from "../shared/modal/Modal";
import "../../styles/modal.scss";

export class AdminRequests extends Component {
constructor() {
Expand Down Expand Up @@ -71,7 +73,6 @@ export class AdminRequests extends Component {
);
// Change page
const paginate = (currentPage) => this.setState({ currentPage });

const elements = currentElements.map((request) => (
<tr className="table-row" key={request.id}>
<td className="table-element" id={request.id}>
Expand Down Expand Up @@ -162,9 +163,18 @@ export class AdminRequests extends Component {
Actions
</button>
<div className="dropdown-content">
<a href="#">Accept</a>
<a href="#">Reject</a>
<a href="#">View more</a>
<Modal
triggerText="Approve"
data={request}
action="approve"
status="Approved"
/>
<Modal
triggerText="Reject"
data={request}
action="reject"
status="Rejected"
/>
</div>
</div>
</td>
Expand Down
3 changes: 2 additions & 1 deletion src/components/protected-route/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-shadow */
/* eslint-disable comma-dangle */
/* eslint-disable arrow-parens */
/* eslint-disable object-curly-newline */
Expand All @@ -8,7 +9,7 @@ import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
import { getJwt } from "../../services/authServices";

class ProtectedRoute extends Component {
export class ProtectedRoute extends Component {
render() {
const { isAuthenticated, component: Component, ...rest } = this.props;
return (
Expand Down
Loading

0 comments on commit b88d6bc

Please sign in to comment.