Skip to content

Commit

Permalink
feat(wishlist): implement buyer wishlist
Browse files Browse the repository at this point in the history
- implement wishlist UI components

[Delivers #96]
  • Loading branch information
jkarenzi committed Jul 18, 2024
1 parent bcb61cb commit 379446e
Show file tree
Hide file tree
Showing 15 changed files with 542 additions and 22 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"react-loading-skeleton": "^3.4.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-slider": "^2.0.6",
"react-simple-maps": "^3.0.0",
"react-slider": "^2.0.6",
"react-spinners": "^0.14.1",
"react-toastify": "^10.0.5",
"redux": "^5.0.1",
Expand All @@ -60,8 +60,8 @@
"@types/node": "^20.14.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.2.22",
"@types/react-slider": "^1.3.6",
"@types/react-simple-maps": "^3.0.4",
"@types/react-slider": "^1.3.6",
"@types/redux-mock-store": "^1.0.6",
"@types/testing-library__react": "^10.2.0",
"@types/webfontloader": "^1.6.38",
Expand Down
33 changes: 28 additions & 5 deletions src/__test__/home/ProductGridFour.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { describe, it, expect } from 'vitest';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import ProductGridFour from '@/components/home/ProductGridFour';
import { Product } from '@/types/Product';
import User from '@/types/User';
import signInReducer from '@/features/Auth/SignInSlice';

// Mock Product Data
const mockProducts: Product[] = [
Expand Down Expand Up @@ -149,34 +153,53 @@ const mockProducts: Product[] = [
},
];

const renderWithProviders = (
ui: React.ReactElement,
{
store = configureStore({
reducer: {
signIn: signInReducer,
},
}),
} = {}
) => {
return render(
<Provider store={store}>
<MemoryRouter>{ui}</MemoryRouter>
</Provider>
);
};

describe('ProductGridFour Component', () => {
it('renders ProductGridFour with up to 4 products', () => {
render(<ProductGridFour products={mockProducts.slice(0, 4)} />);
renderWithProviders(
<ProductGridFour products={mockProducts.slice(0, 4)} />
);

// Check that exactly 4 products are displayed
expect(screen.getAllByText(/Sample Product/).length).toBe(4);
});

it('handles empty product list', () => {
render(<ProductGridFour products={[]} />);
renderWithProviders(<ProductGridFour products={[]} />);

// Check that "No Products Found" message is displayed
expect(screen.getByText(/No Products Found/i)).toBeInTheDocument();
});

it('renders the correct product details', () => {
render(<ProductGridFour products={mockProducts} />);
renderWithProviders(<ProductGridFour products={mockProducts} />);

// Check that product details are displayed correctly
mockProducts.slice(0, 4).forEach((product) => {
expect(
screen.getByText(
`${product.name.substring(0, 17)}${product.name.length > 18 ? '...' : ''}`
`${product.name.substring(0, 17)}${product.name.length > 17 ? '...' : ''}`
)
).toBeInTheDocument();
expect(
screen.getAllByText(
`${product.shortDesc.substring(0, 27)}${product.shortDesc.length > 28 ? '...' : ''}`
`${product.shortDesc.substring(0, 27)}${product.shortDesc.length > 27 ? '...' : ''}`
)[0]
).toBeInTheDocument();
expect(screen.getByAltText(product.name)).toBeInTheDocument();
Expand Down
27 changes: 24 additions & 3 deletions src/__test__/home/productCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { describe, it, expect } from 'vitest';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import ProductCard from '@/components/home/ProductCard';
import { Product } from '@/types/Product';
import User from '@/types/User';
import signInReducer from '@/features/Auth/SignInSlice';

// Mock Product Data
const mockProduct: Product = {
Expand Down Expand Up @@ -35,17 +39,34 @@ const mockProduct: Product = {
} as User,
};

const renderWithProviders = (
ui: React.ReactElement,
{
store = configureStore({
reducer: {
signIn: signInReducer,
},
}),
} = {}
) => {
return render(
<Provider store={store}>
<MemoryRouter>{ui}</MemoryRouter>
</Provider>
);
};

describe('ProductCard Component', () => {
it('renders the ProductCard component with product details', () => {
render(<ProductCard product={mockProduct} />);
renderWithProviders(<ProductCard product={mockProduct} />);

const productName = screen.getByText(
`${mockProduct.name.substring(0, 17)}${mockProduct.name.length > 18 ? '...' : ''}`
`${mockProduct.name.substring(0, 17)}${mockProduct.name.length > 17 ? '...' : ''}`
);
expect(productName).toBeInTheDocument();

const productDesc = screen.getByText(
`${mockProduct.shortDesc.substring(0, 27)}${mockProduct.shortDesc.length > 28 ? '...' : ''}`
`${mockProduct.shortDesc.substring(0, 27)}${mockProduct.shortDesc.length > 27 ? '...' : ''}`
);
expect(productDesc).toBeInTheDocument();

Expand Down
7 changes: 5 additions & 2 deletions src/__test__/home/productList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ describe('ProductsList Component', () => {

beforeEach(() => {
store = mockStore({
signIn: {
token: 'fake token',
},
products: {
products: mockProducts,
},
Expand All @@ -89,12 +92,12 @@ describe('ProductsList Component', () => {

mockProducts.forEach((product) => {
const productName = screen.getByText(
`${product.name.substring(0, 17)}${product.name.length > 18 ? '...' : ''}`
`${product.name.substring(0, 17)}${product.name.length > 17 ? '...' : ''}`
);
expect(productName).toBeInTheDocument();

const productDesc = screen.getByText(
`${product.shortDesc.substring(0, 27)}${product.shortDesc.length > 28 ? '...' : ''}`
`${product.shortDesc.substring(0, 27)}${product.shortDesc.length > 27 ? '...' : ''}`
);
expect(productDesc).toBeInTheDocument();

Expand Down
2 changes: 2 additions & 0 deletions src/__test__/shop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import productsReducer, {
import categoryReducer from '@/features/Products/categorySlice';
import Shop from '@/pages/Shop';
import { AppDispatch, RootState } from '@/app/store';
import signInReducer from '@/features/Auth/SignInSlice';

const mock = new MockAdapter(axios);

Expand All @@ -24,6 +25,7 @@ const renderWithProviders = (
reducer: {
products: productsReducer,
categories: categoryReducer,
signIn: signInReducer,
},
}),
} = {}
Expand Down
186 changes: 186 additions & 0 deletions src/__test__/wishlist.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React from 'react';
import { describe, it, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import { MemoryRouter } from 'react-router-dom';
import productReducer, {
fetchWishlistProducts,
addToWishlist,
removeFromWishlist,
} from '@/features/Products/ProductSlice';
import categoryReducer from '@/features/Products/categorySlice';
import signInReducer from '@/features/Auth/SignInSlice';
import WishlistCard from '@/components/WishlistCard';
import Wishlist from '@/pages/Wishlist';
import { AppDispatch, RootState } from '@/app/store';
import { Product } from '@/types/Product';

const mock = new MockAdapter(axios);

const renderWithProviders = (
ui: React.ReactElement,
{
store = configureStore({
reducer: {
products: productReducer,
categories: categoryReducer,
signIn: signInReducer,
},
}),
} = {}
) => {
return render(
<Provider store={store}>
<MemoryRouter>{ui}</MemoryRouter>
</Provider>
);
};

const product = {
id: 1,
name: 'Product name',
shortDesc: 'Short description',
salesPrice: 230,
regularPrice: 280,
averageRating: 4.7,
image: 'path_to_image',
gallery: ['path_to_image', 'path_to_image'],
category: {
id: 1,
name: 'Category name',
description: 'Category description',
},
longDesc: 'Long description',
quantity: 10,
tags: ['tag1', 'tag2'],
type: 'Simple',
isAvailable: true,
isFeatured: true,
reviews: [],
vendor: {
id: 1,
firstName: 'Vendor',
lastName: 'Name',
email: 'example@gmail.com',
password: 'hashed_password',
userType: {
id: 1,
name: 'Vendor',
permissions: ['manage_products', 'view_orders'],
},
orders: [],
googleId: '',
facebookId: '',
picture: 'path_to_picture',
provider: 'local',
isVerified: true,
status: 'active',
},
} as Product;

describe('Wishlist async actions', () => {
let store: ReturnType<typeof configureStore>;

beforeEach(() => {
store = configureStore({
reducer: {
products: productReducer,
},
});
mock.reset();
});

it('fetches wishlist products', async () => {
const products = [
{ id: 1, name: 'Product 1' },
{ id: 2, name: 'Product 2' },
];
mock
.onGet(`${import.meta.env.VITE_BASE_URL}/buyer/getOneWishlist`)
.reply(200, {
data: { product: products },
});

const { dispatch }: { dispatch: AppDispatch } = store;
await dispatch(fetchWishlistProducts('token'));

const state = (store.getState() as RootState).products;
expect(state.wishlistProducts).toEqual(products);
});

it('adds a product to the wishlist', async () => {
const products = [{ id: 1, name: 'Product 1' }];
mock
.onPost(`${import.meta.env.VITE_BASE_URL}/buyer/addItemToWishlist`)
.reply(200, {
data: { product: products },
});

const { dispatch }: { dispatch: AppDispatch } = store;
await dispatch(addToWishlist({ id: 1, token: 'token' }));

const state = (store.getState() as RootState).products;
expect(state.wishlistProducts).toEqual(products);
});

it('removes a product from the wishlist', async () => {
mock
.onDelete(`${import.meta.env.VITE_BASE_URL}/buyer/removeToWishlist`)
.reply(200, {
data: { product: [] },
});

const { dispatch }: { dispatch: AppDispatch } = store;
await dispatch(removeFromWishlist({ id: 1, token: 'token' }));

const state = (store.getState() as RootState).products;
expect(state.wishlistProducts).toEqual([]);
});
});

describe('WishlistCard', () => {
it('renders the wishlist card', () => {
renderWithProviders(<WishlistCard product={product} />);

expect(screen.getByText(/Product name/i)).toBeInTheDocument();
expect(screen.getByText(/In Stock/i)).toBeInTheDocument();
expect(screen.getByText(/\$500/i)).toBeInTheDocument();
expect(screen.getByText(/\$700/i)).toBeInTheDocument();
});
});

describe('Wishlist Page', () => {
it('renders wishlist products', async () => {
const products = [product];

mock
.onGet(`${import.meta.env.VITE_BASE_URL}/buyer/getOneWishlist`)
.reply(200, {
data: { product: products },
});

renderWithProviders(<Wishlist />);

await waitFor(() => {
expect(screen.getByText(/My Wishlist/i)).toBeInTheDocument();
expect(screen.getByText(/Product name/i)).toBeInTheDocument();
});
});

it('shows no products message when wishlist is empty', () => {
mock
.onGet(`${import.meta.env.VITE_BASE_URL}/buyer/getOneWishlist`)
.reply(200, {
data: { product: [] },
});

renderWithProviders(<Wishlist />);

expect(
screen.getByText(/You currently have no products in your wishlist/i)
).toBeInTheDocument();
});
});
Loading

0 comments on commit 379446e

Please sign in to comment.