Skip to content

Commit

Permalink
bg-fix(search):A buyer should be able to search for products
Browse files Browse the repository at this point in the history
  • Loading branch information
PrinceRWIGIMBA committed Jul 12, 2024
1 parent f6cfec4 commit e261d81
Show file tree
Hide file tree
Showing 14 changed files with 507 additions and 192 deletions.
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/product-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions src/__tests__/landingpage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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', () => () => <div>Side Component</div>);
jest.mock('@/components/Header', () => () => <div>Header Component</div>);
jest.mock('@/components/Footer', () => () => <div>Footer Component</div>);
jest.mock('@/components/ProductList', () => ({ searchResults }: { searchResults: any[] }) => (
<div>Product List with {searchResults.length} results</div>
));

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

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

});

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

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


});
});
26 changes: 1 addition & 25 deletions src/__tests__/productList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,5 @@ describe('ProductList', () => {
</Provider>,
);
expect(await findByText('test')).toBeInTheDocument();
});
it('renders Page without crashing', async () => {
const { findByText } = render(
<Provider>
<Page />
</Provider>,
);
expect(await findByText('All Products')).toBeInTheDocument();
});
it('renders single Page without crashing', async () => {
const { findByText } = render(
<Provider>
<Page />
</Provider>,
);
expect(await findByText('All Products')).toBeInTheDocument();
});
it('should render productList', async () => {
const { getByText } = render(
<QueryClientProvider client={queryClient}>
<SideBar />
</QueryClientProvider>,
);
expect(getByText('Filter Product')).toBeInTheDocument();
});
});
});
2 changes: 1 addition & 1 deletion src/__tests__/reset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ describe('ResetPassword component', () => {
);
expect(getByText("Please insert your new password you'd like to use")).toBeInTheDocument();
});
});
});
50 changes: 50 additions & 0 deletions src/__tests__/userCart.test.tsx
Original file line number Diff line number Diff line change
@@ -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');
  });
});
2 changes: 1 addition & 1 deletion src/app/products/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,4 @@ function Page() {
</div>
);
}
export default Page;
export default Page;
120 changes: 93 additions & 27 deletions src/app/products/page.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,112 @@
'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 (
<>
<Header/>

<div className="w-full h-[50%] flex flex-col px-10 py-5">
<div className="w-full flex justify-between px-10">
<h1>All Products</h1>
<div className="flex self-end">
<select name="" id="" className="border">
<option value="volvo">Popular</option>
<option value="saab">Recent</option>
<option value="opel">Clothes</option>
<option value="audi">Electronics</option>
</select>
<Header />
<div className="w-full flex flex-col px-4 py-5 sm:px-10 sm:py-5">
<div className="w-full flex flex-col sm:flex-row justify-between px-4 sm:px-10">
<h1 className="text-lg sm:text-2xl">All Products</h1>
<div className="flex flex-col sm:flex-row self-end mt-4 sm:mt-0">
<form onSubmit={handleSearch} className="flex items-center">
<div className="relative flex items-center border px-2 py-1">
<input type="text" name="search" placeholder="Search products" className="border-none outline-none"
value={Value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit" className="flex items-center justify-center bg-transparent text-[#8F8F8F] absolute right-2">
<AiOutlineSearch size={24} />
</button>
</div>
</form>
</div>
</div>
<div className="w-full flex mx-auto px-10">
<div className="w-full flex flex-col sm:flex-row mx-auto px-4 sm:px-10">
<Side />
<ProductList />
<div className="flex flex-wrap w-full">
{searched && searchResults.length === 0 ? (
<p>No product found</p>
) : (
<ProductList searchResults={searchResults} />
)}
</div>
</div>
</div>
<Footer />

</>
);
}

export default Page;
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SuspenseWrapper />
</Suspense>
);
}
Loading

0 comments on commit e261d81

Please sign in to comment.