diff --git a/jest.config.js b/jest.config.js index 9af4af4e..be8009f3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,9 @@ export default { preset: 'ts-jest', testEnvironment: 'jsdom', + testEnvironmentOptions: { + customExportConditions: [] // don't load "browser" field + }, verbose: true, collectCoverage: true, coverageReporters: ['lcov', 'text'], diff --git a/package-lock.json b/package-lock.json index 4523d9f6..828a54c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,12 @@ "@babel/preset-react": "^7.22.5", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@fullcalendar/core": "^5.11.5", - "@fullcalendar/daygrid": "^5.11.5", - "@fullcalendar/interaction": "^5.11.5", - "@fullcalendar/list": "^5.11.5", - "@fullcalendar/react": "^5.11.5", - "@fullcalendar/timegrid": "^5.11.5", + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", + "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/list": "^6.1.15", + "@fullcalendar/react": "^6.1.15", + "@fullcalendar/timegrid": "^6.1.15", "@headlessui/react": "^1.7.15", "@heroicons/react": "^1.0.6", "@iconify/react": "^3.2.2", @@ -2670,72 +2670,63 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" }, - "node_modules/@fullcalendar/common": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/common/-/common-5.11.5.tgz", - "integrity": "sha512-3iAYiUbHXhjSVXnYWz27Od2cslztUPsOwiwKlfGvQxBixv2Kl6a8IPwaijKFYJHXdwYmfPoEgK7rvqAGVoIYwA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@fullcalendar/core": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-5.11.5.tgz", - "integrity": "sha512-M/WQuq1+uUHxFDEIu2ib/aaPZ70VsRk2ITECo/WCLSLTVWcHPXwEg83reyP3G8JrMM4gRL4vScEHhX0U5aoNSw==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", + "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", + "license": "MIT", "dependencies": { - "@fullcalendar/common": "~5.11.5", - "preact": "~10.12.1", - "tslib": "^2.1.0" + "preact": "~10.12.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-5.11.5.tgz", - "integrity": "sha512-hMpq0U3Nucys2jDD+crbkJCr+tVt3fDw04OE3fbpisuzqtrHxIzRmnUOdbWUjJQyToAAkt7UVUQ9E7hYdmvyGA==", - "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", + "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" } }, "node_modules/@fullcalendar/interaction": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-5.11.5.tgz", - "integrity": "sha512-Vg9uw8zKXZc2RP7it88U8R/kxJIQsK4pyv+s+RhlvT5NBZ9KLOh5y2xGCS4A4hyY7qLrzugxnKYlu6NwNqJ/RQ==", - "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", + "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" } }, "node_modules/@fullcalendar/list": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-5.11.5.tgz", - "integrity": "sha512-ZYMPT4CVt9tIYkVVNx7CKkB2xc+n9L56+vgXkurptgYgPsacXYkcpF/1Hy/B5LKlg0ROEF9Qfftjow8xjANqaA==", - "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", + "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" } }, "node_modules/@fullcalendar/react": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-5.11.5.tgz", - "integrity": "sha512-PbBlDyKJ8IQYf5mBdD1mjDas2v3eEU1UfWYLv0e6uGCktH+g4mgaG/LCDOwE65V5VH5FH8+kVkFjIScwA54WwA==", - "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" - }, + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.15.tgz", + "integrity": "sha512-L0b9hybS2J4e7lq6G2CD4nqriyLEqOH1tE8iI6JQjAMTVh5JicOo5Mqw+fhU5bJ7hLfMw2K3fksxX3Ul1ssw5w==", + "license": "MIT", "peerDependencies": { - "react": "^16.7.0 || ^17 || ^18", - "react-dom": "^16.7.0 || ^17 || ^18" + "@fullcalendar/core": "~6.1.15", + "react": "^16.7.0 || ^17 || ^18 || ^19", + "react-dom": "^16.7.0 || ^17 || ^18 || ^19" } }, "node_modules/@fullcalendar/timegrid": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-5.11.5.tgz", - "integrity": "sha512-OEH5mrTclwxgUbb51N6qr7ifzNkR74ygUEFpiMLyyUjkp7a76N6BsAP5mBQnTOpTTUZBu9tAOmfcnvi7skUayQ==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", + "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", + "license": "MIT", "dependencies": { - "@fullcalendar/common": "~5.11.5", - "@fullcalendar/daygrid": "~5.11.5", - "tslib": "^2.1.0" + "@fullcalendar/daygrid": "~6.1.15" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" } }, "node_modules/@graphql-typed-document-node/core": { diff --git a/package.json b/package.json index eeec242e..82fba72c 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "@babel/preset-react": "^7.22.5", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@fullcalendar/core": "^5.11.5", - "@fullcalendar/daygrid": "^5.11.5", - "@fullcalendar/interaction": "^5.11.5", - "@fullcalendar/list": "^5.11.5", - "@fullcalendar/react": "^5.11.5", - "@fullcalendar/timegrid": "^5.11.5", + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", + "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/list": "^6.1.15", + "@fullcalendar/react": "^6.1.15", + "@fullcalendar/timegrid": "^6.1.15", "@headlessui/react": "^1.7.15", "@heroicons/react": "^1.0.6", "@iconify/react": "^3.2.2", diff --git a/src/Mutations/User.tsx b/src/Mutations/User.tsx index aac7b8a4..ea3e3b7d 100644 --- a/src/Mutations/User.tsx +++ b/src/Mutations/User.tsx @@ -53,8 +53,10 @@ export const GET_ALL_TTL_USERS = gql` getAllTTLUsers(orgToken: $orgToken) { profile { name + id } email + role team { name cohort { diff --git a/src/Mutations/event.tsx b/src/Mutations/event.tsx index 9c7d5e84..25357ff9 100644 --- a/src/Mutations/event.tsx +++ b/src/Mutations/event.tsx @@ -3,34 +3,96 @@ import { gql } from '@apollo/client'; export const GET_EVENTS = gql` query GetEvents($authToken: String) { getEvents(authToken: $authToken) { + id + user end hostName start timeToEnd timeToStart title + invitees { + email + } } } `; +export const GET_EVENT = gql` + query GetEvent($eventId: String!, $authToken: String!) { + getEvent(eventId: $eventId,authToken: $authToken) { + id + user + end + hostName + start + timeToEnd + timeToStart + title + invitees { + email + } + } + } +` + export const ADD_EVENT = gql` mutation CreateEvent( $title: String! $end: String! $timeToStart: String! - $timeToFinish: String! + $timeToEnd: String! $hostName: String! $start: String! - $authToken: String + $authToken: String! + $orgToken: String! + $invitees: [String]! ) { createEvent( title: $title end: $end timeToStart: $timeToStart - timeToEnd: $timeToFinish + timeToEnd: $timeToEnd + hostName: $hostName + start: $start + authToken: $authToken + orgToken: $orgToken + invitees: $invitees + ) { + end + hostName + start + timeToEnd + title + timeToStart + } + } +`; + +export const EDIT_EVENT = gql` +mutation EditEvent( + $eventId: String! + $title: String! + $end: String! + $timeToStart: String! + $timeToEnd: String! + $hostName: String! + $start: String! + $authToken: String! + $orgToken: String! + $invitees: [String]! + ) { + editEvent( + eventId: $eventId + title: $title + end: $end + timeToStart: $timeToStart + timeToEnd: $timeToEnd hostName: $hostName start: $start authToken: $authToken + orgToken: $orgToken + invitees: $invitees ) { end hostName @@ -41,3 +103,45 @@ export const ADD_EVENT = gql` } } `; + +export const CANCEL_EVENT = gql` + mutation CancelEvent( + $eventId: String! + $authToken: String! + ) { + cancelEvent( + eventId: $eventId + authToken: $authToken + ) { + end + hostName + start + timeToEnd + title + timeToStart + } + } +` + +export const RESPOND_TO_EVENT_INVITATION = gql` + mutation RespondToEventInvitation( + $eventToken: String! + $authToken: String! + ) { + respondToEventInvitation( + eventToken: $eventToken, + authToken: $authToken + ) { + end + hostName + start + timeToEnd + title + timeToStart + invitees { + email, + status + } + } + } +` diff --git a/src/Mutations/manageStudentMutations.tsx b/src/Mutations/manageStudentMutations.tsx index 91ed6632..34b9f4cd 100644 --- a/src/Mutations/manageStudentMutations.tsx +++ b/src/Mutations/manageStudentMutations.tsx @@ -12,6 +12,20 @@ export const GET_USERS_QUERY = gql` } `; +export const GET_ALL_USERS_QUERY = gql` + query GetUsers($orgToken: String) { + getAllUsers(orgToken: $orgToken) { + id + email + role + profile{ + firstName + lastName + } + } + } +`; + export const DROP_TRAINEE = gql` mutation DropTrainee( $traineeId: String! @@ -36,6 +50,7 @@ export const GET_TRAINEES_QUERY = gql` id user { id + role status { status date diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index f4cc467f..2c81533f 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -1,7 +1,8 @@ /* eslint-disable */ import React, { useEffect, useState } from 'react'; -import FullCalendar, { EventInput, EventContentArg } from '@fullcalendar/react'; +import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; +import { EventContentArg, EventInput } from '@fullcalendar/core'; import { useTranslation } from 'react-i18next'; import interactionPlugin from '@fullcalendar/interaction'; import DatePicker from 'react-datepicker'; @@ -9,10 +10,11 @@ import 'react-datepicker/dist/react-datepicker.css'; import ReactTooltip from 'react-tooltip'; import useDocumentTitle from '../hook/useDocumentTitle'; import { useLazyQuery, useMutation } from '@apollo/client'; -import { ADD_EVENT, GET_EVENTS } from '../Mutations/event'; +import { ADD_EVENT, GET_EVENTS, EDIT_EVENT, CANCEL_EVENT } from '../Mutations/event'; import moment from 'moment'; import CalendarSkeleton from '../Skeletons/Calender.skeleton' - +import { toast } from 'react-toastify'; +import EventGuestList from './EventGuestList'; /* istanbul ignore next */ const Calendar = () => { @@ -20,63 +22,54 @@ const Calendar = () => { const [addEventModel, setAddEventModel] = useState(false); const [newEvent, setNewEvent] = useState({ title: '', - start: '', - end: '', + start: new Date(), + end: new Date(), hostName: '', timeToStart: '', - timeToFinish: '', + timeToEnd: '', }); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [getEvents] = useLazyQuery(GET_EVENTS); + + const [getEvents, { loading, data }] = useLazyQuery(GET_EVENTS); const [createEvent] = useMutation(ADD_EVENT); - useEffect(() => { - const fetchData = async () => { - /* istanbul ignore next */ - try { - const { data: out } = await getEvents({ - variables: { - authToken: localStorage.getItem('auth_token'), - }, - }); - let all = out.getEvents.map((one: EventInput) => ({ - end: moment(one.end).format('YYYY-MM-DD'), - start: moment(one.start).format('YYYY-MM-DD'), - hostName: one.hostName, - timeToStart: one.timeToStart, - title: one.title, - timeToFinish: one.timeToEnd, - })); - setData(all); - } catch (error) { - console.log({ eventsError: data }); - // toast.error(error?.message || 'Something went wrong'); - }finally { - setLoading(false); - } - }; - fetchData(); - }, []); + + const [showTraineeDropdown, setShowTraineeDropdown] = useState(false); + const [selectedGuests, setSelectedGuests] = useState([]); + const { t } = useTranslation(); - const renderEvent = + + const fetchData = async () => { /* istanbul ignore next */ + try { + await getEvents({ + variables: { + authToken: localStorage.getItem('auth_token'), + }, + fetchPolicy: 'network-only', + }); + } catch (error: any) { + toast.error(error.message) + } + }; - (e: EventContentArg) => ( - /* istanbul ignore next */ -
${e.event.title}
${e.event.extendedProps.hostName}
${e.event.extendedProps.timeToStart} - ${e.event.extendedProps.timeToFinish}
`} - className="bg-primary text-white max-w-full min-w-full overflow-auto text-xs md:text-sm" - > -

{e.event.title}

-

{e.event.extendedProps.hostName}

-

- {e.event.extendedProps.timeToStart} -{' '} - {e.event.extendedProps.timeToFinish} -

- - - ); + useEffect(() => { + fetchData() + }, []); + + const renderEvent = (e: EventContentArg) => ( +
${e.event.title}
${e.event.extendedProps.hostName}
${e.event.extendedProps.timeToStart} - ${e.event.extendedProps.timeToEnd}
`} + className="bg-primary text-white max-w-full min-w-full overflow-auto text-xs md:text-sm" + > +

{e.event.title}

+

{e.event.extendedProps.hostName}

+

+ {e.event.extendedProps.timeToStart} - {e.event.extendedProps.timeToEnd} +

+ + + ); const removeModel = (e: any) => { e.preventDefault(); @@ -84,38 +77,171 @@ const Calendar = () => { setAddEventModel(newState); }; - const handleDateClick = () => { + const handleAddEventModal = () => { const newState = !addEventModel; setAddEventModel(newState); }; - /* istanbul ignore next */ const handleAddEvent = (e: any) => { e.preventDefault(); createEvent({ - variables: { ...newEvent, authToken: localStorage.getItem('auth_token') }, - }); - setData([...data, newEvent]); - setNewEvent({ - title: '', - start: '', - end: '', - hostName: '', - timeToStart: '', - timeToFinish: '', - }); - setTimeout(() => { - setAddEventModel(false); - }, 1000); + variables: { + ...newEvent, + authToken: localStorage.getItem('auth_token'), + orgToken: localStorage.getItem('orgToken'), + invitees: selectedGuests, + } + }) + .then(() => { + fetchData() + toast.success('Event has been added!'); // {{ edit_1 }} + setNewEvent({ + title: '', + start: new Date(), + end: new Date(), + hostName: '', + timeToStart: '', + timeToEnd: '', + }); + setSelectedGuests([]) + setTimeout(() => { + setAddEventModel(false); + }, 1000); + + }) + .catch((error) => { + toast.error(error.message); // Handle error if needed + }); + }; + + const handleAddGuest = (guestEmail: string) => { + setSelectedGuests((prev) => + prev.includes(guestEmail) + ? prev.filter((id) => id !== guestEmail) + : [...prev, guestEmail], + ); + }; + + //edit section + const [editEvent] = useMutation(EDIT_EVENT) + const [editEventModel, setEditEventModel] = useState(false) + const [editedEvent, setEditedEvent] = useState({ + id: '', + title: '', + start: '', + end: '', + hostName: '', + timeToStart: '', + timeToEnd: '', + }); + + const handleEditEventModel = async (e: EventInput) => { + const event = data?.getEvents.find((event: any)=> event.id === e.event?.id) + if (event) { + if(event.user !== JSON.parse(localStorage.getItem('auth')!).userId) return + setEditedEvent((prev) => { + return { + ...prev, + id: event.id, + title: event.title, + start: event.start, + end: event.end, + hostName: event.hostName, + timeToStart: event.timeToStart, + timeToEnd: event.timeToEnd, + } + }) + setSelectedGuests(event.invitees.map((invitee: any) => invitee.email)) + setEditEventModel(true); + } }; + const handleEditEvent = async (e: any) => { + e.preventDefault() + const { id, ...rest } = editedEvent + editEvent({ + variables: { + eventId: id, + ...rest, + authToken: localStorage.getItem('auth_token'), + orgToken: localStorage.getItem('orgToken'), + invitees: selectedGuests, + }, + }) + .then(() => { + fetchData() + toast.success('Event has been updated!'); + setEditedEvent({ + id: '', + title: '', + start: '', + end: '', + hostName: '', + timeToStart: '', + timeToEnd: '', + }); + setSelectedGuests([]) + setTimeout(() => { + setEditEventModel(false); + }, 1000); + }) + .catch((error) => { + toast.error(error.message); // Handle error if needed + }); + } + + const removeEditModel = (e: any) => { + e.preventDefault() + setSelectedGuests([]) + setEditEventModel(!editEventModel) + } + + // delete section + const [showDeleteModal, setShowDeleteModal] = useState(false) + const [cancelEvent] = useMutation(CANCEL_EVENT) + + const handleDeleteConfirmation = (e: any) => { + e.preventDefault() + setShowDeleteModal(prev => !prev) + } + + const handleDelete = async (e: any) => { + e.preventDefault() + cancelEvent({ + variables: { + eventId: editedEvent.id, + authToken: localStorage.getItem('auth_token') + }, + }) + .then(() => { + fetchData() + toast.success('Event cancelled successfully') + setEditedEvent({ + id: '', + title: '', + start: '', + end: '', + hostName: '', + timeToStart: '', + timeToEnd: '', + }); + setSelectedGuests([]) + setTimeout(() => { + setShowDeleteModal(false) + setEditEventModel(false); + }, 1000); + } + ) + .catch(err => { + toast.error(err.message) + }) + } + return ( <> - {/* =========================== Start:: RegisterTraineeModel =========================== */}
@@ -125,14 +251,10 @@ const Calendar = () => {
- {/* istanbul ignore next */}
- /* istanbul ignore next */ - handleAddEvent(e) - } + data-testid="addEventForm" + className=" py-3 px-8 overflow-y-auto h-full " + onSubmit={(e) => handleAddEvent(e)} >
@@ -143,7 +265,6 @@ const Calendar = () => { className=" dark:bg-dark-tertiary border dark:text-white border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full" placeholder={t('Event title')} value={newEvent.title} - // eslint-disable-next-line prettier/prettier onChange={(e) => setNewEvent({ ...newEvent, title: e.target.value }) } @@ -159,8 +280,8 @@ const Calendar = () => { className=" dark:bg-dark-tertiary dark:text-white border border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full" placeholder={t('Host name')} value={newEvent.hostName} - onChange /* istanbul ignore next */={(e) => - /* istanbul ignore next */ setNewEvent({ + onChange={(e) => + setNewEvent({ ...newEvent, hostName: e.target.value, }) @@ -176,8 +297,8 @@ const Calendar = () => { placeholderText={t('Start Date')} style={{ marginRight: '10px' }} selected={newEvent.start} - onChange /* istanbul ignore next */={(start: any) => - /* istanbul ignore next */ setNewEvent({ + onChange={(start: any) => + setNewEvent({ ...newEvent, start, }) @@ -193,25 +314,109 @@ const Calendar = () => { placeholderText={t('End Date')} style={{ marginRight: '10px' }} selected={newEvent.end} - onChange=/* istanbul ignore next */ {(end: any) => - /* istanbul ignore next */ - setNewEvent({ ...newEvent, end }) - } + onChange={(end: any) => setNewEvent({ ...newEvent, end })} />
+
+
+
+ + setNewEvent({ + ...newEvent, + timeToStart: e.target.value, + }) + } + /> +
+
+ + setNewEvent({ + ...newEvent, + timeToEnd: e.target.value, + }) + } + /> +
+
+
+ +
+
+ {t('Add guests')} + +
+ {showTraineeDropdown ? + : ''} +
+ +
+ + +
+
+
+
+
+ +
+
+
+

+ {t('Edit event')} +

+
+
+
+
handleEditEvent(e)} + >
- /* istanbul ignore next */ - setNewEvent({ ...newEvent, timeToStart: e.target.value }) + type="text" + data-testid="editEventTitle" + name="eventTitle" + className=" dark:bg-dark-tertiary border dark:text-white border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full" + placeholder={t('Event title')} + value={editedEvent.title} + onChange={(e) => + setEditedEvent({ ...editedEvent, title: e.target.value }) } />
@@ -220,56 +425,186 @@ const Calendar = () => {
- /* istanbul ignore next */ - setNewEvent({ ...newEvent, timeToFinish: e.target.value }) + className=" dark:bg-dark-tertiary dark:text-white border border-primary rounded outline-none px-5 font-sans text-xs py-2 w-full" + placeholder={t('Host name')} + value={editedEvent.hostName} + onChange={(e) => + setEditedEvent({ + ...editedEvent, + hostName: e.target.value, + }) + } + /> +
+
+ +
+
+ + setEditedEvent({ + ...editedEvent, + start, + }) } />
+
+
+ setEditedEvent({ ...editedEvent, end })} + /> +
+
+ +
+
+
+ + setEditedEvent({ + ...editedEvent, + timeToStart: e.target.value, + }) + } + /> +
+
+ + setEditedEvent({ + ...editedEvent, + timeToEnd: e.target.value, + }) + } + /> +
+
+
+ +
+
+ {t('Add guests')} + +
+ {showTraineeDropdown ? + : ''} +
+
- +
+ + +
- {/* =========================== End:: RegisterTraineeModel =============================== */} + +
+
+
+

