Skip to content

Commit

Permalink
Merge pull request #36 from atlp-rwanda/ft-delete-product
Browse files Browse the repository at this point in the history
Enable to delete a product
  • Loading branch information
faid-terence authored and Calebgisa72 committed Jul 6, 2024
2 parents b6fa239 + aac9a68 commit c510169
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 83 deletions.
2 changes: 1 addition & 1 deletion src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const Router = () => {
path="/"
element={
<MainLayout>
<PageTitle title="Knights Store | Register" />
<PageTitle title="Knights Store" />
<Home />
</MainLayout>
}
Expand Down
119 changes: 118 additions & 1 deletion src/__test__/actions/productActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
updateCoupon,
deleteCoupon,
getCoupon,
createCoupon
createCoupon,
deleteProduct,
updateProduct
} from '../../redux/actions/productAction';
import { ProductActionTypes } from '../../redux/types/productTypes'; // Adjust the path accordingly
import { describe, it, beforeEach } from 'vitest';
Expand Down Expand Up @@ -173,4 +175,119 @@ describe('Product Actions', () => {
expect(actions[1].error.message).toEqual('Failed to update coupon');
}
}, 50000);

it('creates DELETE_PRODUCT_SUCCESS when deleting a product has been done', async () => {
const productId = 'mockedProductId';
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).reply(200);

await store.dispatch(deleteProduct(productId) as any);

expect(actions[0].type).toEqual('products/deleteProduct/pending');
expect(actions[1].type).toEqual('products/deleteProduct/fulfilled');
expect(actions[1].payload).toEqual(productId);
});

it('creates DELETE_PRODUCT_FAIL when deleting a product fails', async () => {
const productId = 'mockedProductId';
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).reply(404);

await store.dispatch(deleteProduct(productId) as any);

expect(actions[0].type).toEqual('products/deleteProduct/pending');
expect(actions[1].type).toEqual('products/deleteProduct/rejected');
expect(actions[1].error?.message).toEqual('Rejected');
});

it('handles unauthorized deletion with expired token', async () => {
const productId = 'mockedProductId';
const expiredToken = 'expiredToken';
localStorage.setItem('userToken', JSON.stringify({ token: expiredToken }));

mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).reply(401);

try {
await store.dispatch(deleteProduct(productId) as any);
} catch (error) {
expect(actions[0].type).toEqual('products/deleteProduct/pending');
expect(actions[1].type).toEqual('products/deleteProduct/rejected');
expect(actions[1].error?.message).toEqual('Unauthorized');
}

// Clear localStorage after unauthorized access
localStorage.removeItem('userToken');

// Ensure localStorage is cleared
expect(localStorage.getItem('userToken')).toBeNull();
});

it('handles network errors when deleting a product', async () => {
const productId = 'mockedProductId';
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).networkError();

try {
await store.dispatch(deleteProduct(productId) as any);
} catch (error) {
expect(actions[0].type).toEqual('products/deleteProduct/pending');
expect(actions[1].type).toEqual('products/deleteProduct/rejected');
expect(actions[1].error?.message).toEqual('Network Error');
}
});

it('creates UPDATE_PRODUCT_SUCCESS when updating a product has been done', async () => {
const productId = 'mockedProductId';
const formData = new FormData();
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onPut(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).reply(200);

await store.dispatch(updateProduct({ id: productId, formData }) as any);

expect(actions[0].type).toEqual('products/updateProduct/pending');
expect(actions[1].type).toEqual('products/updateProduct/fulfilled');
expect(actions[1].payload).toEqual(undefined); // Adjust this based on your actual payload expectation
});

it('creates UPDATE_PRODUCT_FAIL when updating a product fails', async () => {
const productId = 'mockedProductId';
const formData = new FormData();
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onPut(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).reply(404);

try {
await store.dispatch(updateProduct({ id: productId, formData }) as any);
} catch (error: any) {
expect(actions[0].type).toEqual('products/updateProduct/pending');
expect(actions[1].type).toEqual('products/updateProduct/rejected');
expect(error.message).toEqual('Request failed with status code 404'); // Adjust this based on the actual error message structure
}
});

