Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Increase test coverage #132

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/__test__/Cart/Cart.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { configureStore } from '@reduxjs/toolkit';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { Provider } from 'react-redux';
import { render, screen } from '@testing-library/react';
import cartReducer, {
fetchCartItems,
addCartItem,
updateCartItemQuantity,
removeCartItem,
} from '@/features/Cart/cartSlice';
import CartItem from '@/components/Cart/CartItem';

describe('cartSlice', () => {
let store = configureStore({ reducer: { cartItems: cartReducer } });
Expand Down Expand Up @@ -79,3 +82,22 @@ describe('cartSlice', () => {
expect(state.error).toBeNull();
});
});

describe('Cart component', () => {
const store = configureStore({
reducer: {},
});

it('renders cart item', async () => {
render(
<Provider store={store}>
<CartItem id={1} price={100} name="Test Product" quantity={3} />
</Provider>
);

expect(screen.getByText('$300')).toBeInTheDocument();
expect(screen.getByText('Test Product')).toBeInTheDocument();
expect(screen.getByText('Size')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});
});
169 changes: 164 additions & 5 deletions src/__test__/productDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -42,7 +56,8 @@ const mockProduct = {
gallery: [],
tags: ['testTag'],
vendor: {
name: 'Tester',
firstName: 'Tester',
lastName: 'Testing',
email: '[email protected]',
picture: 'https://fake.png',
},
Expand All @@ -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,
},
}),
} = {}
) => {
Expand Down Expand Up @@ -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();
Expand All @@ -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(<ProductDetails />);

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(<ProductDetails />);

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('[email protected]')[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(<ProductDetails />);

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(<ProductDetails />);

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', () => {
Expand Down
13 changes: 13 additions & 0 deletions src/__test__/shop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Shop />);
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', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/salesMap/SalesMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function SalesMap() {
(label as any).html(
`<div style="background-color: white; border: 1px solid white; outline: 10px solid white; border-radius: 6px; min-height: 70px; width: 150px; color: black; padding-left: 10px;">
<p><b>${(label as any).html()}</b></p>
<p>Sales: <b>${Data ? Data[code] : 0}</b></p>
<p>Sales: <b>${code in Data ? Data[code] : 0}</b></p>
</div>`
);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/features/Auth/SignInSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface User {
};
}

interface SignInState {
export interface SignInState {
token: string | null;
user: User | null;
loading: boolean;
Expand Down
27 changes: 26 additions & 1 deletion src/features/Orders/ordersSlice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';
import Order from '@/interfaces/order';
import { RootState } from '../../app/store';
import { RootState, store } from '../../app/store';

interface OrdersState {
orders: Order[];
Expand Down Expand Up @@ -35,6 +35,31 @@ export const fetchOrders = createAsyncThunk('orders/fetchOrders', async () => {
},
};
});

const { signIn } = store.getState();

if (signIn.user?.userType.name === 'Admin') {
return orders;
}
if (signIn.user?.userType.name === 'Vendor') {
const filteredOrders: Order[] = [];
/* eslint-disable no-restricted-syntax */
for (const order of orders) {
const newDetails = [];
/* eslint-disable no-restricted-syntax */
for (const orderDetail of order.orderDetails) {
if (orderDetail.product.vendor.id === signIn.user.id) {
newDetails.push(orderDetail);
}
}

if (newDetails.length > 0) {
filteredOrders.push({ ...order, orderDetails: newDetails });
}
}

return filteredOrders;
}
return orders;
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Coupons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ function Coupons() {
<IoIosSearch size={24} />
<input
id="searchInput"
placeholder="Search order..."
placeholder="Search coupons..."
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/ProductDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ function ProductDetails() {
<div className="flex items-center gap-2">
{Array.from({ length: 5 }, (_, i) => (
<FaStar
title="inputStar"
size="25"
color={
review.rating && i + 1 <= review.rating
Expand All @@ -706,6 +707,7 @@ function ProductDetails() {
Your review<span className="text-red-700"> *</span>
</h2>
<textarea
title="inputContent"
className="xs:w-full lg:w-4/5 h-40 rounded-md border-[1.5px] outline-none border-textareaBorder"
onChange={(e) =>
setReview({
Expand Down
1 change: 1 addition & 0 deletions src/pages/Shop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ function Shop() {
</div>
<div className="xs:flex lg:hidden pl-2 py-2 mt-4 items-center justify-start gap-2 rounded-lg w-full bg-grayLight">
<CiFilter
title="filter"
size={20}
onClick={() => setToggleFilterMenu(true)}
className="cursor-pointer"
Expand Down
19 changes: 16 additions & 3 deletions src/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,22 @@ function AppRoutes() {
}
/>
<Route path="product-details/:id" element={<ProductDetails />} />
<Route path="wishlist" element={<Wishlist />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<CheckoutPage />} />
<Route
path="cart"
element={
<ProtectedRoute roles={['Buyer']}>
<Cart />
</ProtectedRoute>
}
/>
<Route
path="checkout"
element={
<ProtectedRoute roles={['Buyer']}>
<CheckoutPage />
</ProtectedRoute>
}
/>
</Route>
<Route path="/signup" element={<SignUp />} />
<Route path="/signIn" element={<SignIn />} />
Expand Down
Loading
Loading