+ {t('Confirm Dialog')} +

+
+
+

Please confirm the cancellation of event {editedEvent.title}.

+
+ + +
+
+

{t('Calendar')}

- + :''} {loading ? ( ) : ( ({ + id: event.id, + end: moment(event.end).add({days:1}).format('YYYY-MM-DD'), + start: moment(event.start).format('YYYY-MM-DD'), + hostName: event.hostName, + timeToStart: event.timeToStart, + title: event.title, + timeToEnd: event.timeToEnd, + allDay: true, + }))} plugins={[dayGridPlugin, interactionPlugin]} initialView="dayGridMonth" + eventClick={handleEditEventModel} /> )}
diff --git a/src/components/CalendarConfirmation.tsx b/src/components/CalendarConfirmation.tsx new file mode 100644 index 00000000..d6fc3ad3 --- /dev/null +++ b/src/components/CalendarConfirmation.tsx @@ -0,0 +1,87 @@ +import { useMutation } from "@apollo/client"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "react-toastify" +import moment from "moment"; +import { Link } from "react-router-dom"; +import { RESPOND_TO_EVENT_INVITATION } from "../Mutations/event"; + +function CalendarConfirmation() { + const { t } = useTranslation() + const [respondToEventInvitation, { data, loading, error }] = useMutation(RESPOND_TO_EVENT_INVITATION) + const params = new URLSearchParams(window.location.search) + const respond = async () => { + try { + await respondToEventInvitation({ + variables: { + eventToken: params.get('eventToken') || 'missing_token', + authToken: localStorage.getItem('auth_token'), + }, + }) + toast.success("Successfully responded to invitation") + } catch (err: any) { + toast.error(err.message) + } + } + useEffect(() => { + respond() + }, []) + return ( +
+
+
+

+ {t('Event Response')} +

+
+
+ { + loading ? +
+

..Loading

+
+ : '' + } + { + error ? +
+

An Error Occured!

+
+ : '' + } + { + data ? +
+
    +
  • + Title: {data?.respondToEventInvitation.title} +
  • +
  • + Hostname: {data?.respondToEventInvitation.hostName} +
  • +
  • + When: {moment(data?.respondToEventInvitation.start).format("YYYY-MM-DD")} to {moment(data?.respondToEventInvitation.end).format("YYYY-MM-DD")} +
  • +
  • + Time: {data?.respondToEventInvitation.timeToStart} to {data?.respondToEventInvitation.timeToEnd} +
  • +
  • + Guests: +
      + {data?.respondToEventInvitation.invitees?.map((invitee: any) => ( +
    • {invitee.email} {invitee.status}
    • + ))} +
    +
  • +
+
+ : '' + } + Go to calendar +
+
+ + ) +} + +export default CalendarConfirmation \ No newline at end of file diff --git a/src/components/CoordinatorCard.tsx b/src/components/CoordinatorCard.tsx index d1a1bb38..3eb33e30 100644 --- a/src/components/CoordinatorCard.tsx +++ b/src/components/CoordinatorCard.tsx @@ -55,6 +55,7 @@ export const GET_TEAMS_CARDS = gql` coordinator { email + role profile { name firstName diff --git a/src/components/EventGuestList.tsx b/src/components/EventGuestList.tsx new file mode 100644 index 00000000..f9a53204 --- /dev/null +++ b/src/components/EventGuestList.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { useQuery } from "@apollo/client"; +import { toast } from "react-toastify"; +import { GET_ALL_USERS_QUERY } from "../Mutations/manageStudentMutations"; + +export const getRoleColor = (role: string) => { + switch (role) { + case 'admin': + return 'bg-red-500'; + case 'ttl': + return 'bg-yellow-500'; + case 'trainee': + return 'bg-green-500'; + case 'coordinator': + return 'bg-blue-500'; + case 'manager': + return 'bg-violet-500'; + default: + return 'bg-gray-500'; + } +}; + +export const getRoleTitle = (role: string) => { + switch (role) { + case 'admin': + return 'Admins'; + case 'ttl': + return 'TTLs'; + case 'trainee': + return 'Trainees'; + case 'coordinator': + return 'Coordinators'; + case 'manager': + return 'Managers'; + default: + return 'Others'; + } +} + +function EventGuestList({ selectedGuests, handleAddGuest }: { selectedGuests: string[], handleAddGuest: any }) { + const { loading: guestDataLoading, data: guestData } = useQuery( + GET_ALL_USERS_QUERY, + { + variables: { + orgToken: localStorage.getItem('orgToken'), + }, + fetchPolicy: 'network-only', + onError: (error) => { + toast.error(error.message); + }, + }, + ); + + const roles = ["admin", "trainee", "ttl", "coordinator","manager"] + const authUser = JSON.parse(localStorage.getItem('auth')!) + + return ( +
+ {guestDataLoading ? ( +

Loading...

+ ) : ( + <> + {roles.map((role: string, index: number) => ( +
+

{getRoleTitle(role)}

+ {guestData?.getAllUsers + .filter((user: any) => user.role === role) + .map((guest: any) => ( +
+ handleAddGuest(guest.email)} + disabled={guest.email === authUser?.email} + className="mr-2" + /> + +
+ ))} + {index === roles.length-1 ? ' ':
} +
+ ))} + + )} +
+ ) +} + +export default EventGuestList \ No newline at end of file diff --git a/src/components/InvitationTable.tsx b/src/components/InvitationTable.tsx index ad0b5a66..e6b3cbef 100644 --- a/src/components/InvitationTable.tsx +++ b/src/components/InvitationTable.tsx @@ -1,12 +1,8 @@ // @ts-nocheck -import React, { useState,useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { - useGlobalFilter, - usePagination, - useSortBy, - useTable, -} from 'react-table'; +import { useGlobalFilter, usePagination, useSortBy, useTable } from 'react-table'; +import { toast } from 'react-toastify'; import DataPagination from './DataPagination'; interface TableData { @@ -31,7 +27,7 @@ function DataTableStats({ data, columns, error, loading }: TableData) { { data: memoizedData, columns: memoizedColumns, - initialState: {pageIndex, pageSize: 3, globalFilter: filterInput }, + initialState: { pageIndex, pageSize: 3, globalFilter: filterInput }, }, useGlobalFilter, useSortBy, @@ -55,10 +51,17 @@ function DataTableStats({ data, columns, error, loading }: TableData) { prepareRow, state: { pageIndex: currentPageIndex, pageSize }, } = tableInstance; - + useEffect(() => { setPageIndex(currentPageIndex); }, [currentPageIndex]); + + useEffect(() => { + if (error) { + toast.error('Network error. Please, try again later.'); + } + }, [error]); + const handleFilterChange = (e) => { const value = e.target.value || ''; setGlobalFilter(value); @@ -86,7 +89,17 @@ function DataTableStats({ data, columns, error, loading }: TableData) { ))} - {!loading && memoizedData.length === 0 ? ( + {loading && ( + + + Loading... + + + )} + {!loading && memoizedData.length === 0 && ( - ) : ( + )} + {!loading && + memoizedData.length > 0 && page.map((row) => { prepareRow(row); return ( @@ -121,40 +136,16 @@ function DataTableStats({ data, columns, error, loading }: TableData) { ))} ); - }) - )} - {loading && ( - - - Loading... - - - )} - {error && ( - - - Error occurred - - - )} + })} {!loading && !error && data.length === 0 && ( - {' '}
- {' '}

- {' '} - No records available{' '} + No records available

-
{' '} - {' '} +
+ )} @@ -179,4 +170,4 @@ function DataTableStats({ data, columns, error, loading }: TableData) { ); } -export default DataTableStats; +export default DataTableStats; \ No newline at end of file diff --git a/src/containers/Routes.tsx b/src/containers/Routes.tsx index 3b083aad..dac7a3f5 100644 --- a/src/containers/Routes.tsx +++ b/src/containers/Routes.tsx @@ -39,6 +39,8 @@ import Noredirect from '../pages/Noredirect'; import RedirectHandler from '../pages/RedirectHandler'; import ProtectedRoutes from '../ProtectedRoute'; import RemoveTokenPage from '../utils/RemoveTokenPage'; +import PrivateRoute from '../utils/PrivateRoute' +import CalendarConfirmation from '../components/CalendarConfirmation'; function MainRoutes() { return ( @@ -161,6 +163,12 @@ function MainRoutes() { /> } /> } /> + + + + }> + } /> diff --git a/src/pages/invitation.tsx b/src/pages/invitation.tsx index e1598608..fbf3d6de 100644 --- a/src/pages/invitation.tsx +++ b/src/pages/invitation.tsx @@ -317,8 +317,7 @@ function Invitation() { validateEmail(newEmail); // Validate on change }; - // Defining invitation table - let content; + // Defining invitation table Actions const capitalizeStrings = (str: string): string => { if (!str) return ''; if (str === 'ttl') { @@ -472,6 +471,8 @@ function Invitation() { }, ]; + // Table definition + let content; const datum: any = []; if (invitations && invitations.length > 0) { invitations.forEach((invitation) => { diff --git a/tests/components/Calendar.test.tsx b/tests/components/Calendar.test.tsx index e9678fad..ed04678c 100644 --- a/tests/components/Calendar.test.tsx +++ b/tests/components/Calendar.test.tsx @@ -1,73 +1,229 @@ -import { fireEvent, render } from '@testing-library/react'; +import "@testing-library/jest-dom" +import { fireEvent, screen, render, waitFor, cleanup} from '@testing-library/react'; import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client'; import Calendar from '../../src/components/Calendar'; +import { MockedProvider, MockedResponse } from "@apollo/client/testing"; +import { toast } from "react-toastify"; +import { GET_EVENTS, ADD_EVENT, EDIT_EVENT, CANCEL_EVENT } from "../../src/Mutations/event"; -const client = new ApolloClient({ cache: new InMemoryCache() }); +const getEventsMock: MockedResponse = { + request: { + query: GET_EVENTS, + variables: { + authToken: 'mocked_auth_token' + } + }, + result:{ + data:{ + getEvents:[ + { + id: "1", + user: "1", + end: "2024-10-02T00:00:00.000Z", + hostName: "Jack", + start: "2024-10-02T00:00:00.000Z", + timeToEnd: "03:00", + timeToStart: "04:00", + title: "Mocked Event", + invitees: [{email: "testing@gmail.com"},{email: "testing2@gmail.com"}] + }, + { + id: "2", + user: "1", + end: "2024-10-02T00:00:00.000Z", + hostName: "Jones", + start: "2024-10-02T00:00:00.000Z", + timeToEnd: "03:00", + timeToStart: "04:00", + title: "Another Mocked Event", + invitees: [{email: "testing@gmail.com"},{email: "testing2@gmail.com"}] + }, + ]} + } +} + +const addEventMock: MockedResponse = { + request: { + query: ADD_EVENT, + }, + variableMatcher: () => true, + result: { + data: { + createEvent: { + end: "2024-09-12T00:00:00.000Z", + hostName: "Morales", + start: "2024-09-12T00:00:00.000Z", + timeToEnd: "08:00", + timeToStart: "09:00", + title: "Another Event", + } + } + } +} + +const addEventErrorMock: MockedResponse = { + request: { + query: ADD_EVENT, + }, + variableMatcher: () => true, + error: new Error("An error occured") +} + +const editEventMock = { + request: { + query: EDIT_EVENT + }, + variableMatcher: () => true, + result: { + data: { + editEvent: { + end: "2024-09-12T00:00:00.000Z", + hostName: "Jack", + start: "2024-09-12T00:00:00.000Z", + timeToEnd: "10:00", + timeToStart: "09:00", + title: "Edited Mock Event", + } + } + } +} + +const cancelEventMock: MockedResponse = { + request: { + query: CANCEL_EVENT, + variables: { + eventId: '1', + authToken: 'mocked_auth_token' + } + }, + result: { + data: { + cancelEvent: { + end: "2024-09-12T00:00:00.000Z", + hostName: "Jack", + start: "2024-09-12T00:00:00.000Z", + timeToEnd: "10:00", + timeToStart: "09:00", + title: "Mock Event", + } + } + } +} + +jest.mock('react-toastify',()=>({ + toast: { + success: jest.fn(), + error: jest.fn(), + } +})) + +beforeEach(()=>{ + localStorage.setItem('auth_token','mocked_auth_token') + localStorage.setItem('orgToken','mocked_org_token') + localStorage.setItem('auth', JSON.stringify({ + auth: true, + email: "testing@gmail.com", + firstName: "Jack", + role: "admin", + userId: "1" + })) + jest.useFakeTimers() +}) + +afterEach(()=>{ + localStorage.clear() + jest.runAllTimers() + cleanup() +}) describe('Calendar Tests', () => { - it('should open Calendar model', () => { - const removeModelMck = jest.fn(); - const { getByTestId } = render( - - - - - , + it('should display Calendar events', async () => { + render( + + + ); - const removeModel = getByTestId('removeModel'); - fireEvent.click(removeModel); - expect(removeModelMck).toBeCalledTimes(0); + await waitFor(() => { + expect(screen.getByText("Mocked Event")).toBeInTheDocument() + expect(screen.getByText("Jack")).toBeInTheDocument() + expect(screen.getByText("Another Mocked Event")).toBeInTheDocument() + expect(screen.getByText("Jones")).toBeInTheDocument() + }) }); - it('should handle add event', () => { - const handleAddEventMck = jest.fn(); - const { getByTestId } = render( - - - - - , + + it('should add event when addEventForm is submitted', async () => { + render( + + + ); - const handleAddEvent = getByTestId('handleAddEvent'); - fireEvent.click(handleAddEvent); - expect(handleAddEventMck).toBeCalledTimes(0); + + const addEventModal = screen.getByTestId('addEventModal'); + const handleAddEventModal = screen.getByTestId('handleAddEventModal'); + const addEventForm = screen.getByTestId("addEventForm") + expect(addEventForm).toBeInTheDocument() + await waitFor(async()=>{ + fireEvent.click(handleAddEventModal); + fireEvent.submit(addEventForm) + expect(toast.success).toHaveBeenCalledWith('Event has been added!') + expect(addEventModal).toHaveClass("hidden") + }) + screen.debug() }); - it('should handle Date Click', () => { - const handleDateClickMck = jest.fn(); - const { getByTestId } = render( - - - - - , + + it('should show an error toast when addEvent fails', async () => { + render( + + + ); - const handleDateClick = getByTestId('handleDateClick'); - fireEvent.click(handleDateClick); - expect(handleDateClickMck).toBeCalledTimes(0); + + const handleAddEventModal = screen.getByTestId('handleAddEventModal'); + const addEventForm = screen.getByTestId("addEventForm") + expect(addEventForm).toBeInTheDocument() + await waitFor(async()=>{ + fireEvent.click(handleAddEventModal); + fireEvent.submit(addEventForm) + expect(toast.error).toHaveBeenCalledWith('An error occured') + }) }); - it('should set new event', () => { - const setNewEventMck = jest.fn(); - const { getByTestId } = render( - - - - - , + it('should edit event when editEventForm is submitted', async () => { + render( + + + ); - const setNewEvent = getByTestId('setNewEvent'); - fireEvent.click(setNewEvent); - expect(setNewEventMck).toBeCalledTimes(0); - }); - it('should set add event model', () => { - const setAddEventModelMck = jest.fn(); - expect(setAddEventModelMck).toBeCalledTimes(0); + const editEventModal = screen.getByTestId('editEventModal') + const editEventForm = screen.getByTestId("editEventForm") + expect(editEventModal).toHaveClass("hidden") + await waitFor(() => { + const event = screen.getByTestId("event-1") + fireEvent.click(event) + expect(editEventModal).toHaveClass("block") + fireEvent.submit(editEventForm) + expect(toast.success).toHaveBeenCalledWith('Event has been updated!') + }) }); - it('should set data', () => { - const setDataMck = jest.fn(); - expect(setDataMck).toBeCalledTimes(0); + it('should delete event when delete button is clicked', async () => { + render( + + + + ); + + const deleteEventModal = screen.getByTestId("deleteEventModal") + const handleDeleteModal = screen.getByTestId("handleDeleteModal") + const handleDelete = screen.getByTestId("handleDelete") + await waitFor(() => { + const event = screen.getByTestId("event-1") + fireEvent.click(event) + fireEvent.click(handleDeleteModal) + expect(deleteEventModal).toHaveClass("block") + fireEvent.click(handleDelete) + expect(toast.success).toHaveBeenCalledWith('Event cancelled successfully') + }) }); }); diff --git a/tests/components/CalendarConfirmation.test.tsx b/tests/components/CalendarConfirmation.test.tsx new file mode 100644 index 00000000..c84fbd24 --- /dev/null +++ b/tests/components/CalendarConfirmation.test.tsx @@ -0,0 +1,158 @@ +import "@testing-library/jest-dom" +import { screen, render, waitFor, cleanup } from '@testing-library/react'; +import React from 'react'; +import { MockedProvider } from "@apollo/client/testing"; +import { toast } from "react-toastify"; +import CalendarConfirmation from "../../src/components/CalendarConfirmation"; +import { RESPOND_TO_EVENT_INVITATION } from "../../src/Mutations/event"; +import { MemoryRouter } from "react-router"; + +const acceptedEventInvitationMock = [ + { + request: { + query: RESPOND_TO_EVENT_INVITATION, + variables: { + eventToken: "mocked_event_token", + authToken: "mocked_auth_token", + } + }, + result: { + data: { + respondToEventInvitation: { + end: "2024-09-12T00:00:00.000Z", + hostName: "Jack", + start: "2024-09-12T00:00:00.000Z", + timeToEnd: "09:30", + title: "Mock Event", + timeToStart: "08:30", + invitees: [ + { + email: "testing@gmail.com", + status: "accepted" + } + ] + } + } + } + } +] + +const declinedEventInvitationMock = [ + { + request: { + query: RESPOND_TO_EVENT_INVITATION, + variables: { + eventToken: "mocked_event_token", + authToken: "mocked_auth_token", + } + }, + result: { + data: { + respondToEventInvitation: { + end: "2024-09-12T00:00:00.000Z", + hostName: "Jack", + start: "2024-09-12T00:00:00.000Z", + timeToEnd: "09:30", + title: "Mock Event", + timeToStart: "08:30", + invitees: [ + { + email: "testing@gmail.com", + status: "declined" + } + ] + } + } + } + } +] + +const eventInvitationResponseErrorMock = [ + { + request: { + query: RESPOND_TO_EVENT_INVITATION, + variables: { + eventToken: "mocked_event_token", + authToken: "mocked_auth_token", + } + }, + error: new Error("An error occured") + } +] + +jest.mock('react-toastify',()=>({ + toast: { + success: jest.fn(), + error: jest.fn(), + } +})) + +beforeEach(()=>{ + localStorage.setItem('auth_token','mocked_auth_token') + localStorage.setItem('orgToken','mocked_org_token') + localStorage.setItem('auth', JSON.stringify({ + auth: true, + email: "testing@gmail.com", + firstName: "Jack", + role: "admin", + userId: "1" + })) + }) + +afterEach(()=>{ + localStorage.clear() + cleanup() +}) + +describe("CalendarConfirmation Tests", () => { + + it("should render accepted event invitation", () => { + window.location.search = "?eventToken=mocked_event_token" + render( + + + + + + ) + waitFor(()=>{ + expect(toast.success).toHaveBeenCalledWith("Successfully responded to invitation") + expect(screen.getByText("Mock Event")).toBeInTheDocument() + expect(screen.getByText("testing@gmail.com")).toBeInTheDocument() + expect(screen.getByText("accepted")).toBeInTheDocument() + }) + }) + + it("should render declined event invitation", () => { + window.location.search = "?eventToken=mocked_event_token" + render( + + + + + + ) + waitFor(()=>{ + expect(toast.success).toHaveBeenCalledWith("Successfully responded to invitation") + expect(screen.getByText("Mock Event")).toBeInTheDocument() + expect(screen.getByText("testing@gmail.com")).toBeInTheDocument() + expect(screen.getByText("declined")).toBeInTheDocument() + }) + }) + + it("should render error when mutation returns an error", () => { + window.location.search = "?eventToken=mocked_event_token" + render( + + + + + + ) + waitFor(()=>{ + expect(toast.error).toHaveBeenCalledWith("An error occured") + }) + }) + + +}) \ No newline at end of file diff --git a/tests/components/EventGuestList.test.tsx b/tests/components/EventGuestList.test.tsx new file mode 100644 index 00000000..3e023736 --- /dev/null +++ b/tests/components/EventGuestList.test.tsx @@ -0,0 +1,138 @@ +import "@testing-library/jest-dom" +import { fireEvent, screen, render, waitFor, cleanup } from '@testing-library/react'; +import React from 'react'; +import { MockedProvider } from "@apollo/client/testing"; +import { toast } from "react-toastify"; +import { GET_ALL_USERS_QUERY } from "../../src/Mutations/manageStudentMutations"; +import EventGuestList, { getRoleColor, getRoleTitle } from "../../src/components/EventGuestList"; + +const getAllUsersMock = [{ + request: { + query: GET_ALL_USERS_QUERY, + }, + variableMatcher: () => true, + result: { + data: { + getAllUsers: [ + { + id: "1", + email: "testing@gmail.com", + role: "trainee", + profile: { + firstName: "Jack", + lastName: "Mukundwa" + } + } + ] + } + }, +}] + +const getAllUsersErrorMock = [{ + request: { + query: GET_ALL_USERS_QUERY, + variables: { + orgToken: "mocked_org_token" + } + }, + error: new Error("An error occured") +}] + +jest.mock('react-toastify',()=>({ + toast: { + success: jest.fn(), + error: jest.fn(), + } +})) + +beforeEach(()=>{ + localStorage.setItem('auth_token','mocked_auth_token') + localStorage.setItem('orgToken','mocked_org_token') + localStorage.setItem('auth', JSON.stringify({ + auth: true, + email: "testing@gmail.com", + firstName: "Jack", + role: "admin", + userId: "1" + })) + }) + +afterEach(()=>{ + localStorage.clear() + cleanup() +}) + +describe("EventGuestList Tests", () => { + it("renders event guest list", () => { + const selectedGuestsMock: string[] = [] + const handleAddGuestMock = jest.fn() + + render( + + + + ) + waitFor(()=>{ + expect(screen.getByText("Trainees")).toBeInTheDocument() + expect(screen.getByText("Jack")).toBeInTheDocument() + expect(screen.getByText("Mukundwa")).toBeInTheDocument() + }) + }) + it("should select invitees", () => { + const selectedGuestsMock: string[] = [] + const handleAddGuestMock = jest.fn() + + render( + + + + ) + waitFor(()=>{ + const input1 = screen.getByTestId("input-1") + const span1 = screen.getByTestId("span-1") + fireEvent.click(input1) + expect(handleAddGuestMock).toHaveBeenCalledWith("testing@gmail.com") + expect(span1).toHaveClass("bg-green-500") + }) + }) + it("should display errors", () => { + const selectedGuestsMock: string[] = [] + const handleAddGuestMock = jest.fn() + + render( + + + + ) + waitFor(()=>{ + expect(toast.error).toHaveBeenCalledWith("An error occured") + }) + }) + + it("should return the correct tailwind background class",()=>{ + expect(getRoleColor("admin")).toEqual('bg-red-500') + expect(getRoleColor("ttl")).toEqual('bg-yellow-500') + expect(getRoleColor("trainee")).toEqual('bg-green-500') + expect(getRoleColor("coordinator")).toEqual('bg-blue-500') + expect(getRoleColor("manager")).toEqual('bg-violet-500') + expect(getRoleColor("hello")).toEqual('bg-gray-500') + }) + + it("should return the correct role title",()=>{ + expect(getRoleTitle("admin")).toEqual('Admins') + expect(getRoleTitle("ttl")).toEqual('TTLs') + expect(getRoleTitle("trainee")).toEqual('Trainees') + expect(getRoleTitle("coordinator")).toEqual('Coordinators') + expect(getRoleTitle("manager")).toEqual('Managers') + expect(getRoleTitle("hello")).toEqual('Others') + }) +}) \ No newline at end of file diff --git a/tests/components/__snapshots__/AdminTraineeDashboard.test.tsx.snap b/tests/components/__snapshots__/AdminTraineeDashboard.test.tsx.snap index d19fbdd5..09654d8a 100644 --- a/tests/components/__snapshots__/AdminTraineeDashboard.test.tsx.snap +++ b/tests/components/__snapshots__/AdminTraineeDashboard.test.tsx.snap @@ -141,13 +141,13 @@ Array [ onTouchEnd={[Function]} >