diff --git a/src/__test__/Cart/Cart.test.tsx b/src/__test__/Cart/Cart.test.tsx
index ee4867af..01ef9fb7 100644
--- a/src/__test__/Cart/Cart.test.tsx
+++ b/src/__test__/Cart/Cart.test.tsx
@@ -7,6 +7,9 @@ import cartReducer, {
updateCartItemQuantity,
removeCartItem,
} from '@/features/Cart/cartSlice';
+import { render, screen } from '@testing-library/react';
+import CartItem from '@/components/Cart/CartItem';
+import { Provider } from 'react-redux';
describe('cartSlice', () => {
let store = configureStore({ reducer: { cartItems: cartReducer } });
@@ -79,3 +82,22 @@ describe('cartSlice', () => {
expect(state.error).toBeNull();
});
});
+
+describe('Cart component', () => {
+ const store = configureStore({
+ reducer: {},
+ });
+
+ it('renders cart item', async () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText('$300')).toBeInTheDocument();
+ expect(screen.getByText('Test Product')).toBeInTheDocument();
+ expect(screen.getByText('Size')).toBeInTheDocument();
+ expect(screen.getByText('3')).toBeInTheDocument();
+ });
+});
diff --git a/src/__test__/productDetails.test.tsx b/src/__test__/productDetails.test.tsx
index f60ae28f..58d5e76f 100644
--- a/src/__test__/productDetails.test.tsx
+++ b/src/__test__/productDetails.test.tsx
@@ -3,13 +3,13 @@ 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 { fireEvent, 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 signInReducer, { SignInState } from '@/features/Auth/SignInSlice';
import { AppDispatch, RootState } from '@/app/store';
import ProductDetails from '@/pages/ProductDetails';
import bestSellingReducer from '@/features/Popular/bestSellingProductSlice';
@@ -24,8 +24,22 @@ const mockProduct = {
totalQtySold: 25,
longDesc: 'This is a mock product used for testing purposes.',
shortDesc: 'This is a short description',
- category: 'Electronics',
- similarProducts: [],
+ category: {
+ id: 5,
+ name: 'Electronics',
+ },
+ similarProducts: [
+ {
+ id: 3,
+ name: 'Mock Similar Product',
+ image: '/images/mock.png',
+ averageRating: 0,
+ salesPrice: 100,
+ regularPrice: 200,
+ longDesc: 'This is a mock product used for testing purposes.',
+ shortDesc: 'This is a short description',
+ },
+ ],
reviews: [
{
id: 1,
@@ -42,7 +56,8 @@ const mockProduct = {
gallery: [],
tags: ['testTag'],
vendor: {
- name: 'Tester',
+ firstName: 'Tester',
+ lastName: 'Testing',
email: 'testervendor@gmail.com',
picture: 'https://fake.png',
},
@@ -57,6 +72,22 @@ const renderWithProviders = (
bestSellingProducts: bestSellingReducer,
signIn: signInReducer,
},
+ preloadedState: {
+ signIn: {
+ token: 'fake token',
+ user: null,
+ role: null,
+ loading: null,
+ error: null,
+ message: null,
+ needsVerification: false,
+ needs2FA: false,
+ vendor: {
+ id: null,
+ email: null,
+ },
+ } as unknown as SignInState,
+ },
}),
} = {}
) => {
@@ -101,6 +132,12 @@ describe('ProductDetails Page', () => {
expect(screen.getByText(/\$149.99/i)).toBeInTheDocument();
expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('25')).toBeInTheDocument();
+ expect(screen.getByText('33% Off')).toBeInTheDocument();
+ expect(screen.getByText('Add to Cart')).toBeInTheDocument();
+ expect(screen.getByText('Add to wishlist')).toBeInTheDocument();
+ // expect(screen.getAllByTestId('ratingStar').length).toBe(4);
+ // expect(screen.getAllByTestId('halfStar').length).toBe(1);
+
expect(
screen.getByText(/This is a mock product used for testing purposes./i)
).toBeInTheDocument();
@@ -123,6 +160,128 @@ describe('ProductDetails Page', () => {
).toBeInTheDocument();
});
});
+
+ it('should display similar products', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, { product: mockProduct });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(screen.getByText('Mock Similar Prod...')).toBeInTheDocument();
+ expect(screen.getByText('$100')).toBeInTheDocument();
+ expect(screen.getByText('50% Off')).toBeInTheDocument();
+ });
+ });
+
+ it('should display product details, reviews, about store', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, { product: mockProduct });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(screen.getByText('Product Details')).toBeInTheDocument();
+ expect(
+ screen.getByText('This is a mock product used for testing purposes.')
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('This is a short description')
+ ).toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByRole('button', { name: 'Reviews (1)' }));
+
+ await waitFor(() => {
+ expect(screen.getByText('new user')).toBeInTheDocument();
+ expect(screen.getByText('excellent product')).toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByRole('button', { name: 'About Store' }));
+
+ await waitFor(() => {
+ expect(screen.getAllByText('Tester Testing')[0]).toBeInTheDocument();
+ expect(
+ screen.getAllByText('testervendor@gmail.com')[0]
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('should display error message when no reviews are found', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, { product: { ...mockProduct, reviews: [] } });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ fireEvent.click(screen.getByRole('button', { name: 'Reviews (0)' }));
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText('No reviews found')).toBeInTheDocument();
+ });
+ });
+
+ it('should submit a review successfully', async () => {
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, { product: mockProduct });
+
+ mock
+ .onPost(`${import.meta.env.VITE_BASE_URL}/review`)
+ .reply(200, { message: 'Review submitted successfully' });
+
+ renderWithProviders();
+
+ mock
+ .onGet(`${import.meta.env.VITE_BASE_URL}/buyer/get_product/1`)
+ .reply(200, {
+ product: {
+ ...mockProduct,
+ reviews: [
+ {
+ id: 1,
+ user: {
+ id: 1,
+ firstName: 'new',
+ lastName: 'rating',
+ picture: 'http://fake.png',
+ },
+ rating: 1,
+ content: 'this is a bad product',
+ },
+ ],
+ },
+ });
+
+ await waitFor(() => {
+ const star = screen.getAllByTitle('inputStar')[0];
+ fireEvent.click(star);
+ const contentTextArea = screen.getByTitle('inputContent');
+ fireEvent.change(contentTextArea, {
+ target: {
+ value: 'this is a bad product',
+ },
+ });
+ });
+
+ await waitFor(() => {
+ const submitBtn = screen.getByText('Submit');
+ fireEvent.click(submitBtn);
+ });
+
+ await waitFor(() => {
+ fireEvent.click(screen.getByRole('button', { name: 'Reviews (1)' }));
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText('this is a bad product')).toBeInTheDocument();
+ expect(screen.getByText('new rating')).toBeInTheDocument();
+ });
+ });
});
describe('Product Details async action', () => {
diff --git a/src/__test__/shop.test.tsx b/src/__test__/shop.test.tsx
index d6fe8d26..62f70e81 100644
--- a/src/__test__/shop.test.tsx
+++ b/src/__test__/shop.test.tsx
@@ -108,6 +108,19 @@ describe('Shop Component', () => {
expect(screen.queryByText(/Product 2/i)).not.toBeInTheDocument();
});
});
+
+ it('displays the filter section on mobile devices', async () => {
+ renderWithProviders();
+ const filterBtn = screen.getByTitle('filter');
+ fireEvent.click(filterBtn);
+
+ await waitFor(() => {
+ expect(screen.getAllByText('Filters')[1]).toBeInTheDocument();
+ expect(screen.getAllByText('Clear All')[1]).toBeInTheDocument();
+ expect(screen.getAllByText('Categories')[1]).toBeInTheDocument();
+ expect(screen.getAllByText('Rating')[1]).toBeInTheDocument();
+ });
+ });
});
describe('ProductSlice', () => {
diff --git a/src/features/Auth/SignInSlice.ts b/src/features/Auth/SignInSlice.ts
index 79120e39..d29c48eb 100644
--- a/src/features/Auth/SignInSlice.ts
+++ b/src/features/Auth/SignInSlice.ts
@@ -16,7 +16,7 @@ interface User {
};
}
-interface SignInState {
+export interface SignInState {
token: string | null;
user: User | null;
loading: boolean;
diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx
index aed5e19a..9d1bd5f1 100644
--- a/src/pages/ProductDetails.tsx
+++ b/src/pages/ProductDetails.tsx
@@ -664,6 +664,7 @@ function ProductDetails() {
{Array.from({ length: 5 }, (_, i) => (
*
setToggleFilterMenu(true)}
className="cursor-pointer"