Skip to content

Commit

Permalink
Merge bd72dd8 into d45b4ea
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilome committed Jan 29, 2019
2 parents d45b4ea + bd72dd8 commit 6b2d44f
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 20 deletions.
14 changes: 14 additions & 0 deletions src/actions/index.js
Expand Up @@ -140,6 +140,20 @@ export const getUserOrderHistory = () => async (dispatch) => {
}
};

export const cancelOrder = orderId => async (dispatch) => {
dispatch(startFetching());
try {
await axios.delete(`/orders/${orderId}`);
dispatch({ type: types.CANCEL_ORDER, payload: { orderId } });
dispatch(stopFetching());
return toast.success('Order cancelled successfully 🎉');
} catch (error) {
dispatch({ type: types.CANCEL_ORDER_FAIL });
dispatch(stopFetching(false, errorResponse(error)));
return toast.error(errorResponse(error));
}
};

export const logout = () => (dispatch) => {
dispatch(emptyCart());
removeToken();
Expand Down
2 changes: 2 additions & 0 deletions src/actions/types/index.js
Expand Up @@ -16,4 +16,6 @@ export default {
EMPTY_CART: 'EMPTY_CART',
GET_ORDER_HISTORY: 'GET_ORDER_HISTORY',
GET_ORDER_HISTORY_FAIL: 'GET_ORDER_HISTORY_FAIL',
CANCEL_ORDER: 'CANCEL_ORDER',
CANCEL_ORDER_FAIL: 'CANCEL_ORDER_FAIL',
};
11 changes: 9 additions & 2 deletions src/components/Nav.jsx
Expand Up @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import LogoutLink from './Logout';

export const Nav = ({ isLoggedIn, role }) => {
export const Nav = ({ isLoggedIn, role, cartItemsCount }) => {
const navContent = () => {
if (isLoggedIn && role === 'customer') {
return (
Expand All @@ -13,7 +13,11 @@ export const Nav = ({ isLoggedIn, role }) => {
<Link to="/menu">Menu</Link>
</li>
<li>
<Link to="/cart">Cart</Link>
<Link to="/cart">
Cart [
{cartItemsCount}
]
</Link>
</li>
<li>
<Link to="/order-history">Order History</Link>
Expand Down Expand Up @@ -69,15 +73,18 @@ export const Nav = ({ isLoggedIn, role }) => {
Nav.propTypes = {
isLoggedIn: PropTypes.bool,
role: PropTypes.string.isRequired,
cartItemsCount: PropTypes.number,
};

Nav.defaultProps = {
isLoggedIn: false,
cartItemsCount: 0,
};

const mapStateToProps = state => ({
isLoggedIn: state.user.isLoggedIn,
role: state.user.role,
cartItemsCount: Object.keys(state.cart).length,
});

export default connect(mapStateToProps)(Nav);
12 changes: 10 additions & 2 deletions src/components/OrderCard.jsx
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';

const OrderCard = ({
foodItems, orderPrice, date, orderStatus,
foodItems, orderPrice, date, orderStatus, cancelOrderCallback,
}) => {
const formattedFoodNames = foodItems.map(foodName => (
<p key={Math.random() * foodItems.length * 12039}>{foodName}</p>
Expand Down Expand Up @@ -48,7 +48,14 @@ const OrderCard = ({
.join('/')}
</p>
</div>
<div className={`order-status-${status}`} />
<div className="order-status">
<div className={`order-status-${status}`} />
<div className="cancel-order">
<button className="small" type="button" onClick={cancelOrderCallback}>
cancel
</button>
</div>
</div>
</div>
);
};
Expand All @@ -58,6 +65,7 @@ OrderCard.propTypes = {
orderPrice: PropTypes.number.isRequired,
date: PropTypes.string.isRequired,
orderStatus: PropTypes.string.isRequired,
cancelOrderCallback: PropTypes.func.isRequired,
};

export default OrderCard;
33 changes: 22 additions & 11 deletions src/components/OrderHistory.jsx
Expand Up @@ -2,29 +2,39 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import OrderCard from './OrderCard';
import { getUserOrderHistory } from '../actions';
import { getUserOrderHistory, cancelOrder } from '../actions';

export class OrderHistory extends Component {
async componentDidMount() {
const { getUserOrderHistory: getOrderHistory } = this.props;
await getOrderHistory();
}

onCancelOrder = async (orderId) => {
const { cancelOrder: deleteOrder } = this.props;
await deleteOrder(Number(orderId));
};

render() {
const { orders } = this.props;

return (
<div className="wrapper">
<section className="container order-history">
{orders.map(order => (
<OrderCard
key={order.id}
foodItems={order.items}
orderPrice={order.price}
orderStatus={order.status}
date={order.date}
/>
))}
{orders.length ? (
orders.map(order => (
<OrderCard
key={order.id}
foodItems={order.items}
orderPrice={order.price}
orderStatus={order.status}
date={order.date}
cancelOrderCallback={() => this.onCancelOrder(order.id)}
/>
))
) : (
<h2>No Orders Yet!</h2>
)}
</section>
</div>
);
Expand All @@ -34,6 +44,7 @@ export class OrderHistory extends Component {
OrderHistory.propTypes = {
getUserOrderHistory: PropTypes.func.isRequired,
orders: PropTypes.instanceOf(Array).isRequired,
cancelOrder: PropTypes.func.isRequired,
};

const mapStateToProps = state => ({
Expand All @@ -42,5 +53,5 @@ const mapStateToProps = state => ({

export default connect(
mapStateToProps,
{ getUserOrderHistory },
{ getUserOrderHistory, cancelOrder },
)(OrderHistory);
2 changes: 1 addition & 1 deletion src/index.jsx
Expand Up @@ -15,7 +15,7 @@ const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
const jsx = (
<Provider store={store}>
<AppIndex />
<ToastContainer transition={Flip} position="top-center" autoClose={3500} />
<ToastContainer transition={Flip} position="bottom-right" autoClose={3500} />
</Provider>
);

Expand Down
6 changes: 6 additions & 0 deletions src/reducers/ordersReducer.js
Expand Up @@ -8,7 +8,13 @@ export default (state = intialState, action) => {
switch (action.type) {
case types.GET_ORDER_HISTORY:
return { ...intialState, history: action.payload.orders };
case types.CANCEL_ORDER:
return {
...state,
history: state.history.filter(order => order.id !== action.payload.orderId),
};
case types.GET_ORDER_HISTORY_FAIL:
case types.CANCEL_ORDER_FAIL:
default:
return state;
}
Expand Down
40 changes: 39 additions & 1 deletion src/tests/actions/orders.test.js
@@ -1,7 +1,9 @@
import { getUserOrderHistory } from '../../actions';
import { getUserOrderHistory, cancelOrder } from '../../actions';
import jwt from '../../utils/jwt';
import axios from '../../services/axios';

afterAll(() => jest.restoreAllMocks());

describe('getUserOrderHistory', () => {
jest.spyOn(jwt, 'decode').mockImplementation(() => ({ userId: 1 }));
const dispatch = jest.fn();
Expand Down Expand Up @@ -35,3 +37,39 @@ describe('getUserOrderHistory', () => {
});
});
});

describe('cancelOrder()', () => {
const dispatch = jest.fn();
const errResponse = { response: { data: { message: 'failed to cancel order' } } };
afterEach(() => jest.resetAllMocks());

it('should dispatch correct action to cancel an order', async () => {
jest.spyOn(axios, 'delete').mockImplementation(() => Promise.resolve());
await cancelOrder(7)(dispatch);

expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch.mock.calls[1][0]).toEqual({
type: 'CANCEL_ORDER',
payload: { orderId: 7 },
});
expect(dispatch).toHaveBeenLastCalledWith({
type: 'STOP_FETCHING',
payload: { error: false, message: '' },
});
});

it('should dispatch correct action for order cancellation failure', async () => {
jest.spyOn(axios, 'delete').mockImplementation(() => Promise.reject(errResponse));
await cancelOrder(2)(dispatch);

expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch.mock.calls[1][0]).toEqual({ type: 'CANCEL_ORDER_FAIL' });
expect(dispatch).toHaveBeenLastCalledWith({
type: 'STOP_FETCHING',
payload: {
error: true,
message: 'failed to cancel order',
},
});
});
});
10 changes: 10 additions & 0 deletions src/tests/components/OrderHistory.test.jsx
Expand Up @@ -37,6 +37,7 @@ describe('<OrderHistory />', () => {
const props = {
orders,
getUserOrderHistory: jest.fn(),
cancelOrder: jest.fn(),
};

it('should render correctly', () => {
Expand All @@ -47,4 +48,13 @@ describe('<OrderHistory />', () => {
expect(orderCards.length).toEqual(4);
wrapper.unmount();
});

it('should fire cancel order action creator upon order cancellation request', () => {
const wrapper = mount(<OrderHistory {...props} />);
const fourthOrderCard = wrapper.find('.order-card').last();
fourthOrderCard.find('.cancel-order > button').simulate('click');

expect(props.cancelOrder).toHaveBeenCalled();
expect(props.cancelOrder).toHaveBeenCalledWith(4);
});
});
2 changes: 2 additions & 0 deletions src/tests/components/__snapshots__/App.test.jsx.snap
Expand Up @@ -55,6 +55,7 @@ exports[`App component should render app correctly 1`] = `
>
<Connect(Nav)>
<Nav
cartItemsCount={0}
dispatch={[Function]}
isLoggedIn={null}
role=""
Expand Down Expand Up @@ -262,6 +263,7 @@ exports[`App component should render app correctly for authenticated customers 1
>
<Connect(Nav)>
<Nav
cartItemsCount={0}
dispatch={[Function]}
isLoggedIn={null}
role=""
Expand Down
44 changes: 41 additions & 3 deletions src/tests/reducers/orders.test.js
Expand Up @@ -4,19 +4,52 @@ const initialState = Object.freeze({
history: [],
});

const stateWithOrders = Object.freeze({
history: [
{
id: 2,
items: ['Tasty Prawns', 'Chicken Wings'],
price: 2100,
date: '2018-10-19T00:00:00.000Z',
status: 'cancelled',
},
{
id: 5,
items: ['Tasty Prawns'],
price: 1250,
date: '2018-11-25T00:00:00.000Z',
status: 'new',
},
{
id: 9,
items: ['Tasty Prawns', 'Turkey Wings'],
price: 2200,
date: '2018-12-02T00:00:00.000Z',
status: 'new',
},
],
});

const getOrderHistoryAction = {
type: 'GET_ORDER_HISTORY',
payload: {
orders: [
{
foodName: 'das',
foodPrice: 20,
date: 'yesterday?',
id: 55,
items: ['Tasty Prawns'],
price: 1250,
date: '2018-11-25T00:00:00.000Z',
status: 'new',
},
],
},
};

const cancelOrderAction = {
type: 'CANCEL_ORDER',
payload: { orderId: 5 },
};

const getOrderHistoryFailAction = { type: 'GET_ORDER_HISTORY_FAIL' };

describe('orders reducer', () => {
Expand All @@ -30,4 +63,9 @@ describe('orders reducer', () => {
const state = ordersReducer(undefined, getOrderHistoryFailAction);
expect({ ...state.history }).toEqual({ ...initialState.history });
});

it('should return correct state for cancelling user order', () => {
const state = ordersReducer(stateWithOrders, cancelOrderAction);
expect(state.history).toHaveLength(2);
});
});

0 comments on commit 6b2d44f

Please sign in to comment.