From 362caba696df1fd070911e98a6061cd79435693c Mon Sep 17 00:00:00 2001 From: Joslyn Manzi Karenzi Date: Fri, 26 Jul 2024 15:55:12 +0200 Subject: [PATCH] fix(test-cov): increase test coverage (#132) - add more tests to cover uncovered lines [#Fixes #124] --- src/__test__/Cart/Cart.test.tsx | 22 ++++ src/__test__/productDetails.test.tsx | 169 ++++++++++++++++++++++++++- src/__test__/shop.test.tsx | 13 +++ src/components/salesMap/SalesMap.tsx | 2 +- src/features/Auth/SignInSlice.ts | 2 +- src/features/Orders/ordersSlice.ts | 27 ++++- src/pages/Coupons.tsx | 2 +- src/pages/ProductDetails.tsx | 2 + src/pages/Shop.tsx | 1 + src/routes/AppRoutes.tsx | 19 ++- vite.config.ts | 8 ++ 11 files changed, 255 insertions(+), 12 deletions(-) diff --git a/src/__test__/Cart/Cart.test.tsx b/src/__test__/Cart/Cart.test.tsx index ee4867af..0c3d5611 100644 --- a/src/__test__/Cart/Cart.test.tsx +++ b/src/__test__/Cart/Cart.test.tsx @@ -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 } }); @@ -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/components/salesMap/SalesMap.tsx b/src/components/salesMap/SalesMap.tsx index fc3039f6..b7e8288b 100644 --- a/src/components/salesMap/SalesMap.tsx +++ b/src/components/salesMap/SalesMap.tsx @@ -53,7 +53,7 @@ function SalesMap() { (label as any).html( `

${(label as any).html()}

-

Sales: ${Data ? Data[code] : 0}

+

Sales: ${code in Data ? Data[code] : 0}

` ); }} 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/features/Orders/ordersSlice.ts b/src/features/Orders/ordersSlice.ts index 2918ba7f..1c01b27c 100644 --- a/src/features/Orders/ordersSlice.ts +++ b/src/features/Orders/ordersSlice.ts @@ -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[]; @@ -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; }); diff --git a/src/pages/Coupons.tsx b/src/pages/Coupons.tsx index 2842afa1..f139312a 100644 --- a/src/pages/Coupons.tsx +++ b/src/pages/Coupons.tsx @@ -147,7 +147,7 @@ function Coupons() { setSearchTerm(e.target.value)} diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx index 37d2583e..1580c9be 100644 --- a/src/pages/ProductDetails.tsx +++ b/src/pages/ProductDetails.tsx @@ -689,6 +689,7 @@ function ProductDetails() {
{Array.from({ length: 5 }, (_, i) => ( *