diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 02dec2a6f6..0cea409b99 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -95,6 +95,7 @@ export const USER_LIST = gql` export const EVENT_DETAILS = gql` query Event($id: ID!) { event(id: $id) { + _id title description startDate diff --git a/src/assets/svgs/addEventProject.svg b/src/assets/svgs/addEventProject.svg new file mode 100644 index 0000000000..3c561d5ee8 --- /dev/null +++ b/src/assets/svgs/addEventProject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/checkInRegistrants.svg b/src/assets/svgs/checkInRegistrants.svg new file mode 100644 index 0000000000..0a663e3876 --- /dev/null +++ b/src/assets/svgs/checkInRegistrants.svg @@ -0,0 +1 @@ +check_inCreated with Sketch. \ No newline at end of file diff --git a/src/assets/svgs/eventStats.svg b/src/assets/svgs/eventStats.svg new file mode 100644 index 0000000000..9503758f39 --- /dev/null +++ b/src/assets/svgs/eventStats.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/listEventRegistrants.svg b/src/assets/svgs/listEventRegistrants.svg new file mode 100644 index 0000000000..4d2874d641 --- /dev/null +++ b/src/assets/svgs/listEventRegistrants.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/CheckIn/CheckInWrapper.module.css b/src/components/CheckIn/CheckInWrapper.module.css new file mode 100644 index 0000000000..f5f42546c3 --- /dev/null +++ b/src/components/CheckIn/CheckInWrapper.module.css @@ -0,0 +1,13 @@ +button .iconWrapper { + width: 32px; + padding-right: 4px; + margin-right: 4px; + transform: translateY(4px); +} + +button .iconWrapperSm { + width: 32px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/CheckIn/CheckInWrapper.tsx b/src/components/CheckIn/CheckInWrapper.tsx index a1dae1da42..9a87a12ed3 100644 --- a/src/components/CheckIn/CheckInWrapper.tsx +++ b/src/components/CheckIn/CheckInWrapper.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { CheckInModal } from './CheckInModal'; import { Button } from 'react-bootstrap'; +import IconComponent from 'components/IconComponent/IconComponent'; +import styles from './CheckInWrapper.module.css'; type PropType = { eventId: string; @@ -12,14 +14,19 @@ export const CheckInWrapper = (props: PropType): JSX.Element => { return ( <> {showModal && ( diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css new file mode 100644 index 0000000000..59b31333af --- /dev/null +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css @@ -0,0 +1,13 @@ +button .iconWrapper { + width: 36px; + padding-right: 4px; + margin-right: 4px; + transform: translateY(4px); +} + +button .iconWrapperSm { + width: 36px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx index a45a428bfc..b621f8673a 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { EventRegistrantsModal } from './EventRegistrantsModal'; import { Button } from 'react-bootstrap'; +import IconComponent from 'components/IconComponent/IconComponent'; +import styles from './EventRegistrantsWrapper.module.css'; type PropType = { eventId: string; @@ -13,17 +15,23 @@ export const EventRegistrantsWrapper = (props: PropType): JSX.Element => { return ( <> - {showModal ? ( + + {showModal && ( { @@ -32,7 +40,7 @@ export const EventRegistrantsWrapper = (props: PropType): JSX.Element => { eventId={props.eventId} orgId={props.orgId} /> - ) : null} + )} ); }; diff --git a/src/components/EventStats/EventStatsWrapper.module.css b/src/components/EventStats/EventStatsWrapper.module.css new file mode 100644 index 0000000000..f5f42546c3 --- /dev/null +++ b/src/components/EventStats/EventStatsWrapper.module.css @@ -0,0 +1,13 @@ +button .iconWrapper { + width: 32px; + padding-right: 4px; + margin-right: 4px; + transform: translateY(4px); +} + +button .iconWrapperSm { + width: 32px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/EventStats/EventStatsWrapper.tsx b/src/components/EventStats/EventStatsWrapper.tsx index 953a21a06b..b501e77430 100644 --- a/src/components/EventStats/EventStatsWrapper.tsx +++ b/src/components/EventStats/EventStatsWrapper.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { EventStats } from './EventStats'; import { Button } from 'react-bootstrap'; +import IconComponent from 'components/IconComponent/IconComponent'; +import styles from './EventStatsWrapper.module.css'; type PropType = { eventId: string; @@ -12,23 +14,24 @@ export const EventStatsWrapper = (props: PropType): JSX.Element => { return ( <> - {showModal && ( - setShowModal(false)} - eventId={props.eventId} - /> - )} + setShowModal(false)} + key={props.eventId || 'eventStatsDetails'} + eventId={props.eventId} + /> ); }; diff --git a/src/components/IconComponent/IconComponent.test.tsx b/src/components/IconComponent/IconComponent.test.tsx index 766a6576b8..ff83ebe57c 100644 --- a/src/components/IconComponent/IconComponent.test.tsx +++ b/src/components/IconComponent/IconComponent.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import IconComponent from './IconComponent'; -const screenTestIdMap = { +const screenTestIdMap: Record> = { Dashboard: { name: 'Dashboard', testId: 'Icon-Component-DashboardIcon', @@ -35,65 +35,35 @@ const screenTestIdMap = { name: 'All Organizations', testId: 'Icon-Component-AllOrganizationsIcon', }, + EventProject: { + name: 'Add Event Project', + testId: 'Icon-Component-Add-Event-Project', + }, + ListEventRegistrant: { + name: 'List Event Registrants', + testId: 'Icon-Component-List-Event-Registrants', + }, + CheckInRegistrants: { + name: 'Check In Registrants', + testId: 'Icon-Component-Check-In-Registrants', + }, + EventStats: { + name: 'Event Stats', + testId: 'Icon-Component-Event-Stats', + }, default: { name: 'default', testId: 'Icon-Component-DefaultIcon', }, }; -describe('Testing CollapsibleDropdown component', () => { - it('renders the Dashboard icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.Dashboard.testId}`) - ).toBeInTheDocument(); - }); - it('renders the People icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.People.testId}`) - ).toBeInTheDocument(); - }); - it('renders the Events icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.Events.testId}`) - ).toBeInTheDocument(); - }); - it('renders the Posts icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.Posts.testId}`) - ).toBeInTheDocument(); - }); - it('renders the Block/Unblock icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.BlockUnblock.testId}`) - ).toBeInTheDocument(); - }); - it('renders the Plugins icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.Plugins.testId}`) - ).toBeInTheDocument(); - }); - it('renders the Settings icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.Settings.testId}`) - ).toBeInTheDocument(); - }); - it('renders the All Organizations icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.AllOrganizations.testId}`) - ).toBeInTheDocument(); - }); - it('renders the default icon', () => { - render(); - expect( - screen.getByTestId(`${screenTestIdMap.default.testId}`) - ).toBeInTheDocument(); +describe('Testing Collapsible Dropdown component', () => { + it('Renders the correct icon according to the component', () => { + for (const component in screenTestIdMap) { + render(); + expect( + screen.getByTestId(screenTestIdMap[component].testId) + ).toBeInTheDocument(); + } }); }); diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index a4648a6e03..8504b13d2a 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -8,6 +8,10 @@ import { ReactComponent as PeopleIcon } from 'assets/svgs/people.svg'; import { ReactComponent as PluginsIcon } from 'assets/svgs/plugins.svg'; import { ReactComponent as PostsIcon } from 'assets/svgs/posts.svg'; import { ReactComponent as SettingsIcon } from 'assets/svgs/settings.svg'; +import { ReactComponent as AddEventProjectIcon } from 'assets/svgs/addEventProject.svg'; +import { ReactComponent as ListEventRegistrantsIcon } from 'assets/svgs/listEventRegistrants.svg'; +import { ReactComponent as CheckInRegistrantsIcon } from 'assets/svgs/checkInRegistrants.svg'; +import { ReactComponent as EventStatsIcon } from 'assets/svgs/eventStats.svg'; export interface InterfaceIconComponent { name: string; @@ -56,6 +60,34 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { data-testid="Icon-Component-AllOrganizationsIcon" /> ); + case 'Add Event Project': + return ( + + ); + case 'List Event Registrants': + return ( + + ); + case 'Check In Registrants': + return ( + + ); + case 'Event Stats': + return ( + + ); default: return ( ) : ( {`dummy )} diff --git a/src/components/LeftDrawerEvent/LeftDrawerEvent.module.css b/src/components/LeftDrawerEvent/LeftDrawerEvent.module.css new file mode 100644 index 0000000000..af654f9043 --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEvent.module.css @@ -0,0 +1,291 @@ +.leftDrawer { + width: calc(300px + 2rem); + position: fixed; + top: 0; + bottom: 0; + z-index: 100; + display: flex; + flex-direction: column; + padding: 0.8rem 1rem 0 1rem; + background-color: var(--bs-white); + transition: 0.5s; + overflow-y: scroll; +} + +.leftDrawer::-webkit-scrollbar { + width: 6px; +} + +.leftDrawer::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.leftDrawer::-webkit-scrollbar-thumb { + background: var(--bs-gray-500); +} + +.leftDrawer::-webkit-scrollbar-thumb:hover { + background: var(--bs-gray-600); +} + +.activeDrawer { + width: calc(300px + 2rem); + position: fixed; + top: 0; + left: 0; + bottom: 0; + animation: comeToRightBigScreen 0.5s ease-in-out; +} + +.inactiveDrawer { + position: fixed; + top: 0; + left: calc(-300px - 2rem); + bottom: 0; + animation: goToLeftBigScreen 0.5s ease-in-out; +} + +.leftDrawer .closeModalBtn { + display: none; +} + +.leftDrawer .brandingContainer { + display: flex; + justify-content: flex-start; + align-items: center; +} + +.leftDrawer .organizationContainer button { + position: relative; + margin: 1.25rem 0; + padding: 2.5rem 0.1rem; + border-radius: 0.5rem; + border: 1px solid var(--bs-gray-300); + background-color: var(--bs-gray-100); +} + +.leftDrawer .talawaLogo { + width: 42px; + height: 42px; + margin-right: 0.5rem; +} + +.leftDrawer .talawaText { + font-size: 1.1rem; +} + +.leftDrawer .titleHeader { + margin-bottom: 1rem; + font-weight: 600; +} + +.leftDrawer .optionList button { + display: flex; + align-items: center; + width: 100%; + text-align: start; + margin-bottom: 0.8rem; + border: 1px solid var(--bs-gray-200); + border-radius: 8px; +} + +.leftDrawer button .iconWrapper { + width: 36px; + transform: translateY(3px) translateY(-2px); +} + +.leftDrawer .optionList .collapseBtn { + height: 48px; +} + +.leftDrawer button .iconWrapperSm { + width: 36px; + display: flex; + justify-content: center; + align-items: center; +} + +.leftDrawer .profileContainer { + border: none; + width: 100%; + margin-top: 5rem; + height: 52px; + border-radius: 8px; + background-color: var(--bs-white); + display: flex; + align-items: center; +} + +.leftDrawer .profileContainer:focus { + outline: none; + background-color: var(--bs-gray-100); +} + +.leftDrawer .imageContainer { + width: 68px; +} + +.leftDrawer .profileContainer img { + height: 52px; + width: 52px; + border-radius: 50%; +} + +.leftDrawer .profileContainer .profileText { + flex: 1; + text-align: start; +} + +.leftDrawer .profileContainer .profileText .primaryText { + font-size: 1.1rem; + font-weight: 600; +} + +.leftDrawer .profileContainer .profileText .secondaryText { + font-size: 0.8rem; + font-weight: 400; + color: var(--bs-secondary); + display: block; + text-transform: capitalize; +} + +@media (max-width: 1120px) { + .leftDrawer { + width: calc(250px + 2rem); + padding: 1rem 1rem 0 1rem; + } +} + +/* For tablets */ +@media (max-width: 820px) { + .hideElemByDefault { + display: none; + } + + .leftDrawer { + width: 100%; + left: 0; + right: 0; + } + + .leftDrawer .closeModalBtn { + display: block; + position: absolute; + top: 1rem; + right: 1rem; + z-index: 10; + } + + /* For smaller devices .activeDrawer in real behaves like inactive */ + .activeDrawer { + opacity: 0; + left: 0; + z-index: -1; + animation: closeDrawer 0.4s ease-in-out; + } + + /* For smaller devices .inactiveDrawer in real behaves like active */ + .inactiveDrawer { + display: flex; + z-index: 100; + animation: openDrawer 0.6s ease-in-out; + } +} + +@keyframes goToLeftBigScreen { + from { + left: 0; + } + + to { + opacity: 0.1; + left: calc(-300px - 2rem); + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes goToLeftBigScreen { + from { + left: 0; + } + + to { + opacity: 0.1; + left: calc(-300px - 2rem); + } +} + +@keyframes comeToRightBigScreen { + from { + opacity: 0.4; + left: calc(-300px - 2rem); + } + + to { + opacity: 1; + left: 0; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes comeToRightBigScreen { + from { + opacity: 0.4; + left: calc(-300px - 2rem); + } + + to { + opacity: 1; + left: 0; + } +} + +@keyframes closeDrawer { + from { + left: 0; + opacity: 1; + } + + to { + left: -1000px; + opacity: 0; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes closeDrawer { + from { + left: 0; + opacity: 1; + } + + to { + left: -1000px; + opacity: 0; + } +} + +@keyframes openDrawer { + from { + opacity: 0; + left: -1000px; + } + + to { + left: 0; + opacity: 1; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes openDrawer { + from { + opacity: 0; + left: -1000px; + } + + to { + left: 0; + opacity: 1; + } +} diff --git a/src/components/LeftDrawerEvent/LeftDrawerEvent.test.tsx b/src/components/LeftDrawerEvent/LeftDrawerEvent.test.tsx new file mode 100644 index 0000000000..f6187ce9c6 --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEvent.test.tsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-localstorage-mock'; +import { I18nextProvider } from 'react-i18next'; +import { BrowserRouter } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import i18nForTest from 'utils/i18nForTest'; +import LeftDrawerEvent, { + type InterfaceLeftDrawerProps, +} from './LeftDrawerEvent'; +import { MockedProvider } from '@apollo/react-testing'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +const props: InterfaceLeftDrawerProps = { + event: { + _id: 'testEvent', + title: 'Test Event', + description: 'Test Description', + organization: { + _id: 'Test Organization', + }, + }, + hideDrawer: false, + setHideDrawer: jest.fn(), + setShowAddEventProjectModal: jest.fn(), +}; + +const mocks = [ + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: 'testEvent', + }, + }, + result: { + data: { + event: { + _id: 'testEvent', + feedback: [], + averageFeedbackScore: 5, + }, + }, + }, + }, +]; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// They are required by the feedback statistics component +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +beforeEach(() => { + localStorage.setItem('FirstName', 'John'); + localStorage.setItem('LastName', 'Doe'); + localStorage.setItem( + 'UserImage', + 'https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe' + ); +}); + +afterEach(() => { + jest.clearAllMocks(); + localStorage.clear(); +}); + +describe('Testing Left Drawer component for the Event Dashboard', () => { + test('Component should be rendered properly', async () => { + localStorage.setItem('UserImage', ''); + localStorage.setItem('UserType', 'SUPERADMIN'); + + const { queryByText } = render( + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Talawa Admin Portal')).toBeInTheDocument() + ); + await waitFor(() => expect(queryByText('Test Event')).toBeInTheDocument()); + await waitFor(() => + expect(queryByText('Test Description')).toBeInTheDocument() + ); + await waitFor(() => + expect(queryByText('Event Options')).toBeInTheDocument() + ); + }); + + test('Add Event Project button and profile page button should work properly', async () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + + const { queryByText, queryByTestId } = render( + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Talawa Admin Portal')).toBeInTheDocument() + ); + + fireEvent.click(queryByText('Add an Event Project') as HTMLElement); + expect(props.setShowAddEventProjectModal).toHaveBeenCalled(); + + fireEvent.click(queryByTestId(/profileBtn/i) as HTMLElement); + expect(toast.success).toHaveBeenCalledWith('Profile page coming soon!'); + }); + + test('Testing Drawer when hideDrawer is null', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + ); + }); + + test('Testing Drawer when hideDrawer is true', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + ); + }); + + test('Testing Drawer open close functionality', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + ); + const closeModalBtn = screen.getByTestId(/closeModalBtn/i); + userEvent.click(closeModalBtn); + }); + + test('Testing logout functionality', async () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + ); + + userEvent.click(screen.getByTestId('logoutBtn')); + expect(localStorage.clear).toHaveBeenCalled(); + expect(global.window.location.pathname).toBe('/'); + }); +}); diff --git a/src/components/LeftDrawerEvent/LeftDrawerEvent.tsx b/src/components/LeftDrawerEvent/LeftDrawerEvent.tsx new file mode 100644 index 0000000000..1981eb12f0 --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEvent.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import Button from 'react-bootstrap/Button'; +import { useHistory } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { ReactComponent as AngleRightIcon } from 'assets/svgs/angleRight.svg'; +import { ReactComponent as LogoutIcon } from 'assets/svgs/logout.svg'; +import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; +import styles from './LeftDrawerEvent.module.css'; +import IconComponent from 'components/IconComponent/IconComponent'; +import { EventRegistrantsWrapper } from 'components/EventRegistrantsModal/EventRegistrantsWrapper'; +import { CheckInWrapper } from 'components/CheckIn/CheckInWrapper'; +import { EventStatsWrapper } from 'components/EventStats/EventStatsWrapper'; + +export interface InterfaceLeftDrawerProps { + event: { + _id: string; + title: string; + description: string; + organization: { + _id: string; + }; + }; + hideDrawer: boolean | null; + setHideDrawer: React.Dispatch>; + setShowAddEventProjectModal: React.Dispatch>; +} + +const leftDrawerEvent = ({ + event, + hideDrawer, + setHideDrawer, + setShowAddEventProjectModal, +}: InterfaceLeftDrawerProps): JSX.Element => { + const userType = localStorage.getItem('UserType'); + const firstName = localStorage.getItem('FirstName'); + const lastName = localStorage.getItem('LastName'); + const userImage = localStorage.getItem('UserImage'); + + const history = useHistory(); + const logout = (): void => { + localStorage.clear(); + history.push('/'); + }; + + return ( + <> +
+ {/* Close Drawer Button for small devices */} + + + {/* Branding Section */} +
+ + Talawa Admin Portal +
+ + {/* Event Detail Section */} +
+ +
+ + {/* Options List */} +
+
Event Options
+ + + + +
+ + {/* Profile Section & Logout Btn */} +
+ + +
+
+ + ); +}; + +export default leftDrawerEvent; diff --git a/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.module.css b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.module.css new file mode 100644 index 0000000000..681ac8823d --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.module.css @@ -0,0 +1,60 @@ +.pageContainer { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 1rem 1.5rem 0 calc(300px + 2rem + 1.5rem); +} + +.expand { + padding-left: 1.5rem; + animation: moveLeft 0.5s ease-in-out; +} + +.contract { + padding-left: calc(300px + 2rem + 1.5rem); + animation: moveRight 0.5s ease-in-out; +} + +@media (max-width: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } +} + +/* For tablets */ +@media (max-width: 820px) { + .pageContainer { + padding-left: 1.5rem; + } + + .contract, + .expand { + animation: none; + } +} + +@media (max-width: 820px) { + .pageContainer { + padding: 1rem; + } +} + +@keyframes moveLeft { + from { + padding-left: calc(300px + 2rem + 1.5rem); + } + + to { + padding-left: 1.5rem; + } +} + +@keyframes moveRight { + from { + padding-left: 1.5rem; + } + + to { + padding-left: calc(300px + 2rem + 1.5rem); + } +} diff --git a/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.test.tsx b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.test.tsx new file mode 100644 index 0000000000..72093aaf06 --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.test.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import 'jest-localstorage-mock'; +import { render, waitFor, fireEvent } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { BrowserRouter } from 'react-router-dom'; +import i18nForTest from 'utils/i18nForTest'; +import { + type InterfacePropType, + LeftDrawerEventWrapper, +} from './LeftDrawerEventWrapper'; +import { MockedProvider } from '@apollo/react-testing'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +const props: InterfacePropType = { + event: { + _id: 'testEvent', + title: 'Test Event', + description: 'Test Description', + organization: { + _id: 'Test Organization', + }, + }, + setShowAddEventProjectModal: jest.fn(), + children: null, +}; + +const mocks = [ + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: 'testEvent', + }, + }, + result: { + data: { + event: { + _id: 'testEvent', + feedback: [], + averageFeedbackScore: 5, + }, + }, + }, + }, +]; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// They are required by the feedback statistics component +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +beforeEach(() => { + localStorage.setItem('FirstName', 'John'); + localStorage.setItem('LastName', 'Doe'); + localStorage.setItem( + 'UserImage', + 'https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe' + ); +}); + +afterEach(() => { + jest.clearAllMocks(); + localStorage.clear(); +}); + +describe('Testing Left Drawer Wrapper component for the Event Dashboard', () => { + test('Component should be rendered properly and the close menu button should function', async () => { + const { queryByText, queryByTestId } = render( + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Event Management')).toBeInTheDocument() + ); + fireEvent.click(queryByTestId('closeLeftDrawerBtn') as HTMLElement); + }); +}); diff --git a/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.tsx b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.tsx new file mode 100644 index 0000000000..1b1157385c --- /dev/null +++ b/src/components/LeftDrawerEvent/LeftDrawerEventWrapper.tsx @@ -0,0 +1,62 @@ +import MenuIcon from '@mui/icons-material/Menu'; +import LeftDrawerEvent from './LeftDrawerEvent'; +import React, { useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import styles from './LeftDrawerEventWrapper.module.css'; + +export interface InterfacePropType { + event: { + _id: string; + title: string; + description: string; + organization: { + _id: string; + }; + }; + setShowAddEventProjectModal: React.Dispatch>; + children: React.ReactNode; +} + +export const LeftDrawerEventWrapper = ( + props: InterfacePropType +): JSX.Element => { + const [hideDrawer, setHideDrawer] = useState(null); + + return ( + <> + + +
+
+
+

Event Management

+
+ +
+ {props.children} +
+ + ); +}; diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx index 119a90f0eb..beb7f07206 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx @@ -192,7 +192,7 @@ const leftDrawerOrg = ({ {`profile ) : ( {`dummy )} diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.test.tsx index f730baca73..8020ac03fa 100644 --- a/src/components/UserPortal/Register/Register.test.tsx +++ b/src/components/UserPortal/Register/Register.test.tsx @@ -4,7 +4,6 @@ import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; - import { SIGNUP_MUTATION } from 'GraphQl/Mutations/mutations'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; diff --git a/src/screens/EventDashboard/EventDashboard.mocks.ts b/src/screens/EventDashboard/EventDashboard.mocks.ts index c9b1109e5d..fe5b66a959 100644 --- a/src/screens/EventDashboard/EventDashboard.mocks.ts +++ b/src/screens/EventDashboard/EventDashboard.mocks.ts @@ -1,4 +1,71 @@ import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +const constantMocks = [ + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: 'event123', + }, + }, + result: { + data: { + event: { + _id: 'event123', + feedback: [], + averageFeedbackScore: 0, + }, + }, + }, + }, + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: '', + }, + }, + result: { + data: { + event: { + _id: '', + feedback: [], + averageFeedbackScore: 0, + }, + }, + }, + }, + { + request: { + query: EVENT_DETAILS, + variables: { + id: '', + }, + }, + result: { + data: { + event: { + _id: '', + title: 'Event Title', + description: 'Event Description', + startDate: '1/1/23', + endDate: '2/2/23', + startTime: '08:00:00', + endTime: '09:00:00', + allDay: false, + location: 'India', + organization: { + _id: '', + members: [], + }, + attendees: [], + projects: [], + }, + }, + }, + }, +]; // Mock 1 export const queryMockWithTime = [ @@ -12,6 +79,7 @@ export const queryMockWithTime = [ result: { data: { event: { + _id: 'event123', title: 'Event Title', description: 'Event Description', startDate: '1/1/23', @@ -30,6 +98,7 @@ export const queryMockWithTime = [ }, }, }, + ...constantMocks, ]; // Mock 2 @@ -44,6 +113,7 @@ export const queryMockWithoutTime = [ result: { data: { event: { + _id: 'event123', title: 'Event Title', description: 'Event Description', startDate: '1/1/23', @@ -62,6 +132,7 @@ export const queryMockWithoutTime = [ }, }, }, + ...constantMocks, ]; // Mock 3 @@ -76,6 +147,7 @@ export const queryMockWithProject = [ result: { data: { event: { + _id: 'event123', title: 'Event Title', description: 'Event Description', startDate: '1/1/23', @@ -101,6 +173,7 @@ export const queryMockWithProject = [ }, }, }, + ...constantMocks, ]; // Mock 4 @@ -115,6 +188,7 @@ export const queryMockWithProjectAndTask = [ result: { data: { event: { + _id: 'event123', title: 'Event Title', description: 'Event Description', startDate: '1/1/23', @@ -149,4 +223,5 @@ export const queryMockWithProjectAndTask = [ }, }, }, + ...constantMocks, ]; diff --git a/src/screens/EventDashboard/EventDashboard.test.tsx b/src/screens/EventDashboard/EventDashboard.test.tsx index 2a3340be10..b595f893eb 100644 --- a/src/screens/EventDashboard/EventDashboard.test.tsx +++ b/src/screens/EventDashboard/EventDashboard.test.tsx @@ -1,15 +1,14 @@ import React from 'react'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor, act } from '@testing-library/react'; import EventDashboard from './EventDashboard'; import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; import { MockedProvider } from '@apollo/react-testing'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { type DefaultOptions } from '@apollo/client'; import { queryMockWithProject, queryMockWithTime, @@ -17,6 +16,18 @@ import { queryMockWithProjectAndTask, } from './EventDashboard.mocks'; +// We want to disable all forms of caching so that we do not need to define a custom merge function in testing for the network requests +const defaultOptions: DefaultOptions = { + watchQuery: { + fetchPolicy: 'no-cache', + errorPolicy: 'ignore', + }, + query: { + fetchPolicy: 'no-cache', + errorPolicy: 'all', + }, +}; + // Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) // These modules are used by the Feedback components jest.mock('@mui/x-charts/PieChart', () => ({ @@ -25,6 +36,16 @@ jest.mock('@mui/x-charts/PieChart', () => ({ pieArcClasses: jest.fn(), })); +// We will wait for 500 ms after each test to ensure that the queries and rendering of the nested components such as `Feedback` and `Statistics` is complete before moving on to the next test suite +// This is important to mitigate the cleanup errors due to nesting of components +async function wait(ms = 500): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + describe('Testing Event Dashboard Screen', () => { beforeEach(() => { global.window = Object.create(window); @@ -37,25 +58,25 @@ describe('Testing Event Dashboard Screen', () => { }); test('The page should display event details correctly and also show the time if provided', async () => { - const { queryByText } = render( + const { queryByText, queryAllByText } = render( - + - - - - - - + + ); - await waitFor(() => expect(queryByText('Event Title')).toBeInTheDocument()); + await waitFor(() => expect(queryAllByText('Event Title').length).toBe(2)); await waitFor(() => - expect(queryByText('Event Description')).toBeInTheDocument() + expect(queryAllByText('Event Description').length).toBe(2) ); await waitFor(() => expect(queryByText('India')).toBeInTheDocument()); @@ -66,38 +87,44 @@ describe('Testing Event Dashboard Screen', () => { queryByText('There are no active projects for this event!') ).toBeInTheDocument() ); + + await wait(); }); test('The page should display event details correctly and should not show the time if it is null', async () => { - const { queryByText } = render( + const { queryAllByText } = render( - + - - - - - - + + ); - await waitFor(() => expect(queryByText('Event Title')).toBeInTheDocument()); + await waitFor(() => expect(queryAllByText('Event Title').length).toBe(2)); + + await wait(); }); test('The page should display event project details correctly when provided', async () => { const { queryByText } = render( - + - - - - - - + + + + @@ -108,34 +135,36 @@ describe('Testing Event Dashboard Screen', () => { await waitFor(() => expect(queryByText('Project Description 1')).toBeInTheDocument() ); + + await wait(); }); test('The modals from the page should work properly', async () => { - const { queryByRole, queryByText, getByRole } = render( + const { queryByRole, getByRole, queryAllByText } = render( - + - - - - - - + + ); - await waitFor(() => expect(queryByText('Event Title')).toBeInTheDocument()); + await waitFor(() => expect(queryAllByText('Event Title').length).toBe(2)); - // Edit Event Project Modal + // Add Event Project Modal await waitFor(() => fireEvent.click( getByRole('button', { name: 'addEventProject' }) as HTMLElement ) ); - fireEvent.click(queryByRole('button', { name: /close/i }) as HTMLElement); + fireEvent.click(queryByRole('button', { name: /close/i }) as HTMLElement); // Edit Event Project Modal await waitFor(() => fireEvent.click( @@ -157,5 +186,7 @@ describe('Testing Event Dashboard Screen', () => { fireEvent.click(getByRole('button', { name: 'addTask' }) as HTMLElement) ); fireEvent.click(queryByRole('button', { name: /close/i }) as HTMLElement); + + await wait(); }); }); diff --git a/src/screens/EventDashboard/EventDashboard.tsx b/src/screens/EventDashboard/EventDashboard.tsx index 198e31e508..810e8335c6 100644 --- a/src/screens/EventDashboard/EventDashboard.tsx +++ b/src/screens/EventDashboard/EventDashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import { useQuery } from '@apollo/client'; @@ -8,14 +8,12 @@ import { AddEventProjectModal } from 'components/EventProjectModals/AddEventProj import { UpdateEventProjectModal } from 'components/EventProjectModals/UpdateEventProjectModal'; import { DeleteEventProjectModal } from 'components/EventProjectModals/DeleteEventProjectModal'; import { AddTaskModal } from 'components/TaskModals/AddTaskModal'; -import { EventStatsWrapper } from 'components/EventStats/EventStatsWrapper'; import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; import Button from 'react-bootstrap/Button'; import List from '@mui/material/List'; -import { EventRegistrantsWrapper } from 'components/EventRegistrantsModal/EventRegistrantsWrapper'; import { TaskListItem } from 'components/TaskListItem/TaskListItem'; -import { CheckInWrapper } from 'components/CheckIn/CheckInWrapper'; import Loader from 'components/Loader/Loader'; +import { LeftDrawerEventWrapper } from 'components/LeftDrawerEvent/LeftDrawerEventWrapper'; interface InterfaceEventTask { _id: string; @@ -39,7 +37,12 @@ interface InterfaceEventProject { const EventDashboard = (): JSX.Element => { // Get the Event ID from the URL document.title = 'Event Dashboard'; - const eventId = window.location.href.split('/')[4]; + + const [eventId, setEventId] = useState(''); + + useEffect(() => { + setEventId(window.location.href.split('/')[4]); + }, [window.location.href]); // Data fetching const { @@ -71,7 +74,11 @@ const EventDashboard = (): JSX.Element => { } return ( - <> +
@@ -100,26 +107,6 @@ const EventDashboard = (): JSX.Element => { Registrants: {eventData.event.attendees.length}


- {/* Buttons to trigger different modals */} -

- - - - -

@@ -258,7 +245,7 @@ const EventDashboard = (): JSX.Element => { refetchData={refetchEventData} projectId={currentProject._id} /> - +
); }; diff --git a/src/setupTests.ts b/src/setupTests.ts index feb8764f8b..392ad3e99b 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -32,3 +32,5 @@ jestPreviewConfigure({ // Opt-in to automatic mode to preview failed test case automatically. autoPreview: true, }); + +jest.setTimeout(15000);