Skip to content

Commit

Permalink
feat-wishlist-management
Browse files Browse the repository at this point in the history
  • Loading branch information
Calebgisa72 committed Jul 4, 2024
1 parent 86a5319 commit a59f02e
Show file tree
Hide file tree
Showing 20 changed files with 792 additions and 30 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-spinners": "^0.13.8",
"react-swipeable": "^7.0.1",
"redux": "^5.0.1",
"redux-mock-store": "^1.5.4",
"vitest": "^1.6.0"
Expand Down
31 changes: 28 additions & 3 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import PageTitle from '../components/PageTitle';
import Register from '../pages/Authentication/Register';
import RegisterVendor from '../pages/Authentication/RegisterVendor';
import VerifyEmail from '../pages/Authentication/VerifyEmail';
import Login, { DecodedToken } from '../pages/Authentication/Login';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../redux/store';
import GoogleLoginSuccess from '../pages/Authentication/GoogleLoginSuccess';
import { useJwt } from 'react-jwt';
Expand All @@ -21,6 +21,9 @@ import DashboardNewProducts from '../components/Products/DashboardNewProducts/Da
import MainLayout from '../layout/MainLayout';
import Home from '../pages/LandingPage/Home';
import SearchPage from '../pages/searchPage';
import WishlistPage from '../pages/WishlistPage/WishlistPage';
import { setOnWishlistPage } from '../redux/reducers/wishlistReducer';
import { useLocation } from 'react-router-dom';

