diff --git a/package-lock.json b/package-lock.json index 03205a6..33641f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,6 +149,12 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.10.tgz", "integrity": "sha512-73xxTSYJNKfiJ7C1Ajg+sz5l8y+blb/vNgHYg7O3yem5zLBnfPpidJ1UGg4W4d2Y+jwUVJbZKh8SKJarqAJVUQ==", "peer": true, + "node_modules/@angular/common": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.10.tgz", + "integrity": "sha512-73xxTSYJNKfiJ7C1Ajg+sz5l8y+blb/vNgHYg7O3yem5zLBnfPpidJ1UGg4W4d2Y+jwUVJbZKh8SKJarqAJVUQ==", + "license": "MIT", + "peer": true, "dependencies": { "tslib": "^1.9.0" }, @@ -161,6 +167,7 @@ "version": "6.1.10", "resolved": "https://registry.npmjs.org/@angular/core/-/core-6.1.10.tgz", "integrity": "sha512-61l3rIQTVdT45eOf6/fBJIeVmV10mcrxqS4N/1OWkuDT29YSJTZSxGcv8QjAyyutuhcqWWpO6gVRkN07rWmkPg==", + "license": "MIT", "peer": true, "dependencies": { "tslib": "^1.9.0" @@ -170,6 +177,19 @@ "zone.js": "~0.8.26" } }, + "node_modules/@aw-web-design/x-default-browser": { + "version": "1.4.126", + "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", + "integrity": "sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/core": "6.1.10", + "rxjs": "^6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -29572,4 +29592,5 @@ "peer": true } } + } } diff --git a/public/product-default.png b/public/product-default.png new file mode 100644 index 0000000..1a0d06d Binary files /dev/null and b/public/product-default.png differ diff --git a/src/__tests__/landingpage.test.tsx b/src/__tests__/landingpage.test.tsx new file mode 100644 index 0000000..3af5e9a --- /dev/null +++ b/src/__tests__/landingpage.test.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Page from '@/app/products/page'; +import * as nextNavigation from 'next/navigation'; +import request from '@/utils/axios'; + +// Mock dependencies +jest.mock('next/navigation', () => ({ + useSearchParams: jest.fn(), +})); + +jest.mock('@/utils/axios', () => ({ + get: jest.fn(), +})); + +jest.mock('@/components/Side', () => () =>
Side Component
); +jest.mock('@/components/Header', () => () =>
Header Component
); +jest.mock('@/components/Footer', () => () =>
Footer Component
); +jest.mock('@/components/ProductList', () => ({ searchResults }: { searchResults: any[] }) => ( +
Product List with {searchResults.length} results
+)); + +describe('Page Component', () => { + it('renders without crashing and fetches search results', async () => { + const mockSearchParams = { + toString: jest.fn().mockReturnValue(''), + }; + const mockResponse = [{ id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }]; + + (nextNavigation.useSearchParams as jest.Mock).mockReturnValue(mockSearchParams); + (request.get as jest.Mock).mockResolvedValue(mockResponse); + + render(); + + // Wait for search results to be fetched and rendered + await waitFor(() => { + expect(screen.getByText('Header Component')).toBeInTheDocument(); + expect(screen.getByText('Footer Component')).toBeInTheDocument(); + expect(screen.getByText('Product List with 2 results')).toBeInTheDocument(); + expect(screen.getByText('Side Component')).toBeInTheDocument(); + }); + + // Verify that the select options are rendered + expect(screen.getByRole('combobox')).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Popular' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Recent' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Clothes' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Electronics' })).toBeInTheDocument(); + }); + + it('handles error in fetching search results', async () => { + const mockSearchParams = { + toString: jest.fn().mockReturnValue(''), + }; + const mockError = new Error('Network Error'); + + (nextNavigation.useSearchParams as jest.Mock).mockReturnValue(mockSearchParams); + (request.get as jest.Mock).mockRejectedValue(mockError); + + render(); + + // Wait for error handling + await waitFor(() => { + expect(screen.getByText('Header Component')).toBeInTheDocument(); + expect(screen.getByText('Footer Component')).toBeInTheDocument(); + expect(screen.getByText('Product List with 0 results')).toBeInTheDocument(); + expect(screen.getByText('Side Component')).toBeInTheDocument(); + }); + + // Verify that the select options are rendered + expect(screen.getByRole('combobox')).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Popular' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Recent' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Clothes' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Electronics' })).toBeInTheDocument(); + }); +}); diff --git a/src/__tests__/productList.test.tsx b/src/__tests__/productList.test.tsx index c2e0e12..938e091 100644 --- a/src/__tests__/productList.test.tsx +++ b/src/__tests__/productList.test.tsx @@ -37,29 +37,5 @@ describe('ProductList', () => { , ); expect(await findByText('test')).toBeInTheDocument(); - }); - it('renders Page without crashing', async () => { - const { findByText } = render( - - - , - ); - expect(await findByText('All Products')).toBeInTheDocument(); - }); - it('renders single Page without crashing', async () => { - const { findByText } = render( - - - , - ); - expect(await findByText('All Products')).toBeInTheDocument(); - }); - it('should render productList', async () => { - const { getByText } = render( - - - , - ); - expect(getByText('Filter Product')).toBeInTheDocument(); - }); +}); }); diff --git a/src/__tests__/reset.test.tsx b/src/__tests__/reset.test.tsx index b2e00ce..449ad5c 100644 --- a/src/__tests__/reset.test.tsx +++ b/src/__tests__/reset.test.tsx @@ -61,4 +61,4 @@ describe('ResetPassword component', () => { ); expect(getByText("Please insert your new password you'd like to use")).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/src/__tests__/userCart.test.tsx b/src/__tests__/userCart.test.tsx new file mode 100644 index 0000000..9ab0d80 --- /dev/null +++ b/src/__tests__/userCart.test.tsx @@ -0,0 +1,50 @@ +import { renderHook } from '@testing-library/react'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { useDispatch } from 'react-redux'; +import { useQueryClient } from '@tanstack/react-query'; +import { handleFetchUserCart, handleCartInfoManipulation } from '@/hooks/userCart'; +import { ProductType } from '@/types/Product'; + +// Mocking useDispatch and useQueryClient +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), +})); + +jest.mock('@tanstack/react-query', () => ({ + useQueryClient: jest.fn(), + useQuery: jest.fn(), +})); + + +describe('handleCartInfoManipulation', () => { + it('manipulates cart information correctly', () => { + const mockCart: any = { + product: [{ product: '1', quantity: 2 }], + }; + const mockProducts: ProductType[] = [ + { + id: '1', productName: 'Product 1', productPrice: 10, productDescription: 'Description 1', productThumbnail: 'thumb1.jpg', + productCurrency: '', + productPictures: [], + reviews: [], + related: undefined + }, + { + id: '2', productName: 'Product 2', productPrice: 20, productDescription: 'Description 2', productThumbnail: 'thumb2.jpg', + productCurrency: '', + productPictures: [], + reviews: [], + related: undefined + }, + ]; + + const manipulatedCart = handleCartInfoManipulation(mockCart, mockProducts); + + expect(manipulatedCart).toHaveLength(1); + expect(manipulatedCart[0].name).toEqual('Product 1'); + expect(manipulatedCart[0].unitPrice).toEqual(10); + expect(manipulatedCart[0].description).toEqual('Description 1'); + expect(manipulatedCart[0].thumbnail).toEqual('thumb1.jpg'); +  }); +}); \ No newline at end of file diff --git a/src/app/products/[id]/page.tsx b/src/app/products/[id]/page.tsx index 09bcad4..474d593 100644 --- a/src/app/products/[id]/page.tsx +++ b/src/app/products/[id]/page.tsx @@ -230,4 +230,4 @@ function Page() { ); } -export default Page; +export default Page; \ No newline at end of file diff --git a/src/app/products/page.tsx b/src/app/products/page.tsx index 9441974..fd03fec 100644 --- a/src/app/products/page.tsx +++ b/src/app/products/page.tsx @@ -1,46 +1,118 @@ -'use client'; -import React from 'react'; +'use client' +import React, { useState, useEffect, Suspense } from 'react'; +import { AiOutlineSearch } from 'react-icons/ai'; import Side from '../../components/Side'; import Header from '@/components/Header'; import Footer from '@/components/Footer'; import ProductList from '@/components/ProductList'; -import { checkIsAdmin } from '@/components/isAdmin'; +import { useSearchParams } from 'next/navigation'; +import request from '@/utils/axios'; +function SuspenseWrapper() { + const searchParams = useSearchParams(); + const [Value, setValue] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [searched, setSearched] = useState(false); // New state for tracking if a search has been performed -function Page() { - // const isAdmin = checkIsAdmin(); - - // if (isAdmin) { - // console.log('User is an admin!'); - // } else { - // console.log('User is not an admin.'); - // } + useEffect(() => { + const fetchSearchResults = async () => { + const queryParams = new URLSearchParams(searchParams.toString()); + if (searchQuery) { + queryParams.set('query', searchQuery); + } + try { + const response: any = await request.get(`/search?${queryParams.toString()}`); + setSearchResults(response); + setSearched(true); + } catch (error) { + console.error('Error fetching search results:', error); + } + }; + + fetchSearchResults(); + }, [searchParams, searchQuery]); + + const handleSearch = async (e: { preventDefault: () => void }) => { + e.preventDefault(); + console.log("Search query:", Value); + + try { + let queryParams: any = {}; + const trimmedValue = Value.trim(); + const numericValue = parseFloat(trimmedValue); + + if (!isNaN(numericValue)) { + queryParams.minPrice = numericValue; + } else if (!isNaN(numericValue)) { + queryParams.maxPrice = numericValue; + } else if (!isNaN(numericValue)) { + queryParams.minPrice = numericValue; + queryParams.maxPrice = numericValue; + } else if (trimmedValue.length > 0) { + queryParams.name = Value; + } else if (trimmedValue.length > 0) { + queryParams.category = Value; + } + + const queryString = new URLSearchParams(queryParams).toString(); + console.log(queryString); + const url = `/search?${new URLSearchParams(queryParams).toString()}`; + const response: any = await request.get(url); + + setSearchResults(response); + setSearched(true); + } catch (error) { + console.error('Error fetching search results:', error); + } + }; return ( <> -
- +

All Products

- setValue(e.target.value)} + /> + +
+ +
- +
+ {searched && searchResults.length === 0 ? ( +

No product found

+ ) : ( + + )} +