it('handles network errors when updating a product', async () => {
const productId = 'mockedProductId';
const formData = new FormData();
const token = 'mockedToken';
localStorage.setItem('userToken', JSON.stringify({ token }));

mockAxios.onPut(`${import.meta.env.VITE_APP_API_URL}/product/${productId}`).networkError();

try {
await store.dispatch(updateProduct({ id: productId, formData }) as any);
} catch (error) {
expect(actions[0].type).toEqual('products/updateProduct/pending');
expect(actions[1].type).toEqual('products/updateProduct/rejected');
expect(actions[1].error?.message).toEqual('Network Error');
}
});
});
46 changes: 43 additions & 3 deletions src/__test__/components/ProductCard/ProductCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { render, screen } from '@testing-library/react';
import { describe, it } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import ProductsCard from '../../../components/Products/ProductCard/ProductsCard';
import { Provider } from 'react-redux';
import store from '../../../redux/store';

const mockProduct = {
id: '1',
Expand Down Expand Up @@ -37,9 +39,11 @@ const mockProduct = {
describe('ProductsCard', () => {
it('renders the ProductsCard component without crashing', () => {
render(
<MemoryRouter>
<ProductsCard data={mockProduct} />
</MemoryRouter>
<Provider store={store}>
<MemoryRouter>
<ProductsCard data={mockProduct} />
</MemoryRouter>
</Provider>
);

// Check if the product name is displayed
Expand All @@ -63,4 +67,40 @@ describe('ProductsCard', () => {
// Check if the image is rendered
expect(screen.getByAltText('product-image')).toBeInTheDocument();
});

it('renders without crashing when no images are provided', () => {
const mockProductWithoutImages = { ...mockProduct, images: [] };
render(
<Provider store={store}>
<MemoryRouter>
<ProductsCard data={mockProductWithoutImages} />
</MemoryRouter>
</Provider>
);
expect(screen.getByAltText('product-image')).toBeInTheDocument();
});

it('renders the correct category name', () => {
render(
<Provider store={store}>
<MemoryRouter>
<ProductsCard data={mockProduct} />
</MemoryRouter>
</Provider>
);

expect(screen.getByText('Category 1')).toBeInTheDocument();
});

it('renders without crashing when no images are provided', () => {
const mockProductWithoutImages = { ...mockProduct, images: [] };
render(
<Provider store={store}>
<MemoryRouter>
<ProductsCard data={mockProductWithoutImages} />
</MemoryRouter>
</Provider>
);
expect(screen.getByAltText('product-image')).toBeInTheDocument();
});
});
95 changes: 64 additions & 31 deletions src/__test__/components/ProductCard/clientProductCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { describe, it } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import store from '../../../redux/store';
import { Provider } from 'react-redux';
import store from '../../../redux/store';
import ClientProductCard, { ProductProp } from '../../../components/Products/ProductCard/ClientProductCard';
describe('Home( landing page) test', () => {
beforeAll(() => {});
it('renders the Home Page', () => {
const sampleProduct: ProductProp = {
categories: [
{
name: 'cat1',
id: 'testId',
updatedAt: new Date(),
createdAt: new Date(),
products: [
{
id: 'testId'
}
]
}
],
createdAt: new Date(),
description: 'Description',
images: ['image.jpg'],
isAvailable: true,
name: 'product',
newPrice: '2000',
quantity: '2',
updatedAt: new Date(),
vendor: {
firstName: 'seller',
lastName: 'sellerLastName'

describe('ClientProductCard Component', () => {
const sampleProduct: ProductProp = {
categories: [
{
name: 'cat1',
id: 'testId',
updatedAt: new Date(),
createdAt: new Date(),
products: [
{
id: 'testId'
}
]
}
};
],
createdAt: new Date(),
description: 'Description',
images: ['image.jpg'],
isAvailable: true,
name: 'product',
newPrice: '2000',
quantity: '2',
updatedAt: new Date(),
vendor: {
firstName: 'seller',
lastName: 'sellerLastName'
}
};

it('renders product name', () => {
render(
<Provider store={store}>
<MemoryRouter>
Expand All @@ -44,4 +44,37 @@ describe('Home( landing page) test', () => {
);
expect(screen.getByText(sampleProduct.name)).toBeInTheDocument();
});

it('renders category name if present', () => {
render(
<Provider store={store}>
<MemoryRouter>
<ClientProductCard product={sampleProduct} />
</MemoryRouter>
</Provider>
);
expect(screen.getByText(sampleProduct.categories[0].name)).toBeInTheDocument();
});

it('renders vendor name', () => {
render(
<Provider store={store}>
<MemoryRouter>
<ClientProductCard product={sampleProduct} />
</MemoryRouter>
</Provider>
);
expect(screen.getByText('sellerLastName seller')).toBeInTheDocument();
});

it('renders price', () => {
render(
<Provider store={store}>
<MemoryRouter>
<ClientProductCard product={sampleProduct} />
</MemoryRouter>
</Provider>
);
expect(screen.getByText('RWF 2000')).toBeInTheDocument();
});
});
15 changes: 15 additions & 0 deletions src/__test__/components/ProductCard/searchCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,19 @@ describe('SearchCard', () => {
expect(screen.getByAltText('Product 1')).toHaveAttribute('src', 'image1.jpg');
expect(screen.getByAltText('Product 2')).toHaveAttribute('src', 'image2.jpg');
});

it('renders correctly when productList is empty', () => {
render(
<Router>
<SearchCard productList={[]} />
</Router>
);

expect(screen.queryByText('Product 1')).not.toBeInTheDocument();
expect(screen.queryByText('Product 2')).not.toBeInTheDocument();
expect(screen.queryByText('RWF 100')).not.toBeInTheDocument();
expect(screen.queryByText('RWF 200')).not.toBeInTheDocument();
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
expect(screen.queryByText('Jane Doe')).not.toBeInTheDocument();
});
});
37 changes: 37 additions & 0 deletions src/__test__/pages/Products/DashboardDeleteProduct.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import { describe, it } from 'vitest'; // Assuming 'vitest' is your testing framework
import DeleteCouponModal from '../../../components/Products/DashboardDeleteProduct/DashboardDeleteProduct'; // Adjust the import path as per your project structure
import { render } from '../../utils/test-utils'; // Adjust the path as per your project structure
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import mockStore from '../../utils/mockStore'; // Adjust the path as per your project structure

describe('DeleteCouponModal', () => {
it('renders correctly with modal content', async () => {
render(
<Provider store={mockStore}>
<MemoryRouter>
<DeleteCouponModal />
</MemoryRouter>
</Provider>
);

expect(screen.getByText('Delete coupon FLAT10!')).toBeInTheDocument();
expect(screen.getByText('Are you sure you want to accept this?')).toBeInTheDocument();
expect(screen.getByText('No, Cancel')).toBeInTheDocument();
expect(screen.getByText('Yes, Deactivate')).toBeInTheDocument();
});

it('triggers cancel action when "No, Cancel" button is clicked', async () => {
render(
<Provider store={mockStore}>
<MemoryRouter>
<DeleteCouponModal />
</MemoryRouter>
</Provider>
);

fireEvent.click(screen.getByText('No, Cancel'));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';

const DeleteCouponModal: React.FC = () => {
return (
<div className="bg-white p-4 rounded-lg shadow-md">
<h1 className="text-red-500 font-bold">Delete coupon FLAT10!</h1>
<p>Are you sure you want to accept this?</p>
<div className="flex justify-between mt-4">
<button className="bg-white border border-gray-300 text-black py-2 px-4 rounded hover:bg-gray-100">
No, Cancel
</button>
<button className="bg-red-500 text-white py-2 px-4 rounded hover:bg-red-600">Yes, Deactivate</button>
</div>
</div>
);
};

export default DeleteCouponModal;
Loading

0 comments on commit c510169

Please sign in to comment.