diff --git a/example.env.local b/example.env.local index 922eec0..43f706c 100644 --- a/example.env.local +++ b/example.env.local @@ -10,4 +10,6 @@ NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir" NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crmws.witel.ir" NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir" +NEXT_HAS_NOTIFICATION = "false" + NODE_ENV = "development" \ No newline at end of file diff --git a/mocks/handlers/user.js b/mocks/handlers/user.js index 3f1fbe3..7e84703 100644 --- a/mocks/handlers/user.js +++ b/mocks/handlers/user.js @@ -4,10 +4,14 @@ import {GET_SIDEBAR_NOTIFICATION, GET_USER, SET_USER_PASSWORD} from "@/core/data export const userHandler = [ rest.get(GET_USER, (req, res, ctx) => { return res(ctx.json({ - data: { + data:{ id: 10, - permissions: [ - "manage_users" + full_name: "Witel Company", + position: "Software Engineer", + permissions:[ + "manage_users", + "manage_roles", + "manage_boss" ], } })) diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx index 33348a5..3f1d449 100644 --- a/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx +++ b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx @@ -4,7 +4,7 @@ import SidebarList from "./SidebarList"; import useUser from "@/lib/app/hooks/useUser"; const SidebarDrawer = ({handleDrawerToggle}) => { - const {user} = useUser() + const {user} = useUser(); const t = useTranslations(); return ( <> diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx index 040b455..1e570f5 100644 --- a/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx +++ b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx @@ -10,29 +10,15 @@ function reducer(state, action) { case "COLLAPSE_MENU": return state.map((itemsArr) => itemsArr.map((item) => - action.key == item.key - ? {...item, showSubItem: !item.showSubItem} - : item + action.key === item.key ? { ...item, showSubItem: !item.showSubItem } : item ) ); case "SELECTED": return state.map((itemsArr) => - itemsArr.map((item) => { - if (item.type === "page") { - if (action.route === item.route) - return {...item, selected: true} - else - return {...item, selected: false} - } - return {...item} - // item.subItem.map((subitem) => { - // if (action.route === subitem.route) - // return {...item, showSubItem: true, subItem: {...subitem, selected: true}} - // else - // return {...item, showSubItem: false, subItem: {...subitem, selected: false}} - // }) - } - ) + itemsArr.map((item) => ({ + ...item, + selected: item.type === "page" && action.route === item.route, + })) ); default: throw new Error(); @@ -54,7 +40,7 @@ export default function SidebarList({handleDrawerToggle}) { {itemArr.map((item) => - {(user.permissions.includes(item.permission) || item.permission === "all") && + {(user?.permissions?.includes(item.permission) || item.permission === "all") && { const t = useTranslations(); - const {notification_count} = useNotification() + const { notification_count } = useNotification(); + + const hasSubItems = item.type === "menu" && item.subItem && item.subItem.length > 0; + + const renderBadge = () => ( + + + + ); return ( <> - - - - }> + { }, })} onClick={() => { - if (item.type == "menu") { - dispatch({type: "COLLAPSE_MENU", key: item.key}); + if (hasSubItems) { + dispatch({ type: "COLLAPSE_MENU", key: item.key }); } - handleDrawerToggle(); }} sx={{ minHeight: 48, @@ -66,9 +69,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => { } /> - - {item.type == "menu" && - (item.showSubItem ? : )} + {hasSubItems && (item.showSubItem ? : )} {item.subItem && ( diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx index c9c8a36..1803b96 100644 --- a/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx +++ b/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx @@ -15,25 +15,27 @@ import useNotification from "@/lib/app/hooks/useNotification"; const SidebarListSubItem = ({item, handleDrawerToggle}) => { const t = useTranslations(); - const {notification_count} = useNotification() + const {notification_count} = useNotification(); + + const renderBadge = (subitem) => ( + + + + ); return ( - {item.subItem.map((subitem, index) => ( - - - - }> + {item.subItem.map((subitem) => ( + { + describe('Rendering', () => { + it('renders system name in sidebar', async () => { + render(); + await waitFor(()=>{ + const systemName = screen.getByRole('heading', { level: 6 }); + expect(systemName).toHaveTextContent('سامانه CRM'); + }) + }); + it('renders user full name and position in sidebar', async () => { + localStorage.setItem("_token", 'token'); + render(); + await waitFor(()=>{ + const userName = screen.queryByText(/Witel Company | Software Engineer/i); + expect(userName).toBeInTheDocument(); + }) + }); + }) +}) diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js new file mode 100644 index 0000000..1897c9b --- /dev/null +++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js @@ -0,0 +1,16 @@ +import {render, screen, waitFor} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import SidebarList from "@/components/layouts/Dashboard/Sidebar/SidebarList"; + +describe('SidebarList', () => { + describe('Rendering', () => { + it('renders sidebar items based on user permissions', async () => { + localStorage.setItem("_token", 'token'); + render(); + await waitFor(()=>{ + const sidebarItem = screen.getByText('مدیریت نقش ها'); + expect(sidebarItem).toBeInTheDocument(); + }) + }); + }) +}) diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js new file mode 100644 index 0000000..18313c5 --- /dev/null +++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js @@ -0,0 +1,125 @@ +import AccessibilityIcon from "@mui/icons-material/Accessibility"; +import {fireEvent, render, screen} from "@testing-library/react"; +import SidebarListItem from "@/components/layouts/Dashboard/Sidebar/SidebarListItem"; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; + +describe('SidebarListItemComponent', () => { + describe('Rendering', () => { + it('should show sidebarlistitem name in sidebar', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "page", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + } + render(); + expect(screen.getByText('مدیریت نقش ها')).toBeInTheDocument(); + }); + it('should show sidebarlistitem icon in sidebar', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "page", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + } + render(); + const iconElement = screen.getByLabelText("Sidebar Icon"); + expect(iconElement).toBeInTheDocument(); + }); + it('should have selected class for subitem', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "page", + route: "/dashboard/role-management", + icon: , + selected: true, + permission: "manage_roles", + } + render(); + const element = screen.getByText('مدیریت نقش ها'); + expect(element.parentElement.parentElement).toHaveClass('Mui-selected'); + }); + + it('should not have selected class for subitem', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + } + render(); + const element = screen.getByText('مدیریت نقش ها'); + expect(element.parentElement.parentElement).not.toHaveClass('Mui-selected'); + }); + + it('should show sidebarlistitem secondary in sidebar', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + secondary: "between", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + } + render(); + expect(screen.getByText('میان')).toBeInTheDocument(); + }); + + it('should not show sidebarlistitem secondary in sidebar', () => { + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + } + render(); + expect(screen.queryByText('secondry')).not.toBeInTheDocument(); + }); + }) + describe('Behavior', () => { + it('should call dispatch when hasSubItems is true and item is clicked', () => { + const dispatchMock = jest.fn(); + const sampleItem={ + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + subItem :[ + { + key: "greaterThan", + route: "/dashboard/role-management", + secondary: "between", + name : "sidebar.admin", + selected: true, + icon: , + } + ] + } + render(); + const listItem = screen.getByText('مدیریت نقش ها'); + fireEvent.click(listItem); + expect(dispatchMock).toHaveBeenCalledWith({ + type: 'COLLAPSE_MENU', + key: "sidebar.role-management", + }); + }); + }) +}) \ No newline at end of file diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js new file mode 100644 index 0000000..9551937 --- /dev/null +++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js @@ -0,0 +1,103 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import SidebarListSubItem from "@/components/layouts/Dashboard/Sidebar/SidebarListSubItem"; +import AccessibilityIcon from "@mui/icons-material/Accessibility"; + +describe('SidebarListSubItemComponent', () => { + describe('Rendering', () => { + const sampleItem={ + showSubItem:true, + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + subItem :[ + { + key: "greaterThan", + route: "/dashboard/role-management", + secondary: "between", + name : "sidebar.admin", + selected: true, + icon: , + } + ] + } + it('should show subitem name in sidebar', () => { + render(); + expect(screen.getByText('بزرگتر از')).toBeInTheDocument(); + + }); + it('should not show subitem name in sidebar', () => { + const sampleItem={ + showSubItem:false, + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + subItem :[ + { + key: "greaterThan", + route: "/dashboard/role-management", + secondary: "between", + name : "sidebar.admin", + selected: false, + icon: , + } + ] + } + render(); + expect(screen.queryByText('مدیریت نقش ها')).not.toBeInTheDocument(); + }); + it('should show subitem secondary in sidebar', () => { + render(); + expect(screen.getByText('میان')).toBeInTheDocument(); + }); + + it('should not show subitem secondary in sidebar', () => { + render(); + expect(screen.queryByText('secondry')).not.toBeInTheDocument(); + }); + it('should have selected class for subitem', () => { + render(); + const parentElement = screen.queryByText('بزرگتر از').parentElement.parentElement; + expect(parentElement).toHaveClass('Mui-selected'); + }); + + it('should not have selected class for subitem', () => { + const sampleItem={ + showSubItem:true, + key: "sidebar.role-management", + name : "role_management", + type: "menu", + route: "/dashboard/role-management", + icon: , + selected: false, + permission: "manage_roles", + subItem :[ + { + key: "greaterThan", + route: "/dashboard/role-management", + secondary: "between", + name : "sidebar.admin", + selected: false, + icon: , + } + ] + } + render(); + const parentElement = screen.queryByText('بزرگتر از').parentElement.parentElement; + expect(parentElement).not.toHaveClass('Mui-selected'); + }); + it('should show subitem icon', () => { + render(); + const iconElement = screen.getByLabelText("Sidebar Icon"); + expect(iconElement).toBeInTheDocument(); + }); + }) +}) \ No newline at end of file diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js new file mode 100644 index 0000000..740c384 --- /dev/null +++ b/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js @@ -0,0 +1,17 @@ +import {render, screen, act} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import Sidebar from "@/components/layouts/Dashboard/Sidebar"; + + +describe('Sidebar', () => { + describe('Rendering', () => { + it('Mobile Drawer is displayed when mobileOpen is true', () => { + const props={ + mobileOpen: true, + }; + render(); + const mobileDrawer = screen.getByTestId('mobile-drawer'); + expect(mobileDrawer).toBeVisible(); + }); + }) +}) diff --git a/src/components/layouts/Dashboard/Sidebar/index.jsx b/src/components/layouts/Dashboard/Sidebar/index.jsx index 2498dfd..c0b6d6d 100644 --- a/src/components/layouts/Dashboard/Sidebar/index.jsx +++ b/src/components/layouts/Dashboard/Sidebar/index.jsx @@ -9,6 +9,7 @@ const Sidebar = (props) => { aria-label="mailbox folders" > { + const requestServer = useRequest({auth: true, notification: false}) //swr config @@ -13,7 +15,7 @@ const useNotification = () => { }) }; - const {data, mutate} = useSWR(GET_SIDEBAR_NOTIFICATION, fetcher) + const {data, mutate} = useSWR( isNotificationEnabled ? GET_SIDEBAR_NOTIFICATION : '', fetcher , {keepPreviousData:true}) //swr config // render data