Skip to content

Commit

Permalink
admin should manage user role
Browse files Browse the repository at this point in the history
[Delivers #101]
Co-authored-by: AMBROISE Muhayimana <107347030+ambroisegithub@users.noreply.github.com>
  • Loading branch information
jkarenzi authored and ambroisegithub committed Jul 24, 2024
1 parent 5104bd9 commit 9ad7c99
Show file tree
Hide file tree
Showing 11 changed files with 1,865 additions and 2,578 deletions.
6 changes: 6 additions & 0 deletions .hintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
{
"button-name": "off"
}
],
"axe/parsing": [
"default",
{
"duplicate-id-active": "off"
}
]
}
}
3,655 changes: 1,077 additions & 2,578 deletions package-lock.json

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions src/__test__/TableUserRole.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { vi } from 'vitest';
import axios from 'axios';
import TableUserRole from '@/components/dashBoard/UserRole';
import { showErrorToast } from '@/utils/ToastConfig';
import userRoleSlice from '@/features/userRole/userRoleSlice';

vi.mock('@/utils/ToastConfig', () => ({
showErrorToast: vi.fn(),
showSuccessToast: vi.fn(),
}));

vi.mock('axios');

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

describe('TableUserRole', () => {
beforeEach(() => {
vi.clearAllMocks();
(axios.get as jest.Mock).mockResolvedValue({
data: { data: [{ id: 1, name: 'Admin', permissions: [] }] },
});
});

it('renders TableUserRole component', () => {
renderWithProviders(<TableUserRole />);
expect(screen.getByText('Register Role')).toBeInTheDocument();
});

it('fetches all roles on mount', async () => {
renderWithProviders(<TableUserRole />);
await waitFor(() => {
expect(screen.getByText('Register Role')).toBeInTheDocument();
});
});

it('shows error when role name is empty', async () => {
renderWithProviders(<TableUserRole />);

fireEvent.click(screen.getByText('Add Role'));

await waitFor(() => {
expect(showErrorToast).toHaveBeenCalledWith('Role name cannot be empty');
});
});

it('adds and removes permissions', async () => {
renderWithProviders(<TableUserRole />);

const permissionInput = screen.getByPlaceholderText('Enter permissions');
fireEvent.change(permissionInput, { target: { value: 'Permission 1' } });
fireEvent.click(screen.getByText('+ Add Permissions'));

expect(screen.getByText('Permission 1')).toBeInTheDocument();

const removePermissionButton = screen.getByText('X', {
selector: 'button',
});
fireEvent.click(removePermissionButton);

await waitFor(() => {
expect(screen.queryByText('Permission 1')).not.toBeInTheDocument();
});
});

it('adds a new role successfully', async () => {
renderWithProviders(<TableUserRole />);

const roleNameInput = screen.getByPlaceholderText('Role Name');
const permissionInput = screen.getByPlaceholderText('Enter permissions');
const addPermissionsButton = screen.getByText('+ Add Permissions');
const addRoleButton = screen.getByText('Add Role');

fireEvent.change(roleNameInput, { target: { value: 'New Role' } });
fireEvent.change(permissionInput, { target: { value: 'Permission 1' } });
fireEvent.click(addPermissionsButton);

fireEvent.click(addRoleButton);

await waitFor(() => {
expect(screen.queryByText('Permission 1')).not.toBeInTheDocument();
});
});
});
181 changes: 181 additions & 0 deletions src/__test__/userRoleSlice.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { configureStore } from '@reduxjs/toolkit';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import axios from 'axios';
import userRoleSlice, {
fetchUserRoles,
createUserRole,
deleteUserRole,
updateUserRole,
} from '@/features/userRole/userRoleSlice';

vi.mock('axios');

const initialState = {
roles: [],
status: 'idle' as 'idle' | 'loading' | 'succeeded',
error: null as string | null,
};

