Skip to content

Commit

Permalink
fix(fix admin dashboard transactions)
Browse files Browse the repository at this point in the history
  • Loading branch information
MC-Knight committed Jul 26, 2024
1 parent be40fff commit 67359ee
Show file tree
Hide file tree
Showing 7 changed files with 532 additions and 6 deletions.
10 changes: 5 additions & 5 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import AdminOrders from '../pages/Orders/AdminOrders';
import SingleAdminOrder from '../pages/Orders/SingleAdminOrder';
import { JSX } from 'react/jsx-runtime';
import GoogleLogin from '../pages/Authentication/GoogleLogin';
import Transctions from '../pages/Transactions/Transctions';

const Router = () => {
const { userToken } = useSelector((state: RootState) => state.auth);
Expand All @@ -61,11 +62,7 @@ const Router = () => {
}
}, [location.pathname, dispatch, userToken]);

const conditionalNavigate = (
adminPath: To,
vendorPath: To,
buyerPath: JSX.Element | To | any
) => (
const conditionalNavigate = (adminPath: To, vendorPath: To, buyerPath: JSX.Element | To | any) => (
<>
{userToken && isAdmin && <Navigate to={adminPath} />}
{userToken && isVendor && <Navigate to={vendorPath} />}
Expand Down Expand Up @@ -325,6 +322,9 @@ const Router = () => {
<Route path="account" element={<DashboarInnerLayout />}>
<Route index element={<DashboardAccount />} />
</Route>
<Route path="transaction" element={<DashboarInnerLayout />}>
<Route index element={<Transctions />} />
</Route>
</Route>

<Route path="*" element={<NotFound />} />
Expand Down
79 changes: 79 additions & 0 deletions src/__test__/components/Cart/cartAction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { configureStore } from '@reduxjs/toolkit';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

import rootReducer from '../../../redux/reducers/rootReducer'; // Adjust the path to your rootReducer
import type { AppDispatch } from '../../../redux/store'; // Import types
import Cookies from 'js-cookie';
import { AddToCartData } from '../../../types/cartTypes';
import { addToCart, clearCart, fetchCart, removeFromCart } from '../../../redux/actions/cartAction';

describe('cartActions', () => {
let mockAxios: MockAdapter;
let store: ReturnType<typeof configureStore>;

beforeEach(() => {
mockAxios = new MockAdapter(axios);
store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false
})
});
localStorage.setItem('userToken', JSON.stringify({ token: 'test-token' }));
});

afterEach(() => {
mockAxios.reset();
localStorage.clear();
});

it('fetchCart should make the correct API call and handle the response', async () => {
const cartData = { data: { cart: [{ id: 'cart123', totalAmount: 100 }] } };
mockAxios.onGet(`${import.meta.env.VITE_APP_API_URL}/cart`).reply(200, cartData);

const result = await (store.dispatch as AppDispatch)(fetchCart());

expect(result.type).toBe('cart/fetchCart/fulfilled');
expect(result.payload).toEqual(cartData);
expect(mockAxios.history.get[0].url).toBe(`${import.meta.env.VITE_APP_API_URL}/cart`);
});

it('addToCart should make the correct API call and handle the response', async () => {
const addData: AddToCartData = { productId: 'prod123', quantity: 1 };
const responseData = { data: { cart: { id: 'cart123' } } };
mockAxios.onPost(`${import.meta.env.VITE_APP_API_URL}/cart`).reply(200, responseData);

const result = await (store.dispatch as AppDispatch)(addToCart(addData));

expect(result.type).toBe('cart/addToCart/fulfilled');
expect(result.payload).toEqual(responseData);
expect(mockAxios.history.post[0].url).toBe(`${import.meta.env.VITE_APP_API_URL}/cart`);
expect(Cookies.get('cartId')).toBe('cart123');
});

it('clearCart should make the correct API call and handle the response', async () => {
const responseData = { data: { cart: [] } }; // Mock response should match expected structure
mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/cart`).reply(200, responseData);

const result = await (store.dispatch as AppDispatch)(clearCart());

expect(result.type).toBe('cart/clearCart/fulfilled');
expect(result.payload).toEqual(responseData);
expect(mockAxios.history.delete[0].url).toBe(`${import.meta.env.VITE_APP_API_URL}/cart`);
});

it('removeFromCart should make the correct API call and handle the response', async () => {
const responseData = { data: { cart: [] } };
const itemId = 'item123';
mockAxios.onDelete(`${import.meta.env.VITE_APP_API_URL}/cart/${itemId}`).reply(200, responseData);

const result = await (store.dispatch as AppDispatch)(removeFromCart(itemId));

expect(result.type).toBe('cart/removeFromCart/fulfilled');
expect(result.payload).toEqual(responseData);
expect(mockAxios.history.delete[0].url).toBe(`${import.meta.env.VITE_APP_API_URL}/cart/${itemId}`);
});
});
63 changes: 63 additions & 0 deletions src/__test__/jwt.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import jwtDecode from 'jwt-decode';
import { DecodedToken } from '../types/CouponTypes';
import { decodedToken } from '../services';

// Mock jwt-decode module
vi.mock('jwt-decode', () => ({
default: vi.fn()
}));

describe('decodedToken', () => {
const originalError = console.error;

beforeAll(() => {
console.error = vi.fn(); // Mock console.error
});

afterAll(() => {
console.error = originalError; // Restore console.error
});

afterEach(() => {
localStorage.clear();
vi.clearAllMocks();
});

it('should return testData if provided', () => {
const testData: DecodedToken = {
id: '123',
email: '[email protected]',
userType: 'admin',
iat: 1615552560,
exp: 1615556160
};
const result = decodedToken({ testData });
expect(result).toEqual(testData);
});

it('should return null if no token in localStorage', () => {
const result = decodedToken({});
expect(result).toBeNull();
expect(console.error).toHaveBeenCalledWith('No user token found in localStorage');
});

it('should return null if token structure is invalid', () => {
localStorage.setItem('userToken', JSON.stringify({}));
const result = decodedToken({});
expect(result).toBeNull();
expect(console.error).toHaveBeenCalledWith('Invalid token structure');
});

it('should return null if there is an error in decoding token', () => {
const mockToken = 'mock.jwt.token';
(jwtDecode as any).mockImplementation(() => {
throw new Error('Invalid token');
});

localStorage.setItem('userToken', JSON.stringify({ token: mockToken }));

const result = decodedToken({});
expect(result).toBeNull();
expect(console.error).toHaveBeenCalledWith('Error decoding token', expect.any(Error));
});
});
175 changes: 175 additions & 0 deletions src/__test__/pages/transactions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React from 'react';
import { render, screen, waitFor, act } from '@testing-library/react';
import { describe, it, vi, afterEach } from 'vitest';
import axios from 'axios';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from '../../redux/store';
import Transactions from '../../pages/Transactions/Transctions';

vi.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

const mockData = {
statistics: {
totalAmount: 5000,
averagePaymentAmount: 1000,
totalCapturedAmount: 4000,
totalPayments: 5,
successfulPayments: 4,
pendingPayments: 1
},
payments: [
{
id: 'pay_1',
amount: 1000,
created: 1628353200,
status: 'succeeded',
payment_method_types: ['card']
},
{
id: 'pay_2',
amount: 1200,
created: 12628353200,
status: 'pending',
payment_method_types: ['card']
}
]
};

const emptyPaymentsData = {
statistics: {
totalAmount: 0,
averagePaymentAmount: 0,
totalCapturedAmount: 0,
totalPayments: 0,
successfulPayments: 0,
pendingPayments: 0
},
payments: []
};

describe('Transactions Component', () => {
afterEach(() => {
vi.clearAllMocks();
localStorage.clear();
});

it('renders Transactions component without crashing', async () => {
localStorage.setItem('userToken', JSON.stringify({ token: 'mocked-token' }));
mockedAxios.get.mockResolvedValue({ data: mockData });

await act(async () => {
render(
<Provider store={store}>
<MemoryRouter>
<Transactions />
</MemoryRouter>
</Provider>
);
});

await waitFor(() => {
expect(screen.getByTestId('transactions')).toBeInTheDocument();
});

expect(screen.getByTestId('totalVendors')).toBeInTheDocument();
expect(screen.getByTestId('totalVendors')).toHaveTextContent('5,000 Rwf');
});
it('displays No Transactions Found when payments array is empty', async () => {
localStorage.setItem('userToken', JSON.stringify({ token: 'mocked-token' }));
mockedAxios.get.mockResolvedValue({ data: emptyPaymentsData });

await act(async () => {
render(
<Provider store={store}>
<MemoryRouter>
<Transactions />
</MemoryRouter>
</Provider>
);
});

await waitFor(() => {
expect(screen.getByText('No Transactions Found')).toBeInTheDocument();
});
});

it('displays "No data available" when no data is fetched', async () => {
mockedAxios.get.mockResolvedValueOnce({ data: null });

await act(async () => {
render(
<Provider store={store}>
<MemoryRouter>
<Transactions />
</MemoryRouter>
</Provider>
);
});

await waitFor(() => {
expect(screen.getByText('No data available')).toBeInTheDocument();
});
});

// it('displays "No Transactions Found" when payments array is empty', async () => {
// localStorage.setItem('userToken', JSON.stringify({ token: 'mocked-token' }));
// mockedAxios.get.mockResolvedValue({ data: mockData });

// await act(async () => {
// render(
// <Provider store={store}>
// <MemoryRouter>
// <Transactions />
// </MemoryRouter>
// </Provider>
// );
// });

// await waitFor(() => {
// expect(screen.getByTestId('transactions')).toBeInTheDocument();
// });

// expect(screen.getByTestId('totalVendors')).toBeInTheDocument();
// expect(screen.getByTestId('totalVendors')).toHaveTextContent('5,000 Rwf');
// });

// it('displays "No Transactions Found" when payments array is empty', async () => {
// localStorage.setItem('userToken', JSON.stringify({ token: 'mocked-token' }));
// // mockedAxios.get.mockResolvedValue({ data: emptyPaymentsData });
// mockedAxios.get.mockResolvedValue({ data: mockData });
// await act(async () => {
// render(
// <Provider store={store}>
// <MemoryRouter>
// <Transactions />
// </MemoryRouter>
// </Provider>
// );
// });

// await waitFor(() => {
// expect(screen.getByText('No Transactions Found')).toBeInTheDocument();
// });
// });

it('handles missing token correctly', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

await act(async () => {
render(
<Provider store={store}>
<MemoryRouter>
<Transactions />
</MemoryRouter>
</Provider>
);
});

expect(consoleSpy).toHaveBeenCalledWith('Token not found');
expect(screen.getByText('No data available')).toBeInTheDocument();

consoleSpy.mockRestore();
});
});
13 changes: 12 additions & 1 deletion src/components/Dashboard/DashboardSideBar/DashboardSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import two from '/3.svg';
import three from '/Component 3.svg';
import f from '/user-square.svg';
import dashboardIcon from '/Dashboard.svg';
import { CircleX } from 'lucide-react';
import { BadgeDollarSign, CircleX } from 'lucide-react';
import userIcon from '../../../assets/Enquiry.svg';
import { useJwt } from 'react-jwt';
import { useSelector } from 'react-redux';
Expand Down Expand Up @@ -87,6 +87,17 @@ const DashboardSideBar: React.FC<DashboardSideBarProps> = ({ openNav, setOpenNav
<img src={userIcon} alt="Products" className="w-5 lg:w-6" /> Users
</NavLink>
)}
{decodedToken?.role.toLowerCase() === 'admin' && (
<NavLink
to="transaction"
className={({ isActive }) =>
`flex items-center gap-1 px-3 py-2 w-full rounded transition-all duration-300 ease-in-out hover:bg-primary hover:text-white ${isActive ? 'bg-primary text-white' : ''}`
}
>
<BadgeDollarSign />
Transactions
</NavLink>
)}
</div>
<div className="mt-auto md:pt-4 text-[#7c7c7c] flex flex-col gap-1 w-full pt-4 md:border-t md:border-neutral-300 text-[.75rem] xmd:text-[.82rem] lg:text-[.9rem] ">
<NavLink
Expand Down
Loading

0 comments on commit 67359ee

Please sign in to comment.