const Router = () => {
const { userToken } = useSelector((state: RootState) => state.auth);
Expand All @@ -30,13 +33,24 @@ const Router = () => {
const isVendor = decodedToken?.role.toLowerCase() === 'vendor';
const isBuyer = decodedToken?.role.toLowerCase() === 'buyer';

const location = useLocation();
const dispatch = useDispatch();

useEffect(() => {
if (location.pathname === '/wishlist') {
dispatch(setOnWishlistPage(true));
} else {
dispatch(setOnWishlistPage(false));
}
}, [location.pathname, dispatch]);

return (
<Routes>
<Route
path="/"
element={
<MainLayout>
<PageTitle title="Knights Store | Register" />
<PageTitle title="Knights Store" />
<Home />
</MainLayout>
}
Expand Down Expand Up @@ -144,6 +158,17 @@ const Router = () => {
</MainLayout>
}
/>

<Route
path="/wishlist"
element={
<MainLayout>
<PageTitle title="Knights Store | Wishlist" />
<WishlistPage />
</MainLayout>
}
/>

<Route path="/vendor/dashboard" element={<DashboardLayout />}>
<Route path="products" element={<DashboarInnerLayout />}>
<Route path="" element={<DashboardProducts />} />
Expand Down
22 changes: 17 additions & 5 deletions src/__test__/pages/Authentication/otp.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { Provider } from 'react-redux';
import store from '../../../redux/store';
import Otp from '../../../pages/Authentication/OtpPage';
import { toast } from 'react-hot-toast';

vi.mock('react-hot-toast', () => ({
toast: {
error: vi.fn()
}
}));

vi.mock('axios');

describe('OtpPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('Renders the OtpPage component with all expected elements', () => {
render(
<Provider store={store}>
Expand All @@ -20,7 +33,7 @@ describe('OtpPage', () => {
selector: 'p'
});
expect(descriptionElement).toBeInTheDocument();
// Verify OTP input fields

const otpInputs = screen.getAllByRole('textbox');
expect(otpInputs).toHaveLength(6);
otpInputs.forEach((input) => expect(input).toHaveAttribute('maxLength', '1'));
Expand All @@ -42,6 +55,8 @@ describe('OtpPage', () => {
const verifyButton = screen.getByRole('button', { name: 'Verify' });

fireEvent.click(verifyButton);

expect(toast.error).toHaveBeenCalledWith('Fill all the OTP fields.');
});
});

Expand All @@ -58,7 +73,6 @@ describe('OtpPage handleChange', () => {
expect(input).toHaveValue('');
});

// Simulate entering a digit in each OTP input
otpInputs.forEach((input, index) => {
fireEvent.change(input, { target: { value: `${index + 1}` } });
expect(input).toHaveValue(`${index + 1}`);
Expand All @@ -74,12 +88,10 @@ describe('OtpPage handleChange', () => {

const otpInputs = screen.getAllByRole('textbox');

// Ensure the inputs are initially empty
otpInputs.forEach((input) => {
expect(input).toHaveValue('');
});

// Simulate entering a non-digit character
otpInputs.forEach((input) => {
fireEvent.change(input, { target: { value: 'a' } });
expect(input).toHaveValue('');
Expand Down
141 changes: 141 additions & 0 deletions src/__test__/pages/wishlistPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import WishlistPage from '../../pages/WishlistPage/WishlistPage';
import { Provider } from 'react-redux';
import store from '../../redux/store';
import axios from 'axios';
import { vi } from 'vitest';
import { setOnWishlistPage, setWishlist } from '../../redux/reducers/wishlistReducer';
import { setCredentials } from '../../redux/reducers/authReducer';

vi.mock('axios');
vi.mock('../../utils/errorHandler');

const mockedAxios = axios as jest.Mocked<typeof axios>;

const userToken = 'Testing Login';

const mockProducts = [
{
wishListDetails: {
createdAt: new Date(Date.now()),
id: 1,
productId: '1'
},
productInfo: {
id: '1',
name: 'Product 1',
images: ['image1.jpg'],
categories: [],
newPrice: '100',
oldPrice: '120',
updatedAt: new Date(),
createdAt: new Date(Date.now()),
description: '',
isAvailable: false,
quantity: ''
}
},
{
wishListDetails: {
createdAt: new Date(Date.now()),
id: 2,
productId: '2'
},
productInfo: {
id: '2',
name: 'Product 2',
images: ['image2.jpg'],
categories: [],
newPrice: '200',
oldPrice: '220',
updatedAt: new Date(),
createdAt: new Date(Date.now()),
description: '',
isAvailable: false,
quantity: ''
}
},
{
wishListDetails: {
createdAt: new Date(Date.now()),
id: 3,
productId: '3'
},
productInfo: {
id: '3',
name: 'Product 3',
images: ['image3.jpg'],
categories: [],
newPrice: '200',
oldPrice: '220',
updatedAt: new Date(),
createdAt: new Date(Date.now()),
description: '',
isAvailable: false,
quantity: ''
}
}
];

describe('WishlistPage', () => {
beforeEach(() => {
mockedAxios.get.mockResolvedValueOnce({
data: { productsForBuyer: mockProducts }
});
store.dispatch(setOnWishlistPage(true));
store.dispatch(setCredentials(userToken));
vi.resetAllMocks();
});

it('should tell if there are no products in whichlist', () => {
render(
<Provider store={store}>
<WishlistPage />
</Provider>
);

const paragraph = screen.getByText(/wishlist is empty/i);
expect(paragraph).toBeInTheDocument();

const clearAllButton = screen.queryByText(/Clear All/i);
expect(clearAllButton).not.toBeInTheDocument();
});

it('renders wishlist page with products', async () => {
store.dispatch(setWishlist(mockProducts));

render(
<Provider store={store}>
<WishlistPage />
</Provider>
);

const heading = await screen.findByRole('heading', { name: 'Wishlist' });
expect(heading).toBeInTheDocument();

expect(screen.getAllByTestId('productDiv').length).toBe(3);

mockProducts.forEach((product) => {
expect(screen.getByText(product.productInfo.name)).toBeInTheDocument();
});

const deleteButtons = screen.getAllByTestId('deleteButton');

expect(deleteButtons[0]).toBeInTheDocument();
fireEvent.click(deleteButtons[0]);
});

it('All products should be cleared when clearAll is clicked', async () => {
store.dispatch(setWishlist(mockProducts));

render(
<Provider store={store}>
<WishlistPage />
</Provider>
);

const clearAllButton = await screen.findByText(/Clear All/i);
expect(clearAllButton).toBeInTheDocument;
});
});
64 changes: 64 additions & 0 deletions src/__test__/redux/reducers/wishlistReducer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import wishlistReducer, { setWishlist, setOnWishlistPage } from '../../../redux/reducers/wishlistReducer';
import { prodInWishlistProps } from '../../../redux/reducers/wishlistReducer';

describe('wishlistReducer', () => {
let initialState: { products: prodInWishlistProps[]; onWihlistPage: boolean };

beforeEach(() => {
initialState = {
products: [],
onWihlistPage: false
};
});

it('should return the initial state when given an undefined state', () => {
expect(wishlistReducer(undefined, { type: 'undefined' })).toEqual(initialState);
});

it('should handle setWishlist action', () => {
const mockProducts: prodInWishlistProps[] = [
{
wishListDetails: {
createdAt: new Date(Date.now()),
id: 1,
productId: '1'
},
productInfo: {
id: '1',
name: 'Product 1',
images: ['image1.jpg'],
categories: [],
newPrice: '100',
oldPrice: '120',
updatedAt: new Date(),
createdAt: new Date(Date.now()),
description: '',
isAvailable: false,
quantity: ''
}
}
];

const newState = wishlistReducer(initialState, setWishlist(mockProducts));
expect(newState).toEqual({
products: mockProducts,
onWihlistPage: false
});
});

it('should handle setOnWishlistPage action', () => {
const newState = wishlistReducer(initialState, setOnWishlistPage(true));
expect(newState).toEqual({
products: [],
onWihlistPage: true
});
});

it('should handle setOnWishlistPage action to false', () => {
const newState = wishlistReducer({ ...initialState, onWihlistPage: true }, setOnWishlistPage(false));
expect(newState).toEqual({
products: [],
onWihlistPage: false
});
});
});
2 changes: 1 addition & 1 deletion src/__test__/utils/bannerRateTime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('Time formatting and Rate calculation test', () => {

it('should return "1 Hours ago" when the date is one hour ago', () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
expect(FormatPosted(oneHourAgo)).toBe('1 Hours ago');
expect(FormatPosted(oneHourAgo)).toBe('1 Hour ago');
});

it('should return "1 Minute ago" when the date is one minute ago', () => {
Expand Down
Loading

0 comments on commit a59f02e

Please sign in to comment.