diff --git a/src/__test__/categoriesSection.test.tsx b/src/__test__/categoriesSection.test.tsx
index 83db18af..c795fd44 100644
--- a/src/__test__/categoriesSection.test.tsx
+++ b/src/__test__/categoriesSection.test.tsx
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router';
import CategoriesHome from '@/components/home/CategoriesHome';
import { store } from '@/app/store';
@@ -8,7 +9,9 @@ describe('App', () => {
it('Renders Home Categories Section', () => {
render(
-
+
+
+
);
expect(screen.getByText('New Arrivals')).toBeInTheDocument();
diff --git a/src/__test__/home/categories.test.tsx b/src/__test__/home/categories.test.tsx
index 81a6041f..7ada3981 100644
--- a/src/__test__/home/categories.test.tsx
+++ b/src/__test__/home/categories.test.tsx
@@ -3,6 +3,7 @@ import '@testing-library/jest-dom';
import { describe, it, expect, beforeEach } from 'vitest';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
+import { MemoryRouter } from 'react-router';
import Categories from '@/components/home/sidebar';
const mockStore = configureStore([]);
@@ -32,7 +33,9 @@ describe('Categories Component', () => {
it('renders Categories component with category details', () => {
render(
-
+
+
+
);
diff --git a/src/__test__/home/productList.test.tsx b/src/__test__/home/productList.test.tsx
index 70becc75..7764024f 100644
--- a/src/__test__/home/productList.test.tsx
+++ b/src/__test__/home/productList.test.tsx
@@ -3,6 +3,7 @@ import '@testing-library/jest-dom';
import { describe, it, expect, beforeEach } from 'vitest';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
+import { MemoryRouter } from 'react-router';
import ProductsList from '@/components/home/productList';
import { Product } from '@/types/Product';
import User from '@/types/User';
@@ -86,7 +87,9 @@ describe('ProductsList Component', () => {
it('renders the ProductsList component with products', () => {
render(
-
+
+
+
);
diff --git a/src/__test__/productDetails.test.tsx b/src/__test__/productDetails.test.tsx
new file mode 100644
index 00000000..f60ae28f
--- /dev/null
+++ b/src/__test__/productDetails.test.tsx
@@ -0,0 +1,166 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { Store, configureStore } from '@reduxjs/toolkit';
+import { waitFor } from '@testing-library/dom';
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { MemoryRouter, Route, Routes } from 'react-router';
+import productsReducer, {
+ fetchProductDetails,
+} from '@/features/Products/ProductSlice';
+import signInReducer from '@/features/Auth/SignInSlice';
+import { AppDispatch, RootState } from '@/app/store';
+import ProductDetails from '@/pages/ProductDetails';
+import bestSellingReducer from '@/features/Popular/bestSellingProductSlice';
+
+const mockProduct = {
+ id: 1,
+ name: 'Mock Product',
+ image: '/images/mock-product.jpg',
+ rating: 4.5,
+ salesPrice: 99.99,
+ regularPrice: 149.99,
+ totalQtySold: 25,
+ longDesc: 'This is a mock product used for testing purposes.',
+ shortDesc: 'This is a short description',
+ category: 'Electronics',
+ similarProducts: [],
+ reviews: [
+ {
+ id: 1,
+ user: {
+ id: 1,
+ firstName: 'new',
+ lastName: 'user',
+ picture: 'http://fake.png',
+ },
+ rating: 5,
+ content: 'excellent product',
+ },
+ ],
+ gallery: [],
+ tags: ['testTag'],
+ vendor: {
+ name: 'Tester',
+ email: 'testervendor@gmail.com',
+ picture: 'https://fake.png',
+ },
+};
+
+const renderWithProviders = (
+ ui: React.ReactElement,
+ {
+ store = configureStore({
+ reducer: {
+ products: productsReducer,
+ bestSellingProducts: bestSellingReducer,
+ signIn: signInReducer,
+ },
+ }),
+ } = {}
+) => {
+ return render(
+
+
+
+
+
+
+
+ );
+};
+
+describe('ProductDetails Page', () => {
+ let mock: MockAdapter;
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ it('renders the ProductDetails page correctly', () => {
+ mock.onGet(`${import.meta.env.VITE_BASE_URL}/product/1`).reply(500);
+
+ renderWithProviders();
+
+ expect(screen.getByText(/Product Details/i)).toBeInTheDocument();
+ });
+
+ it('fetches and displays product details', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, {
+ product: mockProduct,
+ });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(screen.getAllByText(/Mock Product/i)[0]).toBeInTheDocument();
+ expect(screen.getByText(/testTag/i)).toBeInTheDocument();
+ expect(screen.getByText(/\$99.99/i)).toBeInTheDocument();
+ expect(screen.getByText(/\$149.99/i)).toBeInTheDocument();
+ expect(screen.getByText('1')).toBeInTheDocument();
+ expect(screen.getByText('25')).toBeInTheDocument();
+ expect(
+ screen.getByText(/This is a mock product used for testing purposes./i)
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/This is a short description/i)
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('shows error message on failed fetch', async () => {
+ mock.onGet(`${import.meta.env.VITE_BASE_URL}/product/1`).reply(500, {
+ message: 'Internal Server Error',
+ });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(
+ screen.getByText(/Failed to load product details/i)
+ ).toBeInTheDocument();
+ });
+ });
+});
+
+describe('Product Details async action', () => {
+ let store: Store;
+ let mock: MockAdapter;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ store = configureStore({
+ reducer: {
+ products: productsReducer,
+ },
+ });
+ });
+
+ it('should handle fetchProductDetails.fulfilled', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, {
+ product: mockProduct,
+ });
+
+ const { dispatch }: { dispatch: AppDispatch } = store;
+ await dispatch(fetchProductDetails(1));
+ const state = (store.getState() as RootState).products;
+ expect(state.productDetailsLoading).toBe(false);
+ expect(state.productDetails).toEqual(mockProduct);
+ });
+
+ it('should handle fetchProductDetails.rejected', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(500);
+
+ const { dispatch }: { dispatch: AppDispatch } = store;
+ await dispatch(fetchProductDetails(1));
+ const state = (store.getState() as RootState).products;
+ expect(state.productDetailsLoading).toBe(false);
+ expect(state.productDetails).toBeNull();
+ });
+});
diff --git a/src/__test__/wishlist.test.tsx b/src/__test__/wishlist.test.tsx
index 164d28fa..367747d4 100644
--- a/src/__test__/wishlist.test.tsx
+++ b/src/__test__/wishlist.test.tsx
@@ -147,8 +147,8 @@ describe('WishlistCard', () => {
expect(screen.getByText(/Product name/i)).toBeInTheDocument();
expect(screen.getByText(/In Stock/i)).toBeInTheDocument();
- expect(screen.getByText(/\$500/i)).toBeInTheDocument();
- expect(screen.getByText(/\$700/i)).toBeInTheDocument();
+ expect(screen.getByText(/\$230/i)).toBeInTheDocument();
+ expect(screen.getByText(/\$280/i)).toBeInTheDocument();
});
});
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
new file mode 100644
index 00000000..c22d4b1b
--- /dev/null
+++ b/src/components/ProtectedRoute.tsx
@@ -0,0 +1,20 @@
+import { ReactNode } from 'react';
+import { Navigate } from 'react-router';
+import { useAppSelector } from '@/app/hooks';
+
+interface ProtectedRouteProps {
+ children: ReactNode;
+ roles: string[];
+}
+
+function ProtectedRoute({ children, roles }: ProtectedRouteProps) {
+ const user = useAppSelector((state) => state.signIn.user);
+
+ if (user && user.userType && roles.includes(user.userType.name)) {
+ return children;
+ }
+
+ return ;
+}
+
+export default ProtectedRoute;
diff --git a/src/components/WishlistCard.tsx b/src/components/WishlistCard.tsx
index dbdf448d..d80ee3cd 100644
--- a/src/components/WishlistCard.tsx
+++ b/src/components/WishlistCard.tsx
@@ -102,8 +102,12 @@ function WishlistCard({ product }: { product: Product }) {
)}
- ${500}
- ${700}
+
+ ${product.salesPrice}
+
+
+ ${product.regularPrice}
+
diff --git a/src/components/home/ProductCard.tsx b/src/components/home/ProductCard.tsx
index d03cafca..0a3ca4c2 100644
--- a/src/components/home/ProductCard.tsx
+++ b/src/components/home/ProductCard.tsx
@@ -1,3 +1,4 @@
+import { useNavigate } from 'react-router';
import { useAppDispatch, useAppSelector } from '@/app/hooks';
import { addToWishlist } from '@/features/Products/ProductSlice';
import { Product } from '@/types/Product';
@@ -7,6 +8,7 @@ interface ProductCardProps {
}
function ProductCard({ product }: ProductCardProps) {
+ const navigate = useNavigate();
const dispatch = useAppDispatch();
const { token } = useAppSelector((state) => state.signIn);
return (
@@ -31,10 +33,14 @@ function ProductCard({ product }: ProductCardProps) {
}}
>
-
+
+