-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wishes):buyer should be able wish a products
-Buyer should wish a products -Buyer should add a wished products to cart -Seller should be able to see a wished products [Deliver #187419141]
- Loading branch information
1 parent
a738f89
commit 8a093ab
Showing
13 changed files
with
304 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import MockAdapter from "axios-mock-adapter"; | ||
import { configureStore } from "@reduxjs/toolkit"; | ||
|
||
import wishListSlice, { | ||
fetchWishes, | ||
addWish, | ||
deleteWish, | ||
} from "../redux/reducers/wishListSlice"; | ||
import api from "../redux/api/action"; | ||
|
||
const mock = new MockAdapter(api); | ||
|
||
describe("wishesSlice test", () => { | ||
beforeEach(() => { | ||
mock.reset(); | ||
}); | ||
|
||
it("fetchWishes dispatches fulfilled action when data is returned", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
const mockWishes = [ | ||
{ | ||
id: 1, | ||
product: { | ||
id: 1, | ||
name: "Product", | ||
images: [], | ||
stockQuantity: 10, | ||
price: 100, | ||
}, | ||
user: { name: "User", userName: "username", email: "[email protected]" }, | ||
}, | ||
]; | ||
mock.onGet(`/wishes`).reply(200, { wishes: mockWishes }); | ||
|
||
await store.dispatch(fetchWishes()); | ||
const state = store.getState(); | ||
expect(state.wishes.wishes).toEqual(mockWishes); | ||
expect(state.wishes.isLoading).toBe(false); | ||
expect(state.wishes.error).toBeNull(); | ||
}); | ||
|
||
it("fetchWishes dispatches rejected action on API failure", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
mock.onGet(`/wishes`).networkError(); | ||
|
||
await store.dispatch(fetchWishes()); | ||
const state = store.getState(); | ||
expect(state.wishes.wishes).toEqual([]); | ||
expect(state.wishes.isLoading).toBe(false); | ||
}); | ||
|
||
it("addWish dispatches fulfilled action when a wish is added", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
const newWish = { | ||
id: 2, | ||
product: { | ||
id: 2, | ||
name: "Product2", | ||
images: [], | ||
stockQuantity: 5, | ||
price: 200, | ||
}, | ||
user: { name: "User2", userName: "user2", email: "[email protected]" }, | ||
}; | ||
mock.onPost(`/wishes`).reply(200, newWish); | ||
|
||
await store.dispatch(addWish({ productId: 2 })); | ||
const state = store.getState(); | ||
expect(state.wishes.wishes).toContainEqual(newWish); | ||
expect(state.wishes.isLoading).toBe(false); | ||
expect(state.wishes.error).toBeNull(); | ||
}); | ||
|
||
it("addWish dispatches rejected action on API failure", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
mock.onPost(`/wishes`).reply(500, { message: "Internal Server Error" }); | ||
|
||
await store.dispatch(addWish({ productId: 999 })); | ||
const state = store.getState(); | ||
expect(state.wishes.isLoading).toBe(false); | ||
expect(state.wishes.error).toEqual("Internal Server Error"); | ||
}); | ||
|
||
it("deleteWish dispatches fulfilled action when a wish is removed", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
mock.onDelete(`/products/2/wishes`).reply(200); | ||
|
||
await store.dispatch(deleteWish({ productId: 2 })); | ||
const state = store.getState(); | ||
expect(state.wishes.wishes).not.toContainEqual( | ||
expect.objectContaining({ id: 2 }), | ||
); | ||
expect(state.wishes.isLoading).toBe(false); | ||
expect(state.wishes.error).toBeNull(); | ||
}); | ||
|
||
it("deleteWish dispatches rejected action on API failure", async () => { | ||
const store = configureStore({ reducer: { wishes: wishListSlice } }); | ||
mock.onDelete(`/products/999/wishes`).reply(404, { message: "Not Found" }); | ||
|
||
await store.dispatch(deleteWish({ productId: 999 })); | ||
const state = store.getState(); | ||
expect(state.wishes.isLoading).toBe(false); | ||
expect(state.wishes.error).toEqual("Not Found"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { useDispatch, useSelector } from "react-redux"; | ||
|
||
import { fetchWishes } from "../../redux/reducers/wishListSlice"; | ||
import { RootState, AppDispatch } from "../../redux/store"; | ||
|
||
import Spinner from "./Spinner"; | ||
|
||
const WishesTable: React.FC = () => { | ||
const dispatch: AppDispatch = useDispatch(); | ||
const { wishes, isLoading, error } = useSelector( | ||
(state: RootState) => state.wishes, | ||
); | ||
useEffect(() => { | ||
const fetchData = async () => { | ||
try { | ||
await dispatch(fetchWishes()); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}; | ||
|
||
fetchData(); | ||
}, [dispatch]); | ||
|
||
return ( | ||
<div className="relative"> | ||
<div className="overflow-x-scroll "> | ||
<table className="min-w-full bg-white border-collapse"> | ||
<thead className="border-b-2"> | ||
<tr className="px-3 bg-light-blue rounded-lg"> | ||
<th className="pr-6 rounded-l-[12px] py-2 pl-4 text-sm font-medium text-left whitespace-nowrap text-black"> | ||
Product | ||
</th> | ||
<th className="py-2 px-4 text-sm font-medium text-left text-black whitespace-nowrap"> | ||
Stock Quantity | ||
</th> | ||
<th className="py-2 px-4 text-sm font-medium text-left text-black whitespace-nowrap"> | ||
Price | ||
</th> | ||
<th className="py-2 px-4 text-sm font-medium text-left text-black whitespace-nowrap"> | ||
Buyer | ||
</th> | ||
<th className="py-2 px-4 text-sm font-medium text-left text-black whitespace-nowrap"> | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{isLoading ? ( | ||
<tr> | ||
<td colSpan={9} className="text-center py-4"> | ||
<Spinner /> | ||
</td> | ||
</tr> | ||
) : wishes.length === 0 ? ( | ||
<tr> | ||
<td | ||
colSpan={6} | ||
className="text-center py-10 text-dark-gray dark:text-white" | ||
> | ||
No products found. | ||
</td> | ||
</tr> | ||
) : ( | ||
wishes.map((item) => ( | ||
<tr key={item.id} className="px-2"> | ||
<td className="pl-4 pr-10 xl:pr-10 py-2 flex items-center whitespace-nowrap space-x-3"> | ||
<img | ||
src={item.product.images[0]} | ||
alt={item.product.name} | ||
className="w-10 h-10 object-cover mr-2" | ||
/> | ||
<span className="text-dark-gray text-sm"> | ||
{item?.product.name} | ||
</span> | ||
</td> | ||
<td className="py-3 px-4 text-gray-700 text-sm"> | ||
{item.product.stockQuantity} | ||
</td> | ||
<td className="py-3 px-4 text-gray-700 text-sm"> | ||
{item.product.price} | ||
</td> | ||
<td className="py-3 px-4 text-gray-700 text-sm whitespace-nowrap"> | ||
{item.user.name} | ||
</td> | ||
<td className="py-3 px-4 text-sm text-gray-700"> | ||
{item.user.email} | ||
</td> | ||
</tr> | ||
)) | ||
)} | ||
</tbody> | ||
</table> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default WishesTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Layout from "../../components/layouts/SellerLayout"; | ||
import WishesTable from "../../components/dashboard/wishesTable"; | ||
|
||
const Wishes = () => ( | ||
<Layout> | ||
<div className="mt-24 mb-4"> | ||
<h1 className="text-2xl font-medium text-black">Wishes List</h1> | ||
<p className="text-dark-gray"> | ||
Detailed information about your wished products | ||
</p> | ||
</div> | ||
<section className="lg:pl-5 bg-white relative xl:w-full px-4"> | ||
<WishesTable /> | ||
</section> | ||
</Layout> | ||
); | ||
|
||
export default Wishes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.