describe('userRoleSlice', () => {
let store: any;

beforeEach(() => {
store = configureStore({
reducer: {
userRoles: userRoleSlice,
},
});
});

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

it('should have the correct initial state', () => {
const state = store.getState().userRoles;
expect(state).toEqual(initialState);
});

it('should handle fetchUserRoles.pending', async () => {
const action = { type: fetchUserRoles.pending.type };
const state = userRoleSlice(initialState, action);
expect(state.status).toBe('loading');
});

it('should handle fetchUserRoles.fulfilled', async () => {
const mockRoles = [
{ id: 1, name: 'Admin', permissions: ['read', 'write'] },
{ id: 2, name: 'User', permissions: ['read'] },
];

(axios.get as any).mockResolvedValue({ data: { roles: mockRoles } });

const action = { type: fetchUserRoles.fulfilled.type, payload: mockRoles };
const state = userRoleSlice(initialState, action);

expect(state.status).toBe('succeeded');
expect(state.roles).toEqual(mockRoles);
});

it('should handle fetchUserRoles.rejected', async () => {
(axios.get as any).mockRejectedValue(new Error('Failed to fetch roles'));

const action = {
type: fetchUserRoles.rejected.type,
error: { message: 'Failed to fetch roles' },
};
const state = userRoleSlice(initialState, action);

expect(state.status).toBe('failed');
expect(state.error).toBe('Failed to fetch roles');
});

it('should handle deleteUserRole.pending', async () => {
const action = { type: deleteUserRole.pending.type };
const state = userRoleSlice(initialState, action);
expect(state.status).toBe('loading');
});

it('should handle deleteUserRole.fulfilled', async () => {
const existingRoles = [
{ id: 1, name: 'Admin', permissions: ['read', 'write'] },
{ id: 2, name: 'User', permissions: ['read'] },
];

(axios.delete as any).mockResolvedValue({});

const action = { type: deleteUserRole.fulfilled.type, payload: 1 };
const state = userRoleSlice(
{ ...initialState, roles: existingRoles },
action
);

expect(state.roles).toEqual([
{ id: 2, name: 'User', permissions: ['read'] },
]);
});

it('should handle deleteUserRole.rejected', async () => {
(axios.delete as any).mockRejectedValue(new Error('Failed to delete role'));

const action = {
type: deleteUserRole.rejected.type,
error: { message: 'Failed to delete role' },
};
const state = userRoleSlice(
{
...initialState,
roles: [{ id: 1, name: 'Admin', permissions: ['read', 'write'] }],
},
action
);

expect(state.status).toBe('failed');
expect(state.error).toBe('Failed to delete role');
});

it('should handle createUserRole.pending', async () => {
const action = { type: createUserRole.pending.type };
const state = userRoleSlice(initialState, action);
expect(state.status).toBe('loading');
});

it('should handle createUserRole.fulfilled', async () => {
const newRole = { id: 3, name: 'Editor', permissions: ['read', 'write'] };

(axios.post as any).mockResolvedValue({ data: { role: newRole } });

const action = { type: createUserRole.fulfilled.type, payload: newRole };
const state = userRoleSlice({ ...initialState, roles: [] }, action);

expect(state.roles).toEqual([newRole]);
});

it('should handle updateUserRole.pending', async () => {
const action = { type: updateUserRole.pending.type };
const state = userRoleSlice(initialState, action);
expect(state.status).toBe('loading');
});

it('should handle updateUserRole.fulfilled', async () => {
const existingRoles = [
{ id: 1, name: 'Admin', permissions: ['read', 'write'] },
{ id: 2, name: 'User', permissions: ['read'] },
];

const updatedRole = {
id: 1,
name: 'Super Admin',
permissions: ['read', 'write', 'delete'],
};

(axios.put as any).mockResolvedValue({ data: updatedRole });

const action = {
type: updateUserRole.fulfilled.type,
payload: updatedRole,
};
const state = userRoleSlice(
{ ...initialState, roles: existingRoles },
action
);

expect(state.roles).toEqual([
updatedRole,
{ id: 2, name: 'User', permissions: ['read'] },
]);
});

it('should handle updateUserRole.rejected', async () => {
(axios.put as any).mockRejectedValue(new Error('Failed to update role'));

const action = {
type: updateUserRole.rejected.type,
error: { message: 'Failed to update role' },
};
const state = userRoleSlice(initialState, action);

expect(state.status).toBe('failed');
expect(state.error).toBe('Failed to update role');
});
});
2 changes: 2 additions & 0 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import checkoutSlice from '@/features/Checkout/checkoutSlice';
import ordersSliceReducer from '@/features/Orders/ordersSlice';
import contactReducer from '@/features/contact/contactSlice';
import couponsSliceReducer from '@/features/Coupons/CouponsFeature';
import userRoleSlice from '@/features/userRole/userRoleSlice';

export const store = configureStore({
reducer: {
Expand All @@ -45,6 +46,7 @@ export const store = configureStore({
contact: contactReducer,
checkout: checkoutSlice,
coupons: couponsSliceReducer,
userRoles: userRoleSlice,
},
});

Expand Down
Binary file added src/assets/TableLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/components/dashBoard/DashboardSideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ChevronDown,
ChevronRight,
Store,
User,
} from 'lucide-react';
import { Link } from 'react-router-dom';
import { useAppSelector } from '@/app/hooks';
Expand Down Expand Up @@ -96,6 +97,18 @@ const sideBarItems = [
],
role: ['Admin', 'Vendor'],
},
{
name: 'user Role',
icon: <User className="icon" />,
subItems: [
{
name: 'Add & All Roles',
path: '/dashboard/userRole',
role: ['Admin'],
},
],
role: ['Admin'],
},
];

interface SideBarItemProps {
Expand Down
1 change: 1 addition & 0 deletions src/components/dashBoard/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import { useState, useEffect } from 'react';
import { FaRegTrashAlt } from 'react-icons/fa';
import { MdOutlineEdit } from 'react-icons/md';
Expand Down
Loading

0 comments on commit 9ad7c99

Please sign in